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