1 package fr.ifremer.quadrige3.core.test;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 import com.google.common.base.Charsets;
28 import com.google.common.collect.Lists;
29 import com.google.common.collect.Sets;
30 import com.google.common.io.Files;
31 import fr.ifremer.quadrige3.core.config.QuadrigeConfiguration;
32 import fr.ifremer.quadrige3.core.config.QuadrigeConfigurationOption;
33 import fr.ifremer.quadrige3.core.dao.technical.Assert;
34 import fr.ifremer.quadrige3.core.dao.technical.Daos;
35 import fr.ifremer.quadrige3.core.dao.technical.DatabaseSchemaDao;
36 import fr.ifremer.quadrige3.core.dao.technical.hibernate.DatabaseSchemaDaoImpl;
37 import fr.ifremer.quadrige3.core.service.ServiceLocator;
38 import org.apache.commons.collections4.CollectionUtils;
39 import org.apache.commons.io.FileUtils;
40 import org.apache.commons.lang3.StringUtils;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
43 import org.junit.Assume;
44 import org.junit.rules.TestRule;
45 import org.junit.runner.Description;
46 import org.junit.runners.model.Statement;
47 import org.nuiton.i18n.I18n;
48 import org.nuiton.i18n.init.DefaultI18nInitializer;
49 import org.nuiton.i18n.init.UserI18nInitializer;
50
51 import java.io.*;
52 import java.sql.Connection;
53 import java.sql.SQLException;
54 import java.util.List;
55 import java.util.Locale;
56 import java.util.Properties;
57 import java.util.Set;
58
59
60
61
62
63
64
65 public abstract class DatabaseResource implements TestRule {
66
67
68 protected static final Log log = LogFactory.getLog(DatabaseResource.class);
69
70
71 public static final String BUILD_ENVIRONMENT_DEFAULT = "hsqldb";
72
73 public static final String HSQLDB_SRC_DATABASE_DIRECTORY_PATTERN = "../%s/src/test/db";
74 public static final String HSQLDB_SRC_DATABASE_NAME = "quadrige3";
75 public static final String HSQLDB_SRC_DATABASE_SCRIPT_FILE = HSQLDB_SRC_DATABASE_NAME + ".script";
76 public static final String HSQLDB_SRC_DATABASE_PROPERTIES_FILE = HSQLDB_SRC_DATABASE_NAME + ".properties";
77
78
79 public static final long BUILD_TIMESTAMP = System.nanoTime();
80
81 private File resourceDirectory;
82
83 private String dbDirectory;
84
85 private final String beanFactoryReferenceLocation;
86
87 private final String beanRefFactoryReferenceId;
88
89 private final boolean writeDb;
90
91 private final String configName;
92
93 private boolean witherror = false;
94
95 private Class<?> testClass;
96
97
98
99
100
101
102
103
104
105 protected DatabaseResource(String configName, String beanFactoryReferenceLocation,
106 String beanRefFactoryReferenceId,
107 boolean writeDb) {
108 this.configName = configName;
109 this.beanFactoryReferenceLocation = beanFactoryReferenceLocation;
110 this.beanRefFactoryReferenceId = beanRefFactoryReferenceId;
111 this.writeDb = writeDb;
112 }
113
114
115
116
117
118
119
120 protected abstract String getConfigFilesPrefix();
121
122 protected abstract String getModuleDirectory();
123
124 protected String getHsqldbSrcDatabaseDirectory() {
125 return String.format(HSQLDB_SRC_DATABASE_DIRECTORY_PATTERN, getModuleDirectory());
126 }
127
128 protected String getHsqldbSrcCreateScript() {
129 return String.format("%s/%s", getHsqldbSrcDatabaseDirectory(), HSQLDB_SRC_DATABASE_SCRIPT_FILE);
130 }
131
132
133
134
135
136
137
138 public File getResourceDirectory(String name) {
139 return new File(resourceDirectory, name);
140 }
141
142
143
144
145
146
147 public File getResourceDirectory() {
148 return resourceDirectory;
149 }
150
151 public String getBeanRefFactoryReferenceId() {
152 return beanRefFactoryReferenceId;
153 }
154
155 public String getBeanFactoryReferenceLocation() {
156 return beanFactoryReferenceLocation;
157 }
158
159
160
161
162
163
164 protected boolean isWriteDb() {
165 return writeDb;
166 }
167
168
169 @Override
170 public Statement apply(final Statement base, final Description description) {
171
172 return new Statement() {
173 @Override
174 public void evaluate() throws Throwable {
175 before(description);
176 try {
177 base.evaluate();
178 } catch (Throwable e) {
179 witherror = true;
180 log.error(e);
181 } finally {
182 after(description);
183 }
184 }
185 };
186 }
187
188
189
190
191
192
193
194 protected void before(Description description) throws Throwable {
195 testClass = description.getTestClass();
196
197 boolean defaultDbName = StringUtils.isEmpty(configName);
198
199 dbDirectory = null;
200
201 if (log.isInfoEnabled()) {
202 log.info("Prepare test " + testClass);
203 }
204
205 resourceDirectory = getTestSpecificDirectory(testClass, "");
206 addToDestroy(resourceDirectory);
207
208
209 String buildEnvironment = getBuildEnvironment();
210
211
212 String configFilename = getConfigFilesPrefix();
213 if (enableDb()) {
214 configFilename += "-" + (writeDb ? "write" : "read");
215 }
216 if (!defaultDbName) {
217 configFilename += "-" + configName;
218 }
219 String configFilenameNoEnv = configFilename + ".properties";
220 if (StringUtils.isNotBlank(buildEnvironment)) {
221 configFilename += "-" + buildEnvironment;
222 }
223 configFilename += ".properties";
224
225 InputStream resourceAsStream = getClass().getResourceAsStream("/" + configFilename);
226 if (resourceAsStream == null && StringUtils.isNotBlank(buildEnvironment)) {
227 resourceAsStream = getClass().getResourceAsStream("/" + configFilenameNoEnv);
228 Assert.notNull(resourceAsStream, "Could not find " + configFilename + " or " + configFilenameNoEnv + " in test class-path");
229 configFilename = configFilenameNoEnv;
230 }
231 else {
232 Assert.notNull(resourceAsStream, "Could not find " + configFilename + " in test class-path");
233 }
234
235
236 if (enableDb() && BUILD_ENVIRONMENT_DEFAULT.equalsIgnoreCase(buildEnvironment)) {
237
238 dbDirectory = getHsqldbSrcDatabaseDirectory();
239 if (!defaultDbName) {
240 dbDirectory += configName;
241 }
242 Tests.checkDbExists(testClass, dbDirectory);
243
244 if (writeDb) {
245 Properties p = new Properties();
246 p.load(resourceAsStream);
247 String jdbcUrl = p.getProperty(QuadrigeConfigurationOption.JDBC_URL.getKey());
248 boolean serverMode = jdbcUrl != null && jdbcUrl.startsWith("jdbc:hsqldb:hsql://");
249
250
251 if (serverMode) {
252
253 log.warn(String.format("Database running in server mode ! Please remove the property '%s' in file %s, to use a file database.",
254 QuadrigeConfigurationOption.JDBC_URL.getKey(), configFilename));
255 }
256 else {
257
258 copyDb(new File(dbDirectory), "db", false, null);
259
260
261 dbDirectory = new File(resourceDirectory, "db").getAbsolutePath();
262 dbDirectory = dbDirectory.replaceAll("[\\\\]", "/");
263 }
264 } else {
265
266 File dbConfig = new File(dbDirectory, getTestDbName() + ".properties");
267 Properties p = new Properties();
268 BufferedReader reader = Files.newReader(dbConfig, Charsets.UTF_8);
269 p.load(reader);
270 reader.close();
271
272 if (log.isDebugEnabled()) {
273 log.debug("Db config: " + dbConfig + "\n" + p);
274 }
275
276
277 String readonly = p.getProperty("readonly");
278 Assert.notNull(readonly, "Could not find readonly property on db confg: " + dbConfig);
279 Assert.state("true".equals(readonly), "readonly property must be at true value in read mode test in db confg: "
280 + dbConfig);
281 }
282 }
283
284
285 initConfiguration(configFilename);
286
287
288 initI18n();
289
290
291 if (beanFactoryReferenceLocation != null) {
292 ServiceLocator.instance().init(
293 beanFactoryReferenceLocation,
294 beanRefFactoryReferenceId);
295 }
296 }
297
298 protected final Set<File> toDestroy = Sets.newHashSet();
299
300
301
302
303
304
305 public void addToDestroy(File dir) {
306 toDestroy.add(dir);
307 }
308
309
310
311
312
313
314
315
316
317 public void setProperty(File file, String key, String value) throws IOException {
318
319 Properties props = new Properties();
320 BufferedReader reader = Files.newReader(file, Charsets.UTF_8);
321 props.load(reader);
322 reader.close();
323
324
325 props.setProperty(key, value);
326 BufferedWriter writer = Files.newWriter(file, Charsets.UTF_8);
327 props.store(writer, "");
328 writer.close();
329 }
330
331
332
333
334
335
336
337
338
339
340 public void copyDb(File sourceDirectory, String targetDbDirectoryName, boolean readonly, Properties p) throws IOException {
341 File targetDirectory = getResourceDirectory(targetDbDirectoryName);
342 copyDb(sourceDirectory, targetDirectory, readonly, p, true);
343 }
344
345
346
347
348
349
350
351
352
353
354
355 public void copyDb(File sourceDirectory, File targetDirectory, boolean readonly, Properties p, boolean destroyAfterTest) throws IOException {
356 if (!sourceDirectory.exists()) {
357
358 if (log.isWarnEnabled()) {
359 log.warn("Could not find db at " + sourceDirectory + ", test [" +
360 testClass + "] is skipped.");
361 }
362 Assume.assumeTrue(false);
363 }
364
365 if (p != null) {
366 String jdbcUrl = Daos.getJdbcUrl(targetDirectory, getTestDbName());
367 Daos.fillConnectionProperties(p, jdbcUrl, "SA", "");
368 }
369
370
371 if (destroyAfterTest) {
372 addToDestroy(targetDirectory);
373 }
374
375 log.debug(String.format("Copy directory %s at %s", sourceDirectory.getPath(), targetDirectory.getPath()));
376 FileUtils.copyDirectory(sourceDirectory, targetDirectory);
377
378
379 log.debug(String.format("Set database properties with readonly=%s", readonly));
380 File dbConfig = new File(targetDirectory, getTestDbName() + ".properties");
381 setProperty(dbConfig, "readonly", String.valueOf(readonly));
382 }
383
384
385
386
387
388
389 protected void after(Description description) {
390 if (log.isInfoEnabled()) {
391 log.info("After test " + testClass);
392 }
393
394 ServiceLocator serviceLocator = ServiceLocator.instance();
395
396
397 if (enableDb() && serviceLocator.isOpen()) {
398 Properties connectionProperties = QuadrigeConfiguration.getInstance().getConnectionProperties();
399
400
401 if (Daos.isHsqlFileDatabase(Daos.getUrl(connectionProperties))) {
402 try {
403 Daos.shutdownDatabase(connectionProperties);
404 } catch (Exception e) {
405 if (log.isErrorEnabled()) {
406 log.error("Could not close database.", e);
407 }
408 witherror = true;
409 }
410 }
411
412
413 serviceLocator.shutdown();
414 }
415
416 if (!witherror) {
417 destroyDirectories(toDestroy, true);
418 }
419
420 if (beanFactoryReferenceLocation != null) {
421
422
423 ServiceLocator.instance().init(null, null);
424 }
425 }
426
427
428
429
430
431
432
433
434
435 public static File getTestSpecificDirectory(Class<?> testClass,
436 String name) throws IOException {
437
438 String tempDirPath = System.getProperty("java.io.tmpdir");
439 if (tempDirPath == null) {
440
441 tempDirPath = "";
442 if (log.isWarnEnabled()) {
443 log.warn("'\"java.io.tmpdir\" not defined");
444 }
445 }
446 File tempDirFile = new File(tempDirPath);
447
448
449 String dataBasePath = testClass.getName()
450 + File.separator
451 + name
452 + '_'
453 + BUILD_TIMESTAMP;
454 File databaseFile = new File(tempDirFile, dataBasePath);
455 FileUtils.forceMkdir(databaseFile);
456
457 return databaseFile;
458 }
459
460
461
462
463
464
465
466
467
468 public Connection createEmptyDb(String dbDirectory,
469 String dbName) throws SQLException {
470 File externalDbFile = getResourceDirectory(dbDirectory);
471 File scriptFile = new File(getHsqldbSrcCreateScript());
472 return createEmptyDb(externalDbFile, dbName, null, scriptFile);
473 }
474
475
476
477
478
479
480
481
482
483
484 public Connection createEmptyDb(String dbDirectory,
485 String dbName, Properties p) throws SQLException {
486 File externalDbFile = getResourceDirectory(dbDirectory);
487 File scriptFile = new File(getHsqldbSrcCreateScript());
488 return createEmptyDb(externalDbFile, dbName, p, scriptFile);
489 }
490
491
492
493
494
495
496
497
498
499
500
501 protected Connection createEmptyDb(
502 File directory,
503 String dbName,
504 Properties p,
505 File scriptFile) throws SQLException {
506
507 QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
508
509 if (log.isInfoEnabled()) {
510 log.info("Create new db at " + directory);
511 }
512 addToDestroy(directory);
513 String jdbcUrl = Daos.getJdbcUrl(directory, dbName);
514 String user = "SA";
515 String password = "";
516
517 p = (p == null) ? config.getConnectionProperties() : p;
518 Daos.fillConnectionProperties(p, jdbcUrl, user, password);
519
520 Assert.state(scriptFile.exists(), "Could not find db script at " + scriptFile);
521
522 DatabaseSchemaDao schemaDao = new DatabaseSchemaDaoImpl(config);
523 schemaDao.generateNewDb(directory, true, scriptFile, p, true);
524 Connection connection = Daos.createConnection(jdbcUrl, user, password);
525
526 if (log.isInfoEnabled()) {
527 log.info("Created connection at " + connection.getMetaData().getURL());
528 }
529 return connection;
530 }
531
532
533
534
535
536
537 public String getBuildEnvironment() {
538 return getBuildEnvironment(null);
539 }
540
541
542
543
544
545
546
547 protected String getBuildEnvironment(String defaultEnvironment) {
548 String buildEnv = System.getProperty("env");
549
550
551 if (buildEnv == null && StringUtils.isNotBlank(defaultEnvironment)) {
552 buildEnv = defaultEnvironment;
553 log.warn("Could not find build environment. Please add -Denv=<hsqldb|oracle|pgsql>. Test [" +
554 testClass + "] will use default environment : " + defaultEnvironment);
555 } else if (!"hsqldb".equals(buildEnv)
556 && !"oracle".equals(buildEnv)
557 && !"pgsql".equals(buildEnv)) {
558
559 if (log.isWarnEnabled()) {
560 log.warn("Could not find build environment. Please add -Denv=<hsqldb|oracle|pgsql>. Test [" +
561 testClass + "] will be skipped.");
562 }
563 Assume.assumeTrue(false);
564 }
565 return buildEnv;
566 }
567
568
569
570
571
572
573 protected String[] getConfigArgs() {
574 List<String> configArgs = Lists.newArrayList();
575 configArgs.addAll(Lists.newArrayList(
576 "--option", QuadrigeConfigurationOption.BASEDIR.getKey(), resourceDirectory.getAbsolutePath()));
577 if (dbDirectory != null) {
578 configArgs.addAll(Lists.newArrayList("--option", QuadrigeConfigurationOption.DB_DIRECTORY.getKey(), dbDirectory));
579 }
580 return configArgs.toArray(new String[configArgs.size()]);
581 }
582
583
584
585
586
587
588 protected void initConfiguration(String configFilename) {
589 String[] configArgs = getConfigArgs();
590 QuadrigeConfiguration config = new QuadrigeConfiguration(configFilename, configArgs);
591 QuadrigeConfiguration.setInstance(config);
592 }
593
594
595
596
597
598
599 protected void initI18n() throws IOException {
600 QuadrigeConfiguration config = QuadrigeConfiguration.getInstance();
601
602
603
604
605 File i18nDirectory = new File(config.getDataDirectory(), "i18n");
606 if (i18nDirectory.exists()) {
607
608 FileUtils.cleanDirectory(i18nDirectory);
609 }
610
611 FileUtils.forceMkdir(i18nDirectory);
612
613 if (log.isDebugEnabled()) {
614 log.debug("I18N directory: " + i18nDirectory);
615 }
616
617 Locale i18nLocale = config.getI18nLocale();
618
619 if (log.isDebugEnabled()) {
620 log.debug(String.format("Starts i18n with locale [%s] at [%s]",
621 i18nLocale, i18nDirectory));
622 }
623 I18n.init(new UserI18nInitializer(
624 i18nDirectory, new DefaultI18nInitializer(getI18nBundleName())),
625 i18nLocale);
626 }
627
628
629
630
631
632
633 protected String getI18nBundleName() {
634 return "quadrige3-core-shared-i18n";
635 }
636
637
638
639
640
641
642 protected String getTestDbName() {
643 return HSQLDB_SRC_DATABASE_NAME;
644 }
645
646
647
648 private boolean enableDb() {
649 return beanFactoryReferenceLocation == null || !beanFactoryReferenceLocation.contains("WithNoDb");
650 }
651
652 private void destroyDirectories(Set<File> toDestroy, boolean retry) {
653 if (CollectionUtils.isEmpty(toDestroy)) {
654 return;
655 }
656
657 Set<File> directoriesToRetry = Sets.newHashSet(toDestroy);
658 for (File file : toDestroy) {
659 if (file.exists()) {
660 if (log.isInfoEnabled()) {
661 log.info("Destroy directory: " + file);
662 }
663 try {
664 FileUtils.deleteDirectory(file);
665
666 } catch (IOException e) {
667 if (retry) {
668 if (log.isErrorEnabled()) {
669 log.error("Could not delete directory: " + file + ". Will retry later.");
670 }
671 directoriesToRetry.add(file);
672 }
673 else {
674 if (log.isErrorEnabled()) {
675 log.error("Could not delete directory: " + file + ". Please delete it manually.");
676 }
677 }
678 }
679 }
680 }
681
682 if (retry && CollectionUtils.isEmpty(directoriesToRetry)) {
683 destroyDirectories(directoriesToRetry, false);
684 }
685 }
686 }