Announcement Announcement Module
Collapse
No announcement yet.
XSLT Chain improvement Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • XSLT Chain improvement

    JIRA ISSUE : http://jira.springframework.org/browse/INT-675

    Spring Integration doesnt use enough "Deferred Transformations".
    There is no support for "really" chained XSL transformers. Current implementation uses temporary DOM Trees... which is bad.

    A chained transformation could handle this :

    XML Producer => XSLT => XSLT => FOP => PDF => OutputStream



    Here is a proof-of-concept for chained XSLT using SAX (ie: javax.xml.transform.sax.TransformerHandler).
    It uses the Russian nesting dolls concept. Each transformation is wrapped inside a DelayedResult object. Next xsl-transformers can handle those DelayedResult to produce another DelayedResult, and so on.

    The last transformer can use the DelayedResult to write the result directly in a StreamResult (for example in a ServletOutputStream).
    The SAX method is the fastest approach. There is no DOM Tree anywhere... only SAX events.

    I will post another example with a FOPTransformer.

    Sorry for my english.


    DelayedResult contains an XSL Transformation, and can write its result in the Result parameter. (Comes from my experience in Mule-ESB).
    Code:
    public interface DelayedResult {
    
    	void write(Result result) throws Exception;
    }
    SourceFactory to transform any Object in javax.xml.transform.Source.
    Code:
    public interface SourceFactory {
    
    	Source createSource(Object payload) throws Exception;
    }
    ResultFactory to transform any DelayedResult to an instance of targetClass. If targetClass is null, return the DelayedResult.
    Code:
    public interface ResultFactory {
    
    	Object createResult(Class<?> returnClass, DelayedResult delayedResult) throws Exception;
    }
    Here is the chained capable XSL transformer :
    Code:
    public class XsltPayloadTransformer implements Transformer, InitializingBean {
        
        private static final Log log = LogFactory.getLog(XsltPayloadTransformer.class);
    
        private Templates templates;
        private SAXTransformerFactory transformerFactory;
        private Class<?> returnClass;
        private Resource xslResource;
        private ResultFactory resultFactory = new DefaultResultFactory();
        private SourceFactory sourceFactory = new DefaultSourceFactory();
        
        public void setReturnClass(Class<?> returnClass) {
            this.returnClass = returnClass;
        }
        
        public void setXslResource(Resource xslResource) {
            this.xslResource = xslResource;
        }
        
        public void setTransformerFactory(SAXTransformerFactory transformerFactory) {
            this.transformerFactory = transformerFactory;
        }
        
        @Override
        public void afterPropertiesSet() throws Exception {
            Assert.notNull(xslResource, "xslResource property required");
            if(this.transformerFactory==null) {
                this.transformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
            }
            this.templates = transformerFactory.newTemplates(new StreamSource(xslResource.getInputStream()));
        }
        
        @Override
        public Message<?> transform(Message<?> message) {
            return MessageBuilder.withPayload(doTransform(message))
                    .copyHeaders(message.getHeaders())
                    .setHeader("Content-Type", "text/xml").build();
        }
        
        protected Object doTransform(Message<?> message) {
            try {
                return resultFactory.createResult(returnClass, doCreateDelayedResult(message.getPayload()));
            }
            catch (Exception e) {
                throw new MessageTransformationException(message, "Failed to transform message payload", e);
            }
        }
        
        protected DelayedResult doCreateDelayedResult(final Object payload) {
            return new DelayedResult() {
                public void write(Result targetResult) throws Exception {
                    if(payload instanceof DelayedResult) {
                        log.info("Executing chained XSL transformation with template ["+xslResource.getFilename()+"]");
                        doTransformDelayedResult((DelayedResult) payload, targetResult);
                    } else {
                        log.info("Executing source XSL transformation with template ["+xslResource.getFilename()+"]");
                        doTransformSource(sourceFactory.createSource(payload), targetResult);
                    }
                }
            };
        }
        
        protected void doTransformSource(Source source, Result result) throws Exception {
            templates.newTransformer().transform(source, result);
        }
        
        protected void doTransformDelayedResult(DelayedResult delayedResult, Result result) throws Exception {
            TransformerHandler handler = transformerFactory.newTransformerHandler(templates);
            handler.setResult(result);
            delayedResult.write(new SAXResult(handler));
        }
    }
    Sample use :
    HTML Code:
    <chain id="exampleChain" input-channel="exampleChannel">
    	<transformer ref="xsl1" />
    	<transformer ref="xsl2" />
    	<transformer ref="xsl3" />
    </chain>
    
    <beans:bean id="xsl1" class="......XsltPayloadTransformer">
    	<beans:property name="xslResource" value="classpath:/test1.xsl" />
    </beans:bean>
    <beans:bean id="xsl2" class="......XsltPayloadTransformer">
    	<beans:property name="xslResource" value="classpath:/test2.xsl" />
    </beans:bean>
    <beans:bean id="xsl3" class="......XsltPayloadTransformer">
    	<beans:property name="xslResource" value="classpath:/test3.xsl" />
    </beans:bean>
    Last edited by bugsan; Jun 18th, 2009, 03:32 PM.

  • #2
    FOPTransformer example :

    HTML Code:
    <chain id="exampleChain" input-channel="exampleChannel">
        <transformer ref="xsl1" />
        <transformer ref="xsl2" />
        <transformer ref="fop" />
    </chain>
    
    <beans:bean id="xsl1" class=".....XsltPayloadTransformer" />
    <beans:bean id="xsl2" class=".....XsltPayloadTransformer" />
    <beans:bean id="fop" class="......FOPTransformer" />
    OutputHandler can write a result in an OutputStream. You can use it in your Web-Controller to write the FOP stream.
    Code:
    public interface OutputHandler {
    
    	void write(OutputStream out) throws Exception;
    }
    Code:
    public class FOPTransformer implements Transformer {
        
        private static final Log log = LogFactory.getLog(XsltPayloadTransformer.class);
    
        private FopFactory fopFactory = FopFactory.newInstance();
        private TransformerFactory transformerFactory = TransformerFactory.newInstance();
        private SourceFactory sourceFactory = new DefaultSourceFactory();
        
        @Override
        public Message<?> transform(Message<?> message) {
            return MessageBuilder.withPayload(doCreateFopHandler(message.getPayload()))
                .copyHeaders(message.getHeaders())
                .setHeader("Content-Type", "application/pdf").build();
        }
        
        protected OutputHandler doCreateFopHandler(final Object payload) {
            return new OutputHandler() {
                public void write(OutputStream out) throws Exception {
                    if(payload instanceof DelayedResult) {
                        log.info("Executing chained FOP transformation");
                        ((DelayedResult) payload).write(getFopResult(out));
                    } else {
                        log.info("Executing source FOP transformation");
                        doTransformSource(sourceFactory.createSource(payload), getFopResult(out));
                    }
                }
            };
        }
        
        protected Result getFopResult(OutputStream out) throws FOPException {
            return new SAXResult(fopFactory.newFop(MimeConstants.MIME_PDF, out).getDefaultHandler());
        }
        
        protected void doTransformSource(Source source, Result result) throws TransformerException {
            transformerFactory.newTransformer().transform(source, result);
        }
    }

    Comment


    • #3
      Thank you for letting us profit from your experience. Highly appreciated.

      After sharing your first impression, I've toyed with the idea of using Source/Result as payloads, but there are some downsides to this.

      The main problem is that if you send a Source or Result over an async channel there is a good chance the underlying stream is closed when the message is picked up. This causes an abstraction leak for the downstream endpoint.

      In my interpretation Spring Integration's xml support is not intended to support streams, if you are looking to do that it is perfectly possible (as you demonstrated), but it is not something that I would advise in general. So only in the particular example of a chain, where you *cannot* use anything but direct channels it would be reasonably safe.

      So in the end I'm happy with the decision that Jonas made here. What I would hate to see is Source payloads being used throughout the message flow.

      I'm not arguing that your suggestion should not make it into the framework, I'm just saying that for now I'm happy it wasn't added prematurely.

      Could you create a JIRA for this and post a link it both ways?

      Comment


      • #4
        Yes currently the xml support is intended to be simple to use, for example Document in Document out or String in String out with XSLT in the middle. This does mean that performance and memory utilisation is not optimal and support for very large documents using streaming is not there yet.

        I like your idea bugsan but maybe as an xslt-pipleine configured as below allowing easy control over the output type which could be a StreamResult, Document or String.

        Code:
        <xslt-pipeline input-channel='in' outputType='StreamResult'>
           <xslt-transformer ref='customTransformer' />
           <xslt-transformer  xsl-resource='mytest.xsl' />
        </xslt-pipeline>

        Comment


        • #5
          Document in, non-XML string out?

          Hi:

          It looks like the JIRA issue associated with the forum thread is still open. What do you recommend as a solution to my problem? I have two XSLT transformations, though not in a single chain. In between there is an xpath-splitter and an xpath-header-enricher.

          The proprietary source of the initial XML parses to a Document, and that's the messages payload produced by my inbound-channel-adapter. I figured keeping the payload this type as long a possible would be more efficient to avoid conversion from/to strings etc.

          But, the second and last xslt-transformer generates non-XML (<xsl:output method="text"/>). But, Spring Integration insists on placing a Document in the output message payload. It seems that perhaps that's the cause of the HIERARCHY_REQUEST_ERR I've been getting.

          If I dumb down the stylesheet and just have it output hardcoded valid XML (though the XSL output method is still text), then the HIERARCHY_REQUEST_ERR goes away, and I get the undesired Document payload type. Just for grins I set result-type="StringResult", but that yields;

          Code:
          org.springframework.integration.MessagingException: Document to Document conversion requires a DOMResult-producing ResultFactory implementation.
          I did not really expect that to do what I wanted.... Anyway, Jonas, you previously said "Document in Document out or String in String out". Do I have to place my own service in front of this last xslt-transformer and serialize the DOM to a string, and then I'll get a string output, which will allow for non-XML?

          I can do that, but it sounds pretty inefficient though. I'm guessing the transformer will parse my string back to a DOM to use as the source of the transformation.

          Thanks for any thoughts on this. BTW, I'm using Spring integration 2.0.0.M7.

          Cheers,

          Jeff

          Comment


          • #6
            Serializing the Document to a string does solve my problem alright. But, I'm still concerned about the overhead.

            Cheers,

            Jeff

            Comment


            • #7
              The namespace for the XSL transformer allows the specification of a result-factory and a result-transformer. Providing custom implementations of these should give you the control you want by providing a ResultFactory implementation which produces a StringResult regardless of the inbound payload. You can then use an instance of org.springframework.integration.xml.transformer.Re sultTransformer to convert the result before a message is created. There is already a built in transformer that should do what you want. Set result-type="StringResult" to get an appropriate transformer created.

              Hope that helps

              Jonas

              Comment

              Working...
              X