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  package ch.elca.el4j.services.xmlmerge.action;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.io.Reader;
23  import java.net.MalformedURLException;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Hashtable;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.jdom.Element;
34  import org.xml.sax.EntityResolver;
35  import org.xml.sax.InputSource;
36  import org.xml.sax.SAXException;
37  
38  import com.wutka.dtd.DTD;
39  import com.wutka.dtd.DTDAny;
40  import com.wutka.dtd.DTDContainer;
41  import com.wutka.dtd.DTDElement;
42  import com.wutka.dtd.DTDItem;
43  import com.wutka.dtd.DTDName;
44  import com.wutka.dtd.DTDParser;
45  
46  import ch.elca.el4j.services.xmlmerge.AbstractXmlMergeException;
47  import ch.elca.el4j.services.xmlmerge.Action;
48  import ch.elca.el4j.services.xmlmerge.DocumentException;
49  import ch.elca.el4j.services.xmlmerge.ElementException;
50  import ch.elca.el4j.services.xmlmerge.XmlMergeContext;
51  
52  /**
53   * Copy the patch element in the output parent with the correct position
54   * according to the DTD declared in doctype.
55   *
56   * @svnLink $Revision: 3874 $;$Date: 2009-08-04 14:25:40 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/xml_merge/common/src/main/java/ch/elca/el4j/services/xmlmerge/action/DtdInsertAction.java $
57   *
58   * @author Laurent Bovet (LBO)
59   * @author Alex Mathey (AMA)
60   */
61  public class DtdInsertAction implements Action {
62  
63  	/**
64  	 * Map containing (ID, DTD) pairs, where ID represents the system ID of a
65  	 * DTD, and DTD represents the corresponding DTD.
66  	 */
67  	static Map s_dtdMap = new Hashtable();
68  	
69  	/**
70  	 * Private logger.
71  	 */
72  	private static Logger s_logger
73  		= LoggerFactory.getLogger(DtdInsertAction.class);
74  		
75  	/**
76  	 * {@inheritDoc}
77  	 */
78  	public void perform(Element originalElement, Element patchElement,
79  		Element outputParentElement) throws AbstractXmlMergeException {
80  
81  		Element element;
82  
83  		if (originalElement != null) {
84  			element = (Element) originalElement.clone();
85  		} else {
86  			element = (Element) patchElement.clone();
87  		}
88  
89  		DTD dtd = getDTD(outputParentElement);
90  
91  		List dtdElements = dtd.getItemsByType(DTDElement.class);
92  
93  		// Find the corresponding element
94  		DTDElement parentDtdElement = null;
95  		for (Iterator it = dtdElements.iterator(); it.hasNext();) {
96  			DTDElement dtdElement = (DTDElement) it.next();
97  
98  			if (dtdElement.getName().equals(outputParentElement.getName())) {
99  				parentDtdElement = dtdElement;
100 			}
101 		}
102 
103 		if (parentDtdElement == null) {
104 			throw new ElementException(element, "Element "
105 				+ outputParentElement.getName() + " not defined in DTD");
106 		} else {
107 
108 			DTDItem item = parentDtdElement.getContent();
109 
110 			if (item instanceof DTDAny) {
111 				// the parent element accepts anything in any order
112 				outputParentElement.addContent(element);
113 			} else if (item instanceof DTDContainer) {
114 
115 				// List existing elements in output parent element
116 				List existingChildren = outputParentElement.getChildren();
117 
118 				if (existingChildren.size() == 0) {
119 					// This is the first child
120 					outputParentElement.addContent(element);
121 				} else {
122 
123 					List orderedDtdElements = getOrderedDtdElements(
124 						(DTDContainer) item);
125 
126 					int indexOfNewElementInDtd = orderedDtdElements
127 						.indexOf(element.getName());
128 					s_logger.debug("index of element " + element.getName()
129 						+ ": " + indexOfNewElementInDtd);
130 
131 					int pos = existingChildren.size();
132 
133 					// Calculate the position in the parent where we insert the
134 					// element
135 					for (int i = 0; i < existingChildren.size(); i++) {
136 						String elementName = ((Element) existingChildren.get(i))
137 							.getName();
138 						s_logger.debug("index of child " + elementName + ": "
139 							+ orderedDtdElements.indexOf(elementName));
140 						if (orderedDtdElements.indexOf(elementName)
141 							> indexOfNewElementInDtd) {
142 							pos = i;
143 							break;
144 						}
145 					}
146 
147 					s_logger.debug("adding element " + element.getName()
148 						+ " add in pos " + pos);
149 					outputParentElement.addContent(pos, element);
150 
151 				}
152 
153 			}
154 
155 		}
156 
157 	}
158 
159 	/**
160 	 * Gets the DTD declared in the doctype of the element's owning document.
161 	 *
162 	 * @param element
163 	 *            The element for which the DTD will be retrieved
164 	 * @return The DTD declared in the doctype of the element's owning document
165 	 * @throws DocumentException
166 	 *             If an error occurred during DTD retrieval
167 	 */
168 	public DTD getDTD(Element element) throws DocumentException {
169 
170 		if (element.getDocument().getDocType() != null) {
171 
172 			String systemId = element.getDocument().getDocType().getSystemID();
173 
174 			DTD dtd = (DTD) s_dtdMap.get(systemId);
175 
176 			// if not in cache, create the DTD and put it in cache
177 			if (dtd == null) {
178 				Reader reader =null;
179 				
180 				// first try the configured EntityResolver
181 				EntityResolver er= XmlMergeContext.getEntityResolver();
182 				if (er != null) {
183 					InputSource is = null;
184 					try {
185 						is = er.resolveEntity("", systemId);
186 					} catch (SAXException e) {
187 						// ignore deliberately
188 					} catch (IOException e) {
189 						// ignore deliberately
190 					}
191 					if (is != null) {
192 						// there can be either a Reader or an Input stream in is
193 						
194 						reader = is.getCharacterStream();
195 						if (reader == null) {
196 							InputStream inputstream = is.getByteStream();
197 							if (inputstream != null) {
198 								reader = new InputStreamReader(inputstream);
199 							}
200 						}
201 					}
202 				}
203 				
204 				if (reader == null) {
205 					// lookup URL of DTD
206 					URL url;
207 					try {
208 						url = new URL(systemId);
209 						reader = new InputStreamReader(url.openStream());
210 					} catch (MalformedURLException e) {
211 						throw new DocumentException(element.getDocument(), e);
212 					} catch (IOException ioe) {
213 						throw new DocumentException(element.getDocument(), ioe);
214 					}
215 				}
216 				
217 				try {
218 					dtd = new DTDParser(reader).parse();
219 				} catch (IOException ioe) {
220 					throw new DocumentException(element.getDocument(), ioe);
221 				}
222 
223 				s_dtdMap.put(systemId, dtd);
224 			}
225 
226 			return dtd;
227 
228 		} else {
229 			throw new DocumentException(element.getDocument(),
230 				"No DTD specified in document " + element.getDocument());
231 		}
232 	}
233 
234 	/**
235 	 * Retieves a list containing the DTD elements of a given DTD container.
236 	 * @param container A DTD container.
237 	 * @return A list containing the DTD elements of a given DTD container
238 	 */
239 	public List getOrderedDtdElements(DTDContainer container) {
240 		List result = new ArrayList();
241 
242 		DTDItem[] items = container.getItems();
243 
244 		for (int i = 0; i < items.length; i++) {
245 			if (items[i] instanceof DTDContainer) {
246 				// recursively add container children
247 				result.addAll(getOrderedDtdElements((DTDContainer) items[i]));
248 			} else if (items[i] instanceof DTDName) {
249 				result.add(((DTDName) items[i]).getValue());
250 			}
251 		}
252 
253 		return result;
254 
255 	}
256 
257 }