1 /*
2 * Copyright 1999-2005 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.opensymphony.xwork2.util;
17
18 import com.opensymphony.xwork2.ObjectFactory;
19 import com.opensymphony.xwork2.XWorkException;
20 import com.opensymphony.xwork2.util.location.Location;
21 import com.opensymphony.xwork2.util.location.LocationAttributes;
22 import com.opensymphony.xwork2.util.logging.Logger;
23 import com.opensymphony.xwork2.util.logging.LoggerFactory;
24 import org.w3c.dom.Document;
25 import org.w3c.dom.Element;
26 import org.w3c.dom.Node;
27 import org.xml.sax;
28 import org.xml.sax.helpers.DefaultHandler;
29
30 import javax.xml.parsers.SAXParser;
31 import javax.xml.parsers.SAXParserFactory;
32 import javax.xml.transform.TransformerFactory;
33 import javax.xml.transform.dom.DOMResult;
34 import javax.xml.transform.sax.SAXTransformerFactory;
35 import javax.xml.transform.sax.TransformerHandler;
36 import java.util.Map;
37
38 /**
39 * Helper class to create and retrieve information from location-enabled
40 * DOM-trees.
41 *
42 * @since 1.2
43 */
44 public class DomHelper {
45
46 private static final Logger LOG = LoggerFactory.getLogger(DomHelper.class);
47
48 public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/";
49
50 public static Location getLocationObject(Element element) {
51 return LocationAttributes.getLocation(element);
52 }
53
54
55 /**
56 * Creates a W3C Document that remembers the location of each element in
57 * the source file. The location of element nodes can then be retrieved
58 * using the {@link #getLocationObject(Element)} method.
59 *
60 * @param inputSource the inputSource to read the document from
61 */
62 public static Document parse(InputSource inputSource) {
63 return parse(inputSource, null);
64 }
65
66
67 /**
68 * Creates a W3C Document that remembers the location of each element in
69 * the source file. The location of element nodes can then be retrieved
70 * using the {@link #getLocationObject(Element)} method.
71 *
72 * @param inputSource the inputSource to read the document from
73 * @param dtdMappings a map of DTD names and public ids
74 */
75 public static Document parse(InputSource inputSource, Map<String, String> dtdMappings) {
76
77 SAXParserFactory factory = null;
78 String parserProp = System.getProperty("xwork.saxParserFactory");
79 if (parserProp != null) {
80 try {
81 Class clazz = ObjectFactory.getObjectFactory().getClassInstance(parserProp);
82 factory = (SAXParserFactory) clazz.newInstance();
83 }
84 catch (ClassNotFoundException e) {
85 LOG.error("Unable to load saxParserFactory set by system property 'xwork.saxParserFactory': " + parserProp, e);
86 }
87 catch (Exception e) {
88 LOG.error("Unable to load saxParserFactory set by system property 'xwork.saxParserFactory': " + parserProp, e);
89 }
90 }
91
92 if (factory == null) {
93 factory = SAXParserFactory.newInstance();
94 }
95
96 factory.setValidating((dtdMappings != null));
97 factory.setNamespaceAware(true);
98
99 SAXParser parser = null;
100 try {
101 parser = factory.newSAXParser();
102 } catch (Exception ex) {
103 throw new XWorkException("Unable to create SAX parser", ex);
104 }
105
106
107 DOMBuilder builder = new DOMBuilder();
108
109 // Enhance the sax stream with location information
110 ContentHandler locationHandler = new LocationAttributes.Pipe(builder);
111
112 try {
113 parser.parse(inputSource, new StartHandler(locationHandler, dtdMappings));
114 } catch (Exception ex) {
115 throw new XWorkException(ex);
116 }
117
118 return builder.getDocument();
119 }
120
121 /**
122 * The <code>DOMBuilder</code> is a utility class that will generate a W3C
123 * DOM Document from SAX events.
124 *
125 * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
126 */
127 static public class DOMBuilder implements ContentHandler {
128
129 /** The default transformer factory shared by all instances */
130 protected static SAXTransformerFactory FACTORY;
131
132 /** The transformer factory */
133 protected SAXTransformerFactory factory;
134
135 /** The result */
136 protected DOMResult result;
137
138 /** The parentNode */
139 protected Node parentNode;
140
141 protected ContentHandler nextHandler;
142
143 static {
144 String parserProp = System.getProperty("xwork.saxTransformerFactory");
145 if (parserProp != null) {
146 try {
147 Class clazz = ObjectFactory.getObjectFactory().getClassInstance(parserProp);
148 FACTORY = (SAXTransformerFactory) clazz.newInstance();
149 }
150 catch (ClassNotFoundException e) {
151 LOG.error("Unable to load SAXTransformerFactory set by system property 'xwork.saxTransformerFactory': " + parserProp, e);
152 }
153 catch (Exception e) {
154 LOG.error("Unable to load SAXTransformerFactory set by system property 'xwork.saxTransformerFactory': " + parserProp, e);
155 }
156 }
157
158 if (FACTORY == null) {
159 FACTORY = (SAXTransformerFactory) TransformerFactory.newInstance();
160 }
161 }
162
163 /**
164 * Construct a new instance of this DOMBuilder.
165 */
166 public DOMBuilder() {
167 this((Node) null);
168 }
169
170 /**
171 * Construct a new instance of this DOMBuilder.
172 */
173 public DOMBuilder(SAXTransformerFactory factory) {
174 this(factory, null);
175 }
176
177 /**
178 * Constructs a new instance that appends nodes to the given parent node.
179 */
180 public DOMBuilder(Node parentNode) {
181 this(null, parentNode);
182 }
183
184 /**
185 * Construct a new instance of this DOMBuilder.
186 */
187 public DOMBuilder(SAXTransformerFactory factory, Node parentNode) {
188 this.factory = factory == null? FACTORY: factory;
189 this.parentNode = parentNode;
190 setup();
191 }
192
193 /**
194 * Setup this instance transformer and result objects.
195 */
196 private void setup() {
197 try {
198 TransformerHandler handler = this.factory.newTransformerHandler();
199 nextHandler = handler;
200 if (this.parentNode != null) {
201 this.result = new DOMResult(this.parentNode);
202 } else {
203 this.result = new DOMResult();
204 }
205 handler.setResult(this.result);
206 } catch (javax.xml.transform.TransformerException local) {
207 throw new XWorkException("Fatal-Error: Unable to get transformer handler", local);
208 }
209 }
210
211 /**
212 * Return the newly built Document.
213 */
214 public Document getDocument() {
215 if (this.result == null || this.result.getNode() == null) {
216 return null;
217 } else if (this.result.getNode().getNodeType() == Node.DOCUMENT_NODE) {
218 return (Document) this.result.getNode();
219 } else {
220 return this.result.getNode().getOwnerDocument();
221 }
222 }
223
224 public void setDocumentLocator(Locator locator) {
225 nextHandler.setDocumentLocator(locator);
226 }
227
228 public void startDocument() throws SAXException {
229 nextHandler.startDocument();
230 }
231
232 public void endDocument() throws SAXException {
233 nextHandler.endDocument();
234 }
235
236 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException {
237 nextHandler.startElement(uri, loc, raw, attrs);
238 }
239
240 public void endElement(String arg0, String arg1, String arg2) throws SAXException {
241 nextHandler.endElement(arg0, arg1, arg2);
242 }
243
244 public void startPrefixMapping(String arg0, String arg1) throws SAXException {
245 nextHandler.startPrefixMapping(arg0, arg1);
246 }
247
248 public void endPrefixMapping(String arg0) throws SAXException {
249 nextHandler.endPrefixMapping(arg0);
250 }
251
252 public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
253 nextHandler.characters(arg0, arg1, arg2);
254 }
255
256 public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
257 nextHandler.ignorableWhitespace(arg0, arg1, arg2);
258 }
259
260 public void processingInstruction(String arg0, String arg1) throws SAXException {
261 nextHandler.processingInstruction(arg0, arg1);
262 }
263
264 public void skippedEntity(String arg0) throws SAXException {
265 nextHandler.skippedEntity(arg0);
266 }
267 }
268
269 public static class StartHandler extends DefaultHandler {
270
271 private ContentHandler nextHandler;
272 private Map<String, String> dtdMappings;
273
274 /**
275 * Create a filter that is chained to another handler.
276 * @param next the next handler in the chain.
277 */
278 public StartHandler(ContentHandler next, Map<String, String> dtdMappings) {
279 nextHandler = next;
280 this.dtdMappings = dtdMappings;
281 }
282
283 @Override
284 public void setDocumentLocator(Locator locator) {
285 nextHandler.setDocumentLocator(locator);
286 }
287
288 @Override
289 public void startDocument() throws SAXException {
290 nextHandler.startDocument();
291 }
292
293 @Override
294 public void endDocument() throws SAXException {
295 nextHandler.endDocument();
296 }
297
298 @Override
299 public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException {
300 nextHandler.startElement(uri, loc, raw, attrs);
301 }
302
303 @Override
304 public void endElement(String arg0, String arg1, String arg2) throws SAXException {
305 nextHandler.endElement(arg0, arg1, arg2);
306 }
307
308 @Override
309 public void startPrefixMapping(String arg0, String arg1) throws SAXException {
310 nextHandler.startPrefixMapping(arg0, arg1);
311 }
312
313 @Override
314 public void endPrefixMapping(String arg0) throws SAXException {
315 nextHandler.endPrefixMapping(arg0);
316 }
317
318 @Override
319 public void characters(char[] arg0, int arg1, int arg2) throws SAXException {
320 nextHandler.characters(arg0, arg1, arg2);
321 }
322
323 @Override
324 public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
325 nextHandler.ignorableWhitespace(arg0, arg1, arg2);
326 }
327
328 @Override
329 public void processingInstruction(String arg0, String arg1) throws SAXException {
330 nextHandler.processingInstruction(arg0, arg1);
331 }
332
333 @Override
334 public void skippedEntity(String arg0) throws SAXException {
335 nextHandler.skippedEntity(arg0);
336 }
337
338 @Override
339 public InputSource resolveEntity(String publicId, String systemId) {
340 if (dtdMappings != null && dtdMappings.containsKey(publicId)) {
341 String val = dtdMappings.get(publicId).toString();
342 return new InputSource(ClassLoaderUtil.getResourceAsStream(val, DomHelper.class));
343 }
344 return null;
345 }
346
347 @Override
348 public void warning(SAXParseException exception) {
349 }
350
351 @Override
352 public void error(SAXParseException exception) throws SAXException {
353 LOG.error(exception.getMessage() + " at (" + exception.getPublicId() + ":" +
354 exception.getLineNumber() + ":" + exception.getColumnNumber() + ")", exception);
355 throw exception;
356 }
357
358 @Override
359 public void fatalError(SAXParseException exception) throws SAXException {
360 LOG.fatal(exception.getMessage() + " at (" + exception.getPublicId() + ":" +
361 exception.getLineNumber() + ":" + exception.getColumnNumber() + ")", exception);
362 throw exception;
363 }
364 }
365
366 }