/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. ****************************************************************/ package org.apache.cayenne.tools; import org.apache.cayenne.dbsync.reverse.dbimport.Catalog; import org.apache.cayenne.dbsync.reverse.dbimport.DbImportConfiguration; import org.apache.cayenne.dbsync.reverse.dbimport.IncludeTable; import org.apache.cayenne.dbsync.reverse.dbimport.Schema; import org.apache.cayenne.test.jdbc.SQLReader; import org.apache.cayenne.test.resource.ResourceUtil; import org.slf4j.Logger; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.codehaus.plexus.util.FileUtils; import org.custommonkey.xmlunit.DetailedDiff; import org.custommonkey.xmlunit.Diff; import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier; import org.custommonkey.xmlunit.XMLUnit; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import java.util.Objects; import static org.apache.commons.lang.StringUtils.isBlank; import static org.mockito.Mockito.mock; public class DbImporterMojoTest extends AbstractMojoTestCase { private static DerbyManager derbyAssembly; static { XMLUnit.setIgnoreWhitespace(true); } @BeforeClass public static void beforeClass() throws IOException, SQLException { derbyAssembly = new DerbyManager("target/derby"); } @AfterClass public static void afterClass() throws IOException, SQLException { derbyAssembly.shutdown(); derbyAssembly = null; } @Test public void testToParameters_MeaningfulPkTables() throws Exception { DbImportConfiguration parameters1 = getCdbImport("dbimporter-pom1.xml").createConfig(mock(Logger.class)); assertNull(parameters1.getMeaningfulPkTables()); assertPathEquals("target/test/org/apache/cayenne/tools/dbimporter-map1.map.xml", parameters1.getTargetDataMap() .getPath()); assertEquals("x,b*", getCdbImport("dbimporter-pom2.xml").createConfig(mock(Logger.class)).getMeaningfulPkTables()); assertEquals("*", getCdbImport("dbimporter-pom3.xml").createConfig(mock(Logger.class)).getMeaningfulPkTables()); } public void testToParameters_Map() throws Exception { DbImportConfiguration parameters1 = getCdbImport("dbimporter-pom1.xml").createConfig(mock(Logger.class)); assertNotNull(parameters1.getTargetDataMap()); assertPathEquals("target/test/org/apache/cayenne/tools/dbimporter-map1.map.xml", parameters1.getTargetDataMap() .getPath()); assertNull(getCdbImport("dbimporter-pom2.xml").createConfig(mock(Logger.class)).getTargetDataMap()); } private DbImporterMojo getCdbImport(String pomFileName) throws Exception { return (DbImporterMojo) lookupMojo("cdbimport", getTestFile("src/test/resources/org/apache/cayenne/tools/" + pomFileName)); } private void assertPathEquals(String expectedPath, String actualPath) { assertEquals(new File(expectedPath), new File(actualPath)); } @Test public void testImportNewDataMap() throws Exception { test("testImportNewDataMap"); } @Test public void testImportWithoutChanges() throws Exception { test("testImportWithoutChanges"); } @Test public void testImportAddTableAndColumn() throws Exception { test("testImportAddTableAndColumn"); } @Test public void testFilteringWithSchema() throws Exception { test("testFilteringWithSchema"); } @Test public void testSchemasAndTableExclude() throws Exception { test("testSchemasAndTableExclude"); } @Test public void testViewsExclude() throws Exception { test("testViewsExclude"); } @Test public void testTableTypes() throws Exception { test("testTableTypes"); } @Test public void testDefaultPackage() throws Exception { test("testDefaultPackage"); } @Test public void testSkipRelationshipsLoading() throws Exception { test("testSkipRelationshipsLoading"); } @Test public void testSkipPrimaryKeyLoading() throws Exception { test("testSkipPrimaryKeyLoading"); } @Test public void testOneToOne() throws Exception { test("testOneToOne"); } /** * Q: what happens if an attribute or relationship is unmapped in the object layer, but then the underlying table * changes. * A: it should not recreate unmapped attributes/relationships. Only add an attribute for the new column. * * @throws Exception */ @Test public void testPreserveCustomObjMappings() throws Exception { test("testPreserveCustomObjMappings"); } /** * Q: what happens if a relationship existed over a column that was later deleted? and ‘skipRelLoading’ is true * A: it should remove relationship and column * * @throws Exception */ @Test public void testPreserveRelationships() throws Exception { test("testPreserveRelationships"); } /** * By default many-to-many are flattened during reverse engineering. * But if a user un-flattens a given N:M manually, we’d like this choice to be preserved on the next run */ @Test public void testUnFlattensManyToMany() throws Exception { // TODO: this should be "xYs" : <db-relationship name="xIes" test("testUnFlattensManyToMany"); } /** * Make sure any merges preserve custom object layer settings, like "usePrimitives", PK mapping as attribute, etc. */ @Test public void testCustomObjectLayerSettings() throws Exception { test("testCustomObjectLayerSettings"); } @Test public void testDbAttributeChange() throws Exception { test("testDbAttributeChange"); } @Test public void testForceDataMapSchema() throws Exception { test("testForceDataMapSchema"); } @Test public void testComplexChangeOrder() throws Exception { test("testComplexChangeOrder"); } /** * CREATE TABLE APP.A ( * id INTEGER NOT NULL, * <p> * PRIMARY KEY (id) * ); * <p> * CREATE TABLE APP.A_A ( * A1_ID INTEGER NOT NULL, * A2_ID INTEGER NOT NULL, * <p> * PRIMARY KEY (A1_ID, A2_ID), * CONSTRAINT A_A1 FOREIGN KEY (A1_ID) REFERENCES APP.A (ID), * CONSTRAINT A_A2 FOREIGN KEY (A2_ID) REFERENCES APP.A (ID) * ); * <p> * If one table has many-to-many relationship with it self ObjEntity should have two * collection attributes in both directions * * @throws Exception */ @Test @Ignore("Investigate why on different environment entity relationships order are different.") public void te_stFlattensManyToManyWithRecursiveLink() throws Exception { test("testFlattensManyToManyWithRecursiveLink"); } @Test public void testFkAttributeRename() throws Exception { test("testFkAttributeRename"); } @Test public void testJava7Types() throws Exception { test("testJava7Types"); } @Test public void testJava8Types() throws Exception { test("testJava8Types"); } @Test public void testInheritance() throws Exception { test("testInheritance"); } @Test public void testFilteringConfig() throws Exception { DbImporterMojo cdbImport = getCdbImport("config/pom-01.xml"); assertEquals(2, cdbImport.getReverseEngineering().getCatalogs().size()); Iterator<Catalog> iterator = cdbImport.getReverseEngineering().getCatalogs().iterator(); assertEquals("catalog-name-01", iterator.next().getName()); Catalog catalog = iterator.next(); assertEquals("catalog-name-02", catalog.getName()); Iterator<Schema> schemaIterator = catalog.getSchemas().iterator(); assertEquals("schema-name-01", schemaIterator.next().getName()); Schema schema = schemaIterator.next(); assertEquals("schema-name-02", schema.getName()); Iterator<IncludeTable> includeTableIterator = schema.getIncludeTables().iterator(); assertEquals("incTable-01", includeTableIterator.next().getPattern()); IncludeTable includeTable = includeTableIterator.next(); assertEquals("incTable-02", includeTable.getPattern()); assertEquals("includeColumn-01", includeTable.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-01", includeTable.getExcludeColumns().iterator().next().getPattern()); assertEquals("includeColumn-02", schema.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-02", schema.getExcludeColumns().iterator().next().getPattern()); assertEquals("includeColumn-03", catalog.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-03", catalog.getExcludeColumns().iterator().next().getPattern()); schemaIterator = cdbImport.getReverseEngineering().getSchemas().iterator(); schema = schemaIterator.next(); assertEquals("schema-name-03", schema.getName()); schema = schemaIterator.next(); assertEquals("schema-name-04", schema.getName()); includeTableIterator = schema.getIncludeTables().iterator(); assertEquals("incTable-04", includeTableIterator.next().getPattern()); assertEquals("excTable-04", schema.getExcludeTables().iterator().next().getPattern()); includeTable = includeTableIterator.next(); assertEquals("incTable-05", includeTable.getPattern()); assertEquals("includeColumn-04", includeTable.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-04", includeTable.getExcludeColumns().iterator().next().getPattern()); assertEquals("includeColumn-04", schema.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-04", schema.getExcludeColumns().iterator().next().getPattern()); assertEquals("includeColumn-03", catalog.getIncludeColumns().iterator().next().getPattern()); assertEquals("excludeColumn-03", catalog.getExcludeColumns().iterator().next().getPattern()); } @Test public void testSupportsCatalogsOnReverseEngineering() throws Exception { DbImporterMojo cdbImport = getCdbImport("dbimport/testSupportsCatalogsOnReverseEngineering-pom.xml"); cdbImport.getReverseEngineering().addCatalog(new Catalog("DbImporterMojoTest2")); Exception exceptedException = null; String exceptedMessage = "Your database does not support catalogs on reverse engineering. " + "It allows to connect to only one at the moment. Please don't note catalogs in <dbimport> configuration."; try { cdbImport.execute(); } catch (MojoExecutionException ex) { exceptedException = ex; } assertNotNull(exceptedException); assertEquals(exceptedMessage, exceptedException.getCause().getMessage()); } private void test(String name) throws Exception { DbImporterMojo cdbImport = getCdbImport("dbimport/" + name + "-pom.xml"); File mapFile = cdbImport.getMap(); File mapFileCopy = new File(mapFile.getParentFile(), "copy-" + mapFile.getName()); if (mapFile.exists()) { FileUtils.copyFile(mapFile, mapFileCopy); cdbImport.setMap(mapFileCopy); } else { mapFileCopy = mapFile; } DbImportConfiguration parameters = cdbImport.createConfig(mock(Logger.class)); prepareDatabase(name, parameters); try { cdbImport.execute(); verifyResult(mapFile, mapFileCopy); } finally { cleanDb(parameters); } } private void cleanDb(DbImportConfiguration dbImportConfiguration) throws Exception { // TODO: refactor to common DB management code... E.g. bootique-jdbc-test? // TODO: with in-memory Derby, it is easier to just stop and delete the database.. again see bootique-jdbc-test Class.forName(dbImportConfiguration.getDriver()).newInstance(); try (Connection connection = DriverManager.getConnection(dbImportConfiguration.getUrl())) { try (Statement stmt = connection.createStatement()) { try (ResultSet views = connection.getMetaData().getTables(null, null, null, new String[]{"VIEW"})) { while (views.next()) { String schema = views.getString("TABLE_SCHEM"); execute(stmt, "DROP VIEW " + (isBlank(schema) ? "" : schema + ".") + views.getString("TABLE_NAME")); } } try (ResultSet tables = connection.getMetaData().getTables(null, null, null, new String[]{"TABLE"})) { while (tables.next()) { String schema = tables.getString("TABLE_SCHEM"); String tableName = tables.getString("TABLE_NAME"); String tableNameFull = (isBlank(schema) ? "" : schema + ".") + tableName; ResultSet keys = connection.getMetaData().getExportedKeys(null, schema, tableName); while (keys.next()) { String fkTableSchem = keys.getString("FKTABLE_SCHEM"); String fkTableName = keys.getString("FKTABLE_NAME"); String fkTableNameFull = (isBlank(fkTableSchem) ? "" : fkTableSchem + ".") + fkTableName; execute(stmt, "ALTER TABLE " + fkTableNameFull + " DROP CONSTRAINT " + keys.getString("FK_NAME")); } String sql = "DROP TABLE " + tableNameFull; execute(stmt, sql); } } try (ResultSet schemas = connection.getMetaData().getSchemas()) { while (schemas.next()) { String schem = schemas.getString("TABLE_SCHEM"); if (schem.startsWith("SCHEMA")) { execute(stmt, "DROP SCHEMA " + schem + " RESTRICT"); } } } } } } private void execute(Statement stmt, String sql) throws SQLException { stmt.execute(sql); } private void verifyResult(File map, File mapFileCopy) { try { FileReader control = new FileReader(map.getAbsolutePath() + "-result"); FileReader test = new FileReader(mapFileCopy); Diff prototype = new Diff(control, test); prototype.overrideElementQualifier(new ElementNameAndAttributeQualifier()); DetailedDiff diff = new DetailedDiff(prototype); if (!diff.similar()) { fail(diff.toString()); } } catch (Exception e) { e.printStackTrace(); fail(); } } private void prepareDatabase(String sqlFile, DbImportConfiguration dbImportConfiguration) throws Exception { URL sqlUrl = Objects.requireNonNull(ResourceUtil.getResource(getClass(), "dbimport/" + sqlFile + ".sql")); // TODO: refactor to common DB management code... E.g. bootique-jdbc-test? Class.forName(dbImportConfiguration.getDriver()).newInstance(); try (Connection connection = DriverManager.getConnection(dbImportConfiguration.getUrl())) { try (Statement stmt = connection.createStatement();) { for (String sql : SQLReader.statements(sqlUrl, ";")) { stmt.execute(sql); } } } } }