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 }