Announcement Announcement Module
Collapse
No announcement yet.
Dynamic Methods for Service Activator Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Dynamic Methods for Service Activator

    I have a Spring-managed Object that I would like to connect an input channel and have method-returns sent to an output channel. The Object has multiple operations, and I would like to reuse the input and output channels (for cross-cutting purposes).

    Is there a way I can define a generic Service Activator for this POJO using SpEL to resolve the method name ? Say something like -

    <service-activator id="exampleServiceActivator" input-channel="inChannel" output-channel = "outChannel" method="headers.methodName">
    <beans:bean class="org.foo.ExampleServiceActivator"/>
    </service-activator>


    Thanks for your time!
    Last edited by kunlenzo247; Oct 8th, 2011, 10:39 PM. Reason: additional info

  • #2
    SpEL expressions, including the method to invoke, are statically parsed during Spring initialization and then evaluated at runtime. Many SI components allow SpEL expressions to be evaluated using the message context. This is not the case for the 'method' attribute in your example. There are ways to do it using SpEL but not too practical IMO. However, this is a great use case for groovy scripting. I tested this using the 'cafe' SI sample application. The configuration:

    Code:
    <int:service-activator input-channel="inputChannel" output-channel="outputChannel">
         <int-groovy:script location="classpath:/dynamicBarista.groovy"> 
            <int-groovy:variable name="barista" ref="barista"/>
         </int-groovy:script>
    </int:service-activator>
    	
    <int:channel id="outputChannel"/>
    <bean id="barista" class="org.springframework.integration.samples.cafe.Barista"/>
    The groovy script:
    Code:
    barista."${headers['method-name']}"(payload)
    In the configuration, the script variable 'barista' is bound to the referenced Spring bean. This is in addition to the 'headers' and 'payload' variables which SI automatically binds. If you are not familiar with groovy syntax, we are invoking the method whose name is passed in the header 'method-name'. Note that org.springframework.integration.samples.cafe.Baris ta is a POJO, but through the magic of Groovy, inherits grooviness such as dynamic method invocation. Barista includes these methods:
    Code:
    Drink prepareColdDrink(OrderItem orderItem);
    Drink prepareHotDrink(OrderItem orderItem);
    Which may be invoked dynamically in Groovy using the method name directly, for example:
    Code:
    barista."prepareHotDrink"(orderItem);
    The final bit is that Groovy allows you to embed variables (delimited by ${}) in Strings, so the method name may be a variable, such as "${headers['method-name']}" (Note the single quotes on the header name are only required because I made an unfortunate naming choice, including a hyphen which Groovy normally interprets as a minus operator).

    The test:

    Code:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("/dynamic-method-config.xml")
    public class DynamicMethodTest {
    	@Autowired
    	MessageChannel inputChannel;
    	
    	@Autowired
    	SubscribableChannel outputChannel;
    	
    	@Test 
    	public void test(){
    		Order order = new Order(1);
    		OrderItem orderItem = new OrderItem(order, DrinkType.ESPRESSO,4,false);
    		Map<String,String> headers = Collections.singletonMap("method-name","prepareHotDrink");
    		Message<?>  msg = MessageBuilder.withPayload(orderItem)
    		.copyHeaders(headers)
    		.build();
    	
    		outputChannel.subscribe(new MessageHandler() {
    
    			@Override
    			public void handleMessage(Message<?> message) throws MessagingException {
    				assertNotNull(message.getPayload());
    				assertTrue(message.getPayload() instanceof Drink);
    				assertEquals(1,((Drink)message.getPayload()).getOrderNumber());
    			}});
    		
    	   inputChannel.send(msg);
    	}
    }
    BTW, In SI 2.1 you will be able to do this using any scripting language of your choice. And thanks for the question, I have been looking for good scripting use cases.

    One final note: If you try this example, remember that 'prepareHotDrink' sleeps for 5 seconds so don't attribute all that time to script overhead
    Last edited by dturanski; Oct 9th, 2011, 08:38 AM.

    Comment


    • #3
      Hello.
      There is other solution (sample from my project):
      HTML Code:
       <service-activator input-channel="inputChannel" output-channel="outputChannel"
                             expression="@beanFactory.getBean(headers.serviceIdentifier + 'Service').execute(headers, payload)"/>
      So, in my case I allocate for each service-method different Spring-Beans with one method execute.

      But anyway thank you, David: I didn't know about presented Groovy's ability.

      Cheers,
      Artem Bilan

      Comment


      • #4
        Thank you so much David! You saved me a lot of redundant bean definitions. In our use case we have about 12 of those POJOs with each having multiple methods to activate. That was also a good introduction to Groovy. We look forward to SI 2.1.

        Thanks Artem for your input too.

        Comment


        • #5
          David - Groovy 101 question here.

          Like I mentioned earlier, we have about 12 of those POJOs. Is it possible to define the groovy script inline ? If not, would that mean that each POJO will have a line entry in the script ?

          barista."${headers['method-name']}"(payload)
          waiter."${headers['method-name']}"(payload)
          ...


          Also, for the sake of reuse, can the same service-activator (input-output channels, with dynamic service and method resolution from header values) be used by all 12 ? Do you foresee any performance issue with this at all ?


          Thanks,
          Kunle.
          Last edited by kunlenzo247; Oct 9th, 2011, 12:56 PM.

          Comment


          • #6
            Hello,
            What's problem to define your service in message's headers also?

            Comment


            • #7
              Artem,

              I have the service defined in the header (service-id) as well but when I try the following line in the groovy script, it doesn't work -

              "${headers['service-id']}"."${headers['method-name']}"(payload)

              Maybe I am missing something ?

              Comment


              • #8
                header (service-id)
                Why do use define only bean Id What's problem to define bean reference?

                <header name="service" ref="myService"/>
                Or inside some java code using MessageBuilder.setHeader
                And note: now inline groovy script doesn't support definition of any script-variables

                Comment


                • #9
                  Kunle,

                  How does your application logic decide which service/method you need to invoke?
                  Is the result of the invocation always handled the same way?
                  Are all the service interfaces known today or are they expected to change?
                  Is there a natural way to route a message to an associated service?
                  How do you plan to test the message to service?
                  Do all 12 POJOs have the same interface?

                  I am not looking for an answer, but these questions should influence your design choices. Having a single endpoint that can invoke any method on any service, although very flexible, may not be the best approach. For example, you could route each message to one of12 service activators.

                  Re your performance question, In general script execution and groovy meta-programming will be slower than Java code. Whether this is an issue for your application is best determined by testing.

                  Having said that, Artem's suggestion works. You no longer need custom bind variables, so can have an inline script:

                  Code:
                  <int:chain input-channel="inputChannel" output-channel="outputChannel">
                  	<int:header-enricher>
                  		<int:header name="service" ref="barista" />
                  	</int:header-enricher>
                  	<int:service-activator>
                  		<int-groovy:script>headers['service']."${headers['method-name']}"(payload)</int-groovy:script>
                  	</int:service-activator>
                  </int:chain>
                  
                  <int:channel id="outputChannel" />
                  	
                  <bean id="barista" class="org.springframework.integration.samples.cafe.Barista" />
                  Last edited by dturanski; Oct 10th, 2011, 09:08 AM. Reason: Add code sample

                  Comment


                  • #10
                    Thanks David and Artem. I was able to test the sample code and it works fine. These should get me started.

                    Comment

                    Working...
                    X