/*
* 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.commons.resourcehandler.config;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.faces.FacesException;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.commons.resourcehandler.config.element.Library;
import org.apache.myfaces.commons.resourcehandler.config.element.MyFacesResourcesConfig;
import org.apache.myfaces.commons.util.ClassUtils;
import org.apache.myfaces.commons.util.WebConfigParamUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* XML-parser for MyFacesResourcesConfig.
*
* @author Jakob Korherr
* @author Leonardo Uribe
*/
public class MyFacesResourceHandlerConfigParser
{
/**
* This param allow to override the default strategy to locate myfaces-resources-config.xml files, that will be parsed later. In this way
* it is possible to include new source locations or handle cases like OSGi specific setup.
*/
@JSFWebConfigParam
public static final String INIT_PARAM_EXTENDED_RESOURCE_HANDLER_CONFIG_URL_PROVIDER = "org.apache.myfaces.commons.EXTENDED_RESOURCE_HANDLER_CONFIG_URL_PROVIDER";
public static final String INIT_PARAM_EXTENDED_RESOURCE_HANDLER_CONFIG_URL_PROVIDER_DEFAULT = DefaultMyFacesResourceHandlerUrlProvider.class.getName();
public MyFacesResourcesConfig parse(FacesContext facesContext)
{
String resourceHandlerUrlProviderClassName = WebConfigParamUtils.getStringInitParameter(facesContext.getExternalContext(),
INIT_PARAM_EXTENDED_RESOURCE_HANDLER_CONFIG_URL_PROVIDER, INIT_PARAM_EXTENDED_RESOURCE_HANDLER_CONFIG_URL_PROVIDER_DEFAULT);
MyFacesResourceHandlerUrlProvider urlProvider = (MyFacesResourceHandlerUrlProvider) ClassUtils.newInstance(resourceHandlerUrlProviderClassName);
List<URL> configUrls = new ArrayList<URL>();
try
{
configUrls.addAll(urlProvider.getMetaInfConfigurationResources(facesContext.getExternalContext()));
}
catch(IOException e)
{
throw new FacesException("Cannot get META-INF/myfaces-resources-config.xml urls", e);
}
try
{
URL webInfConfig = urlProvider.getWebInfConfigurationResource(facesContext.getExternalContext());
if (webInfConfig != null)
{
configUrls.add(webInfConfig);
}
}
catch(IOException e)
{
throw new FacesException(e);
}
List<MyFacesResourcesConfig> configList = new ArrayList<MyFacesResourcesConfig>();
// we have at least one config file and thus can start parsing
for (URL url : configUrls)
{
try
{
MyFacesResourcesConfig mrc = parseFile(url);
if (mrc != null)
{
configList.add(mrc);
}
}
catch (IOException e) {
throw new FacesException("Cannot parse file "+url);
}
}
MyFacesResourcesConfig finalConfig = new MyFacesResourcesConfig();
for (MyFacesResourcesConfig cfg : configList)
{
finalConfig.merge(cfg);
}
return finalConfig;
}
private MyFacesResourcesConfig parseFile(URL configFileUrl) throws IOException
{
InputStream is = null;
URLConnection conn = null;
try
{
ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
boolean schemaValidating = false;
// parse file
LibraryHandler handler = new LibraryHandler(configFileUrl);
SAXParser parser = createSAXParser(handler, externalContext, schemaValidating);
conn = configFileUrl.openConnection();
conn.setUseCaches(false);
is = conn.getInputStream();
parser.parse(is, handler);
return handler.getMyFacesResourcesConfig();
}
catch (SAXException e)
{
IOException ioe = new IOException("Error parsing [" + configFileUrl + "]: ");
ioe.initCause(e);
throw ioe;
}
catch (ParserConfigurationException e)
{
IOException ioe = new IOException("Error parsing [" + configFileUrl + "]: ");
ioe.initCause(e);
throw ioe;
}
finally
{
if (is != null)
is.close();
}
}
private static final SAXParser createSAXParser(LibraryHandler handler, ExternalContext externalContext, boolean schemaValidating) throws SAXException,
ParserConfigurationException
{
SAXParserFactory factory = SAXParserFactory.newInstance();
//Just parse it and do not validate, because it is not necessary.
factory.setNamespaceAware(true);
factory.setFeature("http://xml.org/sax/features/validation", false);
factory.setValidating(false);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
reader.setErrorHandler(handler);
reader.setEntityResolver(handler);
return parser;
}
private static class LibraryHandler extends DefaultHandler
{
private final URL source;
private final StringBuffer buffer;
private Locator locator;
private MyFacesResourcesConfig config;
private String libraryName;
private String redirectName;
private String requestPath;
public LibraryHandler(URL source)
{
this.source = source;
this.buffer = new StringBuffer(64);
this.config = null;
}
public MyFacesResourcesConfig getMyFacesResourcesConfig()
{
return this.config;
}
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
this.buffer.setLength(0);
if ("myfaces-resources-config".equals(qName))
{
this.config = new MyFacesResourcesConfig();
}
else if ("library".equals(qName))
{
this.libraryName = null;
this.redirectName = null;
this.requestPath = null;
}
}
public void endElement(String uri, String localName, String qName) throws SAXException
{
try
{
if (this.config == null)
{
return;
}
if ("library".equals(localName))
{
Library l = new Library();
l.setName(libraryName);
l.setRedirectName(redirectName);
l.setRequestPath(requestPath);
config.addLibrary(l);
}
else if ("library-name".equals(localName))
{
libraryName = captureBuffer();
}
else if ("redirect-name".equals(localName))
{
redirectName = captureBuffer();
}
else if ("request-path".equals(localName))
{
requestPath = captureBuffer();
}
}
catch (Exception e)
{
SAXException saxe = new SAXException("Error Handling [" + this.source + "@"
+ this.locator.getLineNumber() + "," + this.locator.getColumnNumber() + "] <" + qName + ">");
saxe.initCause(e);
throw saxe;
}
}
public void characters(char[] ch, int start, int length) throws SAXException
{
this.buffer.append(ch, start, length);
}
private String captureBuffer() throws Exception
{
String s = this.buffer.toString().trim();
if (s.length() == 0)
{
throw new Exception("Value Cannot be Empty");
}
this.buffer.setLength(0);
return s;
}
public InputSource resolveEntity(String publicId, String systemId) throws SAXException
{
return null;
}
public void error(SAXParseException e) throws SAXException
{
SAXException saxe = new SAXException("Error Handling [" + this.source + "@" + e.getLineNumber() + ","
+ e.getColumnNumber() + "]");
saxe.initCause(e);
throw saxe;
}
public void setDocumentLocator(Locator locator)
{
this.locator = locator;
}
public void fatalError(SAXParseException e) throws SAXException
{
throw e;
}
public void warning(SAXParseException e) throws SAXException
{
throw e;
}
}
}