com.sun.faces.config.manager.tasks.ParseConfigResourceToDOMTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jakarta.faces Show documentation
Show all versions of jakarta.faces Show documentation
EE4J Compatible Implementation for Jakarta Faces API
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.faces.config.manager.tasks;
import static com.sun.faces.config.manager.DbfFactory.FACES_ENTITY_RESOLVER;
import static com.sun.faces.config.manager.DbfFactory.FACES_ERROR_HANDLER;
import static com.sun.faces.util.Util.createTransformerFactory;
import static java.text.MessageFormat.format;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.text.MessageFormat;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
import com.sun.faces.RIConstants;
import com.sun.faces.config.ConfigManager;
import com.sun.faces.config.ConfigurationException;
import com.sun.faces.config.manager.DbfFactory;
import com.sun.faces.config.manager.documents.DocumentInfo;
import com.sun.faces.config.processor.FacesFlowDefinitionConfigProcessor;
import com.sun.faces.util.FacesLogger;
import com.sun.faces.util.Timer;
/**
*
* This Callable
will be used by
* {@link ConfigManager#getXMLDocuments(javax.servlet.ServletContext, java.util.List, java.util.concurrent.ExecutorService, boolean)}.
* It represents a single configuration resource to be parsed into a DOM.
*
*/
public class ParseConfigResourceToDOMTask implements Callable {
/**
* Name of the attribute added by ParseTask to indicate a {@link Document} instance as a representation of
* /WEB-INF/faces-config.xml
.
*/
public static final String WEB_INF_MARKER = "com.sun.faces.webinf";
private static final Logger LOGGER = FacesLogger.CONFIG.getLogger();
private static final String JAVAEE_SCHEMA_LEGACY_DEFAULT_NS = "http://java.sun.com/xml/ns/javaee";
private static final String JAVAEE_SCHEMA_DEFAULT_NS = "http://xmlns.jcp.org/xml/ns/javaee";
private static final String EMPTY_FACES_CONFIG = "com/sun/faces/empty-faces-config.xml";
private static final String FACES_CONFIG_TAGNAME = "faces-config";
private static final String FACELET_TAGLIB_TAGNAME = "facelet-taglib";
private static final String FACES_CONFIG_1_X_DEFAULT_NS = "http://java.sun.com/JSF/Configuration";
private static final String FACELETS_1_0_DEFAULT_NS = "http://java.sun.com/JSF/Facelet";
/**
* Stylesheet to convert 1.0 and 1.1 based faces-config documents to our private 1.1 schema for validation.
*/
private static final String FACES_TO_1_1_PRIVATE_XSL = "/com/sun/faces/jsf1_0-1_1toSchema.xsl";
/**
* Stylesheet to convert 1.0 facelet-taglib documents from 1.0 to 2.0 for schema validation purposes.
*/
private static final String FACELETS_TO_2_0_XSL = "/com/sun/faces/facelets1_0-2_0toSchema.xsl";
private ServletContext servletContext;
private URI documentURI;
private DocumentBuilderFactory factory;
private boolean validating;
// --------------------------------------------------------
// Constructors
/**
*
* Constructs a new ParseTask instance
*
*
* @param servletContext
* the servlet context.
* @param validating
* whether or not we're validating
* @param documentURI
* a URL to the configuration resource to be parsed
* @throws Exception
* general error
*/
public ParseConfigResourceToDOMTask(ServletContext servletContext, boolean validating, URI documentURI) throws Exception {
this.servletContext = servletContext;
this.documentURI = documentURI;
this.validating = validating;
}
// ----------------------------------------------- Methods from
// Callable
/**
* @return the result of the parse operation (a DOM)
* @throws Exception
* if an error occurs during the parsing process
*/
@Override
public DocumentInfo call() throws Exception {
try {
Timer timer = Timer.getInstance();
if (timer != null) {
timer.startTiming();
}
Document document = getDocument();
if (timer != null) {
timer.stopTiming();
timer.logResult("Parse " + documentURI.toURL().toExternalForm());
}
return new DocumentInfo(document, documentURI);
} catch (Exception e) {
throw new ConfigurationException(
format("Unable to parse document ''{0}'': {1}", documentURI.toURL().toExternalForm(), e.getMessage()), e);
}
}
// ----------------------------------------------------- Private
// Methods
/**
* @return Document
based on documentURI
.
* @throws Exception
* if an error occurs during the process of building a Document
*/
private Document getDocument() throws Exception {
Document returnDoc;
DocumentBuilder db = getNonValidatingBuilder();
URL documentURL = documentURI.toURL();
InputSource is = new InputSource(getInputStream(documentURL));
is.setSystemId(documentURI.toURL().toExternalForm());
Document doc = null;
try {
doc = db.parse(is);
} catch (SAXParseException spe) {
// [mojarra-1693]
// Test if this is a zero length or whitespace only
// faces-config.xml file.
// If so, just make an empty Document
InputStream stream = is.getByteStream();
stream.close();
is = new InputSource(getInputStream(documentURL));
stream = is.getByteStream();
if (streamIsZeroLengthOrEmpty(stream) && documentURL.toExternalForm().endsWith("faces-config.xml")) {
ClassLoader loader = this.getClass().getClassLoader();
is = new InputSource(getInputStream(loader.getResource(EMPTY_FACES_CONFIG)));
doc = db.parse(is);
}
}
String documentNS = null;
if (null == doc) {
if (FacesFlowDefinitionConfigProcessor.uriIsFlowDefinition(documentURI)) {
documentNS = RIConstants.JAVAEE_XMLNS;
doc = FacesFlowDefinitionConfigProcessor.synthesizeEmptyFlowDefinition(documentURI);
}
} else {
Element documentElement = doc.getDocumentElement();
documentNS = documentElement.getNamespaceURI();
String rootElementTagName = documentElement.getTagName();
boolean isNonFacesConfigDocument = !FACES_CONFIG_TAGNAME.equals(rootElementTagName)
&& !FACELET_TAGLIB_TAGNAME.equals(rootElementTagName);
if (isNonFacesConfigDocument) {
ClassLoader loader = this.getClass().getClassLoader();
is = new InputSource(getInputStream(loader.getResource(EMPTY_FACES_CONFIG)));
doc = db.parse(is);
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING,
MessageFormat.format(
"Config document {0} with namespace URI {1} is not a faces-config or facelet-taglib file. Ignoring.",
documentURI.toURL().toExternalForm(), documentNS));
}
return doc;
}
}
if (validating && documentNS != null) {
DOMSource domSource = new DOMSource(doc, documentURL.toExternalForm());
/*
* If the Document in question is 1.2 (i.e. it has a namespace matching JAVAEE_SCHEMA_DEFAULT_NS, then perform
* validation using the cached schema and return. Otherwise we assume a 1.0 or 1.1 faces-config in which case we need to
* transform it to reference a special 1.1 schema before validating.
*/
Node documentElement = ((Document) domSource.getNode()).getDocumentElement();
switch (documentNS) {
case JAVAEE_SCHEMA_DEFAULT_NS: {
Attr version = (Attr) documentElement.getAttributes().getNamedItem("version");
Schema schema;
if (version != null) {
String versionStr = version.getValue();
switch (versionStr) {
case "2.2":
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACELET_TAGLIB_22);
} else {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_22);
}
break;
case "2.3":
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACELET_TAGLIB_22);
} else {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_23);
}
break;
default:
throw new ConfigurationException("Unknown Schema version: " + versionStr);
}
DocumentBuilder builder = getBuilderForSchema(schema);
if (builder.isValidating()) {
builder.getSchema().newValidator().validate(domSource);
returnDoc = ((Document) domSource.getNode());
} else {
returnDoc = ((Document) domSource.getNode());
}
} else {
// this shouldn't happen, but...
throw new ConfigurationException("No document version available.");
}
break;
}
case JAVAEE_SCHEMA_LEGACY_DEFAULT_NS: {
Attr version = (Attr) documentElement.getAttributes().getNamedItem("version");
Schema schema;
if (version != null) {
String versionStr = version.getValue();
switch (versionStr) {
case "2.0":
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACELET_TAGLIB_20);
} else {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_20);
}
break;
case "2.1":
if ("facelet-taglib".equals(documentElement.getLocalName())) {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACELET_TAGLIB_20);
} else {
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_21);
}
break;
case "1.2":
schema = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_12);
break;
default:
throw new ConfigurationException("Unknown Schema version: " + versionStr);
}
DocumentBuilder builder = getBuilderForSchema(schema);
if (builder.isValidating()) {
builder.getSchema().newValidator().validate(domSource);
returnDoc = ((Document) domSource.getNode());
} else {
returnDoc = ((Document) domSource.getNode());
}
} else {
// this shouldn't happen, but...
throw new ConfigurationException("No document version available.");
}
break;
}
default:
DOMResult domResult = new DOMResult();
Transformer transformer = getTransformer(documentNS);
transformer.transform(domSource, domResult);
// copy the source document URI to the transformed
// result
// so that processes that need to build URLs relative
// to the
// document will work as expected.
((Document) domResult.getNode()).setDocumentURI(((Document) domSource.getNode()).getDocumentURI());
Schema schemaToApply;
switch (documentNS) {
case FACES_CONFIG_1_X_DEFAULT_NS:
schemaToApply = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACES_11);
break;
case FACELETS_1_0_DEFAULT_NS:
schemaToApply = DbfFactory.getSchema(servletContext, DbfFactory.FacesSchema.FACELET_TAGLIB_20);
break;
default:
throw new IllegalStateException();
}
DocumentBuilder builder = getBuilderForSchema(schemaToApply);
if (builder.isValidating()) {
builder.getSchema().newValidator().validate(new DOMSource(domResult.getNode()));
returnDoc = (Document) domResult.getNode();
} else {
returnDoc = (Document) domResult.getNode();
}
}
} else {
returnDoc = doc;
}
// mark this document as the parsed representation of the
// WEB-INF/faces-config.xml. This is used later in the
// configuration
// processing.
if (documentURL.toExternalForm().contains("/WEB-INF/faces-config.xml")) {
Attr webInf = returnDoc.createAttribute(WEB_INF_MARKER);
webInf.setValue("true");
returnDoc.getDocumentElement().getAttributes().setNamedItem(webInf);
}
return returnDoc;
}
private boolean streamIsZeroLengthOrEmpty(InputStream is) throws IOException {
boolean isZeroLengthOrEmpty = (0 == is.available());
final int size = 1024;
byte[] b = new byte[size];
String s;
while (!isZeroLengthOrEmpty && -1 != is.read(b, 0, size)) {
s = (new String(b, RIConstants.CHAR_ENCODING)).trim();
isZeroLengthOrEmpty = 0 == s.length();
b[0] = 0;
for (int i = 1; i < size; i += i) {
System.arraycopy(b, 0, b, i, ((size - i) < i) ? (size - i) : i);
}
}
return isZeroLengthOrEmpty;
}
/**
* Obtain a Transformer
using the style sheet referenced by the XSL
constant.
*
* @return a new Tranformer instance
* @throws Exception
* if a Tranformer instance could not be created
*/
private static Transformer getTransformer(String documentNS) throws Exception {
TransformerFactory factory = createTransformerFactory();
String xslToApply;
switch (documentNS) {
case FACES_CONFIG_1_X_DEFAULT_NS:
xslToApply = FACES_TO_1_1_PRIVATE_XSL;
break;
case FACELETS_1_0_DEFAULT_NS:
xslToApply = FACELETS_TO_2_0_XSL;
break;
default:
throw new IllegalStateException();
}
return factory.newTransformer(new StreamSource(getInputStream(ConfigManager.class.getResource(xslToApply))));
}
/**
* @return an InputStream
to the resource referred to by url
* @param url
* source URL
* @throws IOException
* if an error occurs
*/
private static InputStream getInputStream(URL url) throws IOException {
URLConnection connection = url.openConnection();
connection.setUseCaches(false);
return new BufferedInputStream(connection.getInputStream());
}
private DocumentBuilder getNonValidatingBuilder() throws Exception {
DocumentBuilderFactory tFactory = DbfFactory.getFactory();
tFactory.setValidating(false);
DocumentBuilder tBuilder = tFactory.newDocumentBuilder();
tBuilder.setEntityResolver(FACES_ENTITY_RESOLVER);
tBuilder.setErrorHandler(FACES_ERROR_HANDLER);
return tBuilder;
}
private DocumentBuilder getBuilderForSchema(Schema schema) throws Exception {
this.factory = DbfFactory.getFactory();
try {
factory.setSchema(schema);
} catch (UnsupportedOperationException upe) {
return getNonValidatingBuilder();
}
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(FACES_ENTITY_RESOLVER);
builder.setErrorHandler(FACES_ERROR_HANDLER);
return builder;
}
} // END ParseTask