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