
org.omnifaces.facesconfigparser.digester.DigesterFactory Maven / Gradle / Ivy
/*
* 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 org.omnifaces.facesconfigparser.digester;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.digester.Digester;
import org.apache.commons.logging.impl.NoOpLog;
import org.omnifaces.facesconfigparser.util.ToolsUtil;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
*
* A simple factory to hide Digester
configuration details.
*
*/
public class DigesterFactory {
private static final Logger logger = ToolsUtil.getLogger(ToolsUtil.FACES_LOGGER + ToolsUtil.CONFIG_LOGGER);
/**
*
* Xerces
specific feature to enable both DTD and Schema validation.
*
*/
private static final String XERCES_VALIDATION = "http://xml.org/sax/features/validation";
/**
*
* Xerces
specific feature to enable both DTD and Schema validation.
*
*/
private static final String XERCES_SCHEMA_VALIDATION = "http://apache.org/xml/features/validation/schema";
/**
*
* Xerces
specific feature to enabled constraint validation.
*
*/
private static final String XERCES_SCHEMA_CONSTRAINT_VALIDATION = "http://apache.org/xml/features/validation/schema-full-checking";
/**
*
* Custom EntityResolver
.
*
*/
private static JsfEntityResolver RESOLVER;
/**
*
* Custom ErrorHandler
.
*
*/
private static final JsfErrorHandler ERROR_HANDLER = new JsfErrorHandler();
/**
*
* Indicates whether or not document validation is requested or not.
*
*/
private boolean validating;
private String schemaDirectory;
/**
*
* The ThreadLocal
variable used to record the VersionListener instance for each processing thread.
*
*/
private static ThreadLocal versionListener = new ThreadLocal() {
@Override
protected Object initialValue() {
return (null);
}
};
// ------------------------------------------------------------ Constructors
/**
*
* Creates a new DigesterFactory instance.
*
*
* @param isValidating - true
if the Digester
instance that is ultimately returned should be
* configured (if possible) for document validation. If validation is not desired, pass false
.
*/
private DigesterFactory(boolean isValidating) {
this(isValidating, (String) null);
}
/**
*
* Creates a new DigesterFactory instance.
*
*
* @param isValidating - true
if the Digester
instance that is ultimately returned should be
* configured (if possible) for document validation. If validation is not desired, pass false
.
*/
private DigesterFactory(boolean isValidating, String schemaDirectory) {
this.validating = isValidating;
this.schemaDirectory = schemaDirectory;
RESOLVER = new JsfEntityResolver(schemaDirectory);
}
// ---------------------------------------------------------- Public Methods
/**
*
* Returns a new DigesterFactory
instance that will create a non-validating Digester
instance.
*
*
* @return a new DigesterFactory
*/
public static DigesterFactory newInstance() {
return DigesterFactory.newInstance(false, (String) null);
}
/**
*
* Creates a new DigesterFactory
instance that will create a Digester
instance where
* validation depends on the value of isValidating
.
*
*
* @param isValidating - true
if the Digester
instance that is ultimately returned should be
* configured (if possible) for document validation. If validation is not desired, pass false
.
* @param schemaDirectory - optional local directory path to retrieve .xsd/.dtd files from.
*
* @return a new DigesterFactory
capable of creating Digester
instances
*/
public static DigesterFactory newInstance(boolean isValidating, String schemaDirectory) {
return new DigesterFactory(isValidating, schemaDirectory);
}
/**
*
* Creates a new DigesterFactory
instance that will create a Digester
instance where
* validation depends on the value of isValidating
.
*
*
* @param isValidating - true
if the Digester
instance that is ultimately returned should be
* configured (if possible) for document validation. If validation is not desired, pass false
.
* @param listener a GrammarListener instance
* @return a new DigesterFactory
capable of creating Digester
instances
*/
public static DigesterFactory newInstance(boolean isValidating, VersionListener listener) {
DigesterFactory result = new DigesterFactory(isValidating);
if (null != listener) {
DigesterFactory.RESOLVER.setVersionListener(listener);
versionListener.set(listener);
}
return result;
}
public static VersionListener getVersionListener() {
return (VersionListener) versionListener.get();
}
public static void releaseDigester(Digester toRelease) {
RESOLVER.setVersionListener(null);
versionListener.set(null);
}
/**
*
* Creates a new Digester
instance configured for use with JSF.
*
*
* @return a new Digester
*/
public Digester createDigester() {
Digester digester = new Digester();
configureDigester(digester);
return digester;
}
/**
*
* Implemented by a class that wants to be called as a particular configuration file is parsed.
*
*
*
* This interface is implemented as an anonymous inner class inside ConfigureListener.digester().
*
*/
public interface VersionListener {
/**
*
* Called from the EntityResolver when we know one of the XML Grammar elements to which this config file conforms.
*
*
* @param grammar the grammar
*/
public void takeActionOnGrammar(String grammar);
/**
*
* Called from the individual digester beans to cause the artifact to be associated with the current JSF spec level of
* the file being parsed.
*
*
* @param artifactName the artifact name
*/
public void takeActionOnArtifact(String artifactName);
}
// --------------------------------------------------------- Private Methods
/**
*
* Configures the provided Digester
instance appropriate for use with JSF.
*
*
* @param digester - the Digester
instance to configure
*/
private void configureDigester(Digester digester) {
digester.setNamespaceAware(true);
digester.setUseContextClassLoader(true);
digester.setEntityResolver(RESOLVER);
digester.setErrorHandler(ERROR_HANDLER);
// disable digester log messages
digester.setLogger(new NoOpLog());
if (validating) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Attempting to configure Digester to perform" + " document validation.");
}
// In order to validate using *both* DTD and Schema, certain
// Xerces specific features are required. Try to set these
// features. If an exception is thrown trying to set these
// features, then disable validation.
try {
digester.setFeature(XERCES_VALIDATION, true);
digester.setFeature(XERCES_SCHEMA_VALIDATION, true);
digester.setFeature(XERCES_SCHEMA_CONSTRAINT_VALIDATION, true);
digester.setValidating(true);
} catch (SAXNotSupportedException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING,
"Attempt to set supported feature on XMLReader, " + "but the value provided was not accepted. " + "Validation will be disabledb.");
}
digester.setValidating(false);
} catch (SAXNotRecognizedException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING,
"Attempt to set unsupported feature on XMLReader" + " necessary for validation. Validation will be" + "disabled.");
}
digester.setValidating(false);
} catch (ParserConfigurationException e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Digester unable to configure underlying parser." + " Validation will be disabled.");
}
digester.setValidating(false);
}
} else {
digester.setValidating(false);
}
} // END configureDigester
// ----------------------------------------------------------- Inner Classes
private static class JsfEntityResolver extends DefaultHandler {
/**
*
* Contains associations between grammar name and the physical resource.
*
*/
private static final String[][] DTD_SCHEMA_INFO = { { "web-facesconfig_1_0.dtd", "/com/sun/faces/web-facesconfig_1_0.dtd" },
{ "web-facesconfig_1_1.dtd", "/com/sun/faces/web-facesconfig_1_1.dtd" },
{ "web-facesconfig_1_2.xsd", "/com/sun/faces/web-facesconfig_1_2.xsd" },
{ "web-facesconfig_2_0.xsd", "/com/sun/faces/web-facesconfig_2_0.xsd" },
{ "web-facesconfig_2_2.xsd", "/com/sun/faces/web-facesconfig_2_2.xsd" }, { "javaee_5.xsd", "/com/sun/faces/javaee_5.xsd" },
{ "javaee_7.xsd", "/com/sun/faces/javaee_7.xsd" }, { "javaee_8.xsd", "/com/sun/faces/javaee_8.xsd" },
{ "javaee_web_services_client_1_2.xsd", "/com/sun/faces/javaee_web_services_client_1_2.xsd" },
{ "javaee_web_services_client_1_4.xsd", "/com/sun/faces/javaee_web_services_client_1_4.xsd" }, { "xml.xsd", "/com/sun/faces/xml.xsd" } };
/**
*
* Contains mapping between grammar name and the local URL to the physical resource.
*
*/
private HashMap entities = new HashMap();
// -------------------------------------------------------- Constructors
public JsfEntityResolver(String schemaDirectory) {
// Add mappings between last segment of system ID and
// the expected local physical resource. If the resource
// cannot be found, then rely on default entity resolution
// and hope a firewall isn't in the way or a proxy has
// been configured
for (String[] aDTD_SCHEMA_INFO : DTD_SCHEMA_INFO) {
URL url = null;
if (schemaDirectory != null) {
try {
url = new File(schemaDirectory, aDTD_SCHEMA_INFO[1]).toURI().toURL();
} catch (MalformedURLException e) {
logger.log(SEVERE, "Exception loading " + aDTD_SCHEMA_INFO[1], e);
}
} else {
url = this.getClass().getResource(aDTD_SCHEMA_INFO[1]);
}
if (url == null) {
if (logger.isLoggable(WARNING)) {
logger.log(WARNING, "Unable to locate local resource '" + aDTD_SCHEMA_INFO[1] + "'. Standard entity "
+ "resolution will be used when request " + "is present for '" + aDTD_SCHEMA_INFO[0] + '\'');
}
} else {
entities.put(aDTD_SCHEMA_INFO[0], url.toString());
}
}
}
private VersionListener versionListener;
public void setVersionListener(VersionListener listener) {
versionListener = listener;
}
public VersionListener getVersionListener() {
return versionListener;
}
// ----------------------------------------- Methods from DefaultHandler
/**
*
* Resolves the physical resource using the last segment of the systemId
(e.g.
* http://java.sun.com/dtds/web-facesconfig_1_1.dtd, the last segment would be web-facesconfig_1_1.dtd). If a mapping
* cannot be found for the segment, then defer to the DefaultHandler
for resolution.
*
*/
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
// publicId is ignored. Resolution performed using
// the systemId.
// If no system ID, defer to superclass
if (systemId == null) {
InputSource result;
try {
result = super.resolveEntity(publicId, systemId);
} catch (Exception e) {
throw new SAXException(e);
}
return result;
}
String grammarName = systemId.substring(systemId.lastIndexOf('/') + 1);
if (null != getVersionListener()) {
getVersionListener().takeActionOnGrammar(grammarName);
}
String entityURL = entities.get(grammarName);
InputSource source;
if (entityURL == null) {
// we don't have a registered mapping, so defer to our
// superclass for resolution
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Unknown entity, deferring to superclass.");
}
try {
source = super.resolveEntity(publicId, systemId);
} catch (Exception e) {
throw new SAXException(e);
}
} else {
try {
source = new InputSource(new URL(entityURL).openStream());
} catch (Exception e) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "Unable to create InputSource for URL '" + entityURL + "'");
}
source = null;
}
}
// Set the System ID of the InputSource with the URL of the local
// resource - necessary to prevent parsing errors
if (source != null) {
source.setSystemId(entityURL);
if (publicId != null) {
source.setPublicId(publicId);
}
}
return source;
}
} // END JsfEntityResolver
private static class JsfErrorHandler implements ErrorHandler {
@Override
public void warning(SAXParseException exception) throws SAXException {
// do nothing
}
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
}
}