/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program 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 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.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.processor.transformer;
import org.orbeon.dom.Node;
import org.orbeon.oxf.cache.CacheKey;
import org.orbeon.oxf.cache.InternalCacheKey;
import org.orbeon.oxf.cache.ObjectCache;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.processor.ProcessorImpl;
import org.orbeon.oxf.processor.ProcessorInput;
import org.orbeon.oxf.processor.ProcessorInputOutputInfo;
import org.orbeon.oxf.processor.ProcessorOutput;
import org.orbeon.oxf.processor.impl.CacheableTransformerOutputImpl;
import org.orbeon.oxf.xml.*;
import org.xml.sax.InputSource;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TemplatesHandler;
import java.util.Arrays;
/**
* Generic TrAX transformer. The XSLTTransformer should be used for XSLT,
* because it handles includes and caching better.
*/
public class TraxTransformer extends ProcessorImpl {
public static final String TRAX_TRANSFORMER_CONFIG_NAMESPACE_URI = "http://orbeon.org/oxf/xml/trax-transformer-config";
private static final String INPUT_TRANSFORMER_CONFIG = "transformer";
private SAXTransformerFactory transformerFactory;
/**
* It is possible to derive from this class and set a schema for the config
* input. The default is no schema.
*/
public TraxTransformer() {
this(null);
}
public TraxTransformer(String schemaURI) {
addInputInfo(new ProcessorInputOutputInfo(INPUT_CONFIG, schemaURI));
addInputInfo(new ProcessorInputOutputInfo(INPUT_TRANSFORMER_CONFIG, TRAX_TRANSFORMER_CONFIG_NAMESPACE_URI));
addInputInfo(new ProcessorInputOutputInfo(INPUT_DATA));
addOutputInfo(new ProcessorInputOutputInfo(OUTPUT_DATA));
}
public void setTransformerFactory(SAXTransformerFactory transformerFactory) {
this.transformerFactory = transformerFactory;
}
@Override
public ProcessorOutput createOutput(String name) {
ProcessorOutput output = new CacheableTransformerOutputImpl(TraxTransformer.this, name) {
public void readImpl(org.orbeon.oxf.pipeline.api.PipelineContext context, XMLReceiver xmlReceiver) {
try {
// Inputs
final ProcessorInput configInput = getInputByName(INPUT_CONFIG);
final ProcessorInput typeInput = getInputByName(INPUT_TRANSFORMER_CONFIG);
final ProcessorInput dataInput = getInputByName(INPUT_DATA);
// Create combined key for (transformer type, config)
final CacheKey typeCacheKey = transformerFactory == null
? getInputKey(context, typeInput)
: new InternalCacheKey(TraxTransformer.this, "transformerType",
transformerFactory.getClass().getName());
final CacheKey configCacheKey = getInputKey(context, configInput);
final InternalCacheKey combinedInputCacheKey = typeCacheKey == null || configCacheKey == null ? null :
new InternalCacheKey(TraxTransformer.this,
Arrays.asList(typeCacheKey, configCacheKey));
// Create combined validity for (transformer type, config)
final Object typeValidity = transformerFactory == null
? getInputValidity(context, typeInput) : new Long(0);
final Object configValidity = getInputValidity(context, configInput);
final Object combinedInputValidity = typeValidity == null || configValidity == null ? null
: Arrays.asList(typeValidity, configValidity);
// Get templates from cache, or create it
Templates templates = (Templates) ObjectCache.instance().findValid
(combinedInputCacheKey, combinedInputValidity);
if (templates == null) {
// Create template handler
if (transformerFactory == null) {
final Node config = readCacheInputAsDOM4J(context, INPUT_TRANSFORMER_CONFIG);
final String transformerClass = XPathUtils.selectStringValueNormalize(config, "/config/class");
transformerFactory = TransformerUtils.getFactory(transformerClass);
}
// TODO: If we were to use setURIResolver(), be careful to null it afterwards so that no ref to TransformerURIResolver occurs
final TemplatesHandler templatesHandler = transformerFactory.newTemplatesHandler();
// Create template
readInputAsSAX(context, configInput, new ForwardingXMLReceiver(templatesHandler));
templates = templatesHandler.getTemplates();
// Save template in cache
ObjectCache.instance().add(combinedInputCacheKey, combinedInputValidity, templates);
}
// Perform transformation
final Transformer transformer = templates.newTransformer();
transformer.setURIResolver(new TransformerURIResolver(TraxTransformer.this, context, INPUT_DATA, XMLParsing.ParserConfiguration.PLAIN));
final SAXResult saxResult = new SAXResult(xmlReceiver);
saxResult.setLexicalHandler(xmlReceiver);
transformer.transform(new SAXSource(new ProcessorOutputXMLReader(context, dataInput.getOutput()), new InputSource()), saxResult);
} catch (Exception e) {
throw new OXFException(e);
}
}
};
addOutput(name, output);
return output;
}
}