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