1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.configuration2;
19
20 import static org.apache.commons.configuration2.TempDirUtils.newFile;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertSame;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.file.StandardCopyOption;
31 import java.util.Random;
32
33 import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
34 import org.apache.commons.configuration2.builder.BuilderConfigurationWrapperFactory;
35 import org.apache.commons.configuration2.builder.ConfigurationBuilder;
36 import org.apache.commons.configuration2.builder.CopyObjectDefaultHandler;
37 import org.apache.commons.configuration2.builder.FileBasedBuilderParametersImpl;
38 import org.apache.commons.configuration2.builder.FileBasedBuilderProperties;
39 import org.apache.commons.configuration2.builder.combined.CombinedConfigurationBuilder;
40 import org.apache.commons.configuration2.builder.combined.MultiFileConfigurationBuilder;
41 import org.apache.commons.configuration2.builder.combined.ReloadingCombinedConfigurationBuilder;
42 import org.apache.commons.configuration2.builder.fluent.Parameters;
43 import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler;
44 import org.apache.commons.configuration2.ex.ConfigurationException;
45 import org.apache.commons.configuration2.interpol.ConfigurationInterpolator;
46 import org.apache.commons.configuration2.interpol.Lookup;
47 import org.apache.commons.configuration2.io.FileHandler;
48 import org.apache.commons.configuration2.sync.LockMode;
49 import org.apache.commons.configuration2.sync.ReadWriteSynchronizer;
50 import org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine;
51 import org.apache.commons.io.FileUtils;
52 import org.junit.jupiter.api.BeforeAll;
53 import org.junit.jupiter.api.Test;
54 import org.junit.jupiter.api.io.TempDir;
55
56 public class TestDynamicCombinedConfiguration {
57 private final class ReaderThread extends Thread {
58 private volatile boolean running = true;
59 private volatile boolean failed;
60 private final CombinedConfigurationBuilder builder;
61 private final Random random;
62
63 public ReaderThread(final CombinedConfigurationBuilder b) {
64 builder = b;
65 random = new Random();
66 }
67
68 public boolean failed() {
69 return failed;
70 }
71
72 @Override
73 public void run() {
74 try {
75 while (running) {
76 final CombinedConfiguration combined = builder.getConfiguration();
77 final String bcId = combined.getString("Product/FIIndex/FI[@id='123456781']");
78 if ("ID0001".equalsIgnoreCase(bcId)) {
79 if (failed) {
80 System.out.println("Thread failed, but recovered");
81 }
82 failed = false;
83 } else {
84 failed = true;
85 }
86 final int sleepTime = random.nextInt(75);
87 Thread.sleep(sleepTime);
88 }
89 } catch (final ConfigurationException cex) {
90 failed = true;
91 } catch (final InterruptedException iex) {
92 Thread.currentThread().interrupt();
93 }
94 }
95
96 public void shutdown() {
97 running = false;
98 interrupt();
99 }
100
101 }
102 private static final class ReloadThread extends Thread {
103 private final CombinedConfigurationBuilder builder;
104 private final int[] failures;
105 private final int index;
106 private final int count;
107 private final String expected;
108 private final String id;
109 private final boolean useId;
110 private final Random random;
111
112 ReloadThread(final CombinedConfigurationBuilder b, final int[] failures, final int index, final int count, final boolean useId, final String id,
113 final String expected) {
114 builder = b;
115 this.failures = failures;
116 this.index = index;
117 this.count = count;
118 this.expected = expected;
119 this.id = id;
120 this.useId = useId;
121 random = new Random();
122 }
123
124 @Override
125 public void run() {
126 failures[index] = 0;
127
128 if (useId) {
129 ThreadLookup.setId(id);
130 }
131 for (int i = 0; i < count; i++) {
132 try {
133 if (random.nextBoolean()) {
134
135 builder.resetResult();
136 }
137 final CombinedConfiguration combined = builder.getConfiguration();
138 final String value = combined.getString("rowsPerPage", null);
139 if (value == null || !value.equals(expected)) {
140 ++failures[index];
141 }
142 } catch (final Exception ex) {
143 ++failures[index];
144 }
145 }
146 }
147 }
148 public static class ThreadLookup implements Lookup {
149 private static final ThreadLocal<String> id = new ThreadLocal<>();
150
151 public static void setId(final String value) {
152 id.set(value);
153 }
154
155 public ThreadLookup() {
156 }
157
158 @Override
159 public String lookup(final String key) {
160 if (key == null || !key.equals("Id")) {
161 return null;
162 }
163 final String value = System.getProperty("Id");
164 if (value != null) {
165 return value;
166 }
167 return id.get();
168
169 }
170 }
171
172 private static final String PATTERN = "${sys:Id}";
173 private static final String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
174
175 private static final String DEFAULT_FILE = "target/test-classes/testMultiConfiguration_default.xml";
176
177 private static final File MULTI_TENENT_FILE = ConfigurationAssert.getTestFile("testMultiTenentConfigurationBuilder4.xml");
178
179 private static final File MULTI_DYNAMIC_FILE = ConfigurationAssert.getTestFile("testMultiTenentConfigurationBuilder5.xml");
180
181
182 private static final int THREAD_COUNT = 3;
183
184
185 private static final int LOOP_COUNT = 100;
186
187
188 private static Parameters parameters;
189
190 @BeforeAll
191 public static void setUpOnce() {
192 parameters = new Parameters();
193 }
194
195
196 @TempDir
197 public File tempFolder;
198
199 private void copyFile(final File input, final File output) throws IOException {
200 FileUtils.copyFile(input, output, StandardCopyOption.REPLACE_EXISTING);
201
202 output.setLastModified(System.currentTimeMillis());
203 }
204
205
206
207
208
209
210
211
212 private SynchronizerTestImpl prepareSynchronizerTest(final Configuration config) {
213 final SynchronizerTestImpl sync = new SynchronizerTestImpl();
214 config.setSynchronizer(sync);
215 config.lock(LockMode.READ);
216 config.unlock(LockMode.READ);
217 sync.clear();
218 return sync;
219 }
220
221
222
223
224 @Test
225 public void testAddConfigurationSynchronized() {
226 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
227 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
228 config.addConfiguration(new PropertiesConfiguration());
229 sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
230 }
231
232 @Test
233 public void testConcurrentGetAndReload() throws Exception {
234 System.getProperties().remove("Id");
235 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
236 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
237 final CombinedConfiguration config = builder.getConfiguration();
238
239 assertEquals("50", config.getString("rowsPerPage"));
240 final Thread[] testThreads = new Thread[THREAD_COUNT];
241 final int[] failures = new int[THREAD_COUNT];
242
243 for (int i = 0; i < testThreads.length; ++i) {
244 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, false, null, "50");
245 testThreads[i].start();
246 }
247
248 int totalFailures = 0;
249 for (int i = 0; i < testThreads.length; ++i) {
250 testThreads[i].join();
251 totalFailures += failures[i];
252 }
253 assertEquals(0, totalFailures);
254 }
255
256 @Test
257 public void testConcurrentGetAndReload2() throws Exception {
258 System.getProperties().remove("Id");
259 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
260 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
261 final CombinedConfiguration config = builder.getConfiguration();
262
263 assertEquals("50", config.getString("rowsPerPage"));
264
265 final Thread[] testThreads = new Thread[THREAD_COUNT];
266 final int[] failures = new int[THREAD_COUNT];
267 System.setProperty("Id", "2002");
268 assertEquals("25", config.getString("rowsPerPage"));
269 for (int i = 0; i < testThreads.length; ++i) {
270 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, false, null, "25");
271 testThreads[i].start();
272 }
273
274 int totalFailures = 0;
275 for (int i = 0; i < testThreads.length; ++i) {
276 testThreads[i].join();
277 totalFailures += failures[i];
278 }
279 System.getProperties().remove("Id");
280 assertEquals(0, totalFailures);
281 }
282
283 @Test
284 public void testConcurrentGetAndReloadFile() throws Exception {
285 final int threadCount = 25;
286 System.getProperties().remove("Id");
287 System.setProperty("TemporaryFolder", tempFolder.getAbsolutePath());
288
289 File input = new File("target/test-classes/testMultiDynamic_default.xml");
290 final File output = newFile("testMultiDynamic_default.xml", tempFolder);
291 output.delete();
292 output.getParentFile().mkdir();
293 copyFile(input, output);
294
295 final ReloadingCombinedConfigurationBuilder builder = new ReloadingCombinedConfigurationBuilder();
296 builder.configure(parameters.combined().setSynchronizer(new ReadWriteSynchronizer())
297 .setDefinitionBuilderParameters(new FileBasedBuilderParametersImpl().setFile(MULTI_DYNAMIC_FILE)).registerChildDefaultsHandler(
298 FileBasedBuilderProperties.class, new CopyObjectDefaultHandler(new FileBasedBuilderParametersImpl().setReloadingRefreshDelay(1L))));
299 CombinedConfiguration config = builder.getConfiguration();
300 assertEquals("ID0001", config.getString("Product/FIIndex/FI[@id='123456781']"));
301
302 final ReaderThread[] testThreads = new ReaderThread[threadCount];
303 for (int i = 0; i < testThreads.length; ++i) {
304 testThreads[i] = new ReaderThread(builder);
305 testThreads[i].start();
306 }
307
308 builder.getReloadingController().checkForReloading(null);
309 Thread.sleep(2000);
310
311 input = new File("target/test-classes/testMultiDynamic_default2.xml");
312 copyFile(input, output);
313
314 Thread.sleep(2000);
315 assertTrue(builder.getReloadingController().checkForReloading(null));
316 config = builder.getConfiguration();
317 final String id = config.getString("Product/FIIndex/FI[@id='123456782']");
318 assertNotNull(id);
319 final String rows = config.getString("rowsPerPage");
320 assertEquals("25", rows);
321
322 for (final ReaderThread testThread : testThreads) {
323 testThread.shutdown();
324 testThread.join();
325 }
326 for (final ReaderThread testThread : testThreads) {
327 assertFalse(testThread.failed());
328 }
329 assertEquals("ID0002", config.getString("Product/FIIndex/FI[@id='123456782']"));
330 output.delete();
331 }
332
333 @Test
334 public void testConcurrentGetAndReloadMultipleClients() throws Exception {
335 System.getProperties().remove("Id");
336 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
337 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
338 final CombinedConfiguration config = builder.getConfiguration();
339
340 assertEquals("50", config.getString("rowsPerPage"));
341
342 final Thread[] testThreads = new Thread[THREAD_COUNT];
343 final int[] failures = new int[THREAD_COUNT];
344 final String[] ids = {null, "2002", "3001", "3002", "3003"};
345 final String[] expected = {"50", "25", "15", "25", "50"};
346 for (int i = 0; i < testThreads.length; ++i) {
347 testThreads[i] = new ReloadThread(builder, failures, i, LOOP_COUNT, true, ids[i], expected[i]);
348 testThreads[i].start();
349 }
350
351 int totalFailures = 0;
352 for (int i = 0; i < testThreads.length; ++i) {
353 testThreads[i].join();
354 totalFailures += failures[i];
355 }
356 System.getProperties().remove("Id");
357 if (totalFailures != 0) {
358 System.out.println("Failures:");
359 for (int i = 0; i < testThreads.length; ++i) {
360 System.out.println("Thread " + i + " " + failures[i]);
361 }
362 }
363 assertEquals(0, totalFailures);
364 }
365
366 @Test
367 public void testConfiguration() throws Exception {
368 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
369 final DefaultListDelimiterHandler listHandler = new DefaultListDelimiterHandler(',');
370 config.setListDelimiterHandler(listHandler);
371 final XPathExpressionEngine engine = new XPathExpressionEngine();
372 config.setExpressionEngine(engine);
373 config.setKeyPattern(PATTERN);
374 final ConfigurationBuilder<XMLConfiguration> multiBuilder = new MultiFileConfigurationBuilder<>(XMLConfiguration.class)
375 .configure(parameters.multiFile().setFilePattern(PATTERN1).setPrefixLookups(ConfigurationInterpolator.getDefaultPrefixLookups())
376 .setManagedBuilderParameters(parameters.xml().setExpressionEngine(engine).setListDelimiterHandler(listHandler)));
377 final BuilderConfigurationWrapperFactory wrapFactory = new BuilderConfigurationWrapperFactory();
378 config.addConfiguration(wrapFactory.createBuilderConfigurationWrapper(HierarchicalConfiguration.class, multiBuilder), "Multi");
379 final XMLConfiguration xml = new XMLConfiguration();
380 xml.setExpressionEngine(engine);
381 final FileHandler handler = new FileHandler(xml);
382 handler.setFile(new File(DEFAULT_FILE));
383 handler.load();
384 config.addConfiguration(xml, "Default");
385
386 verify("1001", config, 15);
387 verify("1002", config, 25);
388 verify("1003", config, 35);
389 verify("1004", config, 50);
390 assertEquals("a,b,c", config.getString("split/list3/@values"));
391 assertEquals(0, config.getMaxIndex("split/list3/@values"));
392 assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
393 assertEquals("OK-1", config.getString("buttons/name"));
394 assertEquals(3, config.getMaxIndex("buttons/name"));
395 assertEquals("a\\,b\\,c", config.getString("split/list2"));
396 assertEquals(18, config.size());
397 config.addProperty("listDelimiterTest", "1,2,3");
398 assertEquals("1", config.getString("listDelimiterTest"));
399 }
400
401
402
403
404 @Test
405 public void testGetConfigurationByIdxSynchronized() {
406 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
407 final Configuration child = new PropertiesConfiguration();
408 config.addConfiguration(child);
409 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
410 assertSame(child, config.getConfiguration(0));
411 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
412 }
413
414
415
416
417 @Test
418 public void testGetConfigurationByNameSynchronized() {
419 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
420 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
421 assertNull(config.getConfiguration("unknown config"));
422 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
423 }
424
425
426
427
428 @Test
429 public void testGetConfigurationNamesSynchronized() {
430 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
431 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
432 config.getConfigurationNames();
433 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
434 }
435
436
437
438
439 @Test
440 public void testGetNumberOfConfigurationsSynchronized() {
441 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
442 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
443 config.getNumberOfConfigurations();
444 sync.verify(Methods.BEGIN_READ, Methods.END_READ);
445 }
446
447
448
449
450 @Test
451 public void testRemoveConfigurationSynchronized() {
452 final DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
453 final String configName = "testConfig";
454 config.addConfiguration(new PropertiesConfiguration(), configName);
455 final SynchronizerTestImpl sync = prepareSynchronizerTest(config);
456 config.removeConfiguration(configName);
457 sync.verifyContains(Methods.BEGIN_WRITE);
458 }
459
460
461
462
463 @Test
464 public void testUpdateConfiguration() throws ConfigurationException {
465 System.getProperties().remove("Id");
466 final CombinedConfigurationBuilder builder = new CombinedConfigurationBuilder();
467 builder.configure(parameters.fileBased().setFile(MULTI_TENENT_FILE).setSynchronizer(new ReadWriteSynchronizer()));
468 final CombinedConfiguration config = builder.getConfiguration();
469 config.getConfiguration(1).setProperty("rowsPerPage", "25");
470 assertEquals("25", config.getString("rowsPerPage"));
471 }
472
473 private void verify(final String key, final DynamicCombinedConfiguration config, final int rows) {
474 System.setProperty("Id", key);
475 assertEquals(config.getInt("rowsPerPage"), rows);
476 }
477 }