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    }