Announcement Announcement Module
No announcement yet.
Gateways with multiple parameters Page Title Module
Move Remove Collapse
Conversation Detail Module
  • Filter
  • Time
  • Show
Clear All
new posts

  • Gateways with multiple parameters

    I'm looking at examples of gateway interfaces, and I see them having a single parameter. I'll lay out a short example:

    Gateway interface:
    public interface INodeStatusSender {
        public void sendStatus(NodeStatus nodeStatus);
    Spring config:
    <int:gateway id="gateway" service-interface="INodeStatusSender" default-request-channel="statusChannel"/>
    Sender code excerpt:
    INodeStatusSender sender = applicationContext.getBean("gateway", INodeStatusSender.class);
    sender.sendStatus(new NodeStatus("nodeName", "nodeStatus"));
    This works great and it's clean and easy. But I have a gateway interface this is more complex than this, with several gateway methods with multiple parameters. In my example above, I also covert the NodeStatus object (the parameter to sendStatus) to xml via an xml transformer. How can I cleanly do this using spring integration so that all the parameters are converted to an xml message?

    Example addition to my gateway interface:
    public interface INodeStatusSender {
        public void sendStatus(NodeStatus nodeStatus);
        public void sendStatusAndMore(NodeStatus nodeStatus, String id, int count);
    What I (think I) need is an interceptor/proxy that combines all the gateway service call parameters into one object, then pass control to an XML transformer, then to the gateway to send the object. Is there a way to do this through integration?

    I hope that make sense!



  • #2
    Scott, the default is to create a payload (for the message that will be sent downstream) from the single arg. However, you can add a @Payload annotation at method-level (or use a payload-expression in XML) in order to control how it's created.


    • #3
      I was going to post a link to the appropriate section of the reference manual, but I'm not seeing the right kind of example (so I just created this issue:

      However, this example from our unit tests (GatewayProxyMessageMappingTests) should give you a decent idea of the types of things you can do:
      public static interface TestGateway {
          void payloadAndHeaderMapWithoutAnnotations(String s, Map<String, Object> map);
          void payloadAndHeaderMapWithAnnotations(@Payload String s, @Headers Map<String, Object> map);
          void headerValuesAndPayloadWithAnnotations(@Header("k1") String x, @Payload String s, @Header("k2") String y);
          void mapOnly(Map<String, Object> map);
          void twoMapsAndOneAnnotatedWithPayload(@Payload Map<String, Object> payload, Map<String, Object> headers);
          @Payload("#args[0] + #args[1] + '!'")
          void payloadAnnotationAtMethodLevel(String a, String b);
          void payloadAnnotationAtMethodLevelUsingBeanResolver(String s);
          void payloadAnnotationWithExpression(@Payload("toUpperCase()") String s);
          void payloadAnnotationWithExpressionUsingBeanResolver(@Payload("@testBean.sum(#this)") String s);
          // invalid
          void twoMapsWithoutAnnotations(Map<String, Object> m1, Map<String, Object> m2);
          // invalid
          void twoPayloads(@Payload String s1, @Payload String s2);
          // invalid
          void payloadAndHeaderAnnotationsOnSameParameter(@Payload @Header("x") String s);
          // invalid
          void payloadAndHeadersAnnotationsOnSameParameter(@Payload @Headers Map<String, Object> map);
      Hope that helps.


      • #4

        It does help. It might not be an option for me to mark up the gateway interface with annotations, since the interface is generic and I would prefer to avoid coupling the interface with messaging, albeit loosely with the annotations. I'll investigate the payload-expression XML syntax as an alternative.

        I think that this could be handled cleanly in SI if there was a way to associate a handler containing a method that builds an instance of an aggregate object. Something like this maybe:

        HTML Code:
        <int:gateway payload-aggregator="" id="gateway" service-interface="INodeStatusSender" default-request-channel="eventChannel"/>
        The aggregator class would need to implement a method conforming to some name pattern (eg, parmsSendStatusAndMore, where parms is the mandatory prefix followed by the name of the gateway method being aggregated) with the same parameter types in the gateway interface. If the handler doesn't have a match for the invoked method, then no transformation occurs.

        It's a little clunky, but you get the idea.

        Thanks for the reply. I can work this out from what you've given me.



        • #5
          After looking at another reply you made to a poster on this forum, and combined with what you've told me here, this is what I came up with:

          HTML Code:
          <bean id="payloadAggregator" class="PayloadAggregator"/>
          <int:gateway id="gateway" service-interface="INodeStatusSender"
              <int:method name="sendStatusAndMore" payload-expression="@payloadAggregator.aggregateSendStatus(#args[0], #args[1])"/>
          Not bad at all! Thanks.


          • #6
            Cool. That's basically what I was going to suggest.


            • #7
              It's me again. I'm wondering if there is a way, or if anyone on the integration team would find it useful to specify in the SpEL syntax of the payload expression to pass all arguments to a payload transformation method?

              Like this:

              HTML Code:
              <int:gateway id="gateway" default-payload-expression="@payloadAggregator.aggregate(#args)"
              service-interface="INodeStatusSender" default-request-channel="eventChannel">
              That way I could write one method that iterates the arguments reflectively, or have SI pass an array of argument meta information for each parameter?

              This gets into the realm of something I could maybe contribute once accepted as a way to go forward.



              • #8
                That should *kind of* work now, I believe. The #args variable is a Map though and not an array (because we try to key each arg not only by index but also by the arg name if possible).


                • #9
                  Here's what I changed it to:

                  HTML Code:
                  <bean id="payloadAggregator" class="PayloadAggregator"/>
                  <int:gateway id="eventGateway" service-interface="INodeStatusSender" default-request-channel="eventChannel">
                      <int:method name="sendStatusAndMore" payload-expression="@payloadAggregator.aggregate(#args)"/>
                  public NodeStatus aggregate(Object[] parms) {
                      return new NodeStatus("sre0", "test");
                  At first I tried the parameter as a Map, but there was an error trying to call the method, so I changed the signature to match what Spring was looking for. The parms array is a collection of names only, so I don't have a reference to the parameter names that match. I'll debug and see if Spring attempted to load a map but couldn't get the meta information.


                  • #10
                    It looks like I cannot rely on the parameter names to exist. I tried adding -g to the java compiler but that either didn't work or didn't take (I'm compiling within my IDE). I can see that there is an attempt to load parameter names in Spring (3.1.0.RELEASE) but not it seems down the path to calling the gateway. Even if it did attempt to load the parameter names, I can see that they are not available from the compiled code. I can't rely on this method if the method names can be there sometimes and sometimes not, so I'll have to do this brute-force.


                    • #11
                      I just found an interesting solution to the problem of getting parameter names via reflection. Apparently in Java 7 parameter names may be finally available, but for now, there are solutions.

                      Using the library Paranamer, the names can be available, but only if certain measures are taken up front. The way that I settled upon is to add this to my project's pom.xml:

                      HTML Code:
                      Now when I execute this code:
                      Method method = MyClass.class.getMethod("myClassMethod", String.class, int.class, int.class, String.class);
                      Paranamer paranamer = new CachingParanamer();
                      String[] parameterNames = paranamer.lookupParameterNames(method);
                      parameterNames contains the names corresponding to the types specified in getMethod above. This doesn't help me for what I'm working on now, unless I can somehow be given the method object in my payload handler.


                      • #12
                        I've looked at the source code and there doesn't appear to be any way to get the parameter names in the current version of the code. Even if the parameter names were accessible in the MethodParameter class, the ParameterNameDiscoverer is never called on the path to invoking the payload method.

                        It would be nice if there a way to specify some other implementation of ParameterNameDiscoverer than LocalVariableTableParameterNameDiscoverer in GatewayMethodInboundMessageMapper.getMethodParamet erList(). I could easily plugin my own Paranamer implementation then--but I would still need to have the MethodParameter consult this to get the parameter name. And there is the issue too of getting this information in the payload method.

                        The reason I would like all this is so that I could handle payload aggregation generically. I'm in the situation where I have many gateway interfaces with many methods apiece, and I will have to meticulously account for each. And whenever a new method is added to any of these gateways, or parameters change, I will have to account for them. It's not the end of the world, but it's not as elegant as it could be.