/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., 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.portletbridge.config;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.faces.FacesException;
import javax.faces.webapp.FacesServlet;
import javax.portlet.PortletContext;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.DefaultHandler;
/**
* @author kenfinnigan
*/
public final class WebXmlProcessor {
private static final String WEB_XML_PATH = "/WEB-INF/web.xml";
private static SAXParserFactory saxFactory = SAXParserFactory.newInstance();
static AtomicBoolean scan = new AtomicBoolean(true);
private static boolean scanned = false;
static {
saxFactory.setValidating(false);
saxFactory.setNamespaceAware(true);
}
private static List<ServletBean> servlets = new ArrayList<ServletBean>();
private static Map<String, ArrayList<String>> urlMappings = new HashMap<String, ArrayList<String>>();
private static Map<String, String> errorPages = new LinkedHashMap<String, String>();
static ServletBean facesServlet;
static Map<Class<? extends Throwable>, String> errorViews = new LinkedHashMap<Class<? extends Throwable>, String>();
public WebXmlProcessor(PortletContext portletContext) {
if (null != portletContext) {
if (scan.compareAndSet(true, false)) {
InputStream inputStream = portletContext.getResourceAsStream(WEB_XML_PATH);
if (null != inputStream) {
this.parse(inputStream);
try {
inputStream.close();
} catch (IOException e) {
portletContext.log("Portlet Bridge error parsing web.xml", e);
}
}
scanned = true;
} else {
while (!scanned) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
break;
}
}
}
}
}
/**
* @return the errorPages
*/
public Map<String, String> getErrorPages() {
return errorPages;
}
/**
* @return the facesServlet
*/
public ServletBean getFacesServlet() {
return facesServlet;
}
/**
* getter for the map between error class and corresponding JSF view id.
*
* @return the {@link #errorViews}
*/
public Map<Class<? extends Throwable>, String> getErrorViews() {
return errorViews;
}
public void parse(InputStream webXml) {
try {
SAXParser parser = saxFactory.newSAXParser();
XMLReader reader = parser.getXMLReader();
WebXmlHandler webXmlHandler = new WebXmlHandler(reader);
reader.setContentHandler(webXmlHandler);
reader.setEntityResolver(NULL_RESOLVER);
reader.setErrorHandler(webXmlHandler);
reader.setDTDHandler(webXmlHandler);
reader.parse(new InputSource(webXml));
createErrorViews();
} catch (Exception e) {
throw new FacesException("XML parsing error", e);
}
}
/**
* Create map between error class and corresponding JSF view id. Map created from the {@link #errorPages}
* string-based map.
*/
protected void createErrorViews() {
errorViews = new LinkedHashMap<Class<? extends Throwable>, String>();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (null == classLoader) {
classLoader = this.getClass().getClassLoader();
}
for (Entry<String, String> entry : errorPages.entrySet()) {
try {
Class<? extends Throwable> clazz = classLoader.loadClass(entry.getKey()).asSubclass(Throwable.class);
String viewId = getViewIdFromLocation(entry.getValue());
if (null != viewId) {
errorViews.put(clazz, viewId);
}
} catch (ClassNotFoundException e) {
// Configuration error, just ignore.
}
}
}
/**
* Convert error page location into JSF viewId.
*
* @param location
* error page location
* @return view id if this location is mapped to the {@link FacesServlet} othervise null.
*/
protected String getViewIdFromLocation(String location) {
String viewId = null;
for (String mapping : facesServlet.getMappings()) {
if (mapping.startsWith("*")) {
// Suffix mapping.
String suffix = mapping.substring(1);
if (location.endsWith(suffix)) {
viewId = location.substring(0, location.length() - suffix.length());
break;
}
} else if (mapping.endsWith("*")) {
// Prefix mapping.
String prefix = mapping.substring(0, mapping.length() - 1);
if (location.startsWith(prefix)) {
int index = prefix.length();
if (prefix.endsWith("/")) {
index--;
}
viewId = location.substring(index);
}
}
}
return viewId;
}
// Methods used for testing only
static void setErrorPages(Map<String, String> pages) {
errorPages = pages;
}
static void setUrlMappings(Map<String, ArrayList<String>> mappings) {
urlMappings = mappings;
}
static void setServlets(List<ServletBean> srvlts) {
servlets = srvlts;
}
public static final EntityResolver2 NULL_RESOLVER = new EntityResolver2() {
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
public InputSource getExternalSubset(String name, String baseURI) throws SAXException, IOException {
return new InputSource(new StringReader(""));
}
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
throws SAXException, IOException {
// Do nothing, to avoid network requests to external DTD/Schema
return new InputSource(new StringReader(""));
}
};
class WebXmlHandler extends DefaultHandler {
private static final String SERVLET_ELEMENT = "servlet";
private static final String SERVLET_NAME_ELEMENT = "servlet-name";
private static final String SERVLET_MAPPING_ELEMENT = "servlet-mapping";
private static final String SERVLET_CLASS_ELEMENT = "servlet-class";
private static final String ERROR_PAGE_ELEMENT = "error-page";
private static final String LOCATION_ELEMENT = "location";
private static final String EXCEPTION_CLASS_ELEMENT = "exception-type";
private static final String URL_PATTERN_ELEMENT = "url-pattern";
private static final String FACES_SERVLET_CLASS = "javax.faces.webapp.FacesServlet";
private XMLReader xmlReader;
public WebXmlHandler(XMLReader xmlReader) {
this.xmlReader = xmlReader;
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (SERVLET_ELEMENT.equals(localName)) {
xmlReader.setContentHandler(new ServletHandler());
} else if (SERVLET_MAPPING_ELEMENT.equals(localName)) {
xmlReader.setContentHandler(new MappingHandler());
} else if (ERROR_PAGE_ELEMENT.equals(localName)) {
xmlReader.setContentHandler(new ErrorPageHandler());
}
}
@Override
public void endDocument() throws SAXException {
for (ServletBean servlet : servlets) {
if (FACES_SERVLET_CLASS.equals(servlet.getClassName())) {
facesServlet = servlet;
List<String> servletMappings = urlMappings.get(servlet.getName());
if (null != servletMappings) {
facesServlet.getMappings().addAll(servletMappings);
}
}
}
}
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
// Do nothing, to avoid network requests to external DTD/Schema
return new InputSource(new StringReader(""));
}
final class ServletHandler extends StateHandler {
private StringBuilder servletName = new StringBuilder();
private StringBuilder servletClass = new StringBuilder();
public ServletHandler() {
super(xmlReader, WebXmlHandler.this);
}
@Override
protected ContentHandler getNextHandler(String uri, String localName, Attributes attributes) {
ContentHandler nextHandler = null;
if (SERVLET_NAME_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, servletName);
} else if (SERVLET_CLASS_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, servletClass);
}
return nextHandler;
}
@Override
protected void endLastElement() {
servlets.add(new ServletBean(servletName.toString().trim(), servletClass.toString().trim()));
}
}
final class MappingHandler extends StateHandler {
private StringBuilder servletName = new StringBuilder();
private StringBuilder urlPattern = new StringBuilder();
public MappingHandler() {
super(xmlReader, WebXmlHandler.this);
}
@Override
protected ContentHandler getNextHandler(String uri, String localName, Attributes attributes) {
ContentHandler nextHandler = null;
if (SERVLET_NAME_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, servletName);
} else if (URL_PATTERN_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, urlPattern);
}
return nextHandler;
}
@Override
protected void endLastElement() {
if (urlMappings.containsKey(servletName.toString().trim())) {
urlMappings.get(servletName.toString().trim()).add(urlPattern.toString().trim());
} else {
urlMappings.put(servletName.toString().trim(), new ArrayList<String>());
urlMappings.get(servletName.toString().trim()).add(urlPattern.toString().trim());
}
}
}
final class ErrorPageHandler extends StateHandler {
private StringBuilder exceptionType = new StringBuilder();
private StringBuilder location = new StringBuilder();
public ErrorPageHandler() {
super(xmlReader, WebXmlHandler.this);
}
@Override
protected ContentHandler getNextHandler(String uri, String localName, Attributes attributes) {
ContentHandler nextHandler = null;
if (EXCEPTION_CLASS_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, exceptionType);
} else if (LOCATION_ELEMENT.equals(localName)) {
nextHandler = new StringContentHandler(getReader(), this, location);
}
return nextHandler;
}
@Override
protected void endLastElement() {
// PBR-575 - Just skip non exception-type error-pages
if(exceptionType.toString()!=null && exceptionType.toString().trim().length() > 0) {
errorPages.put(exceptionType.toString().trim(), location.toString().trim());
}
}
}
}
}