/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.enterprise.deployment.io;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.sun.enterprise.deployment.node.XMLNode;
import com.sun.enterprise.deployment.Descriptor;
import com.sun.enterprise.deployment.deploy.shared.AbstractArchive;
import com.sun.enterprise.deployment.node.SaxParserHandler;
import com.sun.enterprise.deployment.node.SaxParserHandlerFactory;
import com.sun.enterprise.deployment.node.J2EEDocumentBuilder;
import com.sun.enterprise.deployment.node.RootXMLNode;
import com.sun.enterprise.util.LocalStringManagerImpl;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.deployment.util.DOLUtils;
/**
* This abstract class defines common behaviour for classes responsibles
* for loading/saving XML deployment descriptors
*
* @author Jerome Dochez
*/
public abstract class DeploymentDescriptorFile {
public final static String FULL_VALIDATION = "full";
public final static String PARSING_VALIDATION = "parsing";
// should we validate the XML ?
private boolean xmlValidation = true;
// error reporting level
private String validationLevel=PARSING_VALIDATION;
// error reporting string, used for xml validation error
private String errorReportingString=null;
// for i18N
private static LocalStringManagerImpl localStrings=
new LocalStringManagerImpl(DeploymentDescriptorFile.class);
/** Creates a new instance of DeploymentDescriptorFile */
public DeploymentDescriptorFile() {
}
/**
* @return a non validating SAX Parser to read an XML file (containing
* Deployment Descriptors) into DOL descriptors
*/
public SAXParser getSAXParser() {
return getSAXParser(false);
}
/**
* @return a SAX Parser to read an XML file (containing
* Deployment Descriptors) into DOL descriptors
*
* @param validating true if the parser should excercise DTD validation
*/
public SAXParser getSAXParser (boolean validating) {
try {
// always use system SAXParser to parse DDs
System.setProperty("javax.xml.parsers.SAXParserFactory", "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
SAXParserFactory spf = SAXParserFactory.newInstance();
System.clearProperty("javax.xml.parsers.SAXParserFactory");
// set the namespace awareness
spf.setNamespaceAware(true);
// turn validation on for deployment descriptor XML files
spf.setValidating(validating);
// this feature is needed for backward compat with old DDs
// constructed by J2EE1.2 which used Java encoding names
// such as ISO8859_1 etc.
// this is a hack for a few days so people can continue runnning
// with crimson
if (spf.getClass().getName().indexOf("xerces")!=-1) {
spf.setFeature(
"http://apache.org/xml/features/allow-java-encodings", true);
} else {
DOLUtils.getDefaultLogger().log(Level.WARNING, "modify your java command line to include the -Djava.endorsed.dirs option");
}
try {
// Validation part 2a: set the schema language if necessary
spf.setFeature("http://apache.org/xml/features/validation/schema",validating);
SAXParser sp = spf.newSAXParser();
// put the default schema for this deployment file type
String path = getDefaultSchemaSource();
if (path!=null) {
sp.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation",path);
}
// Set Xerces feature to allow dynamic validation. This prevents
// SAX errors from being reported when no schemaLocation attr
// is seen for a DTD based (J2EE1.3) XML descriptor.
sp.getXMLReader().setFeature(
"http://apache.org/xml/features/validation/dynamic", validating);
return sp;
} catch (SAXNotRecognizedException x) {
// This can happen if the parser does not support JAXP 1.2
DOLUtils.getDefaultLogger().log(Level.SEVERE,
"INFO: JAXP SAXParser property not recognized: "
+ SaxParserHandler.JAXP_SCHEMA_LANGUAGE);
DOLUtils.getDefaultLogger().log(Level.SEVERE,
"Check to see if parser conforms to JAXP 1.2 spec.");
}
} catch (Exception e) {
e.printStackTrace();
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.saxParserError",
new Object[]{e.getMessage()});
}
return null;
}
/**
* @return a DOM parser to read XML File into a DOM tree
* @param true if validation should happen
*/
public DocumentBuilder getDocumentBuilder(boolean validating) {
try {
// always use system default to parse DD
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
System.clearProperty("javax.xml.parsers.DocumentBuilderFactory");
// set the namespace awareness
dbf.setNamespaceAware(true);
// turn validation on for deployment descriptor XML files
dbf.setValidating(validating);
// Validation part 2a: set the schema language if necessary
try {
// put the default schema for this deployment file type
String path = getDefaultSchemaSource();
if (path!=null) {
dbf.setAttribute("http://apache.org/xml/properties/schema/external-schemaLocation",path);
}
return dbf.newDocumentBuilder();
} catch (ParserConfigurationException x) {
// This can happen if the parser does not support JAXP 1.2
DOLUtils.getDefaultLogger().log(Level.SEVERE,
"Error: JAXP DOMParser property not recognized: "
+ SaxParserHandler.JAXP_SCHEMA_LANGUAGE);
DOLUtils.getDefaultLogger().log(Level.SEVERE,
"Check to see if parser conforms to JAXP 1.2 spec.");
}
} catch (Exception e) {
e.printStackTrace();
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.saxParserError",
new Object[]{e.getMessage()});
}
return null;
}
/**
* read and parse a J2EE Deployment Descriptor input file and
* return the constructed DOL descriptors for the J2EE Module
*
* @param is the input stream for the XML file
* @return the DOL descriptor for the J2EE Module
*/
public Descriptor read(InputStream is)
throws IOException, SAXParseException {
return read(null, is);
}
/**
* read and parse a J2EE Deployment Descriptor input file and
* return the constructed DOL descriptors for the J2EE Module
*
* @param if the read is incremental, the top node to apply the DDs to
* @param is the input stream for the XML file
* @return the DOL descriptor for the J2EE Module
*/
public Descriptor read(Descriptor descriptor, File in)
throws IOException, SAXParseException {
FileInputStream fis = new FileInputStream(in);
try {
return read(descriptor, fis);
} finally {
fis.close();
}
}
/**
* read and parse a J2EE Deployment Descriptor input file and
* return the constructed DOL descriptors for the J2EE Module
*
* @param if the read is incremental, the top node to apply the DDs to
* @param is the input archive abstraction for the XML file
* @return the DOL descriptor for the J2EE Module
*/
public Descriptor read(Descriptor descriptor, AbstractArchive in)
throws IOException, SAXParseException {
InputStream is = in.getEntry(getDeploymentDescriptorPath());
try {
return read(descriptor, is);
} finally {
is.close();
}
}
/**
* read and parse a J2EE Deployment Descriptor input file and
* return the constructed DOL descriptors for the J2EE Module
*
* @param if the read is incremental, the top node to apply the DDs to
* @param is the input stream for the XML file
* @return the DOL descriptor for the J2EE Module
*/
public Descriptor read(Descriptor descriptor, InputStream is)
throws IOException, SAXParseException {
errorReportingString = FileUtils.revertFriendlyFilenameExtension(errorReportingString);
String error = (errorReportingString == null)? errorReportingString:new File(errorReportingString).getName();
String errorReporting = localStrings.getLocalString(
"enterprise.deployment.io.errorcontext",
"archive {0} and deployment descriptor file {1}",
new Object []{ error, getDeploymentDescriptorPath()});
SAXParser sp = getSAXParser(getXMLValidation());
SaxParserHandler dh = SaxParserHandlerFactory.newInstance();
if (validationLevel.equals(FULL_VALIDATION)) {
dh.setStopOnError(true);
}
if (descriptor!=null) {
dh.setTopNode(getRootXMLNode(descriptor));
}
dh.setErrorReportingString(errorReporting);
InputSource input =new InputSource(is);
try {
sp.parse(input,dh);
} catch(SAXParseException e) {
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.saxParserError",
new Object[]{e.getMessage()});
errorReporting += " " + e.getLocalizedMessage();
SAXParseException spe = new SAXParseException(errorReporting,
e.getSystemId(),
e.getPublicId(),
e.getLineNumber(),
e.getColumnNumber(),
e);
throw spe;
} catch(SAXException e) {
if (e.getException()!=null) {
e.getException().printStackTrace();
}
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.saxParserError",
new Object[]{e.getMessage()});
return null;
} catch (IOException e) {
DOLUtils.getDefaultLogger().log(Level.SEVERE, "enterprise.deployment.backend.saxParserError",
e.getMessage() == null ? "" : new Object[]{e.getMessage()});
// Let's check if the root cause of this IOException is failing to
// connect. If yes, it means two things:
// 1. The public id declared is not one of the pre-defined ones.
// So we need to ask user the check for typo.
// 2. If the user does intend to use the system id to go outside.
// We need to ask them to check whether they have proper
// access to the internet (proxy setting etc).
StackTraceElement[] stElements = e.getStackTrace();
for (int i = 0; i < stElements.length; i++) {
StackTraceElement stElement = stElements[i];
if (stElement.getClassName().equals("java.net.Socket") &&
stElement.getMethodName().equals("connect")) {
String msg = localStrings.getLocalString(
"enterprise.deployment.can_not_locate_dtd",
"Unable to locate the DTD to validate your deployment descriptor file [{1}] in archive [{0}]. Please make sure the DOCTYPE is correct (no typo in public ID or system ID) and you have proper access to the Internet.",
new Object []{ error, getDeploymentDescriptorPath()});
IOException ioe = new IOException(msg);
ioe.initCause(e);
throw ioe;
}
}
IOException ioe = new IOException(localStrings.getLocalString(
"enterprise.deployment.backend.error_parsing_descr",
"Error parsing descriptor: {0}", new Object[] {errorReporting}));
ioe.initCause(e);
throw ioe;
}
if (dh.getTopNode()!=null) {
Object topDesc = dh.getTopNode().getDescriptor();
if (topDesc instanceof Descriptor) {
return (Descriptor) topDesc;
}
}
return null;
}
/**
* @return a Document for the passed descriptor
* @param descriptor
*/
public Document getDocument(Descriptor descriptor) {
return J2EEDocumentBuilder.getDocument(descriptor, getRootXMLNode(descriptor));
}
/**
* writes the descriptor to an output stream
*
* @param the descriptor
* @param the output stream
*/
public void write(Descriptor descriptor, OutputStream os) throws IOException {
try {
J2EEDocumentBuilder.write(descriptor, getRootXMLNode(descriptor), os);
} catch(IOException ioe) {
throw ioe;
} catch(Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
}
/**
* writes the descriptor classes into a new XML file
*
* @param descriptor the DOL descriptor to write
* @param path the file to use
*/
public void write(Descriptor descriptor, String path) throws IOException {
String dir;
String fileName = getDeploymentDescriptorPath();
if (fileName.lastIndexOf('/')!=-1) {
dir = path + File.separator + fileName.substring(0, fileName.lastIndexOf('/'));
fileName = fileName.substring(fileName.lastIndexOf('/')+1);
} else {
dir = path;
}
File dirs = new File(dir.replace('/', File.separatorChar));
if (!dirs.exists()) {
dirs.mkdirs();
}
File out = new File(dirs, fileName);
write(descriptor, out);
}
/**
* writes the descriptor classes into a new XML file
*
* @param descriptor the DOL descriptor to write
* @param out the file to use
*/
public void write(Descriptor descriptor, File out) throws IOException {
FileOutputStream fos = new FileOutputStream(out);
try {
write(descriptor, fos );
} catch(Exception e) {
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
fos.close();
}
/**
* @return the location of the DeploymentDescriptor file for a
* particular type of J2EE Archive
*/
public abstract String getDeploymentDescriptorPath();
/**
* @return a RootXMLNode responsible for handling the deployment
* descriptors associated with this J2EE module
*
* @param the descriptor for which we need the node
*/
public abstract RootXMLNode getRootXMLNode(Descriptor descriptor);
/**
* @return true if XML validation should be performed at load time
*/
protected boolean getXMLValidation() {
return xmlValidation;
}
/**
* sets wether XML validation should be performed at load time
* @param true to validate
*/
public void setXMLValidation(boolean validate) {
xmlValidation = validate;
}
/**
* Sets the xml validation error reporting/recovering level.
* The reporting level is active only when xml validation is
* turned on @see setXMLValidation.
* so far, two values can be passed, medium which reports the
* xml validation and continue and full which reports the
* xml validation and stop the xml parsing.
*/
public void setXMLValidationLevel(String level) {
validationLevel = level;
}
/**
* @return the xml validation reporting level
*/
public String getXMLValidationLevel() {
return validationLevel;
}
/**
* @return the default schema source for this deployment descriptors
*/
protected String getDefaultSchemaSource() {
RootXMLNode node = getRootXMLNode(null);
if (node!=null) {
List<String> systemIDs = node.getSystemIDs();
if (systemIDs != null) {
String path = null;
for (int i = 0; i < systemIDs.size(); i++) {
if (path == null) {
path = systemIDs.get(i) + " ";
} else {
path = path + systemIDs.get(i) + " ";
}
}
return path.trim();
}
}
return null;
}
/**
* Sets the error reporting string
*/
public void setErrorReportingString(String s) {
this.errorReportingString = s;
}
}