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.beanutils;
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.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.util.Collection;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.Map;
31  
32  import org.apache.commons.configuration2.BaseHierarchicalConfiguration;
33  import org.apache.commons.configuration2.HierarchicalConfiguration;
34  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   * Test class for XMLBeanDeclaration.
39   */
40  public class TestXMLBeanDeclaration {
41      /**
42       * A helper class used for testing the createBeanDeclaration() factory method.
43       */
44      private static final class XMLBeanDeclarationTestImpl extends XMLBeanDeclaration {
45          public XMLBeanDeclarationTestImpl(final HierarchicalConfiguration<?> config, final NodeData<?> node) {
46              super(config, node);
47          }
48      }
49  
50      /** An array with some test properties. */
51      private static final String[] TEST_PROPS = {"firstName", "lastName", "department", "age", "hobby"};
52  
53      /** An array with the values for the test properties. */
54      private static final String[] TEST_VALUES = {"John", "Smith", "Engineering", "42", "TV"};
55  
56      /** An array with the names of nested (complex) properties. */
57      private static final String[] COMPLEX_PROPS = {"address", "car"};
58  
59      /** An array with the names of the classes of the complex properties. */
60      private static final String[] COMPLEX_CLASSES = {"org.apache.commons.configuration.test.AddressTest", "org.apache.commons.configuration.test.CarTest"};
61  
62      /** An array with the property names of the complex properties. */
63      private static final String[][] COMPLEX_ATTRIBUTES = {{"street", "zip", "city", "country"}, {"brand", "color"}};
64  
65      /** An array with the values of the complex properties. */
66      private static final String[][] COMPLEX_VALUES = {{"Baker Street", "12354", "London", "UK"}, {"Bentley", "silver"}};
67  
68      /** An array with property names for a complex constructor argument. */
69      private static final String[] CTOR_COMPLEX_ATTRIBUTES = {"secCode", "validTo"};
70  
71      /** An array with values of a complex constructor argument. */
72      private static final String[] CTOR_COMPLEX_VALUES = {"20121110181559", "2015-01-31"};
73  
74      /** Constant for an ID value passed as constructor argument. */
75      private static final String CTOR_ID = "20121110182006";
76  
77      /** Constant for the key with the bean declaration. */
78      private static final String KEY = "myBean";
79  
80      /** Constant for the section with the variables. */
81      private static final String VARS = "variables.";
82  
83      /**
84       * Checks the properties returned by a bean declaration.
85       *
86       * @param beanDecl the bean declaration
87       * @param names an array with the expected property names
88       * @param values an array with the expected property values
89       */
90      private static void checkProperties(final BeanDeclaration beanDecl, final String[] names, final String[] values) {
91          final Map<String, Object> props = beanDecl.getBeanProperties();
92  
93          final Map<String, String> expected  = new HashMap<>();
94          for (int i = 0; i < names.length; i++) {
95              expected.put(names[i], values[i]);
96          }
97          assertEquals(expected, props);
98      }
99  
100     /**
101      * Creates a configuration with data for testing nested bean declarations including constructor arguments.
102      *
103      * @return the initialized test configuration
104      */
105     private static BaseHierarchicalConfiguration prepareNestedBeanDeclarations() {
106         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
107         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
108         final String keyCtorArg = KEY + ".config-constrarg";
109         setupBeanDeclaration(config, keyCtorArg, CTOR_COMPLEX_ATTRIBUTES, CTOR_COMPLEX_VALUES);
110         config.addProperty(keyCtorArg + "[@config-class]", "TestClass");
111         config.addProperty(keyCtorArg + "(-1)[@config-value]", CTOR_ID);
112         config.addProperty(keyCtorArg + "[@config-type]", "long");
113         for (int i = 0; i < COMPLEX_PROPS.length; i++) {
114             setupBeanDeclaration(config, KEY + '.' + COMPLEX_PROPS[i], COMPLEX_ATTRIBUTES[i], COMPLEX_VALUES[i]);
115             config.addProperty(KEY + '.' + COMPLEX_PROPS[i] + "[@config-class]", COMPLEX_CLASSES[i]);
116         }
117         return config;
118     }
119 
120     /**
121      * Initializes a configuration object with a bean declaration. Under the specified key the given properties will be
122      * added.
123      *
124      * @param config the configuration to initialize
125      * @param key the key of the bean declaration
126      * @param names an array with the names of the properties
127      * @param values an array with the corresponding values
128      */
129     private static void setupBeanDeclaration(final HierarchicalConfiguration<?> config, final String key, final String[] names, final String[] values) {
130         for (int i = 0; i < names.length; i++) {
131             config.addProperty(key + "[@" + names[i] + "]", values[i]);
132         }
133     }
134 
135     /**
136      * Tests fetching the bean's class name.
137      */
138     @Test
139     public void testGetBeanClassName() {
140         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
141         config.addProperty(KEY + "[@config-class]", getClass().getName());
142         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
143         assertEquals(getClass().getName(), decl.getBeanClassName());
144     }
145 
146     /**
147      * Tests whether a default bean class name is taken into account.
148      */
149     @Test
150     public void testGetBeanClassNameFromDefault() {
151         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
152         config.addProperty(KEY + "[@someProperty]", Boolean.TRUE);
153         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY, false, getClass().getName());
154         assertEquals(getClass().getName(), decl.getBeanClassName());
155     }
156 
157     /**
158      * Tests fetching the bean's class name if it is undefined.
159      */
160     @Test
161     public void testGetBeanClassNameUndefined() {
162         final XMLBeanDeclaration decl = new XMLBeanDeclaration(new BaseHierarchicalConfiguration());
163         assertNull(decl.getBeanClassName());
164     }
165 
166     /**
167      * Tests that a missing bean class name does not cause an exception.
168      */
169     @Test
170     public void testGetBeanClassNameUndefinedWithEx() {
171         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
172         config.setThrowExceptionOnMissing(true);
173         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
174         assertNull(decl.getBeanClassName());
175     }
176 
177     /**
178      * Tests fetching the name of the bean factory.
179      */
180     @Test
181     public void testGetBeanFactoryName() {
182         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
183         config.addProperty(KEY + "[@config-factory]", "myFactory");
184         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
185         assertEquals("myFactory", decl.getBeanFactoryName());
186     }
187 
188     /**
189      * Tests fetching the name of the bean factory if it is undefined.
190      */
191     @Test
192     public void testGetBeanFactoryNameUndefined() {
193         final XMLBeanDeclaration decl = new XMLBeanDeclaration(new BaseHierarchicalConfiguration());
194         assertNull(decl.getBeanFactoryName());
195     }
196 
197     /**
198      * Tests that a missing bean factory name does not throw an exception.
199      */
200     @Test
201     public void testGetBeanFactoryNameUndefinedWithEx() {
202         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
203         config.setThrowExceptionOnMissing(true);
204         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
205         assertNull(decl.getBeanFactoryName());
206     }
207 
208     /**
209      * Tests fetching the parameter for the bean factory.
210      */
211     @Test
212     public void testGetBeanFactoryParameter() {
213         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
214         config.addProperty(KEY + "[@config-factoryParam]", "myFactoryParameter");
215         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
216         assertEquals("myFactoryParameter", decl.getBeanFactoryParameter());
217     }
218 
219     /**
220      * Tests fetching the parameter for the bean factory if it is undefined.
221      */
222     @Test
223     public void testGetBeanFactoryParameterUndefined() {
224         final XMLBeanDeclaration decl = new XMLBeanDeclaration(new BaseHierarchicalConfiguration());
225         assertNull(decl.getBeanFactoryParameter());
226     }
227 
228     /**
229      * Tests that an undefined bean factory parameter does not cause an exception.
230      */
231     @Test
232     public void testGetBeanFactoryParameterUndefinedWithEx() {
233         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
234         config.setThrowExceptionOnMissing(true);
235         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
236         assertNull(decl.getBeanFactoryParameter());
237     }
238 
239     /**
240      * Tests if the bean's properties are correctly extracted from the configuration object.
241      */
242     @Test
243     public void testGetBeanProperties() {
244         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
245         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
246         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
247         checkProperties(decl, TEST_PROPS, TEST_VALUES);
248     }
249 
250     /**
251      * Tests fetching properties if none are defined.
252      */
253     @Test
254     public void testGetBeanPropertiesEmpty() {
255         final XMLBeanDeclaration decl = new XMLBeanDeclaration(new BaseHierarchicalConfiguration());
256         final Map<String, Object> props = decl.getBeanProperties();
257         assertTrue(props == null || props.isEmpty());
258     }
259 
260     /**
261      * Tests obtaining the bean's properties when reserved attributes are involved. These should be ignored.
262      */
263     @Test
264     public void testGetBeanPropertiesWithReservedAttributes() {
265         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
266         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
267         config.addProperty(KEY + "[@config-testattr]", "yes");
268         config.addProperty(KEY + "[@config-anothertest]", "this, too");
269         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
270         checkProperties(decl, TEST_PROPS, TEST_VALUES);
271     }
272 
273     /**
274      * Tests whether constructor arguments can be queried.
275      */
276     @Test
277     public void testGetConstructorArgs() {
278         final BaseHierarchicalConfiguration config = prepareNestedBeanDeclarations();
279         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
280         final Collection<ConstructorArg> args = decl.getConstructorArgs();
281         assertEquals(2, args.size());
282         final Iterator<ConstructorArg> it = args.iterator();
283         final ConstructorArg arg1 = it.next();
284         assertTrue(arg1.isNestedBeanDeclaration());
285         checkProperties(arg1.getBeanDeclaration(), CTOR_COMPLEX_ATTRIBUTES, CTOR_COMPLEX_VALUES);
286         assertNull(arg1.getTypeName());
287         assertEquals("TestClass", arg1.getBeanDeclaration().getBeanClassName());
288         final ConstructorArg arg2 = it.next();
289         assertFalse(arg2.isNestedBeanDeclaration());
290         assertEquals(CTOR_ID, arg2.getValue());
291         assertEquals("long", arg2.getTypeName());
292     }
293 
294     /**
295      * Tests whether a constructor argument with a null value can be defined.
296      */
297     @Test
298     public void testGetConstructorArgsNullArg() {
299         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
300         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
301         config.addProperty(KEY + ".config-constrarg", "");
302         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
303         final Collection<ConstructorArg> args = decl.getConstructorArgs();
304         assertEquals(1, args.size());
305         final ConstructorArg arg = args.iterator().next();
306         assertFalse(arg.isNestedBeanDeclaration());
307         assertNull(arg.getValue());
308     }
309 
310     /**
311      * Tests whether interpolation of bean properties works.
312      */
313     @Test
314     public void testGetInterpolatedBeanProperties() {
315         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
316         final String[] varValues = new String[TEST_PROPS.length];
317         for (int i = 0; i < TEST_PROPS.length; i++) {
318             varValues[i] = "${" + VARS + TEST_PROPS[i] + "}";
319             config.addProperty(VARS + TEST_PROPS[i], TEST_VALUES[i]);
320         }
321         setupBeanDeclaration(config, KEY, TEST_PROPS, varValues);
322         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
323         checkProperties(decl, TEST_PROPS, TEST_VALUES);
324     }
325 
326     /**
327      * Tests whether interpolation is done on constructor arguments.
328      */
329     @Test
330     public void testGetInterpolatedConstructorArgs() {
331         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
332         final String expectedValue = "ctorArg";
333         config.addProperty("value", expectedValue);
334         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
335         config.addProperty(KEY + ".config-constrarg[@config-value]", "${value}");
336         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
337         final Collection<ConstructorArg> args = decl.getConstructorArgs();
338         final ConstructorArg arg = args.iterator().next();
339         assertEquals(expectedValue, arg.getValue());
340     }
341 
342     /**
343      * Tests fetching nested bean declarations.
344      */
345     @Test
346     public void testGetNestedBeanDeclarations() {
347         final BaseHierarchicalConfiguration config = prepareNestedBeanDeclarations();
348         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
349         checkProperties(decl, TEST_PROPS, TEST_VALUES);
350 
351         final Map<String, Object> nested = decl.getNestedBeanDeclarations();
352         assertEquals(COMPLEX_PROPS.length, nested.size());
353         for (int i = 0; i < COMPLEX_PROPS.length; i++) {
354             final XMLBeanDeclaration d = (XMLBeanDeclaration) nested.get(COMPLEX_PROPS[i]);
355             assertNotNull(d, "No declaration found for " + COMPLEX_PROPS[i]);
356             checkProperties(d, COMPLEX_ATTRIBUTES[i], COMPLEX_VALUES[i]);
357             assertEquals(COMPLEX_CLASSES[i], d.getBeanClassName());
358         }
359     }
360 
361     /**
362      * Tests fetching nested bean declarations if none are defined.
363      */
364     @Test
365     public void testGetNestedBeanDeclarationsEmpty() {
366         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
367         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
368         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
369         final Map<String, Object> nested = decl.getNestedBeanDeclarations();
370         assertTrue(nested == null || nested.isEmpty());
371     }
372 
373     /**
374      * Tests whether the factory method for creating nested bean declarations gets called.
375      */
376     @Test
377     public void testGetNestedBeanDeclarationsFactoryMethod() {
378         final BaseHierarchicalConfiguration config = prepareNestedBeanDeclarations();
379         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY) {
380             @Override
381             BeanDeclaration createBeanDeclaration(final NodeData<?> node) {
382                 return new XMLBeanDeclarationTestImpl(getConfiguration().configurationAt(node.nodeName()), node);
383             }
384         };
385         final Map<String, Object> nested = decl.getNestedBeanDeclarations();
386         for (final String element : COMPLEX_PROPS) {
387             final Object d = nested.get(element);
388             assertInstanceOf(XMLBeanDeclarationTestImpl.class, d, "Wrong class for bean declaration: " + d);
389         }
390     }
391 
392     /**
393      * Tests whether reserved characters in the node names of nested bean declarations are handled correctly. This is
394      * related to CONFIGURATION-567.
395      */
396     @Test
397     public void testGetNestedBeanDeclarationsReservedCharacter() {
398         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
399         final String key = KEY + ".address..private";
400         setupBeanDeclaration(config, key, COMPLEX_ATTRIBUTES[0], COMPLEX_VALUES[0]);
401         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
402 
403         final Map<String, Object> nested = decl.getNestedBeanDeclarations();
404         assertTrue(nested.containsKey("address.private"));
405     }
406 
407     /**
408      * Tests constructing a bean declaration from a key with multiple values. This should cause an exception because keys
409      * must be unique.
410      */
411     @Test
412     public void testInitFromMultiValueKey() {
413         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
414         config.addProperty(KEY, "myFirstKey");
415         config.addProperty(KEY, "mySecondKey");
416         assertThrows(ConfigurationRuntimeException.class, () -> new XMLBeanDeclaration(config, KEY));
417     }
418 
419     /**
420      * Tests creating a declaration from a null configuration. This should cause an exception.
421      */
422     @Test
423     public void testInitFromNullConfiguration() {
424         assertThrows(IllegalArgumentException.class, () -> new XMLBeanDeclaration(null));
425     }
426 
427     /**
428      * Tests creating a declaration from a null configuration with a key. This should cause an exception.
429      */
430     @Test
431     public void testInitFromNullConfigurationAndKey() {
432         assertThrows(IllegalArgumentException.class, () -> new XMLBeanDeclaration(null, KEY));
433     }
434 
435     /**
436      * Tests constructing a bean declaration from an undefined key. This should cause an exception.
437      */
438     @Test
439     public void testInitFromUndefinedKey() {
440         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
441         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
442         assertThrows(ConfigurationRuntimeException.class, () -> new XMLBeanDeclaration(config, "undefined_key"));
443     }
444 
445     /**
446      * Tests constructing a bean declaration from a key, which is undefined when the optional flag is set. In this case an
447      * empty declaration should be created, which can be used for creating beans as long as a default class is provided.
448      */
449     @Test
450     public void testInitFromUndefinedKeyOptional() {
451         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
452         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
453         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, "undefined_key", true);
454         assertNull(decl.getBeanClassName());
455     }
456 
457     /**
458      * Tests interpolate() if no ConfigurationInterpolator is available.
459      */
460     @Test
461     public void testInterpolateNoInterpolator() {
462         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
463         config.addProperty("value", "expectedValue");
464         setupBeanDeclaration(config, KEY, TEST_PROPS, TEST_VALUES);
465         final String value = "${value}";
466         config.addProperty(KEY + ".config-constrarg[@config-value]", value);
467         config.setInterpolator(null);
468         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY);
469         final Collection<ConstructorArg> args = decl.getConstructorArgs();
470         final ConstructorArg arg = args.iterator().next();
471         assertEquals(value, arg.getValue());
472     }
473 
474     /**
475      * Tests whether a default bean class name is overridden by a value in the configuration.
476      */
477     @Test
478     public void tetGetBeanClassNameDefaultOverride() {
479         final BaseHierarchicalConfiguration config = new BaseHierarchicalConfiguration();
480         config.addProperty(KEY + "[@config-class]", getClass().getName());
481         final XMLBeanDeclaration decl = new XMLBeanDeclaration(config, KEY, false, "someDefaultClassName");
482         assertEquals(getClass().getName(), decl.getBeanClassName());
483     }
484 }