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  package org.apache.juneau.config;
18  
19  import static org.junit.jupiter.api.Assertions.*;
20  
21  import org.apache.juneau.*;
22  import org.apache.juneau.common.utils.*;
23  import org.apache.juneau.config.event.*;
24  import org.apache.juneau.config.store.*;
25  import org.junit.jupiter.api.*;
26  
27  /**
28   * Validates aspects of config imports.
29   */
30  class ConfigImportsTest extends TestBase {
31  
32  	//-----------------------------------------------------------------------------------------------------------------
33  	// Value inheritance
34  	//-----------------------------------------------------------------------------------------------------------------
35  
36  	@Test void oneSimpleImport() throws Exception {
37  		var ms = MemoryStore.create().build();
38  		ms.write("A", "", "x=1");
39  		ms.write("B", "", "<A>");
40  		Config c = Config
41  			.create("B")
42  			.store(ms)
43  			.build();
44  		assertEquals("1", c.get("x").get());
45  
46  		c.set("x", "2");
47  		c.commit();
48  		assertEquals("x=1", ms.read("A"));
49  		assertEquals("x = 2\n", ms.read("B"));
50  	}
51  
52  	@Test void twoSimpleImports() throws Exception {
53  		var ms = MemoryStore.create().build();
54  		ms.write("A1", "", "x=1");
55  		ms.write("A2", "", "y=2");
56  		ms.write("B", "", "<A1>\n<A2>");
57  		var c = Config.create("B").store(ms).build();
58  		assertEquals("1", c.get("x").get());
59  		assertEquals("2", c.get("y").get());
60  
61  		c.set("x", "3");
62  		c.set("y", "4");
63  		c.commit();
64  		assertEquals("x=1", ms.read("A1"));
65  		assertEquals("y=2", ms.read("A2"));
66  		assertEquals("x = 3\ny = 4\n", ms.read("B"));
67  	}
68  
69  	@Test void nestedImports() throws Exception {
70  		var ms = MemoryStore.create().build();
71  		ms.write("A1", "", "x=1");
72  		ms.write("A2", "", "<A1>\ny=2");
73  		ms.write("B", "", "<A2>");
74  		var c = Config.create("B").store(ms).build();
75  		assertEquals("1", c.get("x").get());
76  		assertEquals("2", c.get("y").get());
77  
78  		c.set("x", "3");
79  		c.set("y", "4");
80  		c.commit();
81  		assertEquals("x=1", ms.read("A1"));
82  		assertEquals("<A1>\ny=2", ms.read("A2"));
83  		assertEquals("x = 3\ny = 4\n", ms.read("B"));
84  	}
85  
86  	@Test void nestedImportsLoop() {
87  		// This shouldn't blow up.
88  		var ms = MemoryStore.create().build();
89  		ms.write("A1", "", "<A2>\nx=1");
90  		ms.write("A2", "", "<A1>\ny=2");
91  		ms.write("B", "", "<A2>");
92  		assertThrows(Exception.class, ()->Config.create("B").store(ms).build());
93  	}
94  
95  	@Test void importNotFound() {
96  		var ms = MemoryStore.create().build();
97  		ms.write("B", "", "<A>\nx=1");
98  		var c = Config.create("B").store(ms).build();
99  		assertEquals("1", c.get("x").get());
100 	}
101 
102 	@Test void noOverwriteOnImports() {
103 		var ms = MemoryStore.create().build();
104 		ms.write("A", "", "x=1");
105 		ms.write("B", "", "<A>");
106 		var c = Config.create("B").store(ms).build();
107 		assertEquals("1", c.get("x").get());
108 		c.set("x", "2");
109 		assertEquals("2", c.get("x").get());
110 		assertEquals("1", Config.create("A").store(ms).build().get("x").get());
111 	}
112 
113 	@Test void overlappingSections() {
114 		var ms = MemoryStore.create().build();
115 		ms.write("A", "", "x=1\n[A]\na1=1");
116 		ms.write("B", "", "<A>\n[A]\na2=2");
117 		var c = Config.create("B").store(ms).build();
118 		assertEquals("1", c.get("A/a1").get());
119 		assertEquals("2", c.get("A/a2").get());
120 	}
121 
122 	@Test void overlappingSectionsImportAtEnd() {
123 		var ms = MemoryStore.create().build();
124 		ms.write("A", "", "x=1\n[A]\na1=1");
125 		ms.write("B", "", "[A]\na2=2\n<A>");
126 		var c = Config.create("B").store(ms).build();
127 		assertEquals("1", c.get("A/a1").get());
128 		assertEquals("2", c.get("A/a2").get());
129 	}
130 
131 	@Test void overlappingSectionsAndValues() {
132 		var ms = MemoryStore.create().build();
133 		ms.write("A", "", "x=1\n[A]\na1=1");
134 		ms.write("B", "", "<A>\n[A]\na1=2");
135 		var c = Config.create("B").store(ms).build();
136 		assertEquals("2", c.get("A/a1").get());
137 	}
138 
139 	//-----------------------------------------------------------------------------------------------------------------
140 	// Listeners
141 	//-----------------------------------------------------------------------------------------------------------------
142 
143 	public static class TestListener implements ConfigEventListener {
144 
145 		private boolean triggered;
146 		private ConfigEvents events;
147 
148 		@Override
149 		public void onConfigChange(ConfigEvents events) {
150 			this.triggered = true;
151 			this.events = events;
152 		}
153 
154 		public TestListener reset() {
155 			triggered = false;
156 			events = null;
157 			return this;
158 		}
159 
160 		public boolean isTriggered() {
161 			return triggered;
162 		}
163 
164 		public ConfigEvents getEvents() {
165 			return events;
166 		}
167 
168 		public String getNewValue(String section, String key) {
169 			if (events.isEmpty())
170 				return null;
171 			for (ConfigEvent ce : events)
172 				if (Utils.eq(section, ce.getSection()) && Utils.eq(key, ce.getKey()))
173 					return ce.getValue();
174 			return null;
175 		}
176 	}
177 
178 	@Test void a01_updateOnParent() {
179 		var ms = MemoryStore.create().build();
180 
181 		ms.write("A", "", "x=1\n[A]\na1=1");
182 		ms.write("B", "", "<A>\n[B]\nb1=2");
183 
184 		var ca = Config.create("A").store(ms).build();
185 		var cb = Config.create("B").store(ms).build();
186 
187 		assertEquals(2, ca.getConfigMap().getListeners().size());
188 		assertEquals(1, cb.getConfigMap().getListeners().size());
189 
190 		var l = new TestListener();
191 		cb.addListener(l);
192 
193 		assertEquals(0, ca.getListeners().size());
194 		assertEquals(1, cb.getListeners().size());
195 
196 		ms.write("A", "x=1\n[A]\na1=1", "x=1\n[A]\na1=2");
197 
198 		assertTrue(l.isTriggered());
199 		assertEquals(1, l.getEvents().size());
200 		assertEquals("2", l.getNewValue("A", "a1"));
201 
202 		assertEquals("2", cb.get("A/a1").get());
203 		assertEquals("2", cb.get("B/b1").get());
204 
205 		l.reset();
206 		cb.removeListener(l);
207 		assertEquals(0, ca.getListeners().size());
208 		assertEquals(0, cb.getListeners().size());
209 
210 		ms.write("A", "x=1\n[A]\na1=2", "x=1\n[A]\na1=3");
211 
212 		assertFalse(l.isTriggered());
213 
214 		assertEquals("3", cb.get("A/a1").get());
215 		assertEquals("2", cb.get("B/b1").get());
216 	}
217 
218 	@Test void a02_updateOnGrandParent() {
219 		var ms = MemoryStore.create().build();
220 
221 		ms.write("A", "", "x=1\n[A]\na1=1");
222 		ms.write("B", "", "<A>\n[B]\nb1=2");
223 		ms.write("C", "", "<B>\n[C]\nc1=3");
224 
225 		var ca = Config.create("A").store(ms).build();
226 		var cb = Config.create("B").store(ms).build();
227 		var cc = Config.create("C").store(ms).build();
228 
229 		assertEquals(3, ca.getConfigMap().getListeners().size());
230 		assertEquals(2, cb.getConfigMap().getListeners().size());
231 		assertEquals(1, cc.getConfigMap().getListeners().size());
232 
233 		var l = new TestListener();
234 		cc.addListener(l);
235 
236 		assertEquals(0, ca.getListeners().size());
237 		assertEquals(0, cb.getListeners().size());
238 		assertEquals(1, cc.getListeners().size());
239 
240 		ms.write("A", "x=1\n[A]\na1=1", "x=1\n[A]\na1=2");
241 
242 		assertTrue(l.isTriggered());
243 		assertEquals(1, l.getEvents().size());
244 		assertEquals("2", l.getNewValue("A", "a1"));
245 
246 		assertEquals("2", cc.get("A/a1").get());
247 		assertEquals("2", cc.get("B/b1").get());
248 		assertEquals("3", cc.get("C/c1").get());
249 
250 		l.reset();
251 		cc.removeListener(l);
252 		assertEquals(0, ca.getListeners().size());
253 		assertEquals(0, cb.getListeners().size());
254 		assertEquals(0, cc.getListeners().size());
255 
256 		ms.write("A", "x=1\n[A]\na1=2", "x=1\n[A]\na1=3");
257 
258 		assertFalse(l.isTriggered());
259 
260 		assertEquals("3", cc.get("A/a1").get());
261 		assertEquals("2", cc.get("B/b1").get());
262 		assertEquals("3", cc.get("C/c1").get());
263 	}
264 
265 	@Test void a03_updateOnParentSameSection() {
266 		var ms = MemoryStore.create().build();
267 
268 		ms.write("A", "", "x=1\n[A]\na1=1");
269 		ms.write("B", "", "<A>\n[A]\nb1=2");
270 
271 		var ca = Config.create("A").store(ms).build();
272 		var cb = Config.create("B").store(ms).build();
273 
274 		assertEquals(2, ca.getConfigMap().getListeners().size());
275 		assertEquals(1, cb.getConfigMap().getListeners().size());
276 
277 		var l = new TestListener();
278 		cb.addListener(l);
279 
280 		ms.write("A", "x=1\n[A]\na1=1", "x=1\n[A]\na1=2");
281 
282 		assertTrue(l.isTriggered());
283 		assertEquals(1, l.getEvents().size());
284 		assertEquals("2", l.getNewValue("A", "a1"));
285 
286 		assertEquals("2", cb.get("A/a1").get());
287 		assertEquals("2", cb.get("A/b1").get());
288 
289 		l.reset();
290 		cb.removeListener(l);
291 		assertEquals(0, ca.getListeners().size());
292 		assertEquals(0, cb.getListeners().size());
293 
294 		ms.write("A", "x=1\n[A]\na1=2", "x=1\n[A]\na1=3");
295 
296 		assertFalse(l.isTriggered());
297 
298 		assertEquals("3", cb.get("A/a1").get());
299 		assertEquals("2", cb.get("A/b1").get());
300 	}
301 
302 	@Test void a04_updateOnParentSameSectionSameKey() {
303 		var ms = MemoryStore.create().build();
304 
305 		ms.write("A", "", "x=1\n[A]\na1=1");
306 		ms.write("B", "", "<A>\n[A]\na1=2");
307 
308 		var ca = Config.create("A").store(ms).build();
309 		var cb = Config.create("B").store(ms).build();
310 
311 		assertEquals(2, ca.getConfigMap().getListeners().size());
312 		assertEquals(1, cb.getConfigMap().getListeners().size());
313 
314 		var l = new TestListener();
315 		cb.addListener(l);
316 
317 		ms.write("A", "x=1\n[A]\na1=1", "x=1\n[A]\na1=3");
318 
319 		assertFalse(l.isTriggered());
320 		assertEquals("2", cb.get("A/a1").get());
321 	}
322 
323 	@Test void a05_updateOnGrandParentSameSection() {
324 		var ms = MemoryStore.create().build();
325 
326 		ms.write("A", "", "x=1\n[A]\na1=1");
327 		ms.write("B", "", "<A>\n[A]\na1=2");
328 		ms.write("C", "", "<B>\n[A]\na1=3");
329 
330 		var ca = Config.create("A").store(ms).build();
331 		var cb = Config.create("B").store(ms).build();
332 		var cc = Config.create("C").store(ms).build();
333 
334 		assertEquals(3, ca.getConfigMap().getListeners().size());
335 		assertEquals(2, cb.getConfigMap().getListeners().size());
336 		assertEquals(1, cc.getConfigMap().getListeners().size());
337 
338 		var l = new TestListener();
339 		cc.addListener(l);
340 
341 		ms.write("A", "x=1\n[A]\na1=1", "x=1\n[A]\na1=4");
342 
343 		assertFalse(l.isTriggered());
344 		assertEquals("3", cc.get("A/a1").get());
345 	}
346 
347 	//-----------------------------------------------------------------------------------------------------------------
348 	// Listeners and dynamically modifying imports
349 	//-----------------------------------------------------------------------------------------------------------------
350 
351 	@Test void a06_updateOnParentDynamic() {
352 		var ms = MemoryStore.create().build();
353 
354 		ms.write("A", "", "x=1\ny=1\n[A]\na1=1");
355 		ms.write("B", "", "x=2\n[B]\nb1=2");
356 
357 		var ca = Config.create("A").store(ms).build();
358 		var cb = Config.create("B").store(ms).build();
359 
360 		assertEquals(1, ca.getConfigMap().getListeners().size());
361 		assertEquals(1, cb.getConfigMap().getListeners().size());
362 
363 		var l = new TestListener();
364 		cb.addListener(l);
365 
366 		assertEquals(0, ca.getListeners().size());
367 		assertEquals(1, cb.getListeners().size());
368 
369 		ms.write("B", "x=2\n[B]\nb1=2", "x=2\n<A>\n[B]\nb1=2");
370 
371 		assertTrue(l.isTriggered());
372 		assertEquals(2, l.getEvents().size());  // Should contain [SET(y = 1), SET(A/a1 = 1)]
373 		assertEquals("1", l.getNewValue("A", "a1"));
374 
375 		assertEquals("1", cb.get("A/a1").get());
376 		assertEquals("2", cb.get("B/b1").get());
377 
378 		assertEquals(2, ca.getConfigMap().getListeners().size());
379 		assertEquals(1, cb.getConfigMap().getListeners().size());
380 
381 		l.reset();
382 
383 		ms.write("B", "x=2\n<A>\n[B]\nb1=2", "x=2\n[B]\nb1=2");
384 
385 		assertTrue(l.isTriggered());
386 		assertEquals(2, l.getEvents().size());  // Should contain [REMOVE_ENTRY(y), REMOVE_ENTRY(A/a1)]
387  		assertEquals(null, l.getNewValue("A", "a1"));
388 
389 		assertNull(cb.get("A/a1").orElse(null));
390 		assertEquals("2", cb.get("B/b1").get());
391 
392 		l.reset();
393 		cb.removeListener(l);
394 		assertEquals(0, ca.getListeners().size());
395 		assertEquals(0, cb.getListeners().size());
396 
397 		ms.write("B", "x=2\n[B]\nb1=2", "x=2\n<A>\n[B]\nb1=2");
398 
399 		assertFalse(l.isTriggered());
400 
401 		assertEquals("1", cb.get("A/a1").get());
402 		assertEquals("2", cb.get("B/b1").get());
403 	}
404 }