package org.tizzit.cocoon.generic.transformation;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import net.sf.saxon.om.NamespaceConstant;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.configuration.Settings;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.transformation.AbstractTransformer;
import org.apache.commons.io.IOUtils;
import org.apache.excalibur.source.Source;
import org.apache.excalibur.source.SourceValidity;
import org.apache.log4j.Logger;
import org.tizzit.cocoon.generic.helper.ExceptionHelper;
import org.tizzit.util.xml.DOMHelper;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* @author skulawik
* @author <a href="mailto:eduard.siebert@juwimm.com">Eduard Siebert</a>
* company Juwi MacMillan Group GmbH, Walsrode, Germany
* @version $Id$
* @since tizzit-cocoon-components 02.11.2009
*/
public class ContentIncludeTransformer extends AbstractTransformer implements Serviceable {// implements CacheableProcessingComponent {
private static Logger log = Logger.getLogger(ContentIncludeTransformer.class);
public static final String NAMESPACE_CONTENT_INCLUDE_DEPRECATED = "http://cocoon.juwimm.com/contentInclude/1.0";
public static final String NAMESPACE_CONTENT_INCLUDE = "http://cocoon.tizzit.org/contentInclude/1.0";
public static final String CONTENT_INCLUDE_ELEMENT = "contentInclude";
private static final String PARAM_NAME_ENCODING = "encoding";
private SourceResolver resolver;
private HashMap<String, String> namespaces = new HashMap<String, String>();
private long startParseTime = 0L;
/**
* The encoding.
* <p>
* A parameter could be used, to set the encoding <code><map:parameter name="encoding" value="UTF-8"/></code>.<br/>
* Default value: <code>org.apache.cocoon.containerencoding</code>.<br/>
* Fallback value if an error occurred: <code>UTF-8</code>
* </p>
* <p>
* <b>Please make sure, that you use the same encoding in your tidy.properties (<code>char-encoding=utf8</code>).</b>
* </p>
*/
private String defaultEncoding = "UTF-8";
/**
* The source.
*/
private String source;
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
super.startPrefixMapping(prefix, uri);
if (!namespaces.containsKey(prefix)) {
namespaces.put(prefix, uri);
}
}
@Override
public void startDocument() throws SAXException {
this.startParseTime = System.currentTimeMillis();
contentHandler.startDocument();
}
@Override
public void endDocument() throws SAXException {
contentHandler.endDocument();
if (log.isDebugEnabled() && this.source != null) log.debug("'" + this.source + "'" + " Processed by " + this.getClass().getSimpleName() + " in " + (System.currentTimeMillis() - this.startParseTime) + " ms.");
}
public void setResolver(SourceResolver resolver) {
this.resolver = resolver;
}
/* (non-Javadoc)
* @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver, java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
*/
@SuppressWarnings("unchecked")
public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException {
this.resolver = resolver;
try {
this.defaultEncoding = par.getParameter(PARAM_NAME_ENCODING);
} catch (Exception exe) {
}
}
/* (non-Javadoc)
* @see org.apache.cocoon.xml.AbstractXMLPipe#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
@SuppressWarnings("unchecked")
@Override
public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
contentHandler.startElement(uri, localName, qName, attrs);
if (CONTENT_INCLUDE_ELEMENT.equalsIgnoreCase(localName) && (NAMESPACE_CONTENT_INCLUDE.equals(uri) || NAMESPACE_CONTENT_INCLUDE_DEPRECATED.equals(uri))) {
if (NAMESPACE_CONTENT_INCLUDE_DEPRECATED.equals(uri)) {
log.warn("The namespace '" + NAMESPACE_CONTENT_INCLUDE_DEPRECATED + "' for '" + CONTENT_INCLUDE_ELEMENT + "' is deprecated! Please use '" + NAMESPACE_CONTENT_INCLUDE + "' instead!");
}
String src = attrs.getValue("src"); //$NON-NLS-1$
this.source = src;
Source inputSource = null;
try {
inputSource = resolver.resolveURI(src);
String xpathStr = attrs.getValue("xpath"); //$NON-NLS-1$
InputStream stream = inputSource.getInputStream();
try {
if ("/*".equals(xpathStr) || "/".equals(xpathStr) || "".equals(xpathStr) || xpathStr == null) {
String xml = IOUtils.toString(stream, this.defaultEncoding);
DOMHelper.string2sax(xml, contentHandler, this.defaultEncoding);
} else {
Document doc = null;
try {
doc = DOMHelper.inputstream2Dom(stream);
} catch (Exception exe) {
log.warn("Could not create DOM for source '" + this.source + "': " + exe.getMessage(), exe);
ExceptionHelper.throwableToSAX(exe, this.contentHandler, "dev");
}
if (doc != null) {
System.setProperty("javax.xml.xpath.XPathFactory:" + NamespaceConstant.OBJECT_MODEL_SAXON, "net.sf.saxon.xpath.XPathFactoryImpl");
XPath xpath = XPathFactory.newInstance(NamespaceConstant.OBJECT_MODEL_SAXON).newXPath();
xpath.setNamespaceContext(new LocalNamespaceContext());
NodeList nl = (NodeList) xpath.evaluate(xpathStr, doc, XPathConstants.NODESET);
for (int i = 0; i < nl.getLength(); i++) {
DOMHelper.node2sax(nl.item(i), new DOMHelper.OmitXmlDeclarationContentHandler(contentHandler));
}
}
}
} catch (Exception e) {
log.warn("Error executing xpath for source '" + src + "' with xpath '" + xpathStr + "': " + e.getMessage(), e);
ExceptionHelper.throwableToSAX(e, this.contentHandler, "dev");
} finally {
IOUtils.closeQuietly(stream);
resolver.release(inputSource);
//if (log.isDebugEnabled()) log.debug("'" + src + "'" + " Processed by " + this.getClass().getSimpleName() + " in " + (System.currentTimeMillis() - startParseTime) + " ms.");
}
} catch (Exception se) {
log.warn("Error resolving source '" + this.source + "': " + se.getMessage(), se);
ExceptionHelper.throwableToSAX(se, this.contentHandler, "dev");
}
}
}
/* (non-Javadoc)
* @see org.apache.cocoon.xml.AbstractXMLPipe#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
contentHandler.endElement(uri, localName, qName);
}
/**
* Set the default encoding. This will be overided if an encoding parameter is set.
* This is mainly useful together with Spring
* bean inheritance.
*
* @param defaultEncoding the defaultEncoding to set
* @see de.juwimm.cocoon.components.transformation.ContentIncludeTransformer#encoding
*/
public void setDefaultEncoding(String defaultEncoding) {
this.defaultEncoding = defaultEncoding;
}
/**
* @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
* @deprecated use property injection instead
*/
@Deprecated
public void service(ServiceManager manager) throws ServiceException {
final Settings settings = (Settings) manager.lookup(Settings.ROLE);
String defaultEncoding = settings.getContainerEncoding();
if (defaultEncoding != null) {
this.defaultEncoding = defaultEncoding;
}
manager.release(settings);
}
/**
* <p>Return the caching key associated with this transformation.</p>
*
* <p>When including <code>cocoon://</code> sources with dynamic
* content depending on environment (request parameters, session attributes,
* etc), it makes sense to provide such environment values to the transformer
* to be included into the key using <code>key</code> sitemap parameter.</p>
*
* @see CacheableProcessingComponent#getKey()
*/
/*public Serializable getKey() {
* In case of including "cocoon://" or other dynamic sources key
* ideally has to include ProcessingPipelineKey of the included
* "cocoon://" sources, but it's not possible as at this time
* we don't know yet which sources will get included into the
* response.
*
* Hence, javadoc recommends providing key using sitemap parameter.
return key == null? "I": "I" + key;
}*/
/**
* <p>Generate (or return) the {@link SourceValidity} instance used to
* possibly validate cached generations.</p>
*
* @return a <b>non null</b> {@link SourceValidity}.
* @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity()
*/
/*public SourceValidity getValidity() {
if (validity == null) {
validity = new MultiSourceValidity(resolver, -1);
}
return validity;
}*/
private class LocalNamespaceContext implements NamespaceContext {
public String getNamespaceURI(String prefix) {
String retVal = null;
if (namespaces.containsKey(prefix)) {
retVal = namespaces.get(prefix);
}
return retVal;
}
public String getPrefix(String namespaceURI) {
return null;
}
public Iterator<String> getPrefixes(String namespaceURI) {
return null;
}
}
}