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