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