View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2006 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  
18  //Checkstyle: MagicNumber off
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   * This class tests several functionalities of the xml_merge module, using a
62   * <code>DefaultXmlMerge</code> instance.
63   *
64   * @svnLink $Revision: 4010 $;$Date: 2009-12-01 10:59:54 +0100 (Di, 01. Dez 2009) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/xml_merge/common/src/test/java/ch/elca/el4j/tests/xmlmerge/DefaultMergeTest.java $
65   *
66   * @author Laurent Bovet (LBO)
67   * @author Alex Mathey (AMA)
68   */
69  public class DefaultMergeTest {
70  
71  	private static Logger logger
72  		= LoggerFactory.getLogger(DefaultMergeTest.class);
73  	
74  	/**
75  	 * New line.
76  	 */
77  	public static final String NL = System.getProperty("line.separator");
78  	
79  	/**
80  	 * Tests a simple merge of two strings.
81  	 * @throws Exception If an error occurs during the test
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 	 * Tests a merge of three strings.
153 	 * @throws Exception If an error occurs during the test
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 	 * Tests programmatic configuration of an XmlMerge instance, using an
179 	 * XPathOperationFactory.
180 	 *
181 	 * @throws Exception
182 	 *             If an error occurs during the test
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 	 * Tests configuration of an XmlMerge instance with XPath and Properties,
222 	 * using a PropertyXPathConfigurer.
223 	 *
224 	 * @throws Exception
225 	 *             If an error occurs during the test
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 	 * Tests the InsertAction in conjunction with the SkipMatcher, inserting the
258 	 * patch elements after the original elements of the same tag in the result.
259 	 *
260 	 * @throws Exception
261 	 *             If an error occurs during the test
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 	 * Tests the creation of an XML Spring Resource on-the-fly by merging XML
299 	 * documents read from other resources.
300 	 *
301 	 * @throws Exception
302 	 *             If an error occurs during the test
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 	 * Tests a DTD merge of two source files using a DtdInsertAction: inserts
338 	 * the patch elements in the result according to the order specified in the
339 	 * original document's DTD.
340 	 *
341 	 * @throws Exception
342 	 *             If an error occurs during the test
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 	 * Tests a merge of an element's attributes.
396 	 *
397 	 * @throws Exception
398 	 *             If an error occurs during the test
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 	 * Tests a merge using the IdentityMapper.
427 	 *
428 	 * @throws Exception
429 	 *             If an error occurs during the test
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 	 * Tests configuration of an XmlMerge instance with inline attributes in the
473 	 * patch document.
474 	 *
475 	 * @throws Exception
476 	 *             If an error occurs during the test
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 	 * Writes from an InputStream to an OutputStream.
517 	 *
518 	 * @param in
519 	 *            The InputStream to read from
520 	 * @param out
521 	 *            The Outputstream to write to
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 	 * This class is a custom matcher. With this matcher, the original and patch
538 	 * elements match only if their tag name is "servlet-name".
539 	 *
540 	 * @svnLink $Revision: 4010 $;$Date: 2009-12-01 10:59:54 +0100 (Di, 01. Dez 2009) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/xml_merge/common/src/test/java/ch/elca/el4j/tests/xmlmerge/DefaultMergeTest.java $
541 	 *
542 	 * @author Laurent Bovet (LBO)
543 	 * @author Alex Mathey (AMA)
544 	 */
545 	public static class ServletNameMatcher implements Matcher {
546 
547 		/**
548 		 * {@inheritDoc}
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 	 * Tests a merge using a custom matcher.
562 	 *
563 	 * @throws Exception
564 	 *             If an error occurs during the test
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 	 * private EntityResolver that does not use the network.
668 	 *
669 	 *  Inspired by Spring's  BeansDtdResolver
670 	 *   Works for dtd of ibatis
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( /*"ch/elca/el4j/tests/xmlmerge/"+*/
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 			// Use the default behavior -> download from website or wherever.
711 			return null;
712 		}
713 
714 	}
715 
716 }
717 
718 //Checkstyle: MagicNumber on