/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.metadata; import org.jboss.deployment.DeploymentException; import org.jboss.logging.Logger; import org.jboss.metadata.ejb.jboss.JBossMetaData; import org.jboss.util.xml.JBossEntityResolver; import org.w3c.dom.Document; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; /** XmlFileLoader class is used to read ejb-jar.xml, standardjboss.xml, jboss.xml * files, process them using DTDs and create ApplicationMetaData object for * future use. It also provides the local entity resolver for the JBoss * specific DTDs. * * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a> * @author <a href="mailto:WolfgangWerner@gmx.net">Wolfgang Werner</a> * @author <a href="mailto:Darius.D@jbees.com">Darius Davidavicius</a> * @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a> * @author <a href="mailto:Christoph.Jung@infor.de">Christoph G. Jung</a>. * @version $Revision: 81030 $ */ public class XmlFileLoader { // Constants ----------------------------------------------------- // Attributes ---------------------------------------------------- private static boolean defaultValidateDTDs = false; private static Logger log = Logger.getLogger(XmlFileLoader.class); private URLClassLoader classLoader; private ApplicationMetaData metaData; private boolean validateDTDs; // Static -------------------------------------------------------- public static boolean getDefaultValidateDTDs() { return defaultValidateDTDs; } public static void setDefaultValidateDTDs(boolean validate) { defaultValidateDTDs = validate; } // Constructors -------------------------------------------------- public XmlFileLoader() { this(defaultValidateDTDs); } public XmlFileLoader(boolean validateDTDs) { this.validateDTDs = validateDTDs; } // Public -------------------------------------------------------- public ApplicationMetaData getMetaData() { return metaData; } /** * Set the class loader */ public void setClassLoader(URLClassLoader cl) { classLoader = cl; } /** * Gets the class loader * * @return ClassLoader - the class loader */ public ClassLoader getClassLoader() { return classLoader; } /** Get the flag indicating that ejb-jar.dtd, jboss.dtd & * jboss-web.dtd conforming documents should be validated * against the DTD. */ public boolean getValidateDTDs() { return validateDTDs; } /** Set the flag indicating that ejb-jar.dtd, jboss.dtd & * jboss-web.dtd conforming documents should be validated * against the DTD. */ public void setValidateDTDs(boolean validate) { this.validateDTDs = validate; } /** * Creates the ApplicationMetaData. * The configuration files are found in the classLoader when not explicitly given as * the alternativeDD. * * The default jboss.xml and jaws.xml files are always read first, then we override * the defaults if the user provides them * * @param alternativeDD a URL to the alternative DD given in application.xml */ public ApplicationMetaData load(URL alternativeDD) throws Exception { URL ejbjarUrl = null; if (alternativeDD != null) { log.debug("Using alternativeDD: " + alternativeDD); ejbjarUrl = alternativeDD; } else { ejbjarUrl = getClassLoader().getResource("META-INF/ejb-jar.xml"); } if (ejbjarUrl == null) { throw new DeploymentException("no ejb-jar.xml found"); } // create the metadata JBossMetaData realMetaData = new JBossMetaData(); metaData = new ApplicationMetaData(realMetaData); Document ejbjarDocument = getDocumentFromURL(ejbjarUrl); // the url may be used to report errors metaData.setUrl(ejbjarUrl); metaData.importEjbJarXml(ejbjarDocument.getDocumentElement()); // Load jbossdefault.xml from the default classLoader // we always load defaults first // we use the context classloader, because this guy has to know where // this file is URL defaultJbossUrl = Thread.currentThread().getContextClassLoader().getResource("standardjboss.xml"); if (defaultJbossUrl == null) { throw new DeploymentException("no standardjboss.xml found"); } Document defaultJbossDocument = null; try { defaultJbossDocument = getDocumentFromURL(defaultJbossUrl); metaData.setUrl(defaultJbossUrl); metaData.importJbossXml(defaultJbossDocument.getDocumentElement()); } catch (Exception ex) { log.error("failed to load standardjboss.xml. There could be a syntax error.", ex); throw ex; } // Load jboss.xml // if this file is provided, then we override the defaults try { URL jbossUrl = getClassLoader().getResource("META-INF/jboss.xml"); if (jbossUrl != null) { Document jbossDocument = getDocumentFromURL(jbossUrl); metaData.setUrl(jbossUrl); metaData.importJbossXml(jbossDocument.getDocumentElement()); } } catch (Exception ex) { log.error("failed to load jboss.xml. There could be a syntax error.", ex); throw ex; } return metaData; } /** Invokes getDocument(url, defaultValidateDTDs) * */ public static Document getDocument(URL url) throws DeploymentException { return getDocument(url, defaultValidateDTDs); } /** Get the xml file from the URL and parse it into a Document object. * Calls new XmlFileLoader(validateDTDs).getDocumentFromURL(url); * @param url the URL from which the xml doc is to be obtained. * @return Document */ public static Document getDocument(URL url, boolean validateDTDs) throws DeploymentException { XmlFileLoader loader = new XmlFileLoader(validateDTDs); return loader.getDocumentFromURL(url); } /** Get the xml file from the URL and parse it into a Document object. * Calls getDocument(new InputSource(url.openStream()), url.getPath()) * with the InputSource.SystemId set to url.toExternalForm(). * * @param url the URL from which the xml doc is to be obtained. * @return Document */ public Document getDocumentFromURL(URL url) throws DeploymentException { InputStream is = null; try { is = url.openStream(); return getDocument(is, url.toExternalForm()); } catch (IOException e) { throw new DeploymentException("Failed to obtain xml doc from URL", e); } } /** Parses the xml document in is to create a DOM Document. DTD validation * is enabled if validateDTDs is true and we install an EntityResolver and * ErrorHandler to resolve J2EE DTDs and handle errors. We also create an * InputSource for the InputStream and set the SystemId URI to the inPath * value. This allows relative entity references to be resolved against the * inPath URI. The is argument will be closed. * * @param is the InputStream containing the xml descriptor to parse * @param inPath the path information for the xml doc. This is used as the * InputSource SystemId URI for resolving relative entity references. * @return Document */ public Document getDocument(InputStream is, String inPath) throws DeploymentException { InputSource is2 = new InputSource(is); is2.setSystemId(inPath); Document doc = null; try { doc = getDocument(is2, inPath); } finally { // close the InputStream to get around "too many open files" errors // with large heaps try { if( is != null ) is.close(); } catch (Exception e) { // ignore } } return doc; } /** Parses the xml document in is to create a DOM Document. DTD validation * is enabled if validateDTDs is true and we install an EntityResolver and * ErrorHandler to resolve J2EE DTDs and handle errors. We also create an * InputSource for the InputStream and set the SystemId URI to the inPath * value. This allows relative entity references to be resolved against the * inPath URI. * * @param is the InputSource containing the xml descriptor to parse * @param inPath the path information for the xml doc. This is used for * only for error reporting. * @return Document */ public Document getDocument(InputSource is, String inPath) throws DeploymentException { try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); // Enable DTD validation based on our validateDTDs flag docBuilderFactory.setValidating(validateDTDs); // make the parser namespace-aware in case we deal // with ejb2.1 descriptors, will not break dtd parsing in any way // in which case there would be just a default namespace docBuilderFactory.setNamespaceAware(true); // this will (along JAXP in conjunction with // validation+namespace-awareness) enable xml schema checking. // Will currently fail because some J2EE1.4/W3C schemas // are still lacking. //docBuilderFactory.setAttribute // ("http://java.sun.com/xml/jaxp/properties/schemaLanguage","http://www.w3.org/2001/XMLSchema"); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); JBossEntityResolver lr = new JBossEntityResolver(); LocalErrorHandler eh = new LocalErrorHandler( inPath, lr ); docBuilder.setEntityResolver(lr); docBuilder.setErrorHandler(eh ); Document doc = docBuilder.parse(is); if(validateDTDs && eh.hadError()) { throw new DeploymentException("Invalid XML: file=" + inPath, eh.getException()); } return doc; } catch (DeploymentException e) { throw e; } catch (SAXParseException e) { String msg = "Invalid XML: file=" + inPath+"@"+e.getColumnNumber()+":"+e.getLineNumber(); throw new DeploymentException(msg, e); } catch (SAXException e) { throw new DeploymentException("Invalid XML: file=" + inPath, e); } catch (Exception e) { throw new DeploymentException("Invalid XML: file=" + inPath, e); } } // Package protected --------------------------------------------- // Protected ----------------------------------------------------- // Private ------------------------------------------------------- // Inner classes ------------------------------------------------- /** Local error handler for entity resolver to DocumentBuilder parser. * Error is printed to output just if DTD was detected in the XML file. * If DTD was not found in XML file it is assumed that the EJB builder * doesn't want to use DTD validation. Validation may have been enabled via * validateDTDs flag so we look to the isEntityResolved() function in the LocalResolver * and reject errors if DTD not used. **/ private static class LocalErrorHandler implements ErrorHandler { // The xml file being parsed private String theFileName; private JBossEntityResolver localResolver; private boolean error; private SAXParseException exception; public LocalErrorHandler( String inFileName, JBossEntityResolver localResolver ) { this.theFileName = inFileName; this.localResolver = localResolver; this.error = false; } public void error(SAXParseException exception) { this.exception = exception; if ( localResolver.isEntityResolved() ) { this.error = true; log.error("XmlFileLoader: File " + theFileName + " process error. Line: " + String.valueOf(exception.getLineNumber()) + ". Error message: " + exception.getMessage() ); }//end if } public void fatalError(SAXParseException exception) { this.exception = exception; if ( localResolver.isEntityResolved() ) { this.error = true; log.error("XmlFileLoader: File " + theFileName + " process fatal error. Line: " + String.valueOf(exception.getLineNumber()) + ". Error message: " + exception.getMessage() ); }//end if } public void warning(SAXParseException exception) { this.exception = exception; if ( localResolver.isEntityResolved() ) { this.error = true; log.error("XmlFileLoader: File " + theFileName + " process warning. Line: " + String.valueOf(exception.getLineNumber()) + ". Error message: " + exception.getMessage() ); }//end if } public SAXParseException getException() { return exception; } public boolean hadError() { return error; } }// end class LocalErrorHandler }