Announcement Announcement Module
Collapse
No announcement yet.
Strange XPath behaviour using xpath-transformer - tags lost Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Strange XPath behaviour using xpath-transformer - tags lost

    I'm just trying to understand the different behaviour between a xpath-splitter and a xpath-transformer

    I have a requirement where I need to extract the inner part of message (as the xml payload I need is wrapped in an another xml document that has an xs:any node)

    abbreviated example (with namespaces omitted)

    Code:
    <rootDoc>
        <transport>
        </transport>
        <payload>
    
              <!-- nested document starts here -->
              <document>
                  <name>myname</name>
                  <id>123456</id>
              </document>
    
        </payload>
    </rootDoc>
    So initially I thought 'xpath-transformer' would do the job. But the result is the Payload contains a String with the textual values of the nested document, but no tags are present around the data - just empty spaces where tags should be :-


    myname
    123456


    Code:
    	<int-xml:xpath-transformer id="xpath-transformer-extract-payload"
    		input-channel="testXpathChannel" 
    		output-channel="extractedPayloadChannel" 
    		xpath-expression-ref="xpathExtractPayload"
    		evaluation-type="STRING_RESULT"/> 
    
    	<int-xml:xpath-expression id="xpathExtractPayload"  
    		expression="/rootDoc/payload" 
    		namespace-map="namespaceMapSubmission" />
    So note: this is using
    evaluation-type="STRING_RESULT"

    however if instead I choose
    evaluation-type="NODE_RESULT"

    then the Payload result is of Type: DeferredElementNSImpl which if logged shows: [payload: null]
    which I assume is no good to the next step in my processing - which is an xml unmarshall.

    So given this was not working I instead tried an xpath-splitter :-

    Code:
    	<int-xml:xpath-splitter id="itemSplitterTest"
    	                       input-channel="testXpathChannel"
    	                       output-channel="extractedPayloadChannel">
    	    	<int-xml:xpath-expression expression="/rootDoc/payload" 
    							namespace-map="namespaceMapSubmission" />
    	</int-xml:xpath-splitter>
    This seems to work fine, the resulting payload is a String, however I'm not that happy with the solution as a splitter feels wrong - as I'm only splitting into 1 item, so it's really a transformation thats required.


    What I did deduce is that when a xpath-splitter is used the spring code will do a :-

    Code:
     transformer.transform(new DOMSource(nodeFromList), result);
    in the splitDocument() method of the XPathMessageSplitter which seems to do the trick.


    Any thoughts on what's going on ?
    Last edited by PeteTh; Jan 4th, 2013, 12:32 PM.

  • #2
    The xpath-transformer really only delegates to XPathExpression where the spec-compliant way of converting to a String result is to return the string *content* of the matched node(s). What I think you want is the string representation of the sub-document.

    One thing you can do is add a NodeMapper implementation to your xpath-transformer, with something along the lines of what the splitter does, e.g.:
    Code:
    Transformer transformer = TransformerFactory.newInstance().newTransformer();
    StringResult result = new StringResult();
    transformer.transform(new DOMSource(node), result);
    return result.toString();
    I'll ping the rest of the team and see if anyone has a better option to throw out there. Otherwise, perhaps this is worth a feature request in JIRA.

    Hope that helps.
    -Mark

    Comment


    • #3
      Thanks for your reply.

      For the moment I have continued with using my alternative solution the 'xpath-splitter', but this has led me to a further question, regarding catching exceptions in asynchronous processing :-

      All is good where the XML is as expected, but I am now trying to test the scenario where the XML does not contain the correct nested document structure. My use case here is I need to return a different message to another system.

      If the XML is badly formed I see a :-

      Code:
      at org.springframework.integration.xml.DefaultXmlPayloadConverter.convertToDocument(DefaultXmlPayloadConverter.java:73)
      	at org.springframework.integration.xml.splitter.XPathMessageSplitter.splitMessage(XPathMessageSplitter.java:109)
      	... 15 more
      Caused by: org.xml.sax.SAXParseException:
      How do I go about trapping this ?

      My processing starts with an jms backed channel (int-jms)


      Code:
      <int-jms:channel id="submission-jms" queue="submissionQueueDestination"/> 
      
      <int-xml:xpath-splitter id="itemSplitter" 
                             input-channel="submission-jms" 
                             output-channel="submission-extract">
          	<int-xml:xpath-expression expression="/rootDoc/payload" namespace-map="namespaceMapSubmission" />
      </int-xml:xpath-splitter>
      so this process is asynchronous.

      I'm aware that in some instances an errorChannel can be declared - but I can't see that this is possible for jms backed channel or xpath-splitter.

      So I have 2 questions :-
      1. How do I intercept the exception, so that I can put something on another channel to process the error flow. This is the equivalent to a try-catch to deal with certain scenarios.
      2. Where is the Exception going currently ?

      I don't really want to add validation prior to the xpath-splitter (like a XML validating Filter), as full validation of the xml is not necessary this early - that will happen later in the pipeline, and just to the nested message.

      This has made me realise I may get similar issues where I am using an 'unmarshalling-transformer' with Spring-oxm, as again here the XML could be bad - again I would need to intercept exceptions somehow - when asynchronous.

      Comment


      • #4
        1. Use a messge-driven-channel adapter instead of the jms-backed channel. If some other application is writing to the submissionQueueDestination, using a JMS-backed channel here is an anti-pattern in any case. If this same application is sending to the submission-jms channel, replace it with an outbound-channel-adapter and message-driven-channel-adapter - you can declare an error-channel on the mdca and handle the exception there.
        2. It is being thrown back to the message listener container in the jms-backed channel which is transactional by default, so you should see the message being resubmitted, unless the broker is configured to dead-letter it.

        Alternatively, if you prefer to keep the jms-backed channel, you can add a <service-activator/> after it that references a <gateway/>. Then add the splitter after the <gateway/>. The gateway can have an error-channel on it.

        This technique is often used to add a "try-catch" within a flow.

        Code:
        <int:service-activator input-channel="submission-jms" ref="errorHandlingGW" />
        
        <int:gateway id="errorHandlingGW" default-request-channel="toSplitter" error-channel="..." />

        Comment


        • #5
          Thanks for your suggestion, I have tried your alternate suggestion, so retaining the jms backed channel and adding <service-activator/> after it that references a <gateway/>. Then add the splitter after the <gateway/>. I have given the gateway an error-channel on it.

          I'm still getting some strange behaviour

          1) Firstly I seem to find defining the service activator like your suggestion caused start up errors, saying something along the lines of the activator can't find matching method on gateway.

          Code:
          Caused by:
          org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.integration.config.ServiceActivatorFactoryBean#6': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgume
          ntException: Found ambiguous parameter type [interface java.util.List] for method match: [public final void $Proxy89.processSubmission(org.springframework.integration.Message), public final boolean $Proxy89.removeAdvice(org.aopalliance.aop.Advice), p
          ublic final boolean $Proxy89.removeAdvisor(org.springframework.aop.Advisor), public final void $Proxy89.removeAdvisor(int) throws org.springframework.aop.framework.AopConfigException, public final java.lang.String $Proxy89.toProxyConfigString(), publ
          ic final boolean $Proxy89.isInterfaceProxied(java.lang.Class), public final void $Proxy89.setPreFiltered(boolean), public final void $Proxy89.setTargetSource(org.springframework.aop.TargetSource)]
          So I assumed you're example was just illustrative, so I used the same theory but defined a java class as the Service Activator (in front of the gateway), autowired the gateway interface into it, then in the implementation just delegated to the Gateway interface.

          I also had to give the Gateway a Java Service Interface as there is no return value, so I found it waa expecting (and didn't receive) a reply, as the default interface was RequestReplyExchanger, so we have:-

          Code:
          <int:service-activator id="errorHandlingActivator" 
          	 input-channel="submission-jms" ref="processSubmissionGatewayHandler" >
          </int:service-activator>
          
          <int:gateway id="errorHandlingGateway" 
          		default-request-channel="submission-gw"
          		service-interface="....ProcessTransactionGateway"  		 			 
          		error-channel="submissionErrorChannel">
           </int:gateway>	
          
          <int-xml:xpath-splitter id="itemSplitterTest"
          	                       input-channel="submission-gw"
          	                       output-channel="extractedPayloadChannel">
          	    	<int-xml:xpath-expression expression="/rootDoc/payload" 
          							namespace-map="namespaceMapSubmission" />
          </int-xml:xpath-splitter>
          Service Interface for Gateway

          Code:
          public interface ProcessTransactionGateway {
          
          	@Transactional
          	public void processSubmission(Message<String> message);
          }
          The issue now is that when an Exception occurs, the 'errorChannel' processes the exception, but the JMS transaction still seems to see the exception and rollbacks, so the message gets re-delivered by the JMS container. Where as the errorchannel and flow at the gateway is supposed to supress the exception. I think the exception is propagating back to the service activator in front of the gateway.

          The errorChannel flow does execute correctly, this goes to a Transformer' that transforms message, and puts the response onto a jms backed queue.

          Code:
          <int:transformer id="submissionToErrorReasonTransformer" 
          			input-channel="submissionErrorChannel"
                            	output-channel="ackPublishJMSChannel"
                            	ref="submissionErrorTransformer"/>
          
          <int-jms:channel id="ackPublishJMSChannel" queue="ackPublishDestination" 
               	transaction-manager="transactionManager"	concurrency="1-10"/>
          Any thoughts ?

          I also tried try-catch suppress in Service Activator but this isn't reached

          Code:
          	
          @Component("processSubmissionGatewayHandler")
          public class ProcessSubmissionGatewayHandler {
          
          	@Autowired
          	private ProcessTransactionGateway processTransactionGateway;
          
          
                  @ServiceActivator
          	@Transactional
          	public void processSubmission(Message<String> message) {
          
          
          		System.out.println(" #### ProcessSubmissionGatewayHandler");
          
          		try {
          			processTransactionGateway.processSubmission(message);
          		} catch (RuntimeException e) {
          			System.out.println(" #### RuntimeException caught in ProcessSubmissionGatewayHandler");
          			
          		}
          	}
          }
          Last edited by PeteTh; Jan 14th, 2013, 08:36 AM.

          Comment


          • #6
            Option A - Using jms backed channels with Gateway Error Handler

            Looking a bit closer I think that the exception (that has been purposely supressed) has caused the transaction context to be marked for rollback

            "javax.persistence.RollbackException: Transaction marked as rollbackOnly"

            So now I need to to somehow unset this somewhere.

            Option B - Using int-jms:outbound-channel-adapter with int-jms:message-driven-channel-adapter with errorChannel on latter

            I also tried this option, which does seem simpler as no gateway is necessary, nor service activator to delegate to gateway.

            However I have noticed for both options the Message (with Exception payload) getting passed to the errorChannel seems to lose it's Spring Integration Message Headers - they don't get copied to JMS and back, not sure why yet ? as I thought all get passed automatically if primitive types

            Comment


            • #7
              On the error channel, the ErrorMessage object has two properties cause and failedMessage - the headers are on the failedMessage.

              Comment


              • #8
                I have similar requirement, since iam new to spring i would like to know what is the best solution, i need to read the incoming XML payload request and based on the value of one element i need to call respective router method/class which then routes the message to respective end point. Any kind of help is appreciated.

                Comment


                • #9
                  Please don't revive old threads; please start a new thread with a clear explanation of what you need to do.

                  Comment

                  Working...
                  X