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  
18  package org.apache.commons.configuration2.beanutils;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertNull;
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.Collections;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  
34  import org.apache.commons.beanutils.DynaBean;
35  import org.apache.commons.beanutils.LazyDynaBean;
36  import org.apache.commons.configuration2.PropertiesConfiguration;
37  import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
38  import org.junit.jupiter.api.BeforeEach;
39  import org.junit.jupiter.api.Test;
40  
41  /**
42   * Test class for BeanHelper.
43   */
44  public class TestBeanHelper {
45  
46      /**
47       * An implementation of the BeanFactory interface used for testing. This implementation is really simple: If the
48       * BeanCreationTestBean class is provided, a new instance will be created. Otherwise an exception is thrown.
49       */
50      private final class TestBeanFactory implements BeanFactory {
51  
52          private Object parameter;
53  
54          private boolean supportsDefaultClass;
55  
56          /** A counter for the created instances. */
57          private int createBeanCount;
58  
59          @Override
60          public Object createBean(final BeanCreationContext bcc) {
61              createBeanCount++;
62              parameter = bcc.getParameter();
63              if (BeanCreationTestBean.class.equals(bcc.getBeanClass())) {
64                  final BeanCreationTestBean bean = new BeanCreationTestBean();
65                  helper.initBean(bean, bcc.getBeanDeclaration());
66                  return bean;
67              }
68              if (BeanCreationTestBeanWithListChild.class.equals(bcc.getBeanClass())) {
69                  final BeanCreationTestBeanWithListChild bean = new BeanCreationTestBeanWithListChild();
70                  helper.initBean(bean, bcc.getBeanDeclaration());
71                  return bean;
72              }
73              throw new IllegalArgumentException("Unsupported class: " + bcc.getBeanClass());
74          }
75  
76          /**
77           * Returns the number of beans created via this factory.
78           *
79           * @return the number of created beans
80           */
81          public int getCreateBeanCount() {
82              return createBeanCount;
83          }
84  
85          /**
86           * Returns the default class, but only if the supportsDefaultClass flag is set.
87           */
88          @Override
89          public Class<?> getDefaultBeanClass() {
90              return supportsDefaultClass ? BeanCreationTestBean.class : null;
91          }
92      }
93  
94      /** Constant for the test value of the string property. */
95      private static final String TEST_STRING = "testString";
96  
97      /** Constant for the test value of the numeric property. */
98      private static final int TEST_INT = 42;
99  
100     /** Constant for the name of the test bean factory. */
101     private static final String TEST_FACTORY = "testFactory";
102 
103     /** The test bean helper instance. */
104     private BeanHelper helper;
105 
106     /**
107      * Tests if the bean was correctly initialized from the data of the test bean declaration.
108      *
109      * @param bean the bean to be checked
110      */
111     private void checkBean(final BeanCreationTestBean bean) {
112         assertEquals(TEST_STRING, bean.getStringValue());
113         assertEquals(TEST_INT, bean.getIntValue());
114         final BeanCreationTestBean buddy = bean.getBuddy();
115         assertNotNull(buddy);
116         assertEquals("Another test string", buddy.getStringValue());
117         assertEquals(100, buddy.getIntValue());
118     }
119 
120     /**
121      * Tests if the bean was correctly initialized from the data of the test bean declaration.
122      *
123      * @param bean the bean to be checked
124      */
125     private void checkBean(final BeanCreationTestBeanWithListChild bean) {
126         assertEquals(TEST_STRING, bean.getStringValue());
127         assertEquals(TEST_INT, bean.getIntValue());
128         final List<BeanCreationTestBean> children = bean.getChildren();
129         assertNotNull(children);
130         assertEquals(2, children.size());
131         assertNotNull(children.get(0));
132         assertNotNull(children.get(1));
133     }
134 
135     /**
136      * Create a simple bean declaration that has no children for testing of nested children bean declarations.
137      *
138      * @param name A name prefix that can be used to disambiguate the children
139      * @return A simple declaration
140      */
141     private BeanDeclarationTestImpl createChildBean(final String name) {
142         final BeanDeclarationTestImpl childBean = new BeanDeclarationTestImpl();
143         final Map<String, Object> properties2 = new HashMap<>();
144         properties2.put("stringValue", name + " Another test string");
145         properties2.put("intValue", 100);
146         childBean.setBeanProperties(properties2);
147         childBean.setBeanClassName(BeanCreationTestBean.class.getName());
148 
149         return childBean;
150     }
151 
152     @BeforeEach
153     public void setUp() throws Exception {
154         helper = new BeanHelper(new TestBeanFactory());
155     }
156 
157     /**
158      * Returns an initialized bean declaration.
159      *
160      * @return the bean declaration
161      */
162     private BeanDeclarationTestImpl setUpBeanDeclaration() {
163         final BeanDeclarationTestImpl data = new BeanDeclarationTestImpl();
164         final Map<String, Object> properties = new HashMap<>();
165         properties.put("stringValue", TEST_STRING);
166         properties.put("intValue", String.valueOf(TEST_INT));
167         data.setBeanProperties(properties);
168         final BeanDeclarationTestImpl buddyData = new BeanDeclarationTestImpl();
169         final Map<String, Object> properties2 = new HashMap<>();
170         properties2.put("stringValue", "Another test string");
171         properties2.put("intValue", 100);
172         buddyData.setBeanProperties(properties2);
173         buddyData.setBeanClassName(BeanCreationTestBean.class.getName());
174 
175         final Map<String, Object> nested = new HashMap<>();
176         nested.put("buddy", buddyData);
177         data.setNestedBeanDeclarations(nested);
178         return data;
179     }
180 
181     /**
182      * Same as setUpBeanDeclaration, but returns a nested array of beans as a single property. Tests multi-value
183      * (Collection<BeanDeclaration>) children construction.
184      *
185      * @return The bean declaration with a list child bean proerty
186      */
187     private BeanDeclarationTestImpl setUpBeanDeclarationWithListChild() {
188         final BeanDeclarationTestImpl data = new BeanDeclarationTestImpl();
189         final Map<String, Object> properties = new HashMap<>();
190         properties.put("stringValue", TEST_STRING);
191         properties.put("intValue", String.valueOf(TEST_INT));
192         data.setBeanProperties(properties);
193 
194         final List<BeanDeclaration> childData = new ArrayList<>();
195         childData.add(createChildBean("child1"));
196         childData.add(createChildBean("child2"));
197         final Map<String, Object> nested = new HashMap<>();
198         nested.put("children", childData);
199         data.setNestedBeanDeclarations(nested);
200         return data;
201     }
202 
203     /**
204      * Tests whether properties from one bean to another can be copied.
205      */
206     @Test
207     void testCopyProperties() throws Exception {
208         final PropertiesConfiguration src = new PropertiesConfiguration();
209         src.setHeader("TestHeader");
210         src.setFooter("TestFooter");
211         final LazyDynaBean dest = new LazyDynaBean();
212         BeanHelper.copyProperties(dest, src);
213         assertEquals("TestFooter", dest.get("footer"));
214         assertEquals("TestHeader", dest.get("header"));
215     }
216 
217     /**
218      * Tests creating a bean. All necessary information is stored in the bean declaration.
219      */
220     @Test
221     void testCreateBean() {
222         final TestBeanFactory factory = new TestBeanFactory();
223         helper.registerBeanFactory(TEST_FACTORY, factory);
224         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
225         data.setBeanFactoryName(TEST_FACTORY);
226         data.setBeanClassName(BeanCreationTestBean.class.getName());
227         checkBean((BeanCreationTestBean) helper.createBean(data, null));
228         assertNull(factory.parameter);
229         assertEquals(1, factory.getCreateBeanCount());
230     }
231 
232     /**
233      * Tests creating a bean. The bean's class is specified as the default class argument.
234      */
235     @Test
236     void testCreateBeanWithDefaultClass() {
237         helper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
238         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
239         data.setBeanFactoryName(TEST_FACTORY);
240         checkBean((BeanCreationTestBean) helper.createBean(data, BeanCreationTestBean.class));
241     }
242 
243     /**
244      * Tests creating a bean using the default bean factory.
245      */
246     @Test
247     void testCreateBeanWithDefaultFactory() {
248         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
249         data.setBeanClassName(BeanCreationTestBean.class.getName());
250         checkBean((BeanCreationTestBean) helper.createBean(data, null));
251         final TestBeanFactory factory = (TestBeanFactory) helper.getDefaultBeanFactory();
252         assertTrue(factory.getCreateBeanCount() > 0);
253     }
254 
255     /**
256      * Tests creating a bean when the factory throws an exception.
257      */
258     @Test
259     void testCreateBeanWithException() {
260         helper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
261         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
262         data.setBeanFactoryName(TEST_FACTORY);
263         data.setBeanClassName(getClass().getName());
264         assertThrows(ConfigurationRuntimeException.class, () -> helper.createBean(data, null));
265     }
266 
267     /**
268      * Tests creating a bean when the bean's class is specified as the default class of the bean factory.
269      */
270     @Test
271     void testCreateBeanWithFactoryDefaultClass() {
272         final TestBeanFactory factory = new TestBeanFactory();
273         factory.supportsDefaultClass = true;
274         helper.registerBeanFactory(TEST_FACTORY, factory);
275         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
276         data.setBeanFactoryName(TEST_FACTORY);
277         checkBean((BeanCreationTestBean) helper.createBean(data, null));
278         assertEquals(1, factory.getCreateBeanCount());
279     }
280 
281     /**
282      * Tries to create a bean with a non existing class. This should cause an exception.
283      */
284     @Test
285     void testCreateBeanWithInvalidClass() {
286         helper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
287         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
288         data.setBeanFactoryName(TEST_FACTORY);
289         data.setBeanClassName("non.existing.ClassName");
290         assertThrows(ConfigurationRuntimeException.class, () -> helper.createBean(data, null));
291     }
292 
293     /**
294      * Tests whether a bean with a property of type collection can be created.
295      */
296     @Test
297     void testCreateBeanWithListChildBean() {
298         final TestBeanFactory factory = new TestBeanFactory();
299         helper.registerBeanFactory(TEST_FACTORY, factory);
300         final BeanDeclarationTestImpl data = setUpBeanDeclarationWithListChild();
301         data.setBeanFactoryName(TEST_FACTORY);
302         data.setBeanClassName(BeanCreationTestBeanWithListChild.class.getName());
303         checkBean((BeanCreationTestBeanWithListChild) helper.createBean(data, null));
304         assertNull(factory.parameter);
305         assertEquals(1, factory.getCreateBeanCount());
306     }
307 
308     /**
309      * Tries to create a bean if no class is provided. This should cause an exception.
310      */
311     @Test
312     void testCreateBeanWithNoClass() {
313         helper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
314         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
315         data.setBeanFactoryName(TEST_FACTORY);
316         assertThrows(ConfigurationRuntimeException.class, () -> helper.createBean(data, null));
317     }
318 
319     /**
320      * Tests creating a bean when no bean declaration is provided. This should cause an exception.
321      */
322     @Test
323     void testCreateBeanWithNullDeclaration() {
324         assertThrows(IllegalArgumentException.class, () -> helper.createBean(null));
325     }
326 
327     /**
328      * Tests if a parameter is correctly passed to the bean factory.
329      */
330     @Test
331     void testCreateBeanWithParameter() {
332         final Object param = 42;
333         final TestBeanFactory factory = new TestBeanFactory();
334         helper.registerBeanFactory(TEST_FACTORY, factory);
335         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
336         data.setBeanFactoryName(TEST_FACTORY);
337         data.setBeanClassName(BeanCreationTestBean.class.getName());
338         checkBean((BeanCreationTestBean) helper.createBean(data, null, param));
339         assertSame(param, factory.parameter);
340     }
341 
342     /**
343      * Tests creating a bean using a non registered factory.
344      */
345     @Test
346     void testCreateBeanWithUnknownFactory() {
347         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
348         data.setBeanFactoryName(TEST_FACTORY);
349         data.setBeanClassName(BeanCreationTestBean.class.getName());
350         assertThrows(ConfigurationRuntimeException.class, () -> helper.createBean(data, null));
351     }
352 
353     /**
354      * Tests whether a wrapper DynaBean for a Java bean can be created.
355      */
356     @Test
357     void testCreateWrapDynaBean() {
358         final PropertiesConfiguration config = new PropertiesConfiguration();
359         final DynaBean bean = BeanHelper.createWrapDynaBean(config);
360         final String value = "TestFooter";
361         bean.set("footer", value);
362         assertEquals(value, config.getFooter());
363     }
364 
365     /**
366      * Tries to create a wrapper DynaBean for a null bean.
367      */
368     @Test
369     void testCreateWrapDynaBeanNull() {
370         assertThrows(IllegalArgumentException.class, () -> BeanHelper.createWrapDynaBean(null));
371     }
372 
373     /**
374      * Tests whether the correct default bean factory is set.
375      */
376     @Test
377     void testDefaultBeanFactory() {
378         helper = new BeanHelper();
379         assertSame(DefaultBeanFactory.INSTANCE, helper.getDefaultBeanFactory());
380     }
381 
382     /**
383      * Tests the default instance of BeanHelper.
384      */
385     @Test
386     void testDefaultInstance() {
387         assertSame(DefaultBeanFactory.INSTANCE, BeanHelper.INSTANCE.getDefaultBeanFactory());
388     }
389 
390     /**
391      * Tests to deregister a bean factory.
392      */
393     @Test
394     void testDeregisterBeanFactory() {
395         final BeanFactory factory = new TestBeanFactory();
396         helper.registerBeanFactory(TEST_FACTORY, factory);
397         assertSame(factory, helper.deregisterBeanFactory(TEST_FACTORY));
398         assertEquals(Collections.emptySet(), helper.registeredFactoryNames());
399     }
400 
401     /**
402      * Tests deregisterBeanFactory() for a non-existing factory name.
403      */
404     @Test
405     void testDeregisterBeanFactoryNonExisting() {
406         assertNull(helper.deregisterBeanFactory(TEST_FACTORY));
407     }
408 
409     /**
410      * Tests deregisterBeanFactory() for a null factory name.
411      */
412     @Test
413     void testDeregisterBeanFactoryNull() {
414         assertNull(helper.deregisterBeanFactory(null));
415     }
416 
417     /**
418      * Tests initializing a bean.
419      */
420     @Test
421     void testInitBean() {
422         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
423         final BeanCreationTestBean bean = new BeanCreationTestBean();
424         helper.initBean(bean, data);
425         checkBean(bean);
426     }
427 
428     /**
429      * Tries to initialize a bean with a bean declaration that contains an invalid property value. This should cause an
430      * exception.
431      */
432     @Test
433     void testInitBeanWithInvalidProperty() {
434         final BeanDeclarationTestImpl data = setUpBeanDeclaration();
435         data.getBeanProperties().put("nonExistingProperty", Boolean.TRUE);
436         final BeanCreationTestBean bean = new BeanCreationTestBean();
437         assertThrows(ConfigurationRuntimeException.class, () -> helper.initBean(bean, data));
438     }
439 
440     /**
441      * Tests initializing a bean when the bean declaration does not contain any data.
442      */
443     @Test
444     void testInitBeanWithNoData() {
445         final BeanDeclarationTestImpl data = new BeanDeclarationTestImpl();
446         final BeanCreationTestBean bean = new BeanCreationTestBean();
447         helper.initBean(bean, data);
448         assertNull(bean.getStringValue());
449         assertEquals(0, bean.getIntValue());
450         assertNull(bean.getBuddy());
451     }
452 
453     /**
454      * Tests whether a specific default bean factory can be set when constructing an instance.
455      */
456     @Test
457     void testInitWithBeanFactory() {
458         final BeanFactory factory = mock(BeanFactory.class);
459         helper = new BeanHelper(factory);
460         assertSame(factory, helper.getDefaultBeanFactory());
461     }
462 
463     /**
464      * Tests registering a new bean factory.
465      */
466     @Test
467     void testRegisterBeanFactory() {
468         helper.registerBeanFactory(TEST_FACTORY, new TestBeanFactory());
469         assertEquals(Collections.singleton(TEST_FACTORY), helper.registeredFactoryNames());
470     }
471 
472     /**
473      * Tries to register a null factory. This should cause an exception.
474      */
475     @Test
476     void testRegisterBeanFactoryNull() {
477         assertThrows(IllegalArgumentException.class, () -> helper.registerBeanFactory(TEST_FACTORY, null));
478     }
479 
480     /**
481      * Tries to register a bean factory with a null name. This should cause an exception.
482      */
483     @Test
484     void testRegisterBeanFactoryNullName() {
485         final BeanFactory beanFactory = new TestBeanFactory();
486         assertThrows(IllegalArgumentException.class, () -> helper.registerBeanFactory(null, beanFactory));
487     }
488 
489     /**
490      * Tests that a newly created instance does not have any bean factories registered.
491      */
492     @Test
493     void testRegisteredFactoriesEmptyForNewInstance() {
494         assertEquals(Collections.emptySet(), helper.registeredFactoryNames());
495     }
496 }