View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2.builder;
18  
19  import static org.apache.commons.configuration2.TempDirUtils.newFile;
20  import static org.apache.commons.configuration2.TempDirUtils.newFolder;
21  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNotSame;
26  import static org.junit.jupiter.api.Assertions.assertNull;
27  import static org.junit.jupiter.api.Assertions.assertSame;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  import java.io.File;
32  import java.io.FileWriter;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.Writer;
36  import java.net.URL;
37  import java.nio.charset.StandardCharsets;
38  import java.nio.file.Files;
39  import java.nio.file.Path;
40  import java.nio.file.Paths;
41  import java.util.HashMap;
42  import java.util.Map;
43  
44  import org.apache.commons.configuration2.Configuration;
45  import org.apache.commons.configuration2.ConfigurationAssert;
46  import org.apache.commons.configuration2.FileBasedConfiguration;
47  import org.apache.commons.configuration2.PropertiesConfiguration;
48  import org.apache.commons.configuration2.XMLConfiguration;
49  import org.apache.commons.configuration2.XMLPropertiesConfiguration;
50  import org.apache.commons.configuration2.builder.fluent.Parameters;
51  import org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters;
52  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
53  import org.apache.commons.configuration2.ex.ConfigurationException;
54  import org.apache.commons.configuration2.io.FileHandler;
55  import org.apache.commons.configuration2.io.FileLocator;
56  import org.apache.commons.configuration2.io.FileLocatorUtils;
57  import org.apache.commons.configuration2.io.HomeDirectoryLocationStrategy;
58  import org.apache.commons.configuration2.io.URLConnectionOptions;
59  import org.junit.jupiter.api.Test;
60  import org.junit.jupiter.api.io.TempDir;
61  
62  /**
63   * Test class for {@code FileBasedConfigurationBuilder}.
64   */
65  public class TestFileBasedConfigurationBuilder {
66  
67      /** Constant for a test property name. */
68      private static final String PROP = "testProperty";
69  
70      /**
71       * Checks whether a test configuration was saved successfully.
72       *
73       * @param file the file to which the configuration was saved
74       * @param expValue the expected value of the test property
75       * @throws ConfigurationException if an error occurs
76       */
77      private static void checkSavedConfig(final File file, final int expValue) throws ConfigurationException {
78          final PropertiesConfiguration config = new PropertiesConfiguration();
79          final FileHandler handler = new FileHandler(config);
80          handler.load(file);
81          assertEquals(expValue, config.getInt(PROP));
82      }
83  
84      /** A folder for temporary files. */
85      @TempDir
86      public File tempFolder;
87  
88      /**
89       * Creates a test properties file with the given property value
90       *
91       * @param value the value for the test property
92       * @return the File object pointing to the test file
93       */
94      private File createTestFile(final int value) {
95          return assertDoesNotThrow(() -> {
96              final File file = newFile(tempFolder);
97              try (Writer out = new FileWriter(file)) {
98                  out.write(String.format("%s=%d", PROP, value));
99              }
100             return file;
101         });
102     }
103 
104     /**
105      * Tests whether auto save mode works.
106      */
107     @Test
108     void testAutoSave() throws ConfigurationException {
109         final File file = createTestFile(0);
110         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
111                 new FileBasedConfigurationBuilder<>(
112                         PropertiesConfiguration.class)
113                         .configure(new FileBasedBuilderParametersImpl()
114                                 .setFile(file));
115         assertFalse(builder.isAutoSave());
116         builder.setAutoSave(true);
117         assertTrue(builder.isAutoSave());
118         builder.setAutoSave(true); // should have no effect
119         final PropertiesConfiguration config = builder.getConfiguration();
120         config.setProperty(PROP, 1);
121         checkSavedConfig(file, 1);
122     }
123 
124     /**
125      * Tests whether auto save mode works with a properties configuration.
126      * This is related to CONFIGURATION-646.
127      */
128     @Test
129     void testAutoSaveWithPropertiesConfiguration() throws ConfigurationException, IOException {
130         final File file = newFile(tempFolder);
131         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
132                 new FileBasedConfigurationBuilder<>(
133                         PropertiesConfiguration.class)
134                         .configure(new FileBasedBuilderParametersImpl()
135                                 .setFile(file));
136         builder.setAutoSave(true);
137         final PropertiesConfiguration config = builder.getConfiguration();
138         config.setProperty(PROP, 1);
139         checkSavedConfig(file, 1);
140     }
141 
142     /**
143      * Tests that the auto save mechanism survives a reset of the builder's
144      * configuration.
145      */
146     @Test
147     void testAutoSaveWithReset() throws ConfigurationException {
148         final File file = createTestFile(0);
149         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
150                 new FileBasedConfigurationBuilder<>(
151                         PropertiesConfiguration.class)
152                         .configure(new FileBasedBuilderParametersImpl()
153                                 .setFile(file));
154         final PropertiesConfiguration config1 = builder.getConfiguration();
155         builder.setAutoSave(true);
156         builder.resetResult();
157         final PropertiesConfiguration config2 = builder.getConfiguration();
158         assertNotSame(config1, config2);
159         config2.setProperty(PROP, 1);
160         config1.setProperty(PROP, 2);
161         checkSavedConfig(file, 1);
162     }
163 
164     /**
165      * Tests whether the location can be changed after a configuration has been
166      * created.
167      */
168     @Test
169     void testChangeLocationAfterCreation() throws ConfigurationException {
170         final File file1 = createTestFile(1);
171         final File file2 = createTestFile(2);
172         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
173                 new FileBasedConfigurationBuilder<>(
174                         PropertiesConfiguration.class)
175                         .configure(new FileBasedBuilderParametersImpl()
176                                 .setFile(file1));
177         builder.getConfiguration();
178         builder.getFileHandler().setFile(file2);
179         builder.resetResult();
180         final PropertiesConfiguration config = builder.getConfiguration();
181         assertEquals(2, config.getInt(PROP));
182     }
183 
184     /**
185      * Tests whether it is possible to permanently change the location after a
186      * reset of parameters.
187      */
188     @Test
189     void testChangeLocationAfterReset() throws ConfigurationException {
190         final File file1 = createTestFile(1);
191         final File file2 = createTestFile(2);
192         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
193                 new FileBasedConfigurationBuilder<>(
194                         PropertiesConfiguration.class)
195                         .configure(new FileBasedBuilderParametersImpl()
196                                 .setFile(file1));
197         builder.getConfiguration();
198         builder.getFileHandler().setFile(file2);
199         builder.reset();
200         builder.configure(new FileBasedBuilderParametersImpl().setFile(file1));
201         PropertiesConfiguration config = builder.getConfiguration();
202         assertEquals(1, config.getInt(PROP));
203         builder.getFileHandler().setFile(file2);
204         builder.resetResult();
205         config = builder.getConfiguration();
206         assertEquals(2, config.getInt(PROP));
207     }
208 
209     /**
210      * Tests whether a configuration can be created and associated with a file that does
211      * not yet exist. Later the configuration is saved to this file.
212      */
213     @Test
214     void testCreateConfigurationNonExistingFileAndThenSave() throws ConfigurationException {
215         final File outFile = ConfigurationAssert.getOutFile("save.properties");
216         final Parameters parameters = new Parameters();
217         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder = new FileBasedConfigurationBuilder<>(
218                 PropertiesConfiguration.class, null, true).configure(parameters
219                 .properties().setFile(outFile));
220         final Configuration config = builder.getConfiguration();
221         config.setProperty(PROP, 1);
222         builder.save();
223         checkSavedConfig(outFile, 1);
224         assertTrue(outFile.delete());
225     }
226 
227     /**
228      * Tests whether auto save mode can be disabled again.
229      */
230     @Test
231     void testDisableAutoSave() throws ConfigurationException {
232         final File file = createTestFile(0);
233         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
234                 new FileBasedConfigurationBuilder<>(
235                         PropertiesConfiguration.class)
236                         .configure(new FileBasedBuilderParametersImpl()
237                                 .setFile(file));
238         final PropertiesConfiguration config = builder.getConfiguration();
239         builder.setAutoSave(true);
240         config.setProperty(PROP, 1);
241         builder.setAutoSave(false);
242         config.setProperty(PROP, 2);
243         builder.setAutoSave(false); // should have no effect
244         checkSavedConfig(file, 1);
245     }
246 
247     /**
248      * Tests whether HomeDirectoryLocationStrategy can be properly initialized
249      * and that it shouldn't throw {@code ConfigurationException} when
250      * everything is correctly in place. Without the code fix for
251      * <a href="https://issues.apache.org/jira/browse/CONFIGURATION-634">CONFIGURATION-634</a>,
252      * this test will throw {@code ConfigurationException}
253      * @throws IOException              Shouldn't happen
254      * @throws ConfigurationException   Shouldn't happen
255      */
256     @Test
257     void testFileBasedConfigurationBuilderWithHomeDirectoryLocationStrategy() throws IOException, ConfigurationException {
258         final String folderName = "test";
259         final String fileName = "sample.properties";
260         newFolder(folderName, tempFolder);
261         newFile(folderName + File.separator + fileName, tempFolder);
262         final FileBasedConfigurationBuilder<FileBasedConfiguration> homeDirConfigurationBuilder =
263                 new FileBasedConfigurationBuilder<>(
264                         PropertiesConfiguration.class);
265         final PropertiesBuilderParameters homeDirProperties =
266                 new Parameters().properties();
267         final HomeDirectoryLocationStrategy strategy =
268                 new HomeDirectoryLocationStrategy(
269                         tempFolder.getAbsolutePath(), true);
270         final FileBasedConfigurationBuilder<FileBasedConfiguration> builder =
271                 homeDirConfigurationBuilder.configure(homeDirProperties
272                         .setLocationStrategy(strategy).setBasePath(folderName)
273                         .setListDelimiterHandler(
274                                 new DefaultListDelimiterHandler(','))
275                         .setFileName(fileName));
276         assertDoesNotThrow(builder::getConfiguration);
277     }
278 
279     /**
280      * Tests whether a configuration is loaded from file if a location is provided.
281      */
282     @Test
283     void testGetConfigurationLoadFromFile() throws ConfigurationException {
284         final File file = createTestFile(1);
285         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder = new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
286             .configure(new FileBasedBuilderParametersImpl().setFile(file));
287         final PropertiesConfiguration config = builder.getConfiguration();
288         assertEquals(1, config.getInt(PROP));
289         assertSame(config, builder.getFileHandler().getContent());
290     }
291 
292     /**
293      * Tests whether a configuration is loaded from a JAR file if a location is provided. CONFIGURATION-794: Unclosed file
294      * handle when reading config from JAR file URL.
295      */
296     @Test
297     void testGetConfigurationLoadFromJarFile() throws ConfigurationException, IOException {
298         final URL jarResourceUrl = getClass().getClassLoader().getResource("org/apache/commons/configuration2/test.jar");
299         assertNotNull(jarResourceUrl);
300         final Path testJar = Paths.get(tempFolder.getAbsolutePath(), "test.jar");
301         try (InputStream inputStream = jarResourceUrl.openStream()) {
302             Files.copy(inputStream, testJar);
303         }
304         final URL url = new URL("jar:" + testJar.toUri() + "!/configuration.properties");
305 
306         //@formatter:off
307         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
308             new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
309                 .configure(new FileBasedBuilderParametersImpl()
310                 .setURL(url, new URLConnectionOptions().setUseCaches(false)));
311         //@formatter:off
312 
313 // CONFIGURATION-794
314 // the next line causes:
315 //        java.lang.AssertionError: Unable to clean up temporary folder C:\Users\ggregory\AppData\Local\Temp\junit7789840233804508643
316 //        at org.junit.Assert.fail(Assert.java:89)
317 //        at org.junit.rules.TemporaryFolder.delete(TemporaryFolder.java:274)
318 //        at org.junit.rules.TemporaryFolder.after(TemporaryFolder.java:138)
319 //        at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:59)
320 //        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
321 //        at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
322 //        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
323 //        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
324 //        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
325 //        at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
326 //        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
327 //        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
328 //        at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
329 //        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
330 //        at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
331 //        at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
332 //        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
333 //        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
334 //        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
335 //        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
336 //        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
337 //        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
338 
339         // builder contains the current FileHandler which loads the file.
340         final PropertiesConfiguration config = builder.getConfiguration();
341         assertEquals(1, config.getInt(PROP));
342         assertSame(config, builder.getFileHandler().getContent());
343     }
344 
345     /**
346      * Tests whether a configuration can be created if no location is set.
347      */
348     @Test
349     void testGetConfigurationNoLocation() throws ConfigurationException {
350         final Map<String, Object> params = new HashMap<>();
351         params.put("throwExceptionOnMissing", Boolean.TRUE);
352         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder = new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class, params);
353         final PropertiesConfiguration conf = builder.getConfiguration();
354         assertTrue(conf.isThrowExceptionOnMissing());
355         assertTrue(conf.isEmpty());
356     }
357 
358     /**
359      * Tests whether a default encoding can be determined even if it was set for
360      * an interface.
361      */
362     @Test
363     void testGetDefaultEncodingInterface() {
364         final String encoding = "testEncoding";
365         FileBasedConfigurationBuilder.setDefaultEncoding(Configuration.class, encoding);
366         assertEquals(encoding, FileBasedConfigurationBuilder.getDefaultEncoding(XMLConfiguration.class));
367         FileBasedConfigurationBuilder.setDefaultEncoding(Configuration.class, null);
368         assertNull(FileBasedConfigurationBuilder.getDefaultEncoding(XMLConfiguration.class));
369     }
370 
371     /**
372      * Tests whether a default encoding for properties configurations is
373      * defined.
374      */
375     @Test
376     void testGetDefaultEncodingProperties() {
377         assertEquals(PropertiesConfiguration.DEFAULT_ENCODING, FileBasedConfigurationBuilder.getDefaultEncoding(PropertiesConfiguration.class));
378     }
379 
380     /**
381      * Tests whether a default encoding is find even if a sub class is queried.
382      */
383     @Test
384     void testGetDefaultEncodingSubClass() {
385         final PropertiesConfiguration conf = new PropertiesConfiguration() {
386             // empty
387         };
388         assertEquals(PropertiesConfiguration.DEFAULT_ENCODING, FileBasedConfigurationBuilder.getDefaultEncoding(conf.getClass()));
389     }
390 
391     /**
392      * Tests whether a default encoding for XML properties configurations is
393      * defined.
394      */
395     @Test
396     void testGetDefaultEncodingXmlProperties() {
397         assertEquals(XMLPropertiesConfiguration.DEFAULT_ENCODING, FileBasedConfigurationBuilder.getDefaultEncoding(XMLPropertiesConfiguration.class));
398     }
399 
400     /**
401      * Tests whether the allowFailOnInit flag is correctly initialized.
402      */
403     @Test
404     void testInitAllowFailOnInitFlag() {
405         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
406                 new FileBasedConfigurationBuilder<>(
407                         PropertiesConfiguration.class, null, true);
408         assertTrue(builder.isAllowFailOnInit());
409     }
410 
411     /**
412      * Tests whether the default encoding can be overridden when initializing
413      * the file handler.
414      */
415     @Test
416     void testInitFileHandlerOverrideDefaultEncoding() throws ConfigurationException {
417         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
418                 new FileBasedConfigurationBuilder<>(
419                         PropertiesConfiguration.class);
420         final FileHandler handler = new FileHandler();
421         final String encoding = "testEncoding";
422         handler.setEncoding(encoding);
423         builder.initFileHandler(handler);
424         assertEquals(encoding, handler.getEncoding());
425     }
426 
427     /**
428      * Tests whether the default encoding is set for the file handler if none is
429      * specified.
430      */
431     @Test
432     void testInitFileHandlerSetDefaultEncoding() throws ConfigurationException {
433         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
434                 new FileBasedConfigurationBuilder<>(
435                         PropertiesConfiguration.class);
436         final FileHandler handler = new FileHandler();
437         builder.initFileHandler(handler);
438         assertEquals(PropertiesConfiguration.DEFAULT_ENCODING, handler.getEncoding());
439     }
440 
441     /**
442      * Tests whether the location in the FileHandler is fully defined. This
443      * ensures that saving writes to the expected file.
444      */
445     @Test
446     void testLocationIsFullyDefined() throws ConfigurationException {
447         final File file = createTestFile(1);
448         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
449                 new FileBasedConfigurationBuilder<>(
450                         PropertiesConfiguration.class)
451                         .configure(new FileBasedBuilderParametersImpl()
452                                 .setFile(file));
453         builder.getConfiguration();
454         final FileLocator locator = builder.getFileHandler().getFileLocator();
455         assertTrue(FileLocatorUtils.isFullyInitialized(locator));
456     }
457 
458     /**
459      * Tests that the location in the FileHandler remains the same if the
460      * builder's result is reset.
461      */
462     @Test
463     void testLocationSurvivesResetResult() throws ConfigurationException {
464         final File file = createTestFile(1);
465         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
466                 new FileBasedConfigurationBuilder<>(
467                         PropertiesConfiguration.class)
468                         .configure(new FileBasedBuilderParametersImpl()
469                                 .setFile(file));
470         final PropertiesConfiguration config = builder.getConfiguration();
471         builder.resetResult();
472         final PropertiesConfiguration config2 = builder.getConfiguration();
473         assertNotSame(config, config2);
474         assertEquals(1, config2.getInt(PROP));
475     }
476 
477     /**
478      * Tests whether a reset of the builder's initialization parameters also
479      * resets the file location.
480      */
481     @Test
482     void testResetLocation() throws ConfigurationException {
483         final File file = createTestFile(1);
484         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
485                 new FileBasedConfigurationBuilder<>(
486                         PropertiesConfiguration.class)
487                         .configure(new FileBasedBuilderParametersImpl()
488                                 .setFile(file));
489         builder.getConfiguration();
490         builder.reset();
491         final PropertiesConfiguration config = builder.getConfiguration();
492         assertTrue(config.isEmpty());
493         assertFalse(builder.getFileHandler().isLocationDefined());
494     }
495 
496     /**
497      * Tests whether the managed configuration can be saved.
498      */
499     @Test
500     void testSave() throws ConfigurationException {
501         final File file = createTestFile(1);
502         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
503                 new FileBasedConfigurationBuilder<>(
504                         PropertiesConfiguration.class)
505                         .configure(new FileBasedBuilderParametersImpl()
506                                 .setFile(file));
507         final PropertiesConfiguration config = builder.getConfiguration();
508         config.setProperty(PROP, 5);
509         builder.save();
510         checkSavedConfig(file, 5);
511     }
512 
513     /**
514      * Tests whether a new configuration can be saved to a file.
515      */
516     @Test
517     void testSaveNewFile() throws ConfigurationException, IOException {
518         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
519                 new FileBasedConfigurationBuilder<>(
520                         PropertiesConfiguration.class);
521         final PropertiesConfiguration config = builder.getConfiguration();
522         config.setProperty(PROP, 2);
523         final File file = newFile(tempFolder);
524         builder.getFileHandler().setFile(file);
525         builder.save();
526         checkSavedConfig(file, 2);
527     }
528 
529     /**
530      * Tries to set a default encoding for a null class.
531      */
532     @Test
533     void testSetDefaultEncodingNull() {
534         assertThrows(IllegalArgumentException.class, () -> FileBasedConfigurationBuilder.setDefaultEncoding(null, StandardCharsets.UTF_8.name()));
535     }
536 
537     /**
538      * Tests whether a file handler can be accessed and manipulated even if no
539      * file-based parameters are part of the initialization parameters.
540      */
541     @Test
542     void testSetLocationNoFileHandler() throws ConfigurationException {
543         final File file = createTestFile(1);
544         final FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
545                 new FileBasedConfigurationBuilder<>(
546                         PropertiesConfiguration.class);
547         builder.getFileHandler().setFile(file);
548         final PropertiesConfiguration config = builder.getConfiguration();
549         assertFalse(config.isEmpty());
550     }
551 }