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