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