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