/*
* Copyright (C) 2009 eXo Platform SAS.
*
* 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.exoplatform.container.configuration;
import org.exoplatform.commons.utils.PropertyManager;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.util.Utils;
import org.exoplatform.container.xml.Configuration;
import org.exoplatform.container.xml.Deserializer;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IUnmarshallingContext;
import org.jibx.runtime.JiBXException;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
/**
* Unmarshall a configuration.
*
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class ConfigurationUnmarshaller
{
private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.ConfigurationUnmarshaller");
/**
* A private copy of the list of kernel namespaces
*/
private static final String[] KERNEL_NAMESPACES = Namespaces.getKernelNamespaces();
private class Reporter implements ErrorHandler
{
private final URL url;
private boolean valid;
private Reporter(URL url)
{
this.url = url;
this.valid = true;
}
public void warning(SAXParseException exception) throws SAXException
{
LOG.warn(exception.getMessage(), exception);
}
public void error(SAXParseException exception) throws SAXException
{
if (exception.getMessage().equals("cvc-elt.1: Cannot find the declaration of element 'configuration'."))
{
LOG.info("The document "
+ url
+ " does not contain a schema declaration, it should have an "
+ "XML declaration similar to\n"
+ "<configuration\n"
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://www.exoplatform.org/xml/ns/kernel_1_1.xsd "
+ "http://www.exoplatform.org/xml/ns/kernel_1_1.xsd\"\n"
+ " xmlns=\"http://www.exoplatform.org/xml/ns/kernel_1_1.xsd\">");
}
else
{
LOG.error("In document " + url + " at (" + exception.getLineNumber() + "," + exception.getColumnNumber()
+ ") :" + exception.getMessage());
}
valid = false;
}
public void fatalError(SAXParseException exception) throws SAXException
{
LOG.fatal("In document " + url + " at (" + exception.getLineNumber() + "," + exception.getColumnNumber()
+ ") :" + exception.getMessage());
valid = false;
}
}
/** . */
private final Set<String> profiles;
public ConfigurationUnmarshaller(Set<String> profiles)
{
this.profiles = profiles;
}
public ConfigurationUnmarshaller()
{
this.profiles = Collections.emptySet();
}
/**
* Returns true if the configuration file is valid according to its schema declaration. If the file
* does not have any schema declaration, the file will be reported as valid.
*
* @param url the url of the configuration to validate
* @return true if the configuration file is valid
* @throws IOException any IOException thrown by using the provided URL
* @throws NullPointerException if the provided URL is null
*/
public boolean isValid(final URL url) throws NullPointerException, IOException
{
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory
.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", KERNEL_NAMESPACES);
factory.setNamespaceAware(true);
factory.setValidating(true);
return SecurityHelper.doPrivilegedIOExceptionAction(new PrivilegedExceptionAction<Boolean>()
{
public Boolean run() throws Exception
{
try
{
DocumentBuilder builder = factory.newDocumentBuilder();
Reporter reporter = new Reporter(url);
builder.setErrorHandler(reporter);
builder.setEntityResolver(Namespaces.resolver);
String content = Deserializer.resolveVariables(Utils.readStream(url.openStream()));
InputSource is = new InputSource(new StringReader(content));
builder.parse(is);
return reporter.valid;
}
catch (ParserConfigurationException e)
{
LOG.error("Got a parser configuration exception when doing XSD validation");
return false;
}
catch (SAXException e)
{
LOG.error("Got a sax exception when doing XSD validation");
return false;
}
}
});
}
public Configuration unmarshall(final URL url) throws Exception
{
if (PropertyManager.isDevelopping())
{
boolean valid = isValid(url);
if (!valid)
{
LOG.info("The configuration file " + url + " was not found valid according to its XSD");
}
}
//
DocumentBuilderFactory factory = null;
try
{
// With Java 6, it's safer to precise the builder factory class name as it may result:
// java.lang.AbstractMethodError: org.apache.xerces.dom.DeferredDocumentImpl.getXmlStandalone()Z
// at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.setDocumentInfo(Unknown Source)
Method dbfniMethod = DocumentBuilderFactory.class.getMethod("newInstance", String.class, ClassLoader.class);
factory =
(DocumentBuilderFactory)dbfniMethod.invoke(null,
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", Thread.currentThread()
.getContextClassLoader());
}
catch (InvocationTargetException e)
{
Throwable cause = e.getCause();
if (cause instanceof FactoryConfigurationError)
{
// do nothing and let try to instantiate later
LOG.debug("Was not able to find document builder factory class in Java > 5, will use default", cause);
}
else
{
// Rethrow
throw e;
}
}
catch (NoSuchMethodException e)
{
if (LOG.isTraceEnabled())
{
LOG.trace("An exception occurred: " + e.getMessage());
}
}
//
if (factory == null)
{
factory = DocumentBuilderFactory.newInstance();
}
//
factory.setNamespaceAware(true);
final DocumentBuilderFactory builderFactory = factory;
try
{
return SecurityHelper.doPrivilegedExceptionAction(new PrivilegedExceptionAction<Configuration>()
{
public Configuration run() throws Exception
{
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document doc = builder.parse(url.openStream());
// Filter DOM
ProfileDOMFilter filter = new ProfileDOMFilter(profiles);
filter.process(doc.getDocumentElement());
// SAX event stream -> String
StringWriter buffer = new StringWriter();
SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
TransformerHandler hd = tf.newTransformerHandler();
StreamResult result = new StreamResult(buffer);
hd.setResult(result);
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "UTF8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
// Transform -> SAX event stream
SAXResult saxResult = new SAXResult(new NoKernelNamespaceSAXFilter(hd));
// DOM -> Transform
serializer.transform(new DOMSource(doc), saxResult);
// Reuse the parsed document
String document = buffer.toString();
// Debug
if (LOG.isTraceEnabled())
LOG.trace("About to parse configuration file " + document);
//
IBindingFactory bfact = BindingDirectory.getFactory(Configuration.class);
IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
return (Configuration)uctx.unmarshalDocument(new StringReader(document), null);
}
});
}
catch (PrivilegedActionException pae)
{
Throwable cause = pae.getCause();
if (cause instanceof JiBXException)
{
throw (JiBXException)cause;
}
else if (cause instanceof ParserConfigurationException)
{
throw (ParserConfigurationException)cause;
}
else if (cause instanceof IOException)
{
throw (IOException)cause;
}
else if (cause instanceof SAXException)
{
throw (SAXException)cause;
}
else if (cause instanceof IllegalArgumentException)
{
throw (IllegalArgumentException)cause;
}
else if (cause instanceof TransformerException)
{
throw (TransformerException)cause;
}
else if (cause instanceof TransformerConfigurationException)
{
throw (TransformerConfigurationException)cause;
}
else if (cause instanceof TransformerFactoryConfigurationError)
{
throw (TransformerFactoryConfigurationError)cause;
}
else if (cause instanceof RuntimeException)
{
throw (RuntimeException)cause;
}
else
{
throw new RuntimeException(cause);
}
}
}
}