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.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertNotSame;
22  import static org.junit.jupiter.api.Assertions.assertSame;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.mockito.Mockito.mock;
26  import static org.mockito.Mockito.verify;
27  import static org.mockito.Mockito.verifyNoMoreInteractions;
28  import static org.mockito.Mockito.when;
29  
30  import java.util.Collection;
31  import java.util.HashMap;
32  import java.util.HashSet;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.concurrent.CountDownLatch;
36  import java.util.concurrent.TimeUnit;
37  
38  import org.apache.commons.configuration2.BaseConfiguration;
39  import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
40  import org.apache.commons.configuration2.Configuration;
41  import org.apache.commons.configuration2.Initializable;
42  import org.apache.commons.configuration2.PropertiesConfiguration;
43  import org.apache.commons.configuration2.XMLConfiguration;
44  import org.apache.commons.configuration2.beanutils.BeanCreationContext;
45  import org.apache.commons.configuration2.beanutils.BeanDeclaration;
46  import org.apache.commons.configuration2.beanutils.BeanFactory;
47  import org.apache.commons.configuration2.beanutils.BeanHelper;
48  import org.apache.commons.configuration2.beanutils.DefaultBeanFactory;
49  import org.apache.commons.configuration2.beanutils.XMLBeanDeclaration;
50  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
51  import org.apache.commons.configuration2.convert.ListDelimiterHandler;
52  import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
53  import org.apache.commons.configuration2.event.ConfigurationEvent;
54  import org.apache.commons.configuration2.event.ErrorListenerTestImpl;
55  import org.apache.commons.configuration2.event.EventListener;
56  import org.apache.commons.configuration2.event.EventListenerRegistrationData;
57  import org.apache.commons.configuration2.event.EventListenerTestImpl;
58  import org.apache.commons.configuration2.ex.ConfigurationException;
59  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
60  import org.apache.commons.configuration2.reloading.ReloadingController;
61  import org.apache.commons.configuration2.reloading.ReloadingDetector;
62  import org.junit.jupiter.api.BeforeAll;
63  import org.junit.jupiter.api.Test;
64  
65  /**
66   * Test class for {@code BasicConfigurationBuilder}.
67   */
68  public class TestBasicConfigurationBuilder {
69  
70      /**
71       * A test thread class for testing whether the builder's result object can be requested concurrently.
72       */
73      private static final class AccessBuilderThread extends Thread {
74  
75          /** A latch for controlling the start of the thread. */
76          private final CountDownLatch startLatch;
77  
78          /** A latch for controlling the end of the thread. */
79          private final CountDownLatch endLatch;
80  
81          /** The builder to be accessed. */
82          private final ConfigurationBuilder<?> builder;
83  
84          /** The result object obtained from the builder. */
85          private volatile Object result;
86  
87          /**
88           * Creates a new instance of {@code AccessBuilderThread}.
89           *
90           * @param lstart the latch for controlling the thread start
91           * @param lend the latch for controlling the thread end
92           * @param bldr the builder to be tested
93           */
94          public AccessBuilderThread(final CountDownLatch lstart, final CountDownLatch lend, final ConfigurationBuilder<?> bldr) {
95              startLatch = lstart;
96              endLatch = lend;
97              builder = bldr;
98          }
99  
100         @Override
101         public void run() {
102             try {
103                 startLatch.await();
104                 result = builder.getConfiguration();
105             } catch (final Exception ex) {
106                 result = ex;
107             } finally {
108                 endLatch.countDown();
109             }
110         }
111     }
112 
113     /**
114      * A builder test implementation which allows checking exception handling when creating new configuration objects.
115      */
116     private static final class BasicConfigurationBuilderInitFailImpl extends BasicConfigurationBuilder<PropertiesConfiguration> {
117         public BasicConfigurationBuilderInitFailImpl(final boolean allowFailOnInit) {
118             super(PropertiesConfiguration.class, null, allowFailOnInit);
119         }
120 
121         /**
122          * {@inheritDoc} This implementation only throws an exception.
123          */
124         @Override
125         protected void initResultInstance(final PropertiesConfiguration obj) throws ConfigurationException {
126             throw new ConfigurationException("Initialization test exception.");
127         }
128     }
129 
130     /**
131      * A test configuration implementation which also implements Initializable.
132      */
133     public static class InitializableConfiguration extends BaseConfiguration implements Initializable {
134 
135         /** A property which is initialized if the builder works as expected. */
136         private String initProperty;
137 
138         public String getInitProperty() {
139             return initProperty;
140         }
141 
142         /**
143          * Sets the value of the initProperty member based on other flag values. This tests whether the method is called after
144          * other properties have been set.
145          */
146         @Override
147         public void initialize() {
148             initProperty = "Initialized with flag " + isThrowExceptionOnMissing();
149         }
150     }
151 
152     /** A test list delimiter handler. */
153     private static ListDelimiterHandler listHandler;
154 
155     /**
156      * Creates a mock for an event listener.
157      *
158      * @return the event listener mock
159      */
160     @SuppressWarnings("unchecked")
161     private static EventListener<ConfigurationEvent> createEventListener() {
162         return mock(EventListener.class);
163     }
164 
165     /**
166      * Creates a map with test initialization parameters.
167      *
168      * @return the map with parameters
169      */
170     private static Map<String, Object> createTestParameters() {
171         final Map<String, Object> params = new HashMap<>();
172         params.put("throwExceptionOnMissing", Boolean.TRUE);
173         params.put("listDelimiterHandler", listHandler);
174         return params;
175     }
176 
177     @BeforeAll
178     public static void setUpBeforeClass() throws Exception {
179         listHandler = new DefaultListDelimiterHandler(';');
180     }
181 
182     /**
183      * Tests whether configuration listeners can be added.
184      */
185     @Test
186     void testAddConfigurationListener() throws ConfigurationException {
187         final EventListener<ConfigurationEvent> l1 = createEventListener();
188         final EventListener<ConfigurationEvent> l2 = createEventListener();
189         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
190         builder.addEventListener(ConfigurationEvent.ANY, l1);
191         final PropertiesConfiguration config = builder.getConfiguration();
192         builder.addEventListener(ConfigurationEvent.ANY, l2);
193         final Collection<EventListener<? super ConfigurationEvent>> listeners = config.getEventListeners(ConfigurationEvent.ANY);
194         assertTrue(listeners.contains(l1));
195         assertTrue(listeners.contains(l2));
196     }
197 
198     /**
199      * Tests whether additional parameters can be added.
200      */
201     @Test
202     void testAddParameters() {
203         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
204             createTestParameters());
205         final Map<String, Object> params = createTestParameters();
206         params.put("anotherParameter", "value");
207         assertSame(builder, builder.addParameters(params));
208         final Map<String, Object> params2 = builder.getParameters();
209         assertTrue(params2.keySet().containsAll(createTestParameters().keySet()));
210         assertEquals("value", params2.get("anotherParameter"));
211     }
212 
213     /**
214      * Tests whether null parameters are handled correctly by addParameters().
215      */
216     @Test
217     void testAddParametersNull() {
218         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
219             createTestParameters());
220         final Map<String, Object> params = builder.getParameters();
221         builder.addParameters(null);
222         assertEquals(params, builder.getParameters());
223     }
224 
225     /**
226      * Tests whether a configured BeanHelper is used for result creation.
227      */
228     @Test
229     void testBeanHelperInConfiguration() throws ConfigurationException {
230         final Set<Class<?>> classesPassedToFactory = new HashSet<>();
231         final BeanFactory factory = new DefaultBeanFactory() {
232             @Override
233             public Object createBean(final BeanCreationContext bcc) throws Exception {
234                 classesPassedToFactory.add(bcc.getBeanClass());
235                 return super.createBean(bcc);
236             }
237         };
238         final BeanHelper helper = new BeanHelper(factory);
239         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
240         builder.configure(new BasicBuilderParameters().setBeanHelper(helper));
241         final PropertiesConfiguration config = builder.getConfiguration();
242         assertTrue(classesPassedToFactory.contains(config.getClass()));
243     }
244 
245     /**
246      * Tests whether parameters can be set using the configure() method.
247      */
248     @Test
249     void testConfigure() {
250         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class)
251             .configure(new BasicBuilderParameters().setListDelimiterHandler(listHandler).setThrowExceptionOnMissing(true));
252         final Map<String, Object> params2 = new HashMap<>(builder.getParameters());
253         assertEquals(createTestParameters(), params2);
254     }
255 
256     /**
257      * Tests whether a builder can be connected to a reloading controller.
258      */
259     @Test
260     void testConnectToReloadingController() throws ConfigurationException {
261         final ReloadingDetector detector = mock(ReloadingDetector.class);
262         final ReloadingController controller = new ReloadingController(detector);
263         final BasicConfigurationBuilder<Configuration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
264         final Configuration configuration = builder.getConfiguration();
265 
266         when(detector.isReloadingRequired()).thenReturn(Boolean.TRUE);
267 
268         builder.connectToReloadingController(controller);
269         controller.checkForReloading(null);
270         assertTrue(controller.isInReloadingState());
271         assertNotSame(configuration, builder.getConfiguration());
272         assertFalse(controller.isInReloadingState());
273 
274         verify(detector).isReloadingRequired();
275         verify(detector).reloadingPerformed();
276         verifyNoMoreInteractions(detector);
277     }
278 
279     /**
280      * Tries to connect to a null reloading controller.
281      */
282     @Test
283     void testConnectToReloadingControllerNull() {
284         final BasicConfigurationBuilder<Configuration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
285         assertThrows(IllegalArgumentException.class, () -> builder.connectToReloadingController(null));
286     }
287 
288     /**
289      * Tests whether event listeners can be copied to another builder.
290      */
291     @Test
292     void testCopyEventListeners() throws ConfigurationException {
293         final EventListener<ConfigurationEvent> l1 = createEventListener();
294         final EventListener<ConfigurationEvent> l2 = createEventListener();
295         final EventListener<ConfigurationErrorEvent> l3 = new ErrorListenerTestImpl(null);
296         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
297         builder.addEventListener(ConfigurationEvent.ANY, l1);
298         builder.addEventListener(ConfigurationEvent.ANY_HIERARCHICAL, l2);
299         builder.addEventListener(ConfigurationErrorEvent.ANY, l3);
300         final BasicConfigurationBuilder<XMLConfiguration> builder2 = new BasicConfigurationBuilder<>(XMLConfiguration.class);
301         builder.copyEventListeners(builder2);
302         final XMLConfiguration config = builder2.getConfiguration();
303         Collection<EventListener<? super ConfigurationEvent>> listeners = config.getEventListeners(ConfigurationEvent.ANY);
304         assertEquals(1, listeners.size());
305         assertTrue(listeners.contains(l1));
306         listeners = config.getEventListeners(ConfigurationEvent.ANY_HIERARCHICAL);
307         assertEquals(2, listeners.size());
308         assertTrue(listeners.contains(l1));
309         assertTrue(listeners.contains(l2));
310         final Collection<EventListener<? super ConfigurationErrorEvent>> errListeners = config.getEventListeners(ConfigurationErrorEvent.ANY);
311         assertEquals(1, errListeners.size());
312         assertTrue(errListeners.contains(l3));
313     }
314 
315     /**
316      * Tests whether configuration listeners can be defined via the configure() method.
317      */
318     @Test
319     void testEventListenerConfiguration() throws ConfigurationException {
320         final EventListenerTestImpl listener1 = new EventListenerTestImpl(null);
321         final EventListenerRegistrationData<ConfigurationErrorEvent> regData = new EventListenerRegistrationData<>(ConfigurationErrorEvent.WRITE,
322             new ErrorListenerTestImpl(null));
323         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class)
324             .configure(new EventListenerParameters().addEventListener(ConfigurationEvent.ANY, listener1).addEventListener(regData));
325         final PropertiesConfiguration config = builder.getConfiguration();
326         assertTrue(config.getEventListeners(ConfigurationEvent.ANY).contains(listener1));
327         assertTrue(config.getEventListeners(regData.getEventType()).contains(regData.getListener()));
328     }
329 
330     /**
331      * Tests whether the builder can create a correctly initialized configuration object.
332      */
333     @Test
334     void testGetConfiguration() throws ConfigurationException {
335         final PropertiesConfiguration config = new BasicConfigurationBuilder<>(PropertiesConfiguration.class)
336             .configure(new BasicBuilderParameters().setListDelimiterHandler(listHandler).setThrowExceptionOnMissing(true)).getConfiguration();
337         assertTrue(config.isThrowExceptionOnMissing());
338         assertEquals(listHandler, config.getListDelimiterHandler());
339     }
340 
341     /**
342      * Tests whether the builder can be accessed by multiple threads and that only a single result object is produced.
343      */
344     @Test
345     void testGetConfigurationConcurrently() throws Exception {
346         final int threadCount = 32;
347         final CountDownLatch startLatch = new CountDownLatch(1);
348         final CountDownLatch endLatch = new CountDownLatch(threadCount);
349         final ConfigurationBuilder<?> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
350         final AccessBuilderThread[] threads = new AccessBuilderThread[threadCount];
351         for (int i = 0; i < threadCount; i++) {
352             threads[i] = new AccessBuilderThread(startLatch, endLatch, builder);
353             threads[i].start();
354         }
355         startLatch.countDown();
356         assertTrue(endLatch.await(5, TimeUnit.SECONDS));
357         final Set<Object> results = new HashSet<>();
358         for (final AccessBuilderThread t : threads) {
359             results.add(t.result);
360         }
361         assertEquals(1, results.size());
362     }
363 
364     /**
365      * Tests that the map with parameters cannot be modified.
366      */
367     @Test
368     void testGetParametersModify() {
369         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
370             createTestParameters());
371         final Map<String, Object> parameters = builder.getParameters();
372         assertThrows(UnsupportedOperationException.class, parameters::clear);
373     }
374 
375     /**
376      * Tests whether a check for the correct bean class is made.
377      */
378     @Test
379     void testGetResultDeclarationInvalidBeanClass() {
380         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class,
381             createTestParameters()) {
382             @Override
383             protected BeanDeclaration createResultDeclaration(final Map<String, Object> params) {
384                 return new XMLBeanDeclaration(new BaseHierarchicalConfiguration(), "bean", true, Object.class.getName());
385             }
386         };
387         assertThrows(ConfigurationRuntimeException.class, builder::getConfiguration);
388     }
389 
390     /**
391      * Tests whether a configuration implementing {@code Initializable} is correctly handled.
392      */
393     @Test
394     void testInitializableCalled() throws ConfigurationException {
395         final BasicConfigurationBuilder<InitializableConfiguration> builder = new BasicConfigurationBuilder<>(InitializableConfiguration.class);
396         builder.configure(new BasicBuilderParameters().setThrowExceptionOnMissing(true));
397         final InitializableConfiguration config = builder.getConfiguration();
398         assertEquals("Initialized with flag true", config.getInitProperty());
399     }
400 
401     /**
402      * Tests an exception during configuration initialization if the allowFailOnInit flag is true.
403      */
404     @Test
405     void testInitializationErrorAllowed() throws ConfigurationException {
406         final BasicConfigurationBuilderInitFailImpl builder = new BasicConfigurationBuilderInitFailImpl(true);
407         final PropertiesConfiguration config = builder.getConfiguration();
408         assertTrue(config.isEmpty());
409     }
410 
411     /**
412      * Tests an exception during configuration initialization if the allowFailOnInit flag is false.
413      */
414     @Test
415     void testInitializationErrorNotAllowed() {
416         final BasicConfigurationBuilderInitFailImpl builder = new BasicConfigurationBuilderInitFailImpl(false);
417         assertThrows(ConfigurationException.class, builder::getConfiguration);
418     }
419 
420     /**
421      * Tries to create an instance without a result class.
422      */
423     @Test
424     void testInitNoClass() {
425         assertThrows(IllegalArgumentException.class, () -> new BasicConfigurationBuilder<Configuration>(null));
426     }
427 
428     /**
429      * Tests whether initialization parameters can be passed to the constructor.
430      */
431     @Test
432     void testInitWithParameters() {
433         final Map<String, Object> params = createTestParameters();
434         final BasicConfigurationBuilder<Configuration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class, params);
435         final Map<String, Object> params2 = new HashMap<>(builder.getParameters());
436         assertEquals(createTestParameters(), params2);
437     }
438 
439     /**
440      * Tests whether a copy of the passed in parameters is created.
441      */
442     @Test
443     void testInitWithParametersDefensiveCopy() {
444         final Map<String, Object> params = createTestParameters();
445         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class, params);
446         params.put("anotherParameter", "value");
447         final Map<String, Object> params2 = new HashMap<>(builder.getParameters());
448         assertEquals(createTestParameters(), params2);
449     }
450 
451     /**
452      * Tests whether null parameters are handled correctly.
453      */
454     @Test
455     void testInitWithParametersNull() {
456         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class, null);
457         assertTrue(builder.getParameters().isEmpty());
458     }
459 
460     /**
461      * Tests whether configuration listeners can be removed.
462      */
463     @Test
464     void testRemoveConfigurationListener() throws ConfigurationException {
465         final EventListener<ConfigurationEvent> l1 = createEventListener();
466         final EventListener<ConfigurationEvent> l2 = createEventListener();
467         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class);
468         builder.addEventListener(ConfigurationEvent.ANY_HIERARCHICAL, l1);
469         builder.addEventListener(ConfigurationEvent.ANY, l2);
470         assertTrue(builder.removeEventListener(ConfigurationEvent.ANY, l2));
471         final PropertiesConfiguration config = builder.getConfiguration();
472         assertFalse(config.getEventListeners(ConfigurationEvent.ANY).contains(l2));
473         assertTrue(config.getEventListeners(ConfigurationEvent.ANY_HIERARCHICAL).contains(l1));
474         builder.removeEventListener(ConfigurationEvent.ANY_HIERARCHICAL, l1);
475         assertFalse(config.getEventListeners(ConfigurationEvent.ANY_HIERARCHICAL).contains(l1));
476     }
477 
478     /**
479      * Tests whether configuration listeners are removed from the managed configuration when the builder's result object is
480      * reset.
481      */
482     @Test
483     void testRemoveConfigurationListenersOnReset() throws ConfigurationException {
484         final EventListenerTestImpl listener = new EventListenerTestImpl(null);
485         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class)
486             .configure(new EventListenerParameters().addEventListener(ConfigurationEvent.ANY, listener));
487         final PropertiesConfiguration config = builder.getConfiguration();
488         builder.resetResult();
489         config.addProperty("foo", "bar");
490         listener.done();
491     }
492 
493     /**
494      * Tests whether parameters starting with a reserved prefix are filtered out before result objects are initialized.
495      */
496     @Test
497     void testReservedParameter() throws ConfigurationException {
498         final Map<String, Object> params = new HashMap<>();
499         params.put("throwExceptionOnMissing", Boolean.TRUE);
500         params.put("config-test", "a test");
501         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class, params);
502         final PropertiesConfiguration config = builder.getConfiguration();
503         assertTrue(config.isThrowExceptionOnMissing());
504     }
505 
506     /**
507      * Tests a full reset of the builder.
508      */
509     @Test
510     void testReset() throws ConfigurationException {
511         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
512             createTestParameters());
513         final PropertiesConfiguration config = builder.getConfiguration();
514         builder.reset();
515         final PropertiesConfiguration config2 = builder.getConfiguration();
516         assertNotSame(config, config2);
517         assertFalse(config2.isThrowExceptionOnMissing());
518     }
519 
520     /**
521      * Tests whether all parameters can be reset.
522      */
523     @Test
524     void testResetParameters() {
525         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
526             createTestParameters());
527         builder.resetParameters();
528         assertTrue(builder.getParameters().isEmpty());
529     }
530 
531     /**
532      * Tests whether a reset of the result object can be performed.
533      */
534     @Test
535     void testResetResult() throws ConfigurationException {
536         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class,
537             createTestParameters());
538         final PropertiesConfiguration config = builder.getConfiguration();
539         builder.resetResult();
540         final PropertiesConfiguration config2 = builder.getConfiguration();
541         assertNotSame(config, config2);
542         assertTrue(config2.isThrowExceptionOnMissing());
543     }
544 
545     /**
546      * Tests whether new parameters can be set to replace existing ones.
547      */
548     @Test
549     void testSetParameters() {
550         final Map<String, Object> params1 = new HashMap<>();
551         params1.put("someParameter", "value");
552         final BasicConfigurationBuilder<PropertiesConfiguration> builder = new BasicConfigurationBuilder<>(PropertiesConfiguration.class, params1);
553         assertSame(builder, builder.setParameters(createTestParameters()));
554         final Map<String, Object> params2 = new HashMap<>(builder.getParameters());
555         assertEquals(createTestParameters(), params2);
556     }
557 }