001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone;
008:
009: import java.io.File;
010: import java.io.IOException;
011: import java.net.URL;
012:
013: import javax.xml.parsers.DocumentBuilder;
014: import javax.xml.parsers.DocumentBuilderFactory;
015: import javax.xml.parsers.ParserConfigurationException;
016:
017: import org.w3c.dom.Document;
018: import org.xml.sax.EntityResolver;
019: import org.xml.sax.ErrorHandler;
020: import org.xml.sax.InputSource;
021: import org.xml.sax.SAXException;
022: import org.xml.sax.SAXParseException;
023:
024: /**
025: * The web.xml parsing logic. This is used by more than one launcher, so it's shared from here.
026: *
027: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
028: * @version $Id: WebXmlParser.java,v 1.9 2006/12/08 04:08:44 rickknowles Exp $
029: */
030: public class WebXmlParser implements EntityResolver, ErrorHandler {
031:
032: private ClassLoader commonLoader;
033: private boolean rethrowValidationExceptions;
034:
035: public WebXmlParser(ClassLoader commonCL) {
036: this .commonLoader = commonCL;
037: this .rethrowValidationExceptions = true;
038: }
039:
040: private final static String SCHEMA_SOURCE_PROPERTY = "http://java.sun.com/xml/jaxp/properties/schemaSource";
041:
042: /**
043: * Get a parsed XML DOM from the given inputstream. Used to process the
044: * web.xml application deployment descriptors. Returns null if the parse fails,
045: * so the effect is as if there was no web.xml file available.
046: */
047: protected Document parseStreamToXML(File webXmlFile) {
048: DocumentBuilderFactory factory = getBaseDBF();
049:
050: URL localXSD25 = this .commonLoader
051: .getResource(LOCAL_ENTITY_TABLE[3][2]);
052: URL localXSD24 = this .commonLoader
053: .getResource(LOCAL_ENTITY_TABLE[2][2]);
054:
055: // Test for XSD compliance
056: try {
057: factory
058: .setAttribute(
059: "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
060: "http://www.w3.org/2001/XMLSchema");
061: if (localXSD25 != null) {
062: factory.setAttribute(SCHEMA_SOURCE_PROPERTY, localXSD25
063: .toString());
064: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
065: "WebXmlParser.Local25XSDEnabled");
066: } else if (localXSD24 != null) {
067: factory.setAttribute(SCHEMA_SOURCE_PROPERTY, localXSD24
068: .toString());
069: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
070: "WebXmlParser.Local24XSDEnabled");
071: } else {
072: Logger.log(Logger.WARNING, Launcher.RESOURCES,
073: "WebXmlParser.2524XSDNotFound");
074: }
075: } catch (Throwable err) {
076: // if non-compliant parser, then parse as non-XSD compliant
077: Logger.log(Logger.WARNING, Launcher.RESOURCES,
078: "WebXmlParser.NonXSDParser");
079: try {
080: this .rethrowValidationExceptions = false;
081: return parseAsV23Webapp(webXmlFile);
082: } catch (Throwable v23Err) {
083: Logger.log(Logger.ERROR, Launcher.RESOURCES,
084: "WebXmlParser.WebXML23ParseError", v23Err);
085: return null;
086: }
087: }
088:
089: // XSD compliant parser available, so parse as 2.5
090: try {
091: if (localXSD25 != null) {
092: factory.setAttribute(SCHEMA_SOURCE_PROPERTY, localXSD25
093: .toString());
094: } else {
095: factory.setAttribute(SCHEMA_SOURCE_PROPERTY, null);
096: }
097: DocumentBuilder builder = factory.newDocumentBuilder();
098: builder.setEntityResolver(this );
099: builder.setErrorHandler(this );
100: this .rethrowValidationExceptions = true;
101: return builder.parse(webXmlFile);
102: } catch (Throwable errV25) {
103: try {
104: // Try as 2.4
105: if (localXSD24 != null) {
106: factory.setAttribute(SCHEMA_SOURCE_PROPERTY,
107: localXSD24.toString());
108: } else {
109: factory.setAttribute(SCHEMA_SOURCE_PROPERTY, null);
110: }
111: DocumentBuilder builder = factory.newDocumentBuilder();
112: builder.setEntityResolver(this );
113: builder.setErrorHandler(this );
114: this .rethrowValidationExceptions = true;
115: return builder.parse(webXmlFile);
116: } catch (Throwable errV24) {
117: // Try parsing as a v2.3 spec webapp, and if another error happens, report 2.3, 2.4, 2.5
118: try {
119: this .rethrowValidationExceptions = false;
120: return parseAsV23Webapp(webXmlFile);
121: } catch (Throwable errV23) {
122: Logger.log(Logger.ERROR, Launcher.RESOURCES,
123: "WebXmlParser.WebXMLBothErrors");
124: Logger.log(Logger.ERROR, Launcher.RESOURCES,
125: "WebXmlParser.WebXML25ParseError", errV25);
126: Logger.log(Logger.ERROR, Launcher.RESOURCES,
127: "WebXmlParser.WebXML24ParseError", errV24);
128: Logger.log(Logger.ERROR, Launcher.RESOURCES,
129: "WebXmlParser.WebXML23ParseError", errV23);
130: return null;
131: }
132: }
133: }
134: }
135:
136: private Document parseAsV23Webapp(File webXmlFile)
137: throws ParserConfigurationException, SAXException,
138: IOException {
139: DocumentBuilderFactory factory = getBaseDBF();
140: DocumentBuilder builder = factory.newDocumentBuilder();
141: builder.setEntityResolver(this );
142: builder.setErrorHandler(this );
143: return builder.parse(webXmlFile);
144: }
145:
146: private DocumentBuilderFactory getBaseDBF() {
147: // Use JAXP to create a document builder
148: DocumentBuilderFactory factory = DocumentBuilderFactory
149: .newInstance();
150: factory.setExpandEntityReferences(false);
151: factory.setValidating(true);
152: factory.setNamespaceAware(true);
153: factory.setIgnoringComments(true);
154: factory.setCoalescing(true);
155: factory.setIgnoringElementContentWhitespace(true);
156: return factory;
157: }
158:
159: /**
160: * Table mapping public doctypes and system ids against local classloader paths. This
161: * is used to resolve local entities where possible.
162: * Column 0 = public doctype
163: * Column 1 = system id
164: * Column 2 = local path
165: */
166: private static final String LOCAL_ENTITY_TABLE[][] = {
167: { "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN",
168: null, "javax/servlet/resources/web-app_2_2.dtd" },
169: { "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN",
170: null, "javax/servlet/resources/web-app_2_3.dtd" },
171: { null, "http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd",
172: "javax/servlet/resources/web-app_2_4.xsd" },
173: { null, "http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd",
174: "javax/servlet/resources/web-app_2_5.xsd" },
175: { null, "http://www.w3.org/2001/xml.xsd",
176: "javax/servlet/resources/xml.xsd" },
177: { "-//W3C//DTD XMLSCHEMA 200102//EN", null,
178: "javax/servlet/resources/XMLSchema.dtd" },
179: { null, "http://www.w3.org/2001/datatypes.dtd",
180: "javax/servlet/resources/datatypes.dtd" },
181: { null, "http://java.sun.com/xml/ns/j2ee/j2ee_1_4.xsd",
182: "javax/servlet/resources/j2ee_1_4.xsd" },
183: { null, "http://java.sun.com/xml/ns/j2ee/javaee_5.xsd",
184: "javax/servlet/resources/javaee_5.xsd" },
185: { null, "http://java.sun.com/xml/ns/j2ee/jsp_2_0.xsd",
186: "javax/servlet/resources/jsp_2_0.xsd" },
187: { null, "http://java.sun.com/xml/ns/j2ee/jsp_2_1.xsd",
188: "javax/servlet/resources/jsp_2_1.xsd" },
189: {
190: null,
191: "http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_1.xsd",
192: "javax/servlet/resources/j2ee_web_services_client_1_1.xsd" },
193: {
194: null,
195: "http://www.ibm.com/webservices/xsd/j2ee_web_services_client_1_2.xsd",
196: "javax/servlet/resources/javaee_web_services_client_1_2.xsd" } };
197:
198: /**
199: * Implements the EntityResolver interface. This allows us to redirect any
200: * requests by the parser for webapp DTDs to local copies. It's faster and
201: * it means you can run winstone without being web-connected.
202: */
203: public InputSource resolveEntity(String publicName, String url)
204: throws SAXException, IOException {
205: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
206: "WebXmlParser.ResolvingEntity", new String[] {
207: publicName, url });
208: for (int n = 0; n < LOCAL_ENTITY_TABLE.length; n++) {
209: if (((LOCAL_ENTITY_TABLE[n][0] != null)
210: && (publicName != null) && publicName
211: .equals(LOCAL_ENTITY_TABLE[n][0]))
212: || ((LOCAL_ENTITY_TABLE[n][1] != null)
213: && (url != null) && url
214: .equals(LOCAL_ENTITY_TABLE[n][1]))) {
215: if (this .commonLoader
216: .getResource(LOCAL_ENTITY_TABLE[n][2]) != null) {
217: return getLocalResource(url,
218: LOCAL_ENTITY_TABLE[n][2]);
219: }
220: }
221: }
222: if ((url != null) && url.startsWith("jar:")) {
223: return getLocalResource(url, url.substring(url
224: .indexOf("!/") + 2));
225: } else if ((url != null) && url.startsWith("file:")) {
226: return new InputSource(url);
227: } else {
228: Logger.log(Logger.FULL_DEBUG, Launcher.RESOURCES,
229: "WebXmlParser.NoLocalResource", url);
230: return new InputSource(url);
231: }
232: }
233:
234: private InputSource getLocalResource(String url, String local) {
235: if (this .commonLoader.getResource(local) == null)
236: return new InputSource(url);
237: InputSource is = new InputSource(this .commonLoader
238: .getResourceAsStream(local));
239: is.setSystemId(url);
240: return is;
241: }
242:
243: public void error(SAXParseException exception) throws SAXException {
244: if (this .rethrowValidationExceptions) {
245: throw exception;
246: } else {
247: Logger.log(Logger.WARNING, Launcher.RESOURCES,
248: "WebXmlParser.XMLParseError", new String[] {
249: exception.getLineNumber() + "",
250: exception.getMessage() });
251: }
252: }
253:
254: public void fatalError(SAXParseException exception)
255: throws SAXException {
256: error(exception);
257: }
258:
259: public void warning(SAXParseException exception)
260: throws SAXException {
261: Logger.log(Logger.WARNING, Launcher.RESOURCES,
262: "WebXmlParser.XMLParseError", new String[] {
263: exception.getLineNumber() + "",
264: exception.getMessage() });
265: }
266: }
|