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