001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import static org.junit.Assert.assertEquals;
021 import static org.junit.Assert.assertFalse;
022 import static org.junit.Assert.assertNotNull;
023 import static org.junit.Assert.assertTrue;
024
025 import java.io.File;
026 import java.io.FileReader;
027 import java.io.FileWriter;
028 import java.io.IOException;
029 import java.io.Reader;
030 import java.io.Writer;
031
032 import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
033 import org.apache.commons.lang.text.StrLookup;
034 import org.junit.Test;
035
036 public class TestDynamicCombinedConfiguration
037 {
038 private static String PATTERN = "${sys:Id}";
039 private static String PATTERN1 = "target/test-classes/testMultiConfiguration_${sys:Id}.xml";
040 private static String DEFAULT_FILE = "target/test-classes/testMultiConfiguration_default.xml";
041 private static final File MULTI_TENENT_FILE = new File(
042 "conf/testMultiTenentConfigurationBuilder4.xml");
043 private static final File MULTI_DYNAMIC_FILE = new File(
044 "conf/testMultiTenentConfigurationBuilder5.xml");
045
046 /** Constant for the number of test threads. */
047 private static final int THREAD_COUNT = 3;
048
049 /** Constant for the number of loops in the multi-thread tests. */
050 private static final int LOOP_COUNT = 100;
051
052 @Test
053 public void testConfiguration() throws Exception
054 {
055 DynamicCombinedConfiguration config = new DynamicCombinedConfiguration();
056 XPathExpressionEngine engine = new XPathExpressionEngine();
057 config.setExpressionEngine(engine);
058 config.setKeyPattern(PATTERN);
059 config.setDelimiterParsingDisabled(true);
060 MultiFileHierarchicalConfiguration multi = new MultiFileHierarchicalConfiguration(PATTERN1);
061 multi.setExpressionEngine(engine);
062 config.addConfiguration(multi, "Multi");
063 XMLConfiguration xml = new XMLConfiguration();
064 xml.setExpressionEngine(engine);
065 xml.setDelimiterParsingDisabled(true);
066 xml.setFile(new File(DEFAULT_FILE));
067 xml.load();
068 config.addConfiguration(xml, "Default");
069
070 verify("1001", config, 15);
071 verify("1002", config, 25);
072 verify("1003", config, 35);
073 verify("1004", config, 50);
074 assertEquals("a,b,c", config.getString("split/list3/@values"));
075 assertEquals(0, config.getMaxIndex("split/list3/@values"));
076 assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
077 assertEquals("a,b,c", config.getString("split/list1"));
078 assertEquals(0, config.getMaxIndex("split/list1"));
079 assertEquals("a\\,b\\,c", config.getString("split/list2"));
080 }
081
082 @Test
083 public void testConcurrentGetAndReload() throws Exception
084 {
085 System.getProperties().remove("Id");
086 DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
087 factory.setFile(MULTI_TENENT_FILE);
088 CombinedConfiguration config = factory.getConfiguration(true);
089
090 assertEquals(config.getString("rowsPerPage"), "50");
091 Thread testThreads[] = new Thread[THREAD_COUNT];
092 int failures[] = new int[THREAD_COUNT];
093
094 for (int i = 0; i < testThreads.length; ++i)
095 {
096 testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, false, null, "50");
097 testThreads[i].start();
098 }
099
100 int totalFailures = 0;
101 for (int i = 0; i < testThreads.length; ++i)
102 {
103 testThreads[i].join();
104 totalFailures += failures[i];
105 }
106 assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
107 }
108
109 @Test
110 public void testConcurrentGetAndReload2() throws Exception
111 {
112 System.getProperties().remove("Id");
113 DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
114 factory.setFile(MULTI_TENENT_FILE);
115 CombinedConfiguration config = factory.getConfiguration(true);
116
117 assertEquals(config.getString("rowsPerPage"), "50");
118
119 Thread testThreads[] = new Thread[THREAD_COUNT];
120 int failures[] = new int[THREAD_COUNT];
121 System.setProperty("Id", "2002");
122 assertEquals(config.getString("rowsPerPage"), "25");
123 for (int i = 0; i < testThreads.length; ++i)
124 {
125 testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, false, null, "25");
126 testThreads[i].start();
127 }
128
129 int totalFailures = 0;
130 for (int i = 0; i < testThreads.length; ++i)
131 {
132 testThreads[i].join();
133 totalFailures += failures[i];
134 }
135 System.getProperties().remove("Id");
136 assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
137 }
138
139 @Test
140 public void testConcurrentGetAndReloadMultipleClients() throws Exception
141 {
142 System.getProperties().remove("Id");
143 DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
144 factory.setFile(MULTI_TENENT_FILE);
145 CombinedConfiguration config = factory.getConfiguration(true);
146
147 assertEquals(config.getString("rowsPerPage"), "50");
148
149 Thread testThreads[] = new Thread[THREAD_COUNT];
150 int failures[] = new int[THREAD_COUNT];
151 String[] ids = new String[] {null, "2002", "3001", "3002", "3003"};
152 String[] expected = new String[] {"50", "25", "15", "25", "50"};
153 for (int i = 0; i < testThreads.length; ++i)
154 {
155 testThreads[i] = new ReloadThread(config, failures, i, LOOP_COUNT, true, ids[i], expected[i]);
156 testThreads[i].start();
157 }
158
159 int totalFailures = 0;
160 for (int i = 0; i < testThreads.length; ++i)
161 {
162 testThreads[i].join();
163 totalFailures += failures[i];
164 }
165 System.getProperties().remove("Id");
166 if (totalFailures != 0)
167 {
168 System.out.println("Failures:");
169 for (int i = 0; i < testThreads.length; ++i)
170 {
171 System.out.println("Thread " + i + " " + failures[i]);
172 }
173 }
174 assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
175 }
176
177 @Test
178 public void testConcurrentGetAndReloadFile() throws Exception
179 {
180 final int threadCount = 25;
181 System.getProperties().remove("Id");
182 // create a new configuration
183 File input = new File("target/test-classes/testMultiDynamic_default.xml");
184 File output = new File("target/test-classes/testwrite/testMultiDynamic_default.xml");
185 output.delete();
186 output.getParentFile().mkdir();
187 copyFile(input, output);
188
189 DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
190 factory.setFile(MULTI_DYNAMIC_FILE);
191 CombinedConfiguration config = factory.getConfiguration(true);
192
193 assertEquals(config.getString("Product/FIIndex/FI[@id='123456781']"), "ID0001");
194
195 ReaderThread testThreads[] = new ReaderThread[threadCount];
196 for (int i = 0; i < testThreads.length; ++i)
197 {
198 testThreads[i] = new ReaderThread(config);
199 testThreads[i].start();
200 }
201
202 Thread.sleep(2000);
203
204 input = new File("target/test-classes/testMultiDynamic_default2.xml");
205 copyFile(input, output);
206
207 Thread.sleep(2000);
208 String id = config.getString("Product/FIIndex/FI[@id='123456782']");
209 assertNotNull("File did not reload, id is null", id);
210 String rows = config.getString("rowsPerPage");
211 assertTrue("Incorrect value for rowsPerPage", "25".equals(rows));
212
213 for (int i = 0; i < testThreads.length; ++i)
214 {
215 testThreads[i].shutdown();
216 testThreads[i].join();
217 }
218 for (int i = 0; i < testThreads.length; ++i)
219 {
220 assertFalse(testThreads[i].failed());
221 }
222 assertEquals("ID0002", config.getString("Product/FIIndex/FI[@id='123456782']"));
223 output.delete();
224 }
225
226
227 private class ReloadThread extends Thread
228 {
229 CombinedConfiguration combined;
230 int[] failures;
231 int index;
232 int count;
233 String expected;
234 String id;
235 boolean useId;
236
237 ReloadThread(CombinedConfiguration config, int[] failures, int index, int count,
238 boolean useId, String id, String expected)
239 {
240 combined = config;
241 this.failures = failures;
242 this.index = index;
243 this.count = count;
244 this.expected = expected;
245 this.id = id;
246 this.useId = useId;
247 }
248 @Override
249 public void run()
250 {
251 failures[index] = 0;
252
253 if (useId)
254 {
255 ThreadLookup.setId(id);
256 }
257 for (int i = 0; i < count; i++)
258 {
259 try
260 {
261 String value = combined.getString("rowsPerPage", null);
262 if (value == null || !value.equals(expected))
263 {
264 ++failures[index];
265 }
266 }
267 catch (Exception ex)
268 {
269 ++failures[index];
270 }
271 }
272 }
273 }
274
275 private class ReaderThread extends Thread
276 {
277 private boolean running = true;
278 private boolean failed = false;
279 CombinedConfiguration combined;
280
281 public ReaderThread(CombinedConfiguration c)
282 {
283 combined = c;
284 }
285
286 @Override
287 public void run()
288 {
289 while (running)
290 {
291 String bcId = combined.getString("Product/FIIndex/FI[@id='123456781']");
292 if ("ID0001".equalsIgnoreCase(bcId))
293 {
294 if (failed)
295 {
296 System.out.println("Thread failed, but recovered");
297 }
298 failed = false;
299 }
300 else
301 {
302 failed = true;
303 }
304 }
305 }
306
307 public boolean failed()
308 {
309 return failed;
310 }
311
312 public void shutdown()
313 {
314 running = false;
315 }
316
317 }
318
319 private void verify(String key, DynamicCombinedConfiguration config, int rows)
320 {
321 System.setProperty("Id", key);
322 assertTrue(config.getInt("rowsPerPage") == rows);
323 }
324
325 private void copyFile(File input, File output) throws IOException
326 {
327 Reader reader = new FileReader(input);
328 Writer writer = new FileWriter(output);
329 char[] buffer = new char[4096];
330 int n = 0;
331 while (-1 != (n = reader.read(buffer)))
332 {
333 writer.write(buffer, 0, n);
334 }
335 reader.close();
336 writer.close();
337 }
338
339 public static class ThreadLookup extends StrLookup
340 {
341 private static ThreadLocal<String> id = new ThreadLocal<String>();
342
343
344
345 public ThreadLookup()
346 {
347
348 }
349
350 public static void setId(String value)
351 {
352 id.set(value);
353 }
354
355 @Override
356 public String lookup(String key)
357 {
358 if (key == null || !key.equals("Id"))
359 {
360 return null;
361 }
362 String value = System.getProperty("Id");
363 if (value != null)
364 {
365 return value;
366 }
367 return id.get();
368
369 }
370 }
371 }