/** * 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; } }