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.combined;
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.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNotSame;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  import static org.mockito.Mockito.mock;
27  
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  
32  import org.apache.commons.configuration2.ConfigurationLookup;
33  import org.apache.commons.configuration2.DynamicCombinedConfiguration;
34  import org.apache.commons.configuration2.HierarchicalConfiguration;
35  import org.apache.commons.configuration2.XMLConfiguration;
36  import org.apache.commons.configuration2.builder.BasicBuilderParameters;
37  import org.apache.commons.configuration2.builder.BuilderConfigurationWrapperFactory;
38  import org.apache.commons.configuration2.builder.BuilderEventListenerImpl;
39  import org.apache.commons.configuration2.builder.BuilderParameters;
40  import org.apache.commons.configuration2.builder.ConfigurationBuilderEvent;
41  import org.apache.commons.configuration2.builder.ConfigurationBuilderResultCreatedEvent;
42  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
43  import org.apache.commons.configuration2.builder.XMLBuilderParametersImpl;
44  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
45  import org.apache.commons.configuration2.event.ConfigurationEvent;
46  import org.apache.commons.configuration2.event.Event;
47  import org.apache.commons.configuration2.event.EventListener;
48  import org.apache.commons.configuration2.event.EventListenerTestImpl;
49  import org.apache.commons.configuration2.ex.ConfigurationException;
50  import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
51  import org.apache.commons.configuration2.interpol.DefaultLookups;
52  import org.apache.commons.configuration2.tree.ExpressionEngine;
53  import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
54  import org.junit.jupiter.api.Test;
55  import org.xml.sax.SAXParseException;
56  
57  /**
58   * Test class for {@code MultiFileConfigurationBuilder}.
59   */
60  public class TestMultiFileConfigurationBuilder extends AbstractMultiFileConfigurationBuilderTest {
61  
62      /**
63       * Creates a test builder instance which allows access to the managed builders created by it. The returned builder
64       * instance overrides the method for creating managed builders. It stores newly created builders in the passed in
65       * collection.
66       *
67       * @param managedBuilders a collection in which to store managed builders
68       * @return the test builder instance
69       */
70      private static MultiFileConfigurationBuilder<XMLConfiguration> createBuilderWithAccessToManagedBuilders(
71          final Collection<FileBasedConfigurationBuilder<XMLConfiguration>> managedBuilders) {
72          final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<XMLConfiguration>(XMLConfiguration.class) {
73              @Override
74              protected FileBasedConfigurationBuilder<XMLConfiguration> createInitializedManagedBuilder(final String fileName,
75                  final java.util.Map<String, Object> params) throws ConfigurationException {
76                  final FileBasedConfigurationBuilder<XMLConfiguration> result = super.createInitializedManagedBuilder(fileName, params);
77                  managedBuilders.add(result);
78                  return result;
79              }
80          };
81          builder.configure(createTestBuilderParameters(null));
82          return builder;
83      }
84  
85      /**
86       * Creates a test builder object with default settings.
87       *
88       * @param managedParams the parameters for managed configurations
89       * @return the test instance
90       */
91      private static MultiFileConfigurationBuilder<XMLConfiguration> createTestBuilder(final BuilderParameters managedParams) {
92          return new MultiFileConfigurationBuilder<>(XMLConfiguration.class).configure(createTestBuilderParameters(managedParams));
93      }
94  
95      /**
96       * Tests whether configuration listeners are handled correctly.
97       */
98      @Test
99      void testAddConfigurationListener() throws ConfigurationException {
100         final EventListener<ConfigurationEvent> l1 = new EventListenerTestImpl(null);
101         @SuppressWarnings("unchecked")
102         final EventListener<Event> l2 = mock(EventListener.class);
103         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(null);
104         builder.addEventListener(ConfigurationEvent.ANY, l1);
105         switchToConfig(1);
106         final XMLConfiguration config = builder.getConfiguration();
107         assertTrue(config.getEventListeners(ConfigurationEvent.ANY).contains(l1));
108         builder.addEventListener(Event.ANY, l2);
109         assertTrue(config.getEventListeners(Event.ANY).contains(l2));
110         assertTrue(builder.removeEventListener(Event.ANY, l2));
111         assertFalse(builder.removeEventListener(Event.ANY, l2));
112         assertFalse(config.getEventListeners(Event.ANY).contains(l2));
113         switchToConfig(2);
114         final XMLConfiguration config2 = builder.getConfiguration();
115         assertFalse(config2.getEventListeners(Event.ANY).contains(l2));
116     }
117 
118     /**
119      * Tests whether builder events of other types can be received.
120      */
121     @Test
122     void testBuilderListenerOtherTypes() throws ConfigurationException {
123         final BuilderEventListenerImpl listener = new BuilderEventListenerImpl();
124         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(null);
125         builder.addEventListener(ConfigurationBuilderEvent.ANY, listener);
126         switchToConfig(1);
127         builder.getConfiguration();
128         final ConfigurationBuilderEvent event = listener.nextEvent(ConfigurationBuilderEvent.CONFIGURATION_REQUEST);
129         assertEquals(builder, event.getSource());
130         final ConfigurationBuilderResultCreatedEvent createdEvent = listener.nextEvent(ConfigurationBuilderResultCreatedEvent.RESULT_CREATED);
131         assertEquals(builder, createdEvent.getSource());
132         listener.assertNoMoreEvents();
133     }
134 
135     /**
136      * Tests whether builder reset events are handled correctly.
137      */
138     @Test
139     void testBuilderListenerReset() throws ConfigurationException {
140         final BuilderEventListenerImpl listener = new BuilderEventListenerImpl();
141         final Collection<FileBasedConfigurationBuilder<XMLConfiguration>> managedBuilders = new ArrayList<>();
142         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createBuilderWithAccessToManagedBuilders(managedBuilders);
143         switchToConfig(1);
144         builder.addEventListener(ConfigurationBuilderEvent.RESET, listener);
145         final XMLConfiguration configuration = builder.getConfiguration();
146         managedBuilders.iterator().next().resetResult();
147         final ConfigurationBuilderEvent event = listener.nextEvent(ConfigurationBuilderEvent.RESET);
148         assertSame(builder, event.getSource());
149         assertNotSame(configuration, builder.getConfiguration());
150     }
151 
152     /**
153      * Tests whether managed builders are cached.
154      */
155     @Test
156     void testCaching() throws ConfigurationException {
157         final Collection<FileBasedConfigurationBuilder<XMLConfiguration>> managedBuilders = new ArrayList<>();
158         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createBuilderWithAccessToManagedBuilders(managedBuilders);
159         switchToConfig(1);
160         builder.getConfiguration();
161         assertEquals(1, managedBuilders.size());
162         builder.getConfiguration();
163         assertEquals(1, managedBuilders.size());
164         switchToConfig(2);
165         builder.getConfiguration();
166         assertEquals(2, managedBuilders.size());
167     }
168 
169     /**
170      * Tests whether a reset of the builder configuration also flushes the cache.
171      */
172     @Test
173     void testCachingWithReset() throws ConfigurationException {
174         final Collection<FileBasedConfigurationBuilder<XMLConfiguration>> managedBuilders = new ArrayList<>();
175         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createBuilderWithAccessToManagedBuilders(managedBuilders);
176         switchToConfig(1);
177         builder.getConfiguration();
178         builder.resetParameters();
179         builder.configure(createTestBuilderParameters(null));
180         builder.getConfiguration();
181         assertEquals(2, managedBuilders.size());
182     }
183 
184     /**
185      * Tests the behavior if a configuration is accessed which cannot be located.
186      */
187     @Test
188     void testFileNotFound() {
189         switchToConfig("unknown configuration ID");
190         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(null);
191         assertThrows(ConfigurationException.class, builder::getConfiguration);
192     }
193 
194     /**
195      * Tests whether exceptions when creating configurations can be suppressed.
196      */
197     @Test
198     void testFileNotFoundAllowFailOnInit() throws ConfigurationException {
199         final BasicBuilderParameters params = createTestBuilderParameters(null);
200         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class, params.getParameters(),
201             true);
202         switchToConfig("unknown configuration ID");
203         final XMLConfiguration config = builder.getConfiguration();
204         assertTrue(config.isEmpty());
205     }
206 
207     /**
208      * Tests whether access to multiple configurations works.
209      */
210     @Test
211     void testGetConfiguration() throws ConfigurationException {
212         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(null);
213         final String key = "rowsPerPage";
214         switchToConfig(1);
215         assertEquals(15, builder.getConfiguration().getInt(key));
216         switchToConfig(2);
217         assertEquals(25, builder.getConfiguration().getInt(key));
218         switchToConfig(3);
219         assertEquals(35, builder.getConfiguration().getInt(key));
220     }
221 
222     /**
223      * Tests whether initialization parameters of managed builders are cloned before they are applied.
224      */
225     @Test
226     void testGetManagedBuilderClonedParameters() throws ConfigurationException {
227         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(new XMLBuilderParametersImpl());
228         switchToConfig(1);
229         final FileBasedConfigurationBuilder<XMLConfiguration> managedBuilder1 = builder.getManagedBuilder();
230         switchToConfig(2);
231         final FileBasedConfigurationBuilder<XMLConfiguration> managedBuilder2 = builder.getManagedBuilder();
232         assertNotSame(managedBuilder1.getFileHandler(), managedBuilder2.getFileHandler());
233     }
234 
235     /**
236      * Tests whether a {@code ConfigurationInterpolator} is created from properties defined in the parameters object if
237      * necessary.
238      */
239     @Test
240     void testInterpolatorFromParameters() throws ConfigurationException {
241         final BasicBuilderParameters params = new MultiFileBuilderParametersImpl().setFilePattern(PATTERN)
242             .setPrefixLookups(Collections.singletonMap(DefaultLookups.SYSTEM_PROPERTIES.getPrefix(), DefaultLookups.SYSTEM_PROPERTIES.getLookup()));
243         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class);
244         builder.configure(params);
245         switchToConfig(1);
246         assertEquals(15, builder.getConfiguration().getInt("rowsPerPage"));
247     }
248 
249     /**
250      * Tests whether the ConfigurationInterpolator is reset, too.
251      */
252     @Test
253     void testInterpolatorReset() {
254         final BasicBuilderParameters params = new MultiFileBuilderParametersImpl().setFilePattern(PATTERN);
255         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class);
256         builder.configure(params);
257         final ConfigurationInterpolator interpolator = builder.getInterpolator();
258         assertNotNull(interpolator);
259         builder.resetParameters();
260         assertNotSame(interpolator, builder.getInterpolator());
261     }
262 
263     /**
264      * Tests whether a managed configuration is properly initialized.
265      */
266     @Test
267     void testManagedConfigurationSettings() throws ConfigurationException {
268         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class);
269         final ExpressionEngine engine = new XPathExpressionEngine();
270         final BuilderParameters xmlParams = new XMLBuilderParametersImpl().setExpressionEngine(engine)
271             .setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
272         final MultiFileBuilderParametersImpl params = new MultiFileBuilderParametersImpl().setFilePattern(PATTERN).setManagedBuilderParameters(xmlParams);
273         final ConfigurationInterpolator ci = createInterpolator();
274         params.setInterpolator(ci).setListDelimiterHandler(new DefaultListDelimiterHandler('#'));
275         builder.configure(params);
276         switchToConfig(1);
277         final XMLConfiguration config = builder.getConfiguration();
278         assertSame(engine, config.getExpressionEngine());
279         final DefaultListDelimiterHandler listHandler = (DefaultListDelimiterHandler) config.getListDelimiterHandler();
280         assertEquals(';', listHandler.getDelimiter());
281         assertNotSame(ci, config.getInterpolator());
282     }
283 
284     /**
285      * Tests whether a missing file name pattern causes an exception.
286      */
287     @Test
288     void testNoPattern() {
289         final BasicBuilderParameters params = new MultiFileBuilderParametersImpl().setInterpolator(createInterpolator());
290         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class, params.getParameters(),
291             true);
292         switchToConfig(1);
293         assertThrows(ConfigurationException.class, builder::getConfiguration);
294     }
295 
296     /**
297      * Tests whether infinite loops on constructing the file name using interpolation can be handled. This can happen if a
298      * pattern cannot be resolved and the {@code ConfigurationInterpolator} causes again a lookup of the builder's
299      * configuration.
300      */
301     @Test
302     void testRecursiveInterpolation() {
303         final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
304         config.setKeyPattern(PATTERN_VAR);
305         final BasicBuilderParameters params = createTestBuilderParameters(null);
306         final ConfigurationInterpolator ci = new ConfigurationInterpolator();
307         ci.addDefaultLookup(new ConfigurationLookup(config));
308         params.setInterpolator(ci);
309         final MultiFileConfigurationBuilder<XMLConfiguration> builder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class, null, true);
310         builder.configure(params);
311         final BuilderConfigurationWrapperFactory wrapFactory = new BuilderConfigurationWrapperFactory();
312         config.addConfiguration(wrapFactory.createBuilderConfigurationWrapper(HierarchicalConfiguration.class, builder), "Multi");
313         assertTrue(config.isEmpty());
314     }
315 
316     /**
317      * Tests whether listeners at managed builders are removed when the cache is cleared.
318      */
319     @Test
320     void testRemoveBuilderListenerOnReset() throws ConfigurationException {
321         final BuilderEventListenerImpl listener = new BuilderEventListenerImpl();
322         final Collection<FileBasedConfigurationBuilder<XMLConfiguration>> managedBuilders = new ArrayList<>();
323         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createBuilderWithAccessToManagedBuilders(managedBuilders);
324         switchToConfig(1);
325         builder.addEventListener(ConfigurationBuilderEvent.RESET, listener);
326         builder.getConfiguration();
327         builder.resetParameters();
328         managedBuilders.iterator().next().resetResult();
329         listener.assertNoMoreEvents();
330     }
331 
332     /**
333      * Tests whether XML schema validation can be enabled.
334      */
335     @Test
336     void testSchemaValidationError() {
337         final MultiFileConfigurationBuilder<XMLConfiguration> builder = createTestBuilder(
338             new XMLBuilderParametersImpl().setValidating(true).setSchemaValidation(true));
339         switchToConfig("2001");
340         final ConfigurationException ex = assertThrows(ConfigurationException.class, builder::getConfiguration);
341         Throwable cause = ex.getCause();
342         while (cause != null && !(cause instanceof SAXParseException)) {
343             cause = cause.getCause();
344         }
345         assertNotNull(cause);
346     }
347 }