2

I need to be able to add converters to a running Spring integration application context managed by an MVC application, in order to achieve that, I started by a simple POC where I load a new converter on runtime (this converter wasn't scanned at startup), this POC runs as a standalone java app and only includes spring integration libraries, here is the main class.

@Component
public class DynamicApplication {

    @Autowired
    private ConfigurableApplicationContext appContext;

    @Autowired
    private BeanFactory beanFactory;

    private Map<String, String> channelsMap = new HashMap<String, String>();

    private Map<String, String> inboundGWMap = new HashMap<String, String>();

    public static void main(String a[]) throws Exception{
        DynamicApplication dynamicApplication = new DynamicApplication();
        GenericXmlApplicationContext context = dynamicApplication.setupContext();

        DynamicApplication localDynamicApplication = context.getBean(DynamicApplication.class);

        //then we wait for input to add new classes
        final Scanner scanner = new Scanner(System.in);

        while (true) {

            final String input = scanner.nextLine();

            if("q".equals(input.trim())) {
                break;
            }
            else{
                //in the case we got a package definition to be scanned
                localDynamicApplication.addClassesFromAnnotatedPackage(input);
                }
        }
    }

    public  GenericXmlApplicationContext setupContext() {
        final GenericXmlApplicationContext context = new GenericXmlApplicationContext();
        context.load("configuration/inbound-grand-central-configuration.xml");
        context.registerShutdownHook();
        context.refresh();
        DynamicApplication dynamicApplication = context.getBean(DynamicApplication.class);
        for(int i = 0 ; i < 1 ; i++){
            TcpInboundGateway listener = dynamicApplication.createTcpInboundGateway("server" + i, 9877 + i);
            listener.start();
        }

        return context;
    }


}

a very simple service activator

package com.client.connector.inbound.config;

@MessageEndpoint
public class BussinesService {

    @ServiceActivator(inputChannel="toBSChannel")
    public String processIncomingMessage(String message) {
        System.out.println("Bussines logic: " + message);

        return "respondemos:" + message;

    }

}

and also a initial converter package com.client.connector.inbound.config;

@MessageEndpoint
public class ISOConverter {

    @Transformer(inputChannel="iSOConverterChannel", outputChannel="toBSChannel")
    public String iSOConverter(Object payload) throws Exception {
        System.out.println("ISO Conversion place");
        if (payload instanceof byte[]) {
            return new String((byte[]) payload);
        } 
        else if (payload instanceof char[]) {
            return new String((char[]) payload);
        } 
        else { 
            return payload.toString();
        } 
    }

    @Transformer(inputChannel="iSOConverterChannel", outputChannel="toBSChannel")
    public String iSOConverter(byte[] payload) throws Exception {
        System.out.println("ISO Conversion place");
        if (payload instanceof byte[]) {
            return new String((byte[]) payload);
        } 
        else { 
            return payload.toString();
        } 
    }

}

and the xml config file

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ..............................
    ............................">
    <description>Inbound Connectors common configuration file.
    </description>
    <int:annotation-config />
    <context:component-scan base-package="com.client.connector.inbound" />
</beans>

And then I have another converter which wasn't scanned at startup.

package com.client.inboundtest.newclass;

@MessageEndpoint
public class DinamicAddedConverter {

    @Transformer(inputChannel="iSOConverterChannel", outputChannel="toBSChannel")
    public String iSOConverter2(Object payload) throws Exception {
        System.out.println("ISO conversion from new class");
        if (payload instanceof byte[]) {
            return new String((byte[]) payload);
        } 
        else if (payload instanceof char[]) {
            return new String((char[]) payload);
        } 
        else { 
            return payload.toString();
        } 
    }

    @Transformer(inputChannel="iSOConverterChannel", outputChannel="toBSChannel")
    public String iSOConverter2(byte[] payload) throws Exception {
        System.out.println("ISO conversion from new class");
        if (payload instanceof byte[]) {
            return new String((byte[]) payload);
        } 
        else { 
            return payload.toString();
        } 
    }

}

When I start the program, and connect to port 9877 using telnet, and set the word test, I got this in the output of the java app

ISO Conversion place
Bussines logic: test
ISO Conversion place
Bussines logic: test

The, if I write the package com.client.inboundtest.newclass in the java console, it load the new converter, and shows the log

com.client.inboundtest.newclass
Added class com.client.inboundtest.newclass.DinamicAddedConverter
Nov 11, 2015 7:41:51 PM org.springframework.integration.endpoint.EventDrivenConsumer logComponentSubscriptionEvent
INFO: Adding {transformer:com.client.inboundtest.newclass.DinamicAddedConverter.iSOConverter2.transformer} as a subscriber to the 'iSOConverterChannel' channel
Nov 11, 2015 7:41:51 PM org.springframework.integration.channel.AbstractSubscribableChannel adjustCounterIfNecessary
INFO: Channel 'org.springframework.context.support.GenericXmlApplicationContext@4d9cad9d.iSOConverterChannel' has 3 subscriber(s).
Nov 11, 2015 7:41:51 PM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started com.client.inboundtest.newclass.DinamicAddedConverter.iSOConverter2.transformer
Nov 11, 2015 7:41:51 PM org.springframework.integration.endpoint.EventDrivenConsumer logComponentSubscriptionEvent
INFO: Adding {transformer} as a subscriber to the 'iSOConverterChannel' channel
Nov 11, 2015 7:41:51 PM org.springframework.integration.channel.AbstractSubscribableChannel adjustCounterIfNecessary
INFO: Channel 'org.springframework.context.support.GenericXmlApplicationContext@4d9cad9d.iSOConverterChannel' has 4 subscriber(s).
Nov 11, 2015 7:41:51 PM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started com.client.inboundtest.newclass.DinamicAddedConverter.iSOConverter2.transformer#2

and sending a new message to the same opened telnet connection gives this output on the java app console

ISO Conversion place
Bussines logic: test2
ISO Conversion place
Bussines logic: test2
ISO conversion from new class
Bussines logic: test2
ISO conversion from new class
Bussines logic: test2

Which is what I expect.

Then I tried to move my POC to a Spring MVC context I started seeing unexpected behaviors, the first non-expected thing was that the channels wasn't dynamically created as it was happening in the first POC, so I need to define them explicit on the context file like this

<beans ........">
    <context:component-scan base-package="main.package" />

    <int:annotation-config />

    <int:channel id="toBSChannel" />
    <int:channel id="iSOConverterChannel" />
    <int:channel id="restRequestChannel" />

    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix">
            <value>/WEB-INF/views/</value>
        </property>
        <property name="suffix">
            <value>.jsp</value>
        </property>
    </bean>
</beans>

What I did was to create a new controller wired to my original DynamicApplication and change the static call to setup by a @PostConstruct on the bean son the listeners are started.

When the app starts, it shows my serlvlets along with my listener on port 9877, and when I call the refresh servlet, it is showing the exact same output I got from the java app:

Added class com.client.inboundtest.newclass.ConverterA
Nov 11, 2015 7:55:59 PM org.springframework.integration.endpoint.EventDrivenConsumer logComponentSubscriptionEvent
INFO: Adding {transformer:com.client.inboundtest.newclass.ConverterA.iSOConverter.transformer} as a subscriber to the 'iSOConverterChannel' channel
Nov 11, 2015 7:55:59 PM org.springframework.integration.channel.AbstractSubscribableChannel adjustCounterIfNecessary
INFO: Channel 'org.springframework.web.context.WebApplicationContext:/InboundDinamicGC/dispatcher.iSOConverterChannel' has 7 subscriber(s).
Nov 11, 2015 7:55:59 PM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started com.client.inboundtest.newclass.ConverterA.iSOConverter.transformer
Nov 11, 2015 7:55:59 PM org.springframework.integration.endpoint.EventDrivenConsumer logComponentSubscriptionEvent
INFO: Adding {transformer} as a subscriber to the 'iSOConverterChannel' channel
Nov 11, 2015 7:55:59 PM org.springframework.integration.channel.AbstractSubscribableChannel adjustCounterIfNecessary
INFO: Channel 'org.springframework.web.context.WebApplicationContext:/InboundDinamicGC/dispatcher.iSOConverterChannel' has 8 subscriber(s).
Nov 11, 2015 7:55:59 PM org.springframework.integration.endpoint.AbstractEndpoint start
INFO: started com.client.inboundtest.newclass.ConverterA.iSOConverter.transformer#2

The problem is that the message never reaches the new converter as it happened on the plain java POC, any ideas?

Camilo Casadiego
  • 907
  • 3
  • 9
  • 33

1 Answers1

1

Please, take a look to similar question How to hook up a list of message driven adapters without actually writing each one out? and its discussion, plus links there.

What you should do is just create a child application context. Like you do you in your setupContext(), but push that MVC parent there and refresh(), of course.

Community
  • 1
  • 1
Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Excellent! Would you mind to share the final solution? We may utilize it as a sample or even some documentation improvement. As you see this feature becomes popular. I'd even say that we should consider some out-of-the-box component... Well, raise a Jira on the matter with you experience and we'll what to do further. – Artem Bilan Nov 12 '15 at 03:37
  • Hi, I just upload it to https://github.com/camicase82/camicasePOC ;), I still have one question, why the auto create channels feature that works in the simple java version doesn't work on the web version? I mean in the java version there was no need of on the context or else were, but in the web version, if I don't specify that, I got a bean not found. – Camilo Casadiego Nov 12 '15 at 14:31
  • I think it does. But in MVC case you have `parent/child` relationship. So, they are created in the child context and aren't visible for the parent from where you are going to send messages. Having them in the `parent` allows children to see them. – Artem Bilan Nov 12 '15 at 14:46
  • Hi, can you point me to the Jira site? I also will do further digging on the auto create channel thing, I just double check the app, and all the SI components are started on the same context. – Camilo Casadiego Nov 12 '15 at 15:41