package org.docbag.template.transformer.xslt; import java.util.Date; import javax.xml.transform.*; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.docbag.Context; import org.docbag.DefaultContext; import org.docbag.DocumentCreatorException; import org.docbag.creator.fop.ClasspathResourceURIResolver; import org.docbag.stream.MemoryInputStream; import org.docbag.template.DocumentTemplateStream; import org.docbag.template.MemoryTemplateStream; import org.docbag.template.transformer.TemplateTransformer; import org.docbag.template.transformer.content.xml.ContentHandlerFactory; import org.docbag.template.transformer.content.xml.TemplateContentHandler; /** * Performs XSLT transformation on {@link DocumentTemplateStream} producing another {@link DocumentTemplateStream}. * <p/> * <p>It delegates the SAX events to the {@link TemplateContentHandler} instance which by default is * created with {@link DynamicContentHandlerFactory}. If there is no need for resolving dynamic content, the default * behavior can be overridden by replacing the {@link ContentHandlerFactory}</p> * <p/> * <p>This class is thread safe and can be reused for different transformations by many threads.</p> * <p/> * <p>However, it caches and stores XSLT template, so if there is a need for different XSLT transformations, * more instances of XSLTTemplateTransformer have to be created, each per one transformation.</p> * * @author Jakub Torbicki * @see org.docbag.template.transformer.content.xml.XMLDynamicContentHandler */ public class XSLTTemplateTransformer implements TemplateTransformer<DocumentTemplateStream> { private TransformerFactory tFactory; private final DocumentTemplateStream html2foTemplateStream; private final ContentHandlerFactory<String> contentHandlerFactory; private volatile Templates cachedTransformationTemplate; public XSLTTemplateTransformer(DocumentTemplateStream html2foTemplateStream) { this(html2foTemplateStream, new DynamicContentHandlerFactory()); } public XSLTTemplateTransformer(DocumentTemplateStream html2foTemplateStream, ContentHandlerFactory<String> contentHandlerFactory) { this.html2foTemplateStream = html2foTemplateStream; this.contentHandlerFactory = contentHandlerFactory; configure(); } public DocumentTemplateStream transform(DocumentTemplateStream templateStream) { return transform(templateStream, new DefaultContext()); } public DocumentTemplateStream transform(DocumentTemplateStream templateStream, Context context) { if (templateStream == null) { throw new NullPointerException("DocumentTemplateStream can't be null"); } try { Source xsltSrc = new StreamSource(html2foTemplateStream.getStream()); Source src = new StreamSource(templateStream.getStream()); String result = performTransformation(src, xsltSrc, context); return new MemoryTemplateStream(new MemoryInputStream(result.getBytes()), generateName(templateStream, context)); } catch (TransformerConfigurationException e) { throw new DocumentCreatorException("Transformation error: " + e.getLocalizedMessage(), e); } catch (TransformerException e) { throw new DocumentCreatorException("Transformation error: " + e.getLocalizedMessage(), e); } } /** * Do the actual transformation. * <p/> * <p>If there is no {@link TemplateContentHandler} set, store result directly in a {@link StreamResult}. * Otherwise get the result from {@link TemplateContentHandler}</p> * * @param src Template to be transformed * @param xsltSrc XSLT stylesheet * @param context {@link Context} object * @return transformation result * @throws TransformerException */ private String performTransformation(Source src, Source xsltSrc, Context context) throws TransformerException { Templates templates = getCachedOrCreate(xsltSrc); TemplateContentHandler<String> contentHandler = contentHandlerFactory.getContentHandler(context); Transformer transformer = templates.newTransformer(); if (contentHandler == null) { StreamResult stream = new StreamResult(); transformer.transform(src, stream); return stream.getWriter().toString(); } else { transformer.transform(src, new SAXResult(contentHandler)); return contentHandler.getOutput(); } } private String generateName(DocumentTemplateStream templateStream, Context context) { return "XSLTTemplateTransformer{source='" + templateStream.getName() + "', " + "xslt='" + html2foTemplateStream.getName() + "', " + "context='" + context.toString() + "', " + "at'=" + new Date() + "'}"; } private Templates getCachedOrCreate(Source xsltSrc) throws TransformerConfigurationException { Templates templates = cachedTransformationTemplate; if (templates == null) { synchronized (this) { templates = cachedTransformationTemplate; if (templates == null) { templates = tFactory.newTemplates(xsltSrc); cachedTransformationTemplate = templates; } } } return cachedTransformationTemplate; } private void configure() { // System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); tFactory = TransformerFactory.newInstance(); tFactory.setURIResolver(new ClasspathResourceURIResolver()); } public String toString() { return "XSLTDynamicTemplateTransformer{" + "html2foTemplateStream=" + html2foTemplateStream + '}'; } }