/** * 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.oxf.common.OXFException; import org.orbeon.oxf.pipeline.api.PipelineContext; import org.orbeon.oxf.xml.XMLReceiver; import org.orbeon.oxf.processor.Processor; import org.orbeon.oxf.processor.ProcessorImpl; import org.orbeon.oxf.processor.generator.URLGenerator; import org.orbeon.oxf.processor.transformer.xslt.XSLTTransformer; import org.orbeon.oxf.resources.URLFactory; import org.orbeon.oxf.xml.*; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.XMLReader; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.sax.SAXSource; import java.net.URL; import java.util.Arrays; import java.util.List; /** * URI resolver used by transformation processors, including XSLT, XQuery, and XInclude. * * This resolver is able to handle "input:*" URLs as well as regular URLs. */ public class TransformerURIResolver implements URIResolver { private ProcessorImpl processor; private PipelineContext pipelineContext; private String prohibitedInput; private XMLParsing.ParserConfiguration parserConfiguration; private String mode; private boolean destroyPipelineContext; /** * Create a URI resolver. * * @param processor processor of which inputs will be read for "input:*" * @param pipelineContext pipeline context * @param prohibitedInput name of an input which triggers and exception if read (usually "data" or "config") * @param parserConfiguration parser configuration */ public TransformerURIResolver(ProcessorImpl processor, PipelineContext pipelineContext, String prohibitedInput, XMLParsing.ParserConfiguration parserConfiguration) { this(processor, pipelineContext, prohibitedInput, parserConfiguration, null); } /** * Create a URI resolver with a mode. * * @param processor processor of which inputs will be read for "input:*" * @param pipelineContext pipeline context * @param prohibitedInput name of an input which triggers and exception if read (usually "data" or "config") * @param parserConfiguration parser configuration * @param mode "xml", "html", "text" or "binary" */ public TransformerURIResolver(ProcessorImpl processor, PipelineContext pipelineContext, String prohibitedInput, XMLParsing.ParserConfiguration parserConfiguration, String mode) { this.processor = processor; this.pipelineContext = pipelineContext; this.prohibitedInput = prohibitedInput; this.parserConfiguration = parserConfiguration; this.mode = mode; } /** * Create a URI resolver. Use this constructor when using this from outside a processor. * * @param parserConfiguration parser configuration */ public TransformerURIResolver(XMLParsing.ParserConfiguration parserConfiguration) { this(null, new PipelineContext(), null, parserConfiguration); destroyPipelineContext = true; } public SAXSource resolve(String href, String base) throws TransformerException { // Create XML reader for URI final String systemId; XMLReader xmlReader; { final String inputName = ProcessorImpl.getProcessorInputSchemeInputName(href); if (prohibitedInput != null && prohibitedInput.equals(inputName)) { // Don't allow a prohibited input (usually INPUT_DATA) to be read this way. We do this to prevent that input to read twice from XSLT. throw new OXFException("Can't read '" + prohibitedInput + "' input. If you are calling this from XSLT, use a '/' expression in XPath instead."); } else if (inputName != null) { // Resolve to input of current processor if (processor == null) throw new OXFException("Can't read URL '" + href + "'."); xmlReader = new ProcessorOutputXMLReader(pipelineContext, processor.getInputByName(inputName).getOutput()); systemId = href; } else { // Resolve to regular URI final URL url = URLFactory.createURL(base, href); // NOTE: below, we disable use of the URLGenerator's local cache, so that we don't check validity // with HTTP and HTTPS. When would it make sense to use local caching? final String protocol = url.getProtocol(); final boolean cacheUseLocalCache = !(protocol.equals("http") || protocol.equals("https")); final Processor urlGenerator = new URLGenerator(url, null, false, null, false, false, parserConfiguration, true, mode, null, null, null, cacheUseLocalCache, false); xmlReader = new ProcessorOutputXMLReader(pipelineContext, urlGenerator.createOutput(ProcessorImpl.OUTPUT_DATA)); systemId = url.toExternalForm(); } } final URIResolverListener uriResolverListener = (URIResolverListener) pipelineContext.getAttribute(XSLTTransformer.XSLT_STYLESHEET_URI_LISTENER); if (uriResolverListener != null) { // Also send data to listener, if there is one // NOTE: As of 2010-06-25, this is only used by XSLTTransformer xmlReader = new ForwardingXMLReader(xmlReader) { private ContentHandler originalHandler; @Override public void setContentHandler(ContentHandler handler) { originalHandler = handler; // NOTE: We don't need to handle comments in the source stylesheets so we can use new SimpleForwardingXMLReceiver() below final List<XMLReceiver> xmlReceivers = Arrays.asList(uriResolverListener.getXMLReceiver(), new SimpleForwardingXMLReceiver(handler)); super.setContentHandler(new TeeXMLReceiver(xmlReceivers)); } @Override public ContentHandler getContentHandler() { return originalHandler; } }; } // Create SAX Source based on XML Reader return new SAXSource(xmlReader, new InputSource(systemId)); // set system id so that we can get it on the Source object from outside } protected ProcessorImpl getProcessor() { return processor; } protected PipelineContext getPipelineContext() { return pipelineContext; } /** * Make sure this resolver no longer keeps references to foreign objects. * * This is useful when a resolver is used for example by a Saxon PreparedStylesheet and we can't remove the * reference PreparedStylesheet has on the resolver. */ public void destroy() { if (destroyPipelineContext) pipelineContext.destroy(true); this.processor = null; this.pipelineContext = null; this.prohibitedInput = null; } }