001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.metadata;
023:
024: import org.jboss.deployment.DeploymentException;
025: import org.jboss.logging.Logger;
026: import org.jboss.util.xml.JBossEntityResolver;
027: import org.w3c.dom.Document;
028: import org.xml.sax.ErrorHandler;
029: import org.xml.sax.InputSource;
030: import org.xml.sax.SAXException;
031: import org.xml.sax.SAXParseException;
032:
033: import javax.xml.parsers.DocumentBuilder;
034: import javax.xml.parsers.DocumentBuilderFactory;
035: import java.io.IOException;
036: import java.io.InputStream;
037: import java.net.URL;
038: import java.net.URLClassLoader;
039:
040: /** XmlFileLoader class is used to read ejb-jar.xml, standardjboss.xml, jboss.xml
041: * files, process them using DTDs and create ApplicationMetaData object for
042: * future use. It also provides the local entity resolver for the JBoss
043: * specific DTDs.
044: *
045: * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
046: * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
047: * @author <a href="mailto:WolfgangWerner@gmx.net">Wolfgang Werner</a>
048: * @author <a href="mailto:Darius.D@jbees.com">Darius Davidavicius</a>
049: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
050: * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>.
051: * @version $Revision: 57209 $
052: */
053: public class XmlFileLoader {
054: // Constants -----------------------------------------------------
055:
056: // Attributes ----------------------------------------------------
057: private static boolean defaultValidateDTDs = false;
058: private static Logger log = Logger.getLogger(XmlFileLoader.class);
059: private URLClassLoader classLoader;
060: private ApplicationMetaData metaData;
061: private boolean validateDTDs;
062:
063: // Static --------------------------------------------------------
064: public static boolean getDefaultValidateDTDs() {
065: return defaultValidateDTDs;
066: }
067:
068: public static void setDefaultValidateDTDs(boolean validate) {
069: defaultValidateDTDs = validate;
070: }
071:
072: // Constructors --------------------------------------------------
073: public XmlFileLoader() {
074: this (defaultValidateDTDs);
075: }
076:
077: public XmlFileLoader(boolean validateDTDs) {
078: this .validateDTDs = validateDTDs;
079: }
080:
081: // Public --------------------------------------------------------
082: public ApplicationMetaData getMetaData() {
083: return metaData;
084: }
085:
086: /**
087: * Set the class loader
088: */
089: public void setClassLoader(URLClassLoader cl) {
090: classLoader = cl;
091: }
092:
093: /**
094: * Gets the class loader
095: *
096: * @return ClassLoader - the class loader
097: */
098: public ClassLoader getClassLoader() {
099: return classLoader;
100: }
101:
102: /** Get the flag indicating that ejb-jar.dtd, jboss.dtd &
103: * jboss-web.dtd conforming documents should be validated
104: * against the DTD.
105: */
106: public boolean getValidateDTDs() {
107: return validateDTDs;
108: }
109:
110: /** Set the flag indicating that ejb-jar.dtd, jboss.dtd &
111: * jboss-web.dtd conforming documents should be validated
112: * against the DTD.
113: */
114: public void setValidateDTDs(boolean validate) {
115: this .validateDTDs = validate;
116: }
117:
118: /**
119: * Creates the ApplicationMetaData.
120: * The configuration files are found in the classLoader when not explicitly given as
121: * the alternativeDD.
122: *
123: * The default jboss.xml and jaws.xml files are always read first, then we override
124: * the defaults if the user provides them
125: *
126: * @param alternativeDD a URL to the alternative DD given in application.xml
127: */
128: public ApplicationMetaData load(URL alternativeDD) throws Exception {
129: URL ejbjarUrl = null;
130: if (alternativeDD != null) {
131: log.debug("Using alternativeDD: " + alternativeDD);
132: ejbjarUrl = alternativeDD;
133: } else {
134: ejbjarUrl = getClassLoader().getResource(
135: "META-INF/ejb-jar.xml");
136: }
137:
138: if (ejbjarUrl == null) {
139: throw new DeploymentException("no ejb-jar.xml found");
140: }
141:
142: // create the metadata
143: metaData = new ApplicationMetaData();
144: metaData.setResourceClassLoader(classLoader);
145:
146: Document ejbjarDocument = getDocumentFromURL(ejbjarUrl);
147:
148: // the url may be used to report errors
149: metaData.setUrl(ejbjarUrl);
150: metaData.importEjbJarXml(ejbjarDocument.getDocumentElement());
151:
152: // Load jbossdefault.xml from the default classLoader
153: // we always load defaults first
154: // we use the context classloader, because this guy has to know where
155: // this file is
156: URL defaultJbossUrl = Thread.currentThread()
157: .getContextClassLoader().getResource(
158: "standardjboss.xml");
159: if (defaultJbossUrl == null) {
160: throw new DeploymentException("no standardjboss.xml found");
161: }
162:
163: Document defaultJbossDocument = null;
164: try {
165: defaultJbossDocument = getDocumentFromURL(defaultJbossUrl);
166: metaData.setUrl(defaultJbossUrl);
167: metaData.importJbossXml(defaultJbossDocument
168: .getDocumentElement());
169: } catch (Exception ex) {
170: log
171: .error(
172: "failed to load standardjboss.xml. There could be a syntax error.",
173: ex);
174: throw ex;
175: }
176:
177: // Load jboss.xml
178: // if this file is provided, then we override the defaults
179: try {
180: URL jbossUrl = getClassLoader().getResource(
181: "META-INF/jboss.xml");
182: if (jbossUrl != null) {
183: Document jbossDocument = getDocumentFromURL(jbossUrl);
184: metaData.setUrl(jbossUrl);
185: metaData.importJbossXml(jbossDocument
186: .getDocumentElement());
187: }
188: } catch (Exception ex) {
189: log
190: .error(
191: "failed to load jboss.xml. There could be a syntax error.",
192: ex);
193: throw ex;
194: }
195:
196: return metaData;
197: }
198:
199: /** Invokes getDocument(url, defaultValidateDTDs)
200: *
201: */
202: public static Document getDocument(URL url)
203: throws DeploymentException {
204: return getDocument(url, defaultValidateDTDs);
205: }
206:
207: /** Get the xml file from the URL and parse it into a Document object.
208: * Calls new XmlFileLoader(validateDTDs).getDocumentFromURL(url);
209: * @param url the URL from which the xml doc is to be obtained.
210: * @return Document
211: */
212: public static Document getDocument(URL url, boolean validateDTDs)
213: throws DeploymentException {
214: XmlFileLoader loader = new XmlFileLoader(validateDTDs);
215: return loader.getDocumentFromURL(url);
216: }
217:
218: /** Get the xml file from the URL and parse it into a Document object.
219: * Calls getDocument(new InputSource(url.openStream()), url.getPath())
220: * with the InputSource.SystemId set to url.toExternalForm().
221: *
222: * @param url the URL from which the xml doc is to be obtained.
223: * @return Document
224: */
225: public Document getDocumentFromURL(URL url)
226: throws DeploymentException {
227: InputStream is = null;
228: try {
229: is = url.openStream();
230: return getDocument(is, url.toExternalForm());
231: } catch (IOException e) {
232: throw new DeploymentException(
233: "Failed to obtain xml doc from URL", e);
234: }
235: }
236:
237: /** Parses the xml document in is to create a DOM Document. DTD validation
238: * is enabled if validateDTDs is true and we install an EntityResolver and
239: * ErrorHandler to resolve J2EE DTDs and handle errors. We also create an
240: * InputSource for the InputStream and set the SystemId URI to the inPath
241: * value. This allows relative entity references to be resolved against the
242: * inPath URI. The is argument will be closed.
243: *
244: * @param is the InputStream containing the xml descriptor to parse
245: * @param inPath the path information for the xml doc. This is used as the
246: * InputSource SystemId URI for resolving relative entity references.
247: * @return Document
248: */
249: public Document getDocument(InputStream is, String inPath)
250: throws DeploymentException {
251: InputSource is2 = new InputSource(is);
252: is2.setSystemId(inPath);
253: Document doc = null;
254: try {
255: doc = getDocument(is2, inPath);
256: } finally {
257: // close the InputStream to get around "too many open files" errors
258: // with large heaps
259: try {
260: if (is != null)
261: is.close();
262: } catch (Exception e) {
263: // ignore
264: }
265: }
266: return doc;
267: }
268:
269: /** Parses the xml document in is to create a DOM Document. DTD validation
270: * is enabled if validateDTDs is true and we install an EntityResolver and
271: * ErrorHandler to resolve J2EE DTDs and handle errors. We also create an
272: * InputSource for the InputStream and set the SystemId URI to the inPath
273: * value. This allows relative entity references to be resolved against the
274: * inPath URI.
275: *
276: * @param is the InputSource containing the xml descriptor to parse
277: * @param inPath the path information for the xml doc. This is used for
278: * only for error reporting.
279: * @return Document
280: */
281: public Document getDocument(InputSource is, String inPath)
282: throws DeploymentException {
283: try {
284: DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
285: .newInstance();
286:
287: // Enable DTD validation based on our validateDTDs flag
288: docBuilderFactory.setValidating(validateDTDs);
289: // make the parser namespace-aware in case we deal
290: // with ejb2.1 descriptors, will not break dtd parsing in any way
291: // in which case there would be just a default namespace
292: docBuilderFactory.setNamespaceAware(true);
293: // this will (along JAXP in conjunction with
294: // validation+namespace-awareness) enable xml schema checking.
295: // Will currently fail because some J2EE1.4/W3C schemas
296: // are still lacking.
297: //docBuilderFactory.setAttribute
298: // ("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema");
299: DocumentBuilder docBuilder = docBuilderFactory
300: .newDocumentBuilder();
301: JBossEntityResolver lr = new JBossEntityResolver();
302: LocalErrorHandler eh = new LocalErrorHandler(inPath, lr);
303: docBuilder.setEntityResolver(lr);
304: docBuilder.setErrorHandler(eh);
305:
306: Document doc = docBuilder.parse(is);
307: if (validateDTDs && eh.hadError()) {
308: throw new DeploymentException("Invalid XML: file="
309: + inPath, eh.getException());
310: }
311: return doc;
312: } catch (DeploymentException e) {
313: throw e;
314: } catch (SAXParseException e) {
315: String msg = "Invalid XML: file=" + inPath + "@"
316: + e.getColumnNumber() + ":" + e.getLineNumber();
317: throw new DeploymentException(msg, e);
318: } catch (SAXException e) {
319: throw new DeploymentException(
320: "Invalid XML: file=" + inPath, e);
321: } catch (Exception e) {
322: throw new DeploymentException(
323: "Invalid XML: file=" + inPath, e);
324: }
325: }
326:
327: // Package protected ---------------------------------------------
328:
329: // Protected -----------------------------------------------------
330:
331: // Private -------------------------------------------------------
332:
333: // Inner classes -------------------------------------------------
334:
335: /** Local error handler for entity resolver to DocumentBuilder parser.
336: * Error is printed to output just if DTD was detected in the XML file.
337: * If DTD was not found in XML file it is assumed that the EJB builder
338: * doesn't want to use DTD validation. Validation may have been enabled via
339: * validateDTDs flag so we look to the isEntityResolved() function in the LocalResolver
340: * and reject errors if DTD not used.
341: **/
342: private static class LocalErrorHandler implements ErrorHandler {
343: // The xml file being parsed
344: private String theFileName;
345: private JBossEntityResolver localResolver;
346: private boolean error;
347: private SAXParseException exception;
348:
349: public LocalErrorHandler(String inFileName,
350: JBossEntityResolver localResolver) {
351: this .theFileName = inFileName;
352: this .localResolver = localResolver;
353: this .error = false;
354: }
355:
356: public void error(SAXParseException exception) {
357: this .exception = exception;
358: if (localResolver.isEntityResolved()) {
359: this .error = true;
360: log.error("XmlFileLoader: File " + theFileName
361: + " process error. Line: "
362: + String.valueOf(exception.getLineNumber())
363: + ". Error message: " + exception.getMessage());
364: }//end if
365: }
366:
367: public void fatalError(SAXParseException exception) {
368: this .exception = exception;
369: if (localResolver.isEntityResolved()) {
370: this .error = true;
371: log.error("XmlFileLoader: File " + theFileName
372: + " process fatal error. Line: "
373: + String.valueOf(exception.getLineNumber())
374: + ". Error message: " + exception.getMessage());
375: }//end if
376: }
377:
378: public void warning(SAXParseException exception) {
379: this .exception = exception;
380: if (localResolver.isEntityResolved()) {
381: this .error = true;
382: log.error("XmlFileLoader: File " + theFileName
383: + " process warning. Line: "
384: + String.valueOf(exception.getLineNumber())
385: + ". Error message: " + exception.getMessage());
386: }//end if
387: }
388:
389: public SAXParseException getException() {
390: return exception;
391: }
392:
393: public boolean hadError() {
394: return error;
395: }
396: }// end class LocalErrorHandler
397: }
|