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