org.ow2.petals.se.xslt.model.XsltConfigurationHandler Maven / Gradle / Ivy
/**
* Copyright (c) 2010-2012 EBM WebSourcing, 2012-2016 Linagora
*
* This program/library 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 program/library 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 program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.se.xslt.model;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jbi.messaging.MessagingException;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import org.ow2.petals.component.framework.api.exception.PEtALSCDKException;
import org.ow2.petals.component.framework.api.message.Exchange;
import org.ow2.petals.component.framework.util.ClassLoaderUtil;
import org.ow2.petals.se.xslt.utils.LogErrorListener;
import org.ow2.petals.se.xslt.utils.ServiceUnitURIResolver;
import com.ebmwebsourcing.easycommons.pooling.GenericResourcePool;
import com.ebmwebsourcing.easycommons.pooling.PoolPolicy;
/**
* A handler for XSLT configurations.
*
* Every element derived from the SU configuration and required at runtime is
* hold by such a handler.
*
*
* @author Vincent Zurczak - EBM WebSourcing
*/
public class XsltConfigurationHandler {
/**
* The XSLT configuration.
*/
private final XsltConfiguration xsltConfiguration;
/**
* The URI resolver for this service-unit.
*/
private final ServiceUnitURIResolver uriResolver;
/**
* The error listener, used to resolve and log line numbers when errors are
* found.
*/
private final LogErrorListener logErrorListener;
/**
* The class loader associated with this transformer.
*
* This class loader may be specific to the SU if it embeds custom Java
* functions used in the XSL transformation.
*
*/
private ClassLoader classLoader;
/**
* An object that will be used as a lock in the management of the instance's
* state.
*/
private final Object lock = new Object();
/**
* The SU transformer resource pool
*/
private GenericResourcePool suTransformerResourcePool;
/**
* Constructor.
*
* @param xsltConfiguration
* an XSLT configuration
* @param logger
* the component's logger
* @throws PEtALSCDKException
*/
public XsltConfigurationHandler(XsltConfiguration xsltConfiguration, Logger logger) {
this.xsltConfiguration = xsltConfiguration;
this.logErrorListener = new LogErrorListener(logger, xsltConfiguration.getServiceUnitName());
this.uriResolver = new ServiceUnitURIResolver(xsltConfiguration.getSuInstallRoot(), logger,
xsltConfiguration.getServiceUnitName());
}
/**
* Checks the handler.
*
* It tries to create the XSL template which is not cached (deployment)
* If the SU embeds JAR files, they are loaded in the class loader of the
* handler, so that they can be used by the XSL transformation.
*
*
* @param logger
* the logger of the component
* @throws PEtALSCDKException
* if the check fails
*/
public void check(Logger logger) throws PEtALSCDKException {
createTemplate(logger, true);
}
/**
* Starts the handler.
*
* The XSL style sheet is cached and a pool of transformer is created
* If the SU embeds JAR files, they are loaded in the class loader of the
* handler, so that they can be used by the XSL transformation.
*
*
* @param logger
* the logger of the component
* @throws PEtALSCDKException
* if the handler could not be started
*/
public void start(Logger logger) throws PEtALSCDKException {
Templates template = createTemplate(logger, false);
XsltTransformerResourceHandler transformerResourceHandler = new XsltTransformerResourceHandler(
template, uriResolver, logErrorListener, xsltConfiguration);
int transformerPoolSizeMin = this.xsltConfiguration.getTransformerPoolSizeMin();
int transformerPoolSizeMax = this.xsltConfiguration.getTransformerPoolSizeMax();
ClassLoader customClassLoader = getClassLoader();
try {
// this is needed because the pool is susceptible of pre-creating resources
// which are susceptible to precompiling XSLT (for example the default XSLT engine of Java 7 does that)
// and they will need the context classloader in case there is custom functions used in the XSLT
if (customClassLoader != null) {
Thread.currentThread().setContextClassLoader(customClassLoader);
}
this.suTransformerResourcePool = new GenericResourcePool(transformerResourceHandler,
transformerPoolSizeMin, transformerPoolSizeMax, PoolPolicy.WAIT);
} finally {
if (customClassLoader != null) {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
}
}
}
/**
* Creates the XSL template
*
* @param logger
* the logger of the component
* @param logEnabled
* a flag to indicate to log or not
*
* @return the XSL template
*
* @throws PEtALSCDKException
* if the template could not be created
*/
private Templates createTemplate(Logger logger, boolean logEnabled) throws PEtALSCDKException {
Templates template = null;
synchronized (this.lock) {
String suRoot = this.xsltConfiguration.getSuInstallRoot();
String logPrefix = this.xsltConfiguration.getServiceUnitName();
// Try to get the XSL from the file system
InputStream xslStream = null;
try {
// Check if Java classes are provided
// These classes should contain custom XSL functions
boolean clazzProvided = false;
bigLoop: for (File file : new File(suRoot).listFiles()) {
// Skip non-JAR files
if (!file.getName().toLowerCase().endsWith(".jar"))
continue;
// Check that the jar doesn't contain only a META-INF
// directory
JarFile jar = new JarFile(file);
Enumeration entryEnum = jar.entries();
while (entryEnum.hasMoreElements()) {
JarEntry jarEntry = entryEnum.nextElement();
if (jarEntry.getName().toLowerCase().endsWith(".class")) {
clazzProvided = true;
break bigLoop;
}
}
jar.close();
}
// PETALSSEXSLT-9: try to get the input stream as a resource
// (class loader).
// If it fails, try to get it from a file.
ClassLoader tempClassLoader = ClassLoaderUtil.createClassLoader(suRoot, getClass()
.getClassLoader());
xslStream = tempClassLoader
.getResourceAsStream(this.xsltConfiguration.getXslPath());
if (xslStream == null) {
File xslStyleSheet = new File(suRoot, this.xsltConfiguration.getXslPath());
xslStream = new FileInputStream(xslStyleSheet);
}
// PETALSSEXSLT-9
// Instantiate the XSL style sheet
Source source = new StreamSource(xslStream);
String transformerFactoryClassName = this.xsltConfiguration
.getTransformerFactoryClassName();
TransformerFactory transformerFactory = createTransformerFactory(logger, logPrefix,
transformerFactoryClassName, logEnabled);
transformerFactory.setURIResolver(this.uriResolver);
transformerFactory.setErrorListener(this.logErrorListener);
if (clazzProvided) {
this.classLoader = tempClassLoader;
Thread.currentThread().setContextClassLoader(this.classLoader);
} else {
// Do not use it anymore - and speed up the garbage
// collector
tempClassLoader = null;
}
// Register the XSL style sheet, and its class loader
template = transformerFactory.newTemplates(source);
if (this.classLoader != null) {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
if (logger.isLoggable(Level.FINE))
logger.fine(logPrefix
+ ": the XSL style sheet was stored with a custom class loader.");
}
} catch (IOException e) {
throw new PEtALSCDKException(logPrefix + ": failed to retrieve the XSL resources.", e);
} catch (TransformerConfigurationException e) {
throw new PEtALSCDKException(logPrefix + ": Error when creating the XSLT template.", e);
} finally {
// Close the loaded input stream
if (xslStream != null) {
try {
xslStream.close();
} catch (IOException e) {
if (logger.isLoggable(Level.FINE))
logger.fine(logPrefix + ": the XSL input stream could not be closed.");
}
}
}
}
return template;
}
/**
* Create the transformer factory
*
* @param logger
* the logger
* @param logPrefix
* the log prefix
* @param transformerFactoryClassName
* the transformer factory class name
* @param logEnabled
* a flag to indicate to log or not
* @return the transformer factory
*
* @throws PEtALSCDKException
* if the transformer factory can not be created
*/
static final TransformerFactory createTransformerFactory(Logger logger, String logPrefix,
String transformerFactoryClassName, boolean logEnabled) throws PEtALSCDKException {
final TransformerFactory transformerFactory;
if (transformerFactoryClassName == null) {
transformerFactory = TransformerFactory.newInstance();
if(logger.isLoggable(Level.CONFIG)) {
logger.log(Level.CONFIG, "Transformer used: " + transformerFactory.getClass().getName()
+ " provided by the component");
}
} else {
try {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Trying to load the specific XSLT engine factory class "
+ transformerFactoryClassName + " in the following classloader\n"
+ Thread.currentThread().getContextClassLoader());
}
Class> transformerFactoryClass = Class.forName(transformerFactoryClassName);
Object transformerFactoryObj = transformerFactoryClass.newInstance();
if (transformerFactoryObj instanceof TransformerFactory) {
transformerFactory = (TransformerFactory) transformerFactoryObj;
if(logger.isLoggable(Level.CONFIG)) {
logger
.log(Level.CONFIG, "Transformer used: "
+ transformerFactory.getClass().getName()
+ " provided by the SL");
}
} else {
throw new PEtALSCDKException(
logPrefix
+ ": failed to create the pool of transformer. The class defined as XSLT engine factory does not implement TransformerFactory");
}
} catch (ClassNotFoundException e) {
throw new PEtALSCDKException(logPrefix
+ ": failed to create the pool of transformer.", e);
} catch (InstantiationException e) {
throw new PEtALSCDKException(logPrefix
+ ": failed to create the pool of transformer.", e);
} catch (IllegalAccessException e) {
throw new PEtALSCDKException(logPrefix
+ ": failed to create the pool of transformer.", e);
}
}
return transformerFactory;
}
/**
* Stops the handler.
*
* The transformer is deleted and the class loader is freed.
*
*/
public void stop() {
synchronized (this.lock) {
// FIXME: are we sure an exception won't be thrown at the uninstall
// phase?
// Sun's URL class loaders lock JAR files...
this.classLoader = null;
}
}
/**
* @return the associated end-point name
* @see org.ow2.petals.se.xslt.model.XsltConfiguration #getEndpointName()
*/
public String getEndpointName() {
return this.xsltConfiguration.getEndpointName();
}
/**
* @return the classLoader
*/
public ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* @return an output attachment-name
*/
public String getOutputAttachmentName() {
return this.xsltConfiguration.getOutputAttachmentName();
}
/**
* Takes a transformer (with the right properties) in the transformer pool.
*
* @param exchange
* the exchange, to get its properties
* @param logger
* the component logger
*
* @return a new transformer
* @throws TransformerException
* if the transformer could not be created or if a XSL parameter
* could not be overridden
*/
public Transformer takeTransformer(Exchange exchange, Logger logger)
throws TransformerException {
Transformer transformer = this.suTransformerResourcePool.take();
// Prepare the log prefix
String logHint = "Exchange " + exchange.getExchangeId();
// Then, pass the XSL parameters from the exchange
try {
Map suXslParameters = this.xsltConfiguration
.getParamNameToParamBean();
for (String propertyName : exchange.getInMessagePropertyNames()) {
XslParameterBean suXslParamBean = suXslParameters.get(propertyName);
if (suXslParamBean != null && !suXslParamBean.isOverridable())
throw new TransformerException(logHint + ": the XSL parameter " + propertyName
+ " was already set by the configuration and cannot be overridden.");
Object value = exchange.getInMessageProperty(propertyName);
transformer.setParameter(propertyName, value);
}
} catch (MessagingException e) {
if (logger.isLoggable(Level.WARNING)) {
String msg = logHint
+ ": IN message properties could not be read and passed as parameters to the transformer.";
logger.warning(msg);
}
}
return transformer;
}
/**
* Releases a transformer (for the transformer pool).
*
* @param transformer
* the transformer to release
*/
public void releaseTransformer(Transformer transformer) {
this.suTransformerResourcePool.release(transformer);
}
}