1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package ch.elca.el4j.tests.xmlmerge;
21
22 import static org.junit.Assert.assertEquals;
23
24 import java.io.ByteArrayOutputStream;
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.util.LinkedHashMap;
32 import java.util.Map;
33 import java.util.Properties;
34
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.jdom.Element;
38 import org.junit.Test;
39 import org.springframework.context.ApplicationContext;
40 import org.springframework.core.io.ClassPathResource;
41 import org.springframework.core.io.Resource;
42 import org.xml.sax.EntityResolver;
43 import org.xml.sax.InputSource;
44
45 import ch.elca.el4j.core.context.ModuleApplicationContext;
46 import ch.elca.el4j.services.xmlmerge.Configurer;
47 import ch.elca.el4j.services.xmlmerge.Matcher;
48 import ch.elca.el4j.services.xmlmerge.MergeAction;
49 import ch.elca.el4j.services.xmlmerge.XmlMerge;
50 import ch.elca.el4j.services.xmlmerge.XmlMergeContext;
51 import ch.elca.el4j.services.xmlmerge.action.CompleteAction;
52 import ch.elca.el4j.services.xmlmerge.action.OrderedMergeAction;
53 import ch.elca.el4j.services.xmlmerge.config.AttributeMergeConfigurer;
54 import ch.elca.el4j.services.xmlmerge.config.ConfigurableXmlMerge;
55 import ch.elca.el4j.services.xmlmerge.config.PropertyXPathConfigurer;
56 import ch.elca.el4j.services.xmlmerge.factory.XPathOperationFactory;
57 import ch.elca.el4j.services.xmlmerge.merge.DefaultXmlMerge;
58 import ch.elca.el4j.util.codingsupport.annotations.FindBugsSuppressWarnings;
59
60
61
62
63
64
65
66
67
68
69 public class DefaultMergeTest {
70
71 private static Logger logger
72 = LoggerFactory.getLogger(DefaultMergeTest.class);
73
74
75
76
77 public static final String NL = System.getProperty("line.separator");
78
79
80
81
82
83 @Test
84 public void testSimpleMerge() throws Exception {
85
86 String xml1 = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"
87 + "<root attr1=\"1\">"
88 + "some "
89 + "text"
90 + " <a attr=\"old\">"
91 + " <!-- this is a comment -->"
92 + " <xx>"
93 + " <yy/>"
94 + " </xx>"
95 + " <aa/>"
96 + " </a>"
97 + " <b/>"
98 + " <c/>"
99 + " <d/>"
100 + " <e>"
101 + " <f/>"
102 + " </e>"
103 + "</root>";
104
105 String xml2 = "<root attr2=\"2\">"
106 + "other text"
107 + " <a attr=\"new\">"
108 + " <ab/>"
109 + " <!-- this is an other comment -->"
110 + " <xx>"
111 + " <zz/>"
112 + " </xx>"
113 + " <aa/>"
114 + " </a>"
115 + " <c/>"
116 + " <b/>"
117 + " <d/>"
118 + " <g/>"
119 + "</root>";
120
121 String result = new DefaultXmlMerge().merge(new String[] {xml1,
122 xml2});
123
124 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
125 + "<root attr1=\"1\" attr2=\"2\">" + NL
126 + " some text other text" + NL
127 + " <a attr=\"new\">" + NL
128 + " <!-- this is a comment -->" + NL
129 + " <ab />" + NL
130 + " <!-- this is an other comment -->" + NL
131 + " <xx>" + NL
132 + " <yy />" + NL
133 + " <zz />" + NL
134 + " </xx>" + NL
135 + " <aa />" + NL
136 + " </a>" + NL
137 + " <c />" + NL
138 + " <b />" + NL
139 + " <c />" + NL
140 + " <d />" + NL
141 + " <e>" + NL
142 + " <f />" + NL
143 + " </e>" + NL
144 + " <g />" + NL
145 + "</root>";
146
147 assertEquals(expected.trim(), result.trim());
148
149 }
150
151
152
153
154
155 @Test
156 public void testThreeMerges() throws Exception {
157
158 String[] sources = {
159 "<root><a/></root>",
160 "<root><a>hello</a></root>",
161 "<root><a><b/></a></root>" };
162
163 String result = new DefaultXmlMerge().merge(sources);
164
165 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
166 + "<root>" + NL
167 + " <a>" + NL
168 + " hello" + NL
169 + " <b />" + NL
170 + " </a>" + NL
171 + "</root>";
172
173 assertEquals(expected.trim(), result.trim());
174
175 }
176
177
178
179
180
181
182
183
184 @Test
185 public void testXPathOperationFactory() throws Exception {
186
187 String[] sources = {
188 "<root><a/><c/></root>",
189 "<root><a><b/></a><c><d/></c></root>" };
190
191 XmlMerge xmlMerge = new DefaultXmlMerge();
192
193 MergeAction mergeAction = new OrderedMergeAction();
194
195 XPathOperationFactory factory = new XPathOperationFactory();
196 factory.setDefaultOperation(new CompleteAction());
197
198 Map map = new LinkedHashMap();
199 map.put("/root/a", new OrderedMergeAction());
200
201 factory.setOperationMap(map);
202
203 mergeAction.setActionFactory(factory);
204
205 xmlMerge.setRootMergeAction(mergeAction);
206
207 String result = xmlMerge.merge(sources);
208
209 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
210 + "<root>" + NL
211 + " <a>" + NL
212 + " <b />" + NL
213 + " </a>" + NL
214 + " <c />" + NL
215 + "</root>";
216
217 assertEquals(expected.trim(), result.trim());
218 }
219
220
221
222
223
224
225
226
227 @Test
228 @FindBugsSuppressWarnings (value = {"OS_OPEN_STREAM", "OBL_UNSATISFIED_OBLIGATION"},
229 justification = "Not important in test method.")
230 public void testPropertyXPathConfigurer() throws Exception {
231
232
233 String[] sources = {
234 "<root><a/><c/></root>",
235 "<root><a><b/></a><c><d/></c></root>" };
236
237 Properties props = new Properties();
238 props.load(getClass().getResourceAsStream("test.properties"));
239 Configurer configurer = new PropertyXPathConfigurer(props);
240 XmlMerge xmlMerge = new ConfigurableXmlMerge(new DefaultXmlMerge(),
241 configurer);
242
243 String result = xmlMerge.merge(sources);
244
245 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
246 + "<root>" + NL
247 + " <a>" + NL
248 + " <b />" + NL
249 + " </a>" + NL
250 + " <c />" + NL
251 + "</root>";
252
253 assertEquals(expected.trim(), result.trim());
254 }
255
256
257
258
259
260
261
262
263 @Test
264 public void testInsertAction() throws Exception {
265
266 String[] sources = {
267 "<root><a id=\"a1\"/><b id=\"b1\"/>"
268 + "<b id=\"b2\"/><c id=\"c1\"/></root>",
269 "<root><a id=\"a2\"/><b id=\"b3\"/>"
270 + "<b id=\"b4\"/><c id=\"c2\"/><d/></root>"};
271
272 Properties props = new Properties();
273 props.setProperty("action.default", "INSERT");
274 props.setProperty("matcher.default", "SKIP");
275 Configurer configurer = new PropertyXPathConfigurer(props);
276 XmlMerge xmlMerge = new ConfigurableXmlMerge(new DefaultXmlMerge(),
277 configurer);
278
279 String result = xmlMerge.merge(sources);
280
281 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
282 + "<root>" + NL
283 + " <a id=\"a1\" />" + NL
284 + " <a id=\"a2\" />" + NL
285 + " <b id=\"b1\" />" + NL
286 + " <b id=\"b2\" />" + NL
287 + " <b id=\"b3\" />" + NL
288 + " <b id=\"b4\" />" + NL
289 + " <c id=\"c1\" />" + NL
290 + " <c id=\"c2\" />" + NL
291 + " <d />" + NL
292 + "</root>";
293
294 assertEquals(expected.trim(), result.trim());
295 }
296
297
298
299
300
301
302
303
304 @Test
305 public void testSpringResource() throws Exception {
306
307 ApplicationContext appContext = new ModuleApplicationContext(
308 new String[] {
309 "classpath*:mandatory/*.xml",
310 "classpath*:etc/template/xmlmerge-config.xml"
311 }, null, false, null);
312
313 Resource r = (Resource) appContext.getBean("merged");
314
315 InputStream in = r.getInputStream();
316
317 ByteArrayOutputStream bos = new ByteArrayOutputStream();
318
319 int len;
320 byte[] buffer = new byte[1024];
321 while ((len = in.read(buffer)) != -1) {
322 bos.write(buffer, 0, len);
323 }
324
325 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
326 + "<root>" + NL
327 + " <a>" + NL
328 + " <b />" + NL
329 + " </a>" + NL
330 + " <c />" + NL
331 + "</root>";
332
333 assertEquals(expected.trim(), bos.toString().trim());
334 }
335
336
337
338
339
340
341
342
343
344 @Test
345 @FindBugsSuppressWarnings (value = {"OS_OPEN_STREAM", "OBL_UNSATISFIED_OBLIGATION"},
346 justification = "Not important in test method.")
347 public void testDtdMerge() throws Exception {
348
349 InputStream[] streams
350 = new InputStream[] {
351 this.getClass().getResourceAsStream("sqlmap1.xml"),
352 this.getClass().getResourceAsStream("sqlmap2.xml") };
353
354
355 File outputFile
356 = File.createTempFile("xml-merge-common-tests", "out2.xml");
357 outputFile.deleteOnExit();
358
359 OutputStream out = null;
360
361
362 try {
363 out = new FileOutputStream(outputFile);
364 } catch (FileNotFoundException e) {
365 logger.debug("Output file not found: " + e.getLocalizedMessage());
366 }
367 Properties props = new Properties();
368 try {
369 props.load(this.getClass().getResourceAsStream("test-dtd.properties"));
370 } catch (IOException e) {
371 logger.debug("IOException while loading properties: " + e.getLocalizedMessage());
372 }
373
374 ConfigurableXmlMerge xmlMerge = new ConfigurableXmlMerge(
375 new PropertyXPathConfigurer(props));
376 XmlMergeContext.setEntityResolver(new NonNetworkIbatisResolver());
377
378 InputStream in = xmlMerge.merge(streams);
379
380 writeFromTo(in, out);
381 try {
382 if (in != null) {
383 in.close();
384 }
385 if (out != null) {
386 out.close();
387 }
388 } catch (IOException e) {
389 logger.debug("Exception while cloasing streams: " + e.getLocalizedMessage());
390 }
391
392 }
393
394
395
396
397
398
399
400 @Test
401 public void testAttributes() throws Exception {
402
403 String[] sources = {
404
405 "<root>"
406 + " <a id='1' owk='3' bla='4' />"
407 + "</root>",
408
409 "<root>"
410 + " <a id='1' attr='2' ku='3' />"
411 + "</root>"
412 };
413
414 String result = new DefaultXmlMerge().merge(sources);
415
416 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
417 + "<root>" + NL
418 + " <a id=\"1\" owk=\"3\" bla=\"4\" attr=\"2\" ku=\"3\" />" + NL
419 + "</root>";
420
421 assertEquals(expected.trim(), result.trim());
422
423 }
424
425
426
427
428
429
430
431 @Test
432 public void testIdMapper() throws Exception {
433
434 String[] sources = {
435 "<root>"
436 + " <a id='a1'/>"
437 + " <a id='b1'/>"
438 + " <a id='b2'/>"
439 + " <c id='c1'/>"
440 + " <c id='c2'/>"
441 + "</root>",
442
443 "<root>"
444 + " <a id='b1'>"
445 + " <b/>"
446 + " </a>"
447 + " <c id='c1' attr='2'/>"
448 + "</root>" };
449
450 Properties props = new Properties();
451 props.setProperty("matcher.default", "ID");
452 Configurer configurer = new PropertyXPathConfigurer(props);
453 XmlMerge xmlMerge = new ConfigurableXmlMerge(configurer);
454
455 String result = xmlMerge.merge(sources);
456
457 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
458 + "<root>" + NL
459 + " <a id=\"a1\" />" + NL
460 + " <a id=\"b1\">" + NL
461 + " <b />" + NL
462 + " </a>" + NL
463 + " <a id=\"b2\" />" + NL
464 + " <c id=\"c1\" attr=\"2\" />" + NL
465 + " <c id=\"c2\" />" + NL
466 + "</root>";
467
468 assertEquals(expected.trim(), result.trim());
469 }
470
471
472
473
474
475
476
477
478 @Test
479 public void testAttributeMerge() throws Exception {
480
481 String[] sources = {
482 "<root>"
483 + " <a>"
484 + " <b/>"
485 + " </a>"
486 + " <d/>"
487 + " <e id='1'/>"
488 + " <e id='2'/>"
489 + "</root>",
490
491 "<root xmlns:merge='http://xmlmerge.el4j.elca.ch'>"
492 + " <a merge:action='replace'>hello</a>"
493 + " <c/>"
494 + " <d merge:action='delete'/>"
495 + " <e id='2' newAttr='3' merge:matcher='ID'/>"
496 + "</root>"
497 };
498
499
500 String result = new ConfigurableXmlMerge(new AttributeMergeConfigurer())
501 .merge(sources);
502
503 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
504 + "<root>" + NL
505 + " <a>hello</a>" + NL
506 + " <c />" + NL
507 + " <e id=\"1\" />" + NL
508 + " <e id=\"2\" newAttr=\"3\" />" + NL
509 + "</root>";
510
511 assertEquals(expected.trim(), result.trim());
512
513 }
514
515
516
517
518
519
520
521
522
523 private void writeFromTo(InputStream in, OutputStream out) {
524 int len = 0;
525 byte[] buffer = new byte[1024];
526
527 try {
528 while ((len = in.read(buffer)) != -1) {
529 out.write(buffer, 0, len);
530 }
531 } catch (IOException ioe) {
532 throw new RuntimeException(ioe);
533 }
534 }
535
536
537
538
539
540
541
542
543
544
545 public static class ServletNameMatcher implements Matcher {
546
547
548
549
550 public boolean matches(Element originalElement, Element patchElement) {
551 String originalServletName = originalElement
552 .getChildText("servlet-name");
553 String patchServletName = patchElement.getChildText("servlet-name");
554
555 return patchServletName != null && originalServletName != null
556 && originalServletName.trim().equals(patchServletName.trim());
557 }
558 }
559
560
561
562
563
564
565
566 @Test
567 public void testCustomMatcher() throws Exception {
568
569 String[] sources = new String[] {
570 "<web-app>"
571 + " <servlet>"
572 + " <servlet-name>"
573 + " hello"
574 + " </servlet-name>"
575 + " <servlet-class>"
576 + " test.HelloServlet"
577 + " </servlet-class>"
578 + ""
579 + " </servlet>"
580 + " <servlet>"
581 + " <servlet-name>"
582 + " bye"
583 + " </servlet-name>"
584 + " <servlet-class>"
585 + " test.ByeServlet"
586 + " </servlet-class>"
587 + " </servlet>"
588 + ""
589 + " <servlet-mapping> "
590 + " <servlet-name>"
591 + " hello"
592 + " </servlet-name>"
593 + " <url-pattern>"
594 + " /hello"
595 + " </url-pattern>"
596 + " </servlet-mapping>"
597 + ""
598 + " <servlet-mapping>"
599 + " <servlet-name>"
600 + " bye"
601 + " </servlet-name>"
602 + " <url-pattern>"
603 + " /bye"
604 + " </url-pattern>"
605 + " </servlet-mapping>"
606 + "</web-app> ",
607
608 "<web-app>"
609 + " <servlet>"
610 + " <servlet-name>"
611 + " bye"
612 + " </servlet-name>"
613 + "<init-param>"
614 + " <param-name>"
615 + " message"
616 + " </param-name>"
617 + " <param-value>"
618 + " Bye bye!"
619 + " </param-value>"
620 + " </init-param>"
621 + ""
622 + " </servlet>"
623 + " </web-app>"
624 };
625
626 String conf = "xpath.path1=/web-app/servlet" + NL
627 + "matcher.path1=ch.elca.el4j.tests.xmlmerge."
628 + "DefaultMergeTest$ServletNameMatcher" + NL
629 + "xpath.path2=/web-app/servlet/servlet-name" + NL
630 + "action.path2=PRESERVE" + NL
631 + "xpath.path3=/web-app/servlet/init-param" + NL
632 + "action.path3=INSERT";
633
634 Configurer configurer = new PropertyXPathConfigurer(conf);
635 XmlMerge xmlMerge = new ConfigurableXmlMerge(configurer);
636
637 String result = xmlMerge.merge(sources);
638
639 String expected = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL
640 + "<web-app>" + NL
641 + " <servlet>" + NL
642 + " <servlet-name>hello</servlet-name>" + NL
643 + " <servlet-class>test.HelloServlet</servlet-class>" + NL
644 + " </servlet>" + NL
645 + " <servlet>" + NL
646 + " <servlet-name>bye</servlet-name>" + NL
647 + " <servlet-class>test.ByeServlet</servlet-class>" + NL
648 + " <init-param>" + NL
649 + " <param-name>message</param-name>" + NL
650 + " <param-value>Bye bye!</param-value>" + NL
651 + " </init-param>" + NL
652 + " </servlet>" + NL
653 + " <servlet-mapping>" + NL
654 + " <servlet-name>hello</servlet-name>" + NL
655 + " <url-pattern>/hello</url-pattern>" + NL
656 + " </servlet-mapping>" + NL
657 + " <servlet-mapping>" + NL
658 + " <servlet-name>bye</servlet-name>" + NL
659 + " <url-pattern>/bye</url-pattern>" + NL
660 + " </servlet-mapping>" + NL
661 + "</web-app>";
662
663 assertEquals(expected.trim(), result.trim());
664 }
665
666
667
668
669
670
671
672 class NonNetworkIbatisResolver implements EntityResolver {
673
674 private static final String DTD_EXTENSION = ".dtd";
675
676 private final String[] DTD_NAMES = {"sql-map-config-2.dtd"};
677
678
679 public InputSource resolveEntity(String publicId, String systemId) throws IOException {
680 if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
681 int lastPathSeparator = systemId.lastIndexOf("/");
682 for (int i = 0; i < DTD_NAMES.length; ++i) {
683 int dtdNameStart = systemId.indexOf(DTD_NAMES[i]);
684 if (dtdNameStart > lastPathSeparator) {
685 String dtdFile = systemId.substring(dtdNameStart);
686 if (logger.isTraceEnabled()) {
687 logger.trace("Trying to locate [" + dtdFile + "] in Spring jar");
688 }
689 try {
690 Resource resource = new ClassPathResource(
691 dtdFile, getClass());
692 InputSource source = new InputSource(resource.getInputStream());
693 source.setPublicId(publicId);
694 source.setSystemId(systemId);
695 if (logger.isDebugEnabled()) {
696 logger.debug("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
697 }
698 return source;
699 }
700 catch (IOException ex) {
701 if (logger.isDebugEnabled()) {
702 logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in class path", ex);
703 }
704 }
705
706 }
707 }
708 }
709
710
711 return null;
712 }
713
714 }
715
716 }
717
718