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  
18  package org.apache.commons.configuration2;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
22  import static org.junit.jupiter.api.Assertions.assertEquals;
23  import static org.junit.jupiter.api.Assertions.assertFalse;
24  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  import static org.mockito.Mockito.mock;
28  import static org.mockito.Mockito.verify;
29  import static org.mockito.Mockito.verifyNoMoreInteractions;
30  import static org.mockito.Mockito.when;
31  
32  import java.sql.Clob;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.util.Arrays;
36  import java.util.Iterator;
37  import java.util.List;
38  
39  import javax.sql.DataSource;
40  
41  import org.apache.commons.configuration2.builder.fluent.DatabaseBuilderParameters;
42  import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
43  import org.apache.commons.configuration2.event.ConfigurationErrorEvent;
44  import org.apache.commons.configuration2.event.ConfigurationEvent;
45  import org.apache.commons.configuration2.event.ErrorListenerTestImpl;
46  import org.apache.commons.configuration2.event.EventType;
47  import org.apache.commons.configuration2.ex.ConfigurationException;
48  import org.junit.jupiter.api.AfterEach;
49  import org.junit.jupiter.api.BeforeEach;
50  import org.junit.jupiter.api.Test;
51  
52  /**
53   * Test for database stored configurations. Note, when running this Unit Test in Eclipse it sometimes takes a couple
54   * tries. Otherwise you may get database is already in use by another process errors.
55   */
56  public class TestDatabaseConfiguration {
57  
58      /**
59       * A specialized database configuration implementation that can be configured to throw an exception when obtaining a
60       * connection. This way database exceptions can be simulated.
61       */
62      public static class PotentialErrorDatabaseConfiguration extends DatabaseConfiguration {
63  
64          /** A flag whether a getConnection() call should fail. */
65          private boolean failOnConnect;
66  
67          @Override
68          public DataSource getDatasource() {
69              return getDS();
70          }
71  
72          @Override
73          public DataSource getDataSource() {
74              return getDS();
75          }
76  
77          private DataSource getDS() {
78              if (failOnConnect) {
79                  final DataSource ds = mock(DataSource.class);
80                  assertDoesNotThrow(() -> when(ds.getConnection()).thenThrow(new SQLException("Simulated DB error")));
81                  return ds;
82              }
83              return super.getDataSource();
84          }
85      }
86  
87      /** Constant for another configuration name. */
88      private static final String CONFIG_NAME2 = "anotherTestConfig";
89  
90      /** An error listener for testing whether internal errors occurred. */
91      private ErrorListenerTestImpl listener;
92  
93      /** The test helper. */
94      private DatabaseConfigurationTestHelper helper;
95  
96      /**
97       * Checks the error listener for an expected error. The properties of the error event will be compared with the expected
98       * values.
99       *
100      * @param type the expected type of the error event
101      * @param opType the expected operation type
102      * @param key the expected property key
103      * @param value the expected property value
104      */
105     private void checkErrorListener(final EventType<? extends ConfigurationErrorEvent> type, final EventType<?> opType, final String key, final Object value) {
106         final Throwable exception = listener.checkEvent(type, opType, key, value);
107         assertInstanceOf(SQLException.class, exception);
108         listener = null; // mark as checked
109     }
110 
111     @Test
112     void containsValueInternal() throws ConfigurationException {
113         final DatabaseConfiguration config = helper.setUpConfig();
114         config.addPropertyDirect("test", "test1");
115 
116         assertTrue(config.containsValueInternal("test1"));
117         assertFalse(config.containsValue("test9999"));
118     }
119 
120     @BeforeEach
121     public void setUp() throws Exception {
122         /*
123          * Thread.sleep may or may not help with the database is already in use exception.
124          */
125         // Thread.sleep(1000);
126 
127         // set up the datasource
128 
129         helper = new DatabaseConfigurationTestHelper();
130         helper.setUp();
131     }
132 
133     /**
134      * Creates a database configuration with default values.
135      *
136      * @return the configuration
137      * @throws ConfigurationException if an error occurs
138      */
139     private PotentialErrorDatabaseConfiguration setUpConfig() throws ConfigurationException {
140         return helper.setUpConfig(PotentialErrorDatabaseConfiguration.class);
141     }
142 
143     /**
144      * Prepares a test for a database error. Sets up a config and registers an error listener.
145      *
146      * @return the initialized configuration
147      * @throws ConfigurationException if an error occurs
148      */
149     private PotentialErrorDatabaseConfiguration setUpErrorConfig() throws ConfigurationException {
150         final PotentialErrorDatabaseConfiguration config = setUpConfig();
151         setUpErrorListener(config);
152         return config;
153     }
154 
155     /**
156      * Creates an error listener and adds it to the specified configuration.
157      *
158      * @param config the configuration
159      */
160     private void setUpErrorListener(final PotentialErrorDatabaseConfiguration config) {
161         // remove log listener to avoid exception longs
162         config.clearErrorListeners();
163         listener = new ErrorListenerTestImpl(config);
164         config.addEventListener(ConfigurationErrorEvent.ANY, listener);
165         config.failOnConnect = true;
166     }
167 
168     @AfterEach
169     public void tearDown() throws Exception {
170         // if an error listener is defined, we check whether an error occurred
171         if (listener != null) {
172             listener.done();
173         }
174         helper.tearDown();
175     }
176 
177     @Test
178     public void testAddNonStringProperty() throws ConfigurationException {
179         final DatabaseConfiguration config = helper.setUpConfig();
180         config.addPropertyDirect("boolean", Boolean.TRUE);
181 
182         assertTrue(config.containsKey("boolean"));
183     }
184 
185     /**
186      * Tests whether a commit is performed after a property was added.
187      */
188     @Test
189     public void testAddPropertyDirectCommit() throws ConfigurationException {
190         helper.setAutoCommit(false);
191         final DatabaseConfiguration config = helper.setUpConfig();
192         config.addPropertyDirect("key", "value");
193         assertTrue(config.containsKey("key"));
194     }
195 
196     @Test
197     public void testAddPropertyDirectMultiple() throws ConfigurationException {
198         final DatabaseConfiguration config = helper.setUpMultiConfig();
199         config.addPropertyDirect("key", "value");
200 
201         assertTrue(config.containsKey("key"));
202     }
203 
204     @Test
205     public void testAddPropertyDirectSingle() throws ConfigurationException {
206         final DatabaseConfiguration config = helper.setUpConfig();
207         config.addPropertyDirect("key", "value");
208 
209         assertTrue(config.containsKey("key"));
210     }
211 
212     /**
213      * Tests handling of errors in addPropertyDirect().
214      */
215     @Test
216     public void testAddPropertyError() throws ConfigurationException {
217         setUpErrorConfig().addProperty("key1", "value");
218         checkErrorListener(ConfigurationErrorEvent.WRITE, ConfigurationEvent.ADD_PROPERTY, "key1", "value");
219     }
220 
221     /**
222      * Tests adding a property containing the list delimiter. When this property is queried multiple values should be
223      * returned.
224      */
225     @Test
226     public void testAddWithDelimiter() throws ConfigurationException {
227         final DatabaseConfiguration config = setUpConfig();
228         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
229         config.addProperty("keyList", "1;2;3");
230         final String[] values = config.getStringArray("keyList");
231         assertArrayEquals(new String[] {"1", "2", "3"}, values);
232     }
233 
234     /**
235      * Tests whether a commit is performed after a clear operation.
236      */
237     @Test
238     public void testClearCommit() throws ConfigurationException {
239         helper.setAutoCommit(false);
240         final Configuration config = helper.setUpConfig();
241         config.clear();
242         assertTrue(config.isEmpty());
243     }
244 
245     /**
246      * Tests handling of errors in clear().
247      */
248     @Test
249     public void testClearError() throws ConfigurationException {
250         setUpErrorConfig().clear();
251         checkErrorListener(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR, null, null);
252     }
253 
254     @Test
255     public void testClearMultiple() throws ConfigurationException {
256         final Configuration config = helper.setUpMultiConfig();
257         config.clear();
258 
259         assertTrue(config.isEmpty());
260     }
261 
262     /**
263      * Tests whether a commit is performed after a property was cleared.
264      */
265     @Test
266     public void testClearPropertyCommit() throws ConfigurationException {
267         helper.setAutoCommit(false);
268         final Configuration config = helper.setUpConfig();
269         config.clearProperty("key1");
270         assertFalse(config.containsKey("key1"));
271     }
272 
273     /**
274      * Tests handling of errors in clearProperty().
275      */
276     @Test
277     public void testClearPropertyError() throws ConfigurationException {
278         setUpErrorConfig().clearProperty("key1");
279         checkErrorListener(ConfigurationErrorEvent.WRITE, ConfigurationEvent.CLEAR_PROPERTY, "key1", null);
280     }
281 
282     @Test
283     public void testClearPropertyMultiple() throws ConfigurationException {
284         final Configuration config = helper.setUpMultiConfig();
285         config.clearProperty("key1");
286 
287         assertFalse(config.containsKey("key1"));
288     }
289 
290     /**
291      * Tests that another configuration is not affected when clearing properties.
292      */
293     @Test
294     public void testClearPropertyMultipleOtherConfig() throws ConfigurationException {
295         final DatabaseConfiguration config = helper.setUpMultiConfig();
296         final DatabaseConfiguration config2 = helper.setUpMultiConfig(DatabaseConfiguration.class, CONFIG_NAME2);
297         config2.addProperty("key1", "some test");
298         config.clearProperty("key1");
299         assertFalse(config.containsKey("key1"));
300         assertTrue(config2.containsKey("key1"));
301     }
302 
303     @Test
304     public void testClearPropertySingle() throws ConfigurationException {
305         final Configuration config = helper.setUpConfig();
306         config.clearProperty("key1");
307 
308         assertFalse(config.containsKey("key1"));
309     }
310 
311     @Test
312     public void testClearSingle() throws ConfigurationException {
313         final Configuration config = helper.setUpConfig();
314         config.clear();
315 
316         assertTrue(config.isEmpty());
317     }
318 
319     @Test
320     public void testClearSubset() throws ConfigurationException {
321         final Configuration config = setUpConfig();
322 
323         final Configuration subset = config.subset("key1");
324         subset.clear();
325 
326         assertTrue(subset.isEmpty());
327         assertFalse(config.isEmpty());
328     }
329 
330     /**
331      * Tests handling of errors in containsKey().
332      */
333     @Test
334     public void testContainsKeyError() throws ConfigurationException {
335         assertFalse(setUpErrorConfig().containsKey("key1"));
336         checkErrorListener(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, "key1", null);
337     }
338 
339     @Test
340     public void testContainsKeyMultiple() throws ConfigurationException {
341         final Configuration config = helper.setUpMultiConfig();
342         assertTrue(config.containsKey("key1"));
343         assertTrue(config.containsKey("key2"));
344     }
345 
346     @Test
347     public void testContainsKeySingle() throws ConfigurationException {
348         final Configuration config = setUpConfig();
349         assertTrue(config.containsKey("key1"));
350         assertTrue(config.containsKey("key2"));
351     }
352 
353     @Test
354     void testContainsValue() throws ConfigurationException {
355         final DatabaseConfiguration config = helper.setUpConfig();
356         config.addPropertyDirect("test", "test1");
357 
358         assertTrue(config.containsValue("test1"));
359         assertFalse(config.containsValue("test9999"));
360     }
361 
362     /**
363      * Tests whether a CLOB as a property value is handled correctly.
364      */
365     @Test
366     public void testExtractPropertyValueCLOB() throws ConfigurationException, SQLException {
367         final ResultSet rs = mock(ResultSet.class);
368         final Clob clob = mock(Clob.class);
369         final String content = "This is the content of the test CLOB!";
370 
371         when(rs.getObject(DatabaseConfigurationTestHelper.COL_VALUE)).thenReturn(clob);
372         when(clob.length()).thenReturn(Long.valueOf(content.length()));
373         when(clob.getSubString(1, content.length())).thenReturn(content);
374 
375         final DatabaseConfiguration config = helper.setUpConfig();
376         assertEquals(content, config.extractPropertyValue(rs));
377 
378         verify(rs).getObject(DatabaseConfigurationTestHelper.COL_VALUE);
379         verify(clob).length();
380         verify(clob).getSubString(1, content.length());
381         verifyNoMoreInteractions(rs, clob);
382     }
383 
384     /**
385      * Tests whether an empty CLOB is correctly handled by extractPropertyValue().
386      */
387     @Test
388     public void testExtractPropertyValueCLOBEmpty() throws ConfigurationException, SQLException {
389         final ResultSet rs = mock(ResultSet.class);
390         final Clob clob = mock(Clob.class);
391 
392         when(rs.getObject(DatabaseConfigurationTestHelper.COL_VALUE)).thenReturn(clob);
393         when(clob.length()).thenReturn(0L);
394 
395         final DatabaseConfiguration config = helper.setUpConfig();
396         assertEquals("", config.extractPropertyValue(rs));
397 
398         verify(rs).getObject(DatabaseConfigurationTestHelper.COL_VALUE);
399         verify(clob).length();
400         verifyNoMoreInteractions(rs, clob);
401     }
402 
403     @Test
404     public void testGetKeys() throws ConfigurationException {
405         final DatabaseBuilderParameters params = helper.setUpDefaultParameters().setTable("configurationList");
406         final Configuration config1 = helper.createConfiguration(DatabaseConfiguration.class, params);
407         final Iterator<String> i = config1.getKeys();
408         assertTrue(i.hasNext());
409         final Object key = i.next();
410         assertEquals("key3", key.toString());
411         assertFalse(i.hasNext());
412     }
413 
414     /**
415      * Tests handling of errors in getKeys().
416      */
417     @Test
418     public void testGetKeysError() throws ConfigurationException {
419         final Iterator<String> it = setUpErrorConfig().getKeys();
420         checkErrorListener(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null);
421         assertFalse(it.hasNext());
422     }
423 
424     @Test
425     public void testGetKeysInternalNoDatasource() throws Exception {
426         ConfigurationUtils.toString(new DatabaseConfiguration());
427     }
428 
429     @Test
430     public void testGetKeysMultiple() throws ConfigurationException {
431         final Configuration config = helper.setUpMultiConfig();
432         final Iterator<String> it = config.getKeys();
433 
434         assertEquals("key1", it.next());
435         assertEquals("key2", it.next());
436     }
437 
438     @Test
439     public void testGetKeysSingle() throws ConfigurationException {
440         final Configuration config = setUpConfig();
441         final Iterator<String> it = config.getKeys();
442 
443         assertEquals("key1", it.next());
444         assertEquals("key2", it.next());
445     }
446 
447     @Test
448     public void testGetList() throws ConfigurationException {
449         final DatabaseBuilderParameters params = helper.setUpDefaultParameters().setTable("configurationList");
450         final Configuration config1 = helper.createConfiguration(DatabaseConfiguration.class, params);
451         final List<Object> list = config1.getList("key3");
452         assertEquals(3, list.size());
453     }
454 
455     /**
456      * Tests obtaining a property as list whose value contains the list delimiter. Multiple values should be returned.
457      */
458     @Test
459     public void testGetListWithDelimiter() throws ConfigurationException {
460         final DatabaseConfiguration config = setUpConfig();
461         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
462         final List<Object> values = config.getList("keyMulti");
463         assertEquals(Arrays.asList("a", "b", "c"), values);
464     }
465 
466     /**
467      * Tests obtaining a property whose value contains the list delimiter when delimiter parsing is disabled.
468      */
469     @Test
470     public void testGetListWithDelimiterParsingDisabled() throws ConfigurationException {
471         final DatabaseConfiguration config = setUpConfig();
472         assertEquals("a;b;c", config.getString("keyMulti"));
473     }
474 
475     @Test
476     public void testGetPropertyDirectMultiple() throws ConfigurationException {
477         final Configuration config = helper.setUpMultiConfig();
478 
479         assertEquals("value1", config.getProperty("key1"));
480         assertEquals("value2", config.getProperty("key2"));
481         assertNull(config.getProperty("key3"));
482     }
483 
484     @Test
485     public void testGetPropertyDirectSingle() throws ConfigurationException {
486         final Configuration config = setUpConfig();
487 
488         assertEquals("value1", config.getProperty("key1"));
489         assertEquals("value2", config.getProperty("key2"));
490         assertNull(config.getProperty("key3"));
491     }
492 
493     /**
494      * Tests handling of errors in getProperty().
495      */
496     @Test
497     public void testGetPropertyError() throws ConfigurationException {
498         setUpErrorConfig().getProperty("key1");
499         checkErrorListener(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, "key1", null);
500     }
501 
502     /**
503      * Tests handling of errors in isEmpty().
504      */
505     @Test
506     public void testIsEmptyError() throws ConfigurationException {
507         assertTrue(setUpErrorConfig().isEmpty());
508         checkErrorListener(ConfigurationErrorEvent.READ, ConfigurationErrorEvent.READ, null, null);
509     }
510 
511     @Test
512     public void testIsEmptyMultiple() throws ConfigurationException {
513         final Configuration config1 = helper.setUpMultiConfig();
514         assertFalse(config1.isEmpty());
515 
516         final Configuration config2 = helper.setUpMultiConfig(DatabaseConfiguration.class, "testIsEmpty");
517         assertTrue(config2.isEmpty());
518     }
519 
520     @Test
521     public void testIsEmptySingle() throws ConfigurationException {
522         final Configuration config1 = setUpConfig();
523         assertFalse(config1.isEmpty());
524     }
525 
526     /**
527      * Tests whether the configuration has already an error listener registered that is used for logging.
528      */
529     @Test
530     public void testLogErrorListener() throws ConfigurationException {
531         final DatabaseConfiguration config = helper.setUpConfig();
532         assertEquals(1, config.getEventListeners(ConfigurationErrorEvent.ANY).size());
533     }
534 
535     /**
536      * Tests setProperty() if the property value contains the list delimiter.
537      */
538     @Test
539     public void testSetPropertyWithDelimiter() throws ConfigurationException {
540         final DatabaseConfiguration config = helper.setUpMultiConfig();
541         config.setListDelimiterHandler(new DefaultListDelimiterHandler(';'));
542         config.setProperty("keyList", "1;2;3");
543         final String[] values = config.getStringArray("keyList");
544         assertArrayEquals(new String[] {"1", "2", "3"}, values);
545     }
546 
547 }