blob: f119d93c7c254d9ee6c13c83ee32208f5f0589a2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.util;
import org.apache.myfaces.util.lang.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.context.ExternalContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.myfaces.util.lang.ClassUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
public class WebXmlParser
{
private static final Logger LOGGER = Logger.getLogger(WebXmlParser.class.getName());
private static final String ERROR_PAGE_EXCEPTION_TYPE_EXPRESSION =
"*[local-name() = 'error-page']/*[local-name() = 'exception-type']";
private static final String LOCATION_EXPRESSION =
"*[local-name() = 'location']";
private static final String ERROR_CODE_500_LOCATION_EXPRESSION =
"*[local-name() = 'error-page'][*[local-name() = 'error-code'] = '500'] / *[local-name() = 'location']";
private static final String ERROR_PAGE_NO_CODE_AND_TYPE_EXPRESSION =
"*[local-name() = 'error-page'][not(*[local-name() = 'error-code']) and not"
+ "(*[local-name() = 'exception-type'])]/*[local-name() = 'location']";
private static final String KEY_ERROR_PAGES = WebXmlParser.class.getName() + ".errorpages";
private WebXmlParser()
{
}
/**
* Parses the web.xml and web-fragements.xml for error pages.
* "null" as key represents the default error page. Otherwise the key is the exception class.
*
* @param context
* @return
*/
public static Map<String, String> getErrorPages(ExternalContext context)
{
// it would be nicer if the cache would probably directly in DefaultWebConfigProvider
// as its currently the only caller of the method
// however it's recreated every request, we have to refactor the SPI thing a bit probably.
Map<String, String> cached = (Map<String, String>) context.getApplicationMap().get(KEY_ERROR_PAGES);
if (cached != null)
{
return cached;
}
Map<String, String> webXmlErrorPages = getWebXmlErrorPages(context);
Map<String, String> webFragmentXmlsErrorPages = getWebFragmentXmlsErrorPages(context);
Map<String, String> errorPages = webXmlErrorPages;
if (errorPages == null)
{
errorPages = webFragmentXmlsErrorPages;
}
else if (webFragmentXmlsErrorPages != null)
{
for (Map.Entry<String, String> entry : webFragmentXmlsErrorPages.entrySet())
{
if (!errorPages.containsKey(entry.getKey()))
{
errorPages.put(entry.getKey(), entry.getValue());
}
}
}
context.getApplicationMap().put(KEY_ERROR_PAGES, errorPages);
return errorPages;
}
private static Map<String, String> getWebXmlErrorPages(ExternalContext context)
{
try
{
Document webXml = toDocument(context.getResource("/WEB-INF/web.xml"));
if (webXml == null)
{
// Quarkus
webXml = toDocument(ClassUtils.getCurrentLoader(WebXmlParser.class).getResource("META-INF/web.xml"));
}
if (webXml != null)
{
return parseErrorPages(webXml.getDocumentElement());
}
}
catch (Throwable e)
{
LOGGER.log(Level.SEVERE, "Could not load or parse web.xml", e);
}
return null;
}
private static Map<String, String> getWebFragmentXmlsErrorPages(ExternalContext context)
{
Map<String, String> webFragmentXmlsErrorPages = null;
try
{
Enumeration<URL> webFragments = ClassUtils.getContextClassLoader()
.getResources("META-INF/web-fragment.xml");
while (webFragments.hasMoreElements())
{
try
{
URL url = webFragments.nextElement();
Document webFragmentXml = toDocument(url);
if (webFragmentXml != null)
{
if (webFragmentXmlsErrorPages == null)
{
webFragmentXmlsErrorPages = parseErrorPages(webFragmentXml.getDocumentElement());
}
else
{
Map<String, String> temp = parseErrorPages(webFragmentXml.getDocumentElement());
for (Map.Entry<String, String> entry : temp.entrySet())
{
if (!webFragmentXmlsErrorPages.containsKey(entry.getKey()))
{
webFragmentXmlsErrorPages.put(entry.getKey(), entry.getValue());
}
}
}
}
}
catch (Throwable e)
{
LOGGER.log(Level.SEVERE, "Could not load or parse web-fragment.xml", e);
}
}
}
catch (IOException e)
{
LOGGER.log(Level.SEVERE, "Could not get web-fragment.xml from ClassLoader", e);
}
return webFragmentXmlsErrorPages;
}
private static Document toDocument(URL url) throws Exception
{
InputStream is = null;
try
{
// web.xml is optional
if (url == null)
{
return null;
}
is = url.openStream();
if (is == null)
{
return null;
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(false);
factory.setExpandEntityReferences(false);
factory.setIgnoringElementContentWhitespace(true);
factory.setIgnoringComments(true);
try
{
factory.setFeature("http://xml.org/sax/features/namespaces", false);
factory.setFeature("http://xml.org/sax/features/validation", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
}
catch (Throwable e)
{
LOGGER.warning("DocumentBuilderFactory#setFeature not implemented. Skipping...");
}
boolean absolute = false;
try
{
absolute = url.toURI().isAbsolute();
}
catch (URISyntaxException e)
{
// noop
}
DocumentBuilder builder = factory.newDocumentBuilder();
Document document;
if (absolute)
{
InputSource source = new InputSource(url.toExternalForm());
source.setByteStream(is);
document = builder.parse(source);
}
else
{
document = builder.parse(is);
}
return document;
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (IOException e)
{
// ignore
}
}
}
}
private static Map<String, String> parseErrorPages(Element webXml) throws Exception
{
Map<String, String> errorPages = new HashMap<>();
XPath xpath = XPathFactory.newInstance().newXPath();
NodeList exceptionTypes = (NodeList) xpath.compile(ERROR_PAGE_EXCEPTION_TYPE_EXPRESSION)
.evaluate(webXml, XPathConstants.NODESET);
for (int i = 0; i < exceptionTypes.getLength(); i++)
{
Node node = exceptionTypes.item(i);
String exceptionType = node.getTextContent().trim();
String key = Throwable.class.getName().equals(exceptionType) ? null : exceptionType;
String location = xpath.compile(LOCATION_EXPRESSION).evaluate(node.getParentNode()).trim();
if (!errorPages.containsKey(key))
{
errorPages.put(key, location);
}
}
if (!errorPages.containsKey(null))
{
String defaultLocation = xpath.compile(ERROR_CODE_500_LOCATION_EXPRESSION).evaluate(webXml).trim();
if (StringUtils.isBlank(defaultLocation))
{
defaultLocation = xpath.compile(ERROR_PAGE_NO_CODE_AND_TYPE_EXPRESSION).evaluate(webXml).trim();
}
if (!StringUtils.isBlank(defaultLocation))
{
errorPages.put(null, defaultLocation);
}
}
return errorPages;
}
}