View Javadoc
1   package fr.ifremer.tutti.persistence.test;
2   
3   /*
4    * #%L
5    * Tutti :: Persistence
6    * %%
7    * Copyright (C) 2012 - 2014 Ifremer
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.Preconditions;
27  import com.google.common.base.Predicate;
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.tutti.TuttiConfiguration;
32  import fr.ifremer.tutti.TuttiConfigurationOption;
33  import fr.ifremer.tutti.persistence.service.TuttiPersistenceServiceLocator;
34  import fr.ifremer.tutti.util.Jdbcs;
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.junit.Assume;
41  import org.junit.rules.TestRule;
42  import org.junit.runner.Description;
43  import org.junit.runners.model.Statement;
44  import org.nuiton.config.ApplicationConfig;
45  import org.nuiton.converter.ConverterUtil;
46  import org.nuiton.util.FileUtil;
47  
48  import java.io.BufferedReader;
49  import java.io.BufferedWriter;
50  import java.io.File;
51  import java.io.IOException;
52  import java.io.InputStream;
53  import java.io.OutputStream;
54  import java.util.List;
55  import java.util.Properties;
56  import java.util.Set;
57  
58  /**
59   * To box the persistence service as a test resource.
60   *
61   * @author Tony Chemit - chemit@codelutin.com
62   * @since 0.3
63   */
64  public class DatabaseResource implements TestRule {
65  
66      /** Logger. */
67      private static final Log log = LogFactory.getLog(DatabaseResource.class);
68  
69      public static long BUILD_TIMESTAMP = System.nanoTime();
70  
71      private File resourceDirectory;
72  
73      private TuttiConfiguration config;
74  
75      private DatabaseFixtures fixtures;
76  
77      protected final String beanFactoryReferenceLocation;
78  
79      private final String beanRefFactoryReferenceId;
80  
81      private final boolean writeDb;
82  
83      private String dbName;
84  
85      private boolean destroyResources;
86  
87      public static DatabaseResource readDb() {
88          return new DatabaseResource("");
89      }
90  
91      public static DatabaseResource writeDb() {
92          return new DatabaseResource("", true);
93      }
94  
95      public static DatabaseResource readDb(String dbName) {
96          return new DatabaseResource(dbName);
97      }
98  
99      public static DatabaseResource writeDb(String dbName) {
100         return new DatabaseResource(dbName, true);
101     }
102 
103     public static DatabaseResource noDb() {
104         return new DatabaseResource(
105                 "", "beanRefFactoryWitNoDb.xml", "TuttiBeanRefFactoryWithNoDb");
106     }
107 
108     protected DatabaseResource(String dbName) {
109         this(dbName, null, null, false);
110     }
111 
112     protected DatabaseResource(String dbName, boolean writeDb) {
113         this(dbName, null, null, writeDb);
114     }
115 
116     protected DatabaseResource(String dbName, String beanFactoryReferenceLocation,
117                                String beanRefFactoryReferenceId) {
118         this(dbName, beanFactoryReferenceLocation,
119              beanRefFactoryReferenceId, false);
120     }
121 
122     protected DatabaseResource(String dbName, String beanFactoryReferenceLocation,
123                                String beanRefFactoryReferenceId,
124                                boolean writeDb) {
125         this.dbName = dbName;
126         this.beanFactoryReferenceLocation = beanFactoryReferenceLocation;
127         this.beanRefFactoryReferenceId = beanRefFactoryReferenceId;
128         this.writeDb = writeDb;
129         this.destroyResources = true;
130     }
131 
132     public boolean withDb() {
133         return dbName!=null;
134     }
135 
136 
137     public TuttiConfiguration getConfig() {
138         return config;
139     }
140 
141     public DatabaseFixtures getFixtures() {
142         return fixtures;
143     }
144 
145     public File getResourceDirectory(String name) {
146         return new File(resourceDirectory, name);
147     }
148 
149     public void setDestroyResources(boolean destroyResources) {
150         this.destroyResources = destroyResources;
151     }
152 
153     public String getDbName() {
154         return dbName;
155     }
156 
157     public boolean isWriteDb() {
158         return writeDb;
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                 } finally {
171                     after(description);
172                 }
173             }
174         };
175     }
176 
177     Class<?> testClass;
178 
179     public void prepareConfig(ApplicationConfig applicationConfig,
180                               File resourceDirectory) {
181 
182         TuttiConfiguration.getDefaultApplicationConfig(applicationConfig);
183 
184         applicationConfig.setDefaultOption(TuttiConfigurationOption.DATA_DIRECTORY.getKey(),
185                                            new File(resourceDirectory, "data").getAbsolutePath());
186 
187         if (!writeDb) {
188 
189             // set tutti.persistence.db.directory
190             File dbDirectory = FileUtil.getFileFromPaths(new File("src"), "test", "data", dbName);
191 
192             applicationConfig.setDefaultOption(TuttiConfigurationOption.DB_DIRECTORY.getKey(),
193                                                dbDirectory.getAbsolutePath());
194 
195             // set tutti.persistence.jdbc.url
196             applicationConfig.setDefaultOption(TuttiConfigurationOption.JDBC_URL.getKey(),
197                                                String.format("jdbc:hsqldb:file:%s/allegro", dbDirectory.getAbsolutePath()));
198 
199         }
200     }
201 
202     public File copyClassPathResource(String path, String destinationName) throws IOException {
203         InputStream inputStream = getClass().getResourceAsStream("/" + path);
204         Preconditions.checkNotNull(inputStream, "Could not find " + path + " in test class-path");
205         File output = new File(resourceDirectory, destinationName);
206 
207         OutputStream outputStream = FileUtils.openOutputStream(output);
208         try {
209             IOUtils.copy(inputStream, outputStream);
210             inputStream.close();
211             outputStream.close();
212         } finally {
213             IOUtils.closeQuietly(inputStream);
214             IOUtils.closeQuietly(outputStream);
215         }
216         return output;
217     }
218 
219     protected void before(Description description) throws Throwable {
220 
221         TuttiTestSupport.registerDescriptionAndResource(description, this);
222 
223         testClass = description.getTestClass();
224 
225         boolean withDb = withDb();
226 
227         boolean defaultDbName = withDb  && StringUtils.isEmpty(dbName);
228 
229         if (defaultDbName) {
230             dbName = "db";
231         }
232 
233         if (log.isInfoEnabled()) {
234             log.info("Prepare test " + testClass);
235         }
236 
237         fixtures = new DatabaseFixtures();
238 
239         resourceDirectory = FileUtil.getTestSpecificDirectory(testClass, "", description.getMethodName(), BUILD_TIMESTAMP);
240         addToDestroy(resourceDirectory);
241 
242         ConverterUtil.deregister();
243         ConverterUtil.initConverters();
244 
245         ApplicationConfig applicationConfig= createApplicationConfig(defaultDbName);
246 
247         prepareConfig(applicationConfig, resourceDirectory);
248 
249         applicationConfig.parse();
250 
251         config = new TuttiConfiguration(applicationConfig);
252         TuttiConfiguration.setInstance(config);
253 
254         if (withDb) {
255             prepareDb();
256         }
257 
258         config.initConfig();
259 
260         if (withDb) {
261             if (log.isInfoEnabled()) {
262                 log.info("Use db: " + config.getJdbcUrl());
263             }
264         } else {
265             if (log.isInfoEnabled()) {
266                 log.info("No db configured.");
267             }
268         }
269 
270         if (beanFactoryReferenceLocation != null) {
271             TuttiPersistenceServiceLocator.initTutti(
272                     beanFactoryReferenceLocation,
273                     beanRefFactoryReferenceId);
274         }
275     }
276 
277     protected ApplicationConfig createApplicationConfig(boolean defaultDbName) {
278 
279         ApplicationConfig applicationConfig=null;
280         if (withDb()) {
281 
282             String configFilename = writeDb ?
283                                     "tutti-test-write" :
284                                     "tutti-test-read";
285             if (!defaultDbName) {
286                 configFilename += "-" + dbName;
287             }
288 
289             configFilename += ".properties";
290             InputStream resourceAsStream = getClass().getResourceAsStream("/" + configFilename);
291 
292             if (resourceAsStream!=null) {
293 
294                 if (log.isInfoEnabled()) {
295                     log.info("Use configuration file found in classpath at " + configFilename);
296                 }
297 
298                 IOUtils.closeQuietly(resourceAsStream);
299 
300                 applicationConfig = new ApplicationConfig(configFilename);
301 
302             }
303 
304         }
305 
306         if (applicationConfig==null) {
307 
308             if (log.isInfoEnabled()) {
309                 log.info("Use default configuration, with no configuration from class-path");
310             }
311             applicationConfig = new ApplicationConfig();
312         }
313 
314         return applicationConfig;
315 
316     }
317 
318     protected void prepareDb() throws IOException {
319 
320         File db;
321 
322         if (writeDb) {
323 
324             // check db exist in src/test/data/dbName
325             db = FileUtil.getFileFromPaths(new File("src"), "test", "data", dbName);
326 
327         } else {
328             // check config.dbDirectory exist
329 
330             db = config.getDbDirectory();
331         }
332 
333         if (!db.exists()) {
334 
335             if (log.isWarnEnabled()) {
336                 log.warn("Could not find db at " + db + ", test [" + testClass + "] is skipped.");
337             }
338             Assume.assumeTrue(false);
339         }
340 
341         if (writeDb) {
342             copyDb(config.getDbDirectory(), false, null);
343         }
344 
345         addToDestroy(config.getDbAttachmentDirectory());
346 
347         // load db config
348         File dbConfig = new File(config.getDbDirectory(), config.getDbName() + ".properties");
349         Properties p = new Properties();
350         BufferedReader reader = Files.newReader(dbConfig, Charsets.UTF_8);
351         p.load(reader);
352         reader.close();
353 
354         if (log.isDebugEnabled()) {
355             log.debug("Db config: " + dbConfig + "\n" + p);
356         }
357 
358         if (writeDb) {
359 
360             // make sure db is on readonly mode
361             String readonly = p.getProperty("readonly");
362             Preconditions.checkNotNull(readonly, "Could not find readonly property on db confg: " + dbConfig);
363             Preconditions.checkState("false".equals(readonly), "readonly property must be at false value in write mode test in  db confg: " + dbConfig);
364         } else {
365             // make sure db is on readonly mode
366             String readonly = p.getProperty("readonly");
367             Preconditions.checkNotNull(readonly, "Could not find readonly property on db confg: " + dbConfig);
368             Preconditions.checkState("true".equals(readonly), "readonly property must be at true value in read mode test in  db confg: " + dbConfig);
369         }
370 
371     }
372 
373     protected final Set<File> toDetroy = Sets.newHashSet();
374 
375     public void addToDestroy(File dir) {
376         toDetroy.add(dir);
377     }
378 
379     public void copyDb(String dbDirectory, boolean readonly, Properties p) throws IOException {
380         File externalDbFile = getResourceDirectory(dbDirectory);
381         copyDb(externalDbFile, readonly, p);
382     }
383 
384     public void copyDb(File target, boolean readonly, Properties p) throws IOException {
385         File db = FileUtil.getFileFromPaths(new File("src"), "test", "data", dbName);
386         if (!db.exists()) {
387 
388             if (log.isWarnEnabled()) {
389                 log.warn("Could not find db at " + db + ", test [" + testClass + "] is skipped.");
390             }
391             Assume.assumeTrue(false);
392         }
393         addToDestroy(target);
394         FileUtils.copyDirectory(db, target);
395         if (p != null) {
396             Jdbcs.fillConnectionProperties(
397                     p,
398                     Jdbcs.getJdbcUrl(target, config.getDbName()),
399                     config.getJdbcUsername(),
400                     config.getJdbcPassword());
401         }
402 
403         // load db config
404         File dbConfig = new File(target, config.getDbName() + ".properties");
405         Properties dbconf = new Properties();
406         BufferedReader reader = Files.newReader(dbConfig, Charsets.UTF_8);
407         dbconf.load(reader);
408         reader.close();
409 
410         // switch readonly flag according to the write parameter
411         dbconf.setProperty("readonly", String.valueOf(readonly));
412         BufferedWriter writer = Files.newWriter(dbConfig, Charsets.UTF_8);
413         dbconf.store(writer, "");
414         writer.close();
415 
416     }
417 
418     public void cleanResources(Description description) {
419 
420         if (log.isDebugEnabled()) {
421             log.debug("Clean resources for test: " + description);
422         }
423 
424         if (destroyResources) {
425 
426             // can destroy directories
427             for (File file : toDetroy) {
428                 if (file.exists()) {
429                     if (log.isInfoEnabled()) {
430                         log.info("Destroy directory: " + file);
431                     }
432                     try {
433                         FileUtils.deleteDirectory(file);
434                     } catch (IOException e) {
435                         if (log.isErrorEnabled()) {
436                             log.error("Could not delete directory: " + file, e);
437                         }
438                     }
439                 }
440             }
441         } else {
442 
443             if (log.isWarnEnabled()) {
444                 log.warn("Won't destroy directories (destroyResources flag was false).");
445             }
446 
447         }
448 
449         toDetroy.clear();
450 
451     }
452 
453     protected void after(Description description) {
454         if (log.isInfoEnabled()) {
455             log.info("After test " + description);
456         }
457 
458         closeSpring();
459         TuttiConfiguration.setInstance(null);
460 
461         if (description.getMethodName() == null) {
462 
463             // rule used on a class
464             CleanResourcesRule.cleanResources(description);
465 
466         }
467 
468     }
469 
470     protected void closeSpring() {
471 
472         TuttiPersistenceServiceLocator.shutdownTutti();
473 
474         if (beanFactoryReferenceLocation != null) {
475 
476             // push back default tutti configuration
477             TuttiPersistenceServiceLocator.initTuttiDefault();
478         }
479     }
480 
481     protected List<String> getImportScriptSql(File scriptFile) throws IOException {
482         List<String> lines = Files.readLines(scriptFile, Charsets.UTF_8);
483 
484         List<String> result = Lists.newArrayListWithCapacity(lines.size());
485 
486         Predicate<String> predicate = new Predicate<String>() {
487 
488             Set<String> forbiddenStarts = Sets.newHashSet(
489                     "SET ",
490                     "CREATE USER ",
491                     "CREATE SCHEMA ",
492                     "GRANT DBA TO ");
493 
494             @Override
495             public boolean apply(String input) {
496                 boolean accept = true;
497                 for (String forbiddenStart : forbiddenStarts) {
498                     if (input.startsWith(forbiddenStart)) {
499                         accept = false;
500                         break;
501                     }
502                 }
503                 return accept;
504             }
505         };
506         for (String line : lines) {
507             if (predicate.apply(line.trim().toUpperCase())) {
508                 result.add(line);
509             }
510         }
511         return result;
512     }
513 }