View Javadoc
1   package fr.ifremer.quadrige3.core.test;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 Test Shared
6    * %%
7    * Copyright (C) 2017 Ifremer
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Affero General Public License as published by
11   * the Free Software Foundation, either version 3 of the License, or
12   * (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU Affero General Public License
20   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21   * #L%
22   */
23  
24  import com.google.common.base.Charsets;
25  import com.google.common.io.Files;
26  import fr.ifremer.quadrige3.core.config.QuadrigeConfiguration;
27  import fr.ifremer.quadrige3.core.config.QuadrigeConfigurationOption;
28  import fr.ifremer.quadrige3.core.dao.technical.Daos;
29  import fr.ifremer.quadrige3.core.dao.technical.DatabaseSchemaDao;
30  import fr.ifremer.quadrige3.core.dao.technical.hibernate.DatabaseSchemaDaoImpl;
31  import fr.ifremer.quadrige3.core.exception.DatabaseSchemaUpdateException;
32  import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
33  import fr.ifremer.quadrige3.core.service.ServiceLocator;
34  import org.apache.commons.io.FileUtils;
35  import org.apache.commons.io.IOUtils;
36  import org.apache.commons.lang3.ArrayUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.dbunit.DatabaseUnitException;
41  import org.dbunit.database.DatabaseConfig;
42  import org.dbunit.database.DatabaseConnection;
43  import org.dbunit.database.IDatabaseConnection;
44  import org.dbunit.dataset.IDataSet;
45  import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
46  import org.dbunit.ext.hsqldb.HsqldbDataTypeFactory;
47  import org.dbunit.ext.oracle.WktSupportOracle10DataTypeFactory;
48  import org.dbunit.ext.postgresql.PostgresqlDataTypeFactory;
49  import org.dbunit.operation.DatabaseOperation;
50  import org.junit.Assume;
51  import org.junit.rules.ExternalResource;
52  import org.nuiton.i18n.I18n;
53  import org.nuiton.i18n.init.DefaultI18nInitializer;
54  import org.nuiton.i18n.init.UserI18nInitializer;
55  
56  import java.io.BufferedReader;
57  import java.io.BufferedWriter;
58  import java.io.File;
59  import java.io.IOException;
60  import java.net.URL;
61  import java.sql.Connection;
62  import java.sql.SQLException;
63  import java.util.Locale;
64  import java.util.Properties;
65  import java.util.StringTokenizer;
66  
67  /**
68   * @author peck7 on 13/10/2017.
69   */
70  public class InitTests extends ExternalResource {
71  
72      private static final String DATASET_COMMON_XML_FILE = "quadrige3.core.test.dataset.common";
73      private static final String DATASET_ADDITIONAL_XML_FILES = "quadrige3.core.test.dataset.additional";
74  
75      private static final Log log = LogFactory.getLog(InitTests.class);
76  
77      /**
78       * Main method is used by clients projects, to generate and deploy a test DB
79       * (e.g. for Dali project see http://doc.e-is.pro/dali/dali-data.properties)
80       * @param args arguments
81       * @throws Throwable if any
82       */
83      public static void main(String[] args) throws Throwable{
84  
85          // Check arguments
86          if (ArrayUtils.isEmpty(args)) {
87              log.error("Missing target directory, as first argument of InitTests.main(). Skipping");
88              System.exit(-1);
89          }
90  
91          InitTests initTests = new InitTests();
92  
93          try {
94  
95              // arg[0] : target DB directory
96              String targetDbArg = args[0];
97              initTests.setTargetDbDirectory(targetDbArg);
98  
99              // arg[1] : replace if exists
100             if (args.length >= 2) {
101                 boolean replaceIfExists = Boolean.parseBoolean(args[1]);
102                 initTests.setReplaceDbIfExists(replaceIfExists);
103             }
104 
105             log.info(String.format("Creating test database into [%s]", initTests.getTargetDbDirectory()));
106 
107             // Execute the DB creation + data load
108             initTests.compactDatabase = true;
109             initTests.before();
110         } catch (Throwable ex) {
111             log.error(ex.getLocalizedMessage(), ex);
112             throw ex;
113         }
114     }
115 
116     protected QuadrigeConfiguration config;
117 
118     private String targetDbDirectory = null;
119 
120     private boolean replaceDbIfExists = false;
121 
122     private boolean compactDatabase = false;
123 
124     public void setTargetDbDirectory(String targetDbDirectory) {
125         this.targetDbDirectory = targetDbDirectory;
126     }
127 
128     public String getTargetDbDirectory() {
129         return targetDbDirectory;
130     }
131 
132     public void setReplaceDbIfExists(boolean replaceDbIfExists) {
133         this.replaceDbIfExists = replaceDbIfExists;
134     }
135 
136     public boolean getReplaceDbIfExists() {
137         return replaceDbIfExists;
138     }
139 
140     protected String getDbEnumerationResource() {
141         return "classpath*:quadrige3-db-enumerations.properties";
142     }
143 
144     protected String getModuleName() {
145         return "quadrige3-test-shared";
146     }
147 
148     protected String getEnvironment() {
149         String env = System.getProperty("env");
150         if (StringUtils.isNotBlank(env))
151             return "-" + env;
152         return "";
153     }
154 
155     protected String[] getConfigArgs() {
156         return new String[]{
157                 "--option", QuadrigeConfigurationOption.DB_DIRECTORY.getKey(), getTargetDbDirectory(),
158                 "--option", QuadrigeConfigurationOption.DB_ENUMERATION_RESOURCE.getKey(), getDbEnumerationResource()
159         };
160     }
161 
162     protected QuadrigeConfiguration createConfig() {
163 
164         QuadrigeConfiguration config = new QuadrigeConfiguration(
165                 String.format("%s-test-write%s.properties", getModuleName(), getEnvironment()),
166                 getConfigArgs()
167         );
168         QuadrigeConfiguration.setInstance(config);
169         return config;
170 
171     }
172 
173     protected void initServiceLocator() {
174 
175         ServiceLocator.initQuadrigeDefault();
176     }
177 
178     @Override
179     protected void before() throws Throwable {
180 
181         config = createConfig();
182         Assume.assumeNotNull(config);
183 
184         initServiceLocator();
185 
186         initI18n();
187 
188         log.info("Init test data in database... [" + config.getJdbcURL() + "]");
189         boolean isFileDatabase = Daos.isHsqlFileDatabase(config.getJdbcURL());
190         boolean needSchemaUpdate = true;
191 
192         if (isFileDatabase) {
193 
194             File dbDirectory = new File(getTargetDbDirectory());
195             File dbConfigFile = new File(dbDirectory, DatabaseResource.HSQLDB_SRC_DATABASE_PROPERTIES_FILE);
196             File dbScriptFile = new File(dbDirectory, DatabaseResource.HSQLDB_SRC_DATABASE_SCRIPT_FILE);
197 
198             // db not exists: create it
199             if (!dbConfigFile.exists() || !dbScriptFile.exists() || replaceDbIfExists) {
200                 // Create DB
201                 generateNewDb(dbDirectory, replaceDbIfExists);
202 
203                 // Update schema
204                 updateSchema(dbDirectory);
205                 needSchemaUpdate = false;
206             }
207 
208             // Set database to readonly=false
209             try {
210                 setProperty(dbConfigFile, "readonly", "false");
211             } catch (IOException e) {
212                 Assume.assumeNoException(e);
213             }
214         }
215 
216         Connection conn = null;
217         try {
218             // Update DB schema
219             if (needSchemaUpdate) {
220                 log.info("Updating database schema...");
221                 ServiceLocator.instance().getDatabaseSchemaService().updateSchema();
222             }
223 
224             conn = Daos.createConnection(config.getConnectionProperties());
225 
226             // Import Common dataset
227             try {
228                 String commonDataSetFile = config.getApplicationConfig().getOption(DATASET_COMMON_XML_FILE);
229                 Assume.assumeTrue(
230                         String.format("Missing value for configuration option [%s].\nPlease set this properties in the test configuration.",
231                                 DATASET_COMMON_XML_FILE),
232                         commonDataSetFile != null);
233 
234                 URL commonDataSetFileUrl = getClass().getResource("/" + commonDataSetFile);
235                 Assume.assumeTrue(
236                         String.format("Unable to find resource for configuration option [%s] resource = %s. \nPlease review your properties in the test configuration.",
237                                 DATASET_COMMON_XML_FILE, commonDataSetFile),
238                         commonDataSetFileUrl != null);
239 
240                 // Prepare Database (e.g. disabling constraints, ...)
241                 beforeInsert(conn);
242 
243                 // Delete all
244                 log.info(String.format("Deleting data, from tables found in file {%s}...", commonDataSetFile));
245                 deleteAllFromXmlDataSet(commonDataSetFileUrl, conn);
246 
247                 afterInsert(conn);
248                 beforeInsert(conn);
249 
250                 // Insert common data
251                 log.info(String.format("Importing data from file {%s}...", commonDataSetFile));
252                 insertFromXmlDataSet(commonDataSetFileUrl, conn);
253 
254                 conn.commit();
255             } finally {
256                 afterInsert(conn);
257             }
258 
259             // DEV ONLY: on server DB, cleaning all previous database change log
260             // if (!isHsqlFileDatabase) {
261             // Daos.sqlUpdate(conn, "DELETE FROM DATABASECHANGELOG");
262             // Daos.sqlUpdate(conn, "DELETE FROM DATABASECHANGELOGLOCK");
263             // conn.commit();
264             // }
265 
266             // Import additional datasets
267             try {
268 
269                 String importFileNames = config.getApplicationConfig().getOption(DATASET_ADDITIONAL_XML_FILES);
270                 Assume.assumeTrue(
271                         String.format("Missing value for configuration option [%s].\nPlease set this properties in the test configuration.",
272                                 DATASET_ADDITIONAL_XML_FILES),
273                         importFileNames != null);
274 
275                 // Prepare Database (e.g. disabling constraints, ...)
276                 beforeInsert(conn);
277 
278                 // If multiple files, split and loop over
279                 StringTokenizer st = new StringTokenizer(importFileNames, ",");
280                 while (st.hasMoreTokens()) {
281                     String importFileName = st.nextToken();
282 
283                     log.info(String.format("Importing data from file {%s}...", importFileNames));
284                     URL importFileUrl = getClass().getResource("/" + importFileName);
285                     Assume.assumeTrue(
286                             String.format("Unable to find resource for configuration option [%s] resource = %s. \nPlease review your properties in the test configuration.",
287                                     DATASET_ADDITIONAL_XML_FILES, importFileName),
288                             importFileUrl != null);
289 
290                     // Insert
291                     insertFromXmlDataSet(importFileUrl, conn);
292                 }
293 
294                 // Comitting insertions
295                 conn.commit();
296 
297             } finally {
298                 // Restoring constraints
299                 afterInsert(conn);
300             }
301 
302         } finally {
303 
304             if (conn != null && !conn.isClosed()) {
305                 if (isFileDatabase) {
306 
307                     // Shutdown database
308                     if (compactDatabase) {
309                         Daos.compactDatabase(conn);
310                     }
311 
312                     // Shutdown database
313                     Daos.shutdownDatabase(conn);
314                 }
315 
316                 Daos.closeSilently(conn);
317             }
318 
319             // Shutdown spring context
320             IOUtils.closeQuietly(ServiceLocator.instance());
321         }
322 
323         // Set database to readonly=true
324         if (isFileDatabase) {
325             File dbDirectory = new File(getTargetDbDirectory());
326             File dbConfigFile = new File(dbDirectory, DatabaseResource.HSQLDB_SRC_DATABASE_PROPERTIES_FILE);
327 
328             try {
329                 setProperty(dbConfigFile, "readonly", "true");
330             } catch (IOException e) {
331                 Assume.assumeNoException(e);
332             }
333         }
334 
335         log.info("Test database has been loaded");
336     }
337 
338     public static void setProperty(File file, String key, String value) throws IOException {
339         // Load old properties values
340         Properties props = new Properties();
341         try (BufferedReader reader = Files.newReader(file, Charsets.UTF_8)) {
342             props.load(reader);
343         }
344 
345         // Store new properties values
346         props.setProperty(key, value);
347         try (BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8)) {
348             props.store(writer, "");
349         }
350     }
351 
352     public void insertFromXmlDataSet(URL fileURL, Connection conn) throws SQLException, DatabaseUnitException {
353 
354         IDatabaseConnection connection = createDbUnitConnection(conn);
355         IDataSet dataSet = new FlatXmlDataSetBuilder()
356                 .setColumnSensing(true)
357                 .build(fileURL);
358         DatabaseOperation.INSERT.execute(connection, dataSet);
359     }
360 
361     public void deleteAllFromXmlDataSet(URL fileURL, Connection conn) throws SQLException, DatabaseUnitException {
362 
363         IDatabaseConnection connection = createDbUnitConnection(conn);
364         IDataSet dataSet = new FlatXmlDataSetBuilder()
365                 .setColumnSensing(true)
366                 .build(fileURL);
367         DatabaseOperation.DELETE_ALL.execute(connection, dataSet);
368     }
369 
370 	/* -- internal methods -- */
371 
372     protected void initI18n() throws IOException {
373         QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
374 
375         // --------------------------------------------------------------------//
376         // init i18n
377         // --------------------------------------------------------------------//
378         File i18nDirectory = new File(config.getDataDirectory(), "i18n");
379         if (i18nDirectory.exists()) {
380             // clean i18n cache
381             FileUtils.cleanDirectory(i18nDirectory);
382         }
383 
384         FileUtils.forceMkdir(i18nDirectory);
385 
386         if (log.isDebugEnabled()) {
387             log.debug("I18N directory: " + i18nDirectory);
388         }
389 
390         Locale i18nLocale = config.getI18nLocale();
391 
392         if (log.isDebugEnabled()) {
393             log.debug(String.format("Starts i18n with locale [%s] at [%s]",
394                     i18nLocale, i18nDirectory));
395         }
396         I18n.init(new UserI18nInitializer(
397                         i18nDirectory, new DefaultI18nInitializer(getI18nBundleName())),
398                 i18nLocale);
399     }
400 
401     protected String getI18nBundleName() {
402         return getModuleName() + "-i18n";
403     }
404 
405     protected void generateNewDb(File outputDirectory, boolean replaceDbIfExists) {
406         QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
407         DatabaseSchemaDao databaseSchemaDao = new DatabaseSchemaDaoImpl(config);
408 
409         try {
410             // Create the database
411             databaseSchemaDao.generateNewDb(outputDirectory, replaceDbIfExists);
412         } catch (QuadrigeTechnicalException e) {
413             log.error(e.getMessage());
414             Assume.assumeNoException(e);
415         }
416     }
417 
418     protected void updateSchema(File outputDirectory) {
419         QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
420         DatabaseSchemaDao databaseSchemaDao = new DatabaseSchemaDaoImpl(config);
421 
422         try {
423             // Update the DB schema
424             databaseSchemaDao.updateSchema(outputDirectory);
425         } catch (QuadrigeTechnicalException | DatabaseSchemaUpdateException e) {
426             log.error(e.getMessage());
427             Assume.assumeNoException(e);
428         }
429     }
430 
431     protected IDatabaseConnection createDbUnitConnection(Connection jdbcConnection) throws DatabaseUnitException {
432 
433         IDatabaseConnection dbUnitConnection;
434 
435         // HsqldDB connecion
436         if (Daos.isHsqlDatabase(config.getJdbcURL())) {
437             dbUnitConnection = new DatabaseConnection(jdbcConnection);
438             dbUnitConnection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new HsqldbDataTypeFactory());
439         }
440         // Oracle connecion
441         else if (Daos.isOracleDatabase(config.getJdbcURL())){
442             dbUnitConnection = new DatabaseConnection(jdbcConnection, config.getJdbcSchema());
443             dbUnitConnection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new WktSupportOracle10DataTypeFactory());
444             dbUnitConnection.getConfig().setProperty(DatabaseConfig.FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, Boolean.TRUE);
445         }
446         // Postgresql connecion
447         else if (Daos.isPostgresqlDatabase(config.getJdbcURL())){
448             dbUnitConnection = new DatabaseConnection(jdbcConnection, config.getJdbcSchema());
449             dbUnitConnection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new PostgresqlDataTypeFactory());
450             dbUnitConnection.getConfig().setProperty(DatabaseConfig.FEATURE_CASE_SENSITIVE_TABLE_NAMES, Boolean.FALSE);
451         }
452         else {
453             throw new QuadrigeTechnicalException("Unable to create DBUnit connection: Unknown DB type for URL [" + config.getJdbcURL() + "]");
454         }
455 
456         return dbUnitConnection;
457     }
458 
459     protected void beforeInsert(Connection connection) throws SQLException {
460         // Disable integrity constraints
461         log.debug("Disabling database constraints...");
462         Daos.setIntegrityConstraints(connection, false);
463 
464     }
465 
466     protected void afterInsert(Connection connection) throws SQLException {
467 
468         // Enable integrity constraints
469         log.debug("Enabling database constraints...");
470         Daos.setIntegrityConstraints(connection, true);
471     }
472 
473 }