In Java Spring Integration, can a transformer element include router functionality? - java

I need a component that:
Receives a message, enacts a transformation on both the payload and header of the message (so far it acts like a transformer).
Then, based on the values passed in on the header route to an appropriate channel (acting like a header-router).
I'd like to configure this purely using annotations in java rather than XML, but I'll absolutely take what I can get in that regard. If someone knows the XML solution please pass it along.
If it helps, my use case is that I want the transformation that the transformer enacts on a message payload to be dependent on a custom loaded header value. I also want the channel that is used to sent the message from the transformer to be dependent on the header value.
I am aware of the solution that involves multiple transformers, one per transformation type as defined in the header value, and then a router. I'm trying to avoid this solution and only use at most a single router and single transformer.
Thank you for your help.

In Spring Integration channels act as any other beans. You could use a service activator to invoke a method on any bean. That bean could have the required channels injected. You could use the #Qualifier annotation to select, which channel should be injected, or just autowire a Map<String, MessageChannel> wich would get indexed by the bean name of the channel. It could pass transformed messages to those channels.
A Spring Boot app:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.config.EnableIntegration;
#SpringBootApplication
#EnableIntegration
#ImportResource("classpath:int.xml")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
The gateway interface:
package demo;
public interface MyGateway {
public void send(Object o);
}
The service to be invoked:
package demo;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.stereotype.Service;
#Service
public class MyService {
private final Map<String, MessageChannel> channels;
#Autowired
public MyService(Map<String, MessageChannel> channels) {
super();
this.channels = channels;
}
public void transformAndRoute(Message<?> in) {
// do your business logic here
Message<?> out = MessageBuilder.fromMessage(in).build();
// if(something)...
channels.get("fooChannel").send(out);
}
}
Integration config:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd">
<int:gateway id="myGateway" service-interface="demo.MyGateway"
default-request-channel="inChannel" />
<int:service-activator input-channel="inChannel"
ref="myService" method="transformAndRoute" />
<int:channel id="inChannel" />
<int:logging-channel-adapter id="fooChannel" level="INFO" log-full-message="true"/>
</beans>
And finally a simple integration test:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=DemoApplication.class)
public class MyGatewayIT {
#Autowired
MyGateway myGateway;
#Test
public void test() {
myGateway.send(new Object());
// do your assertions here
}
}
Output:
14:17:19.733 [main] DEBUG o.s.i.handler.LoggingHandler - org.springframework.integration.handler.LoggingHandler#0 received message: GenericMessage [payload=java.lang.Object#6f2cfcc2, headers={id=6791344c-07b4-d420-0d17-e2344f4bf15b, timestamp=1437826639733}]
BUT
The main benefit of developing message based systems is that you create your application out of small, loosely coupled components that are easy to test, reuse and change. Creating a component that plays two roles in a process brakes that rule. Additionally your code is realy tied to Spring Integration if you create it as above. What you could do instead is create a couple of components that each have a single responsibility, and then configure them to act in a chain.
What you could do is create a transformer that modifies the payload and headers of the message. That transformer would encapsulate your business logic and would set a header (say, myRoutingHeader) that can be later used in routing. It would probably be even better to have a transformer for business logic, and a header enricher for adding the header. But let's assume that you are doing it in a single class. Define that class as a bean (say myTransformer). Then in your config :
<int:channel id="inChannel/>
<!-- if performance is important consider using a SpEL expression to
invoke your method instead as they can be configured to be compiled -->
<int:transformer ref="myTransformer" input-channel="inChannel"
method="transform" output-channel="routingChannel"/>
<int:channel id="routingChannel/>
<int:header-value-router input-channel="routingChannel" header-name="myRoutingHeader">
<int:mapping value="foo" channel="fooChannel" />
<int:mapping value="bar" channel="barChannel" />
</int:header-value-router>

sounds like chaining will do it for you
<chain input-chanel="source" output-channel="routing-channel">
<transform/>
<!-- any enrichment process or intermediate process can go here -->
</chain>

Related

Spring Boot and Spring Integration. Problems

I have a SpringBoot application. I have a dedicated server, where I shall read data by the HTTP GET requests. I configured the http-outbound-config.xml file for Spring Integration module.
When I run the following code, everything is fine:
http-outbound-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-http="http://www.springframework.org/schema/integration/http"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/http https://www.springframework.org/schema/integration/http/spring-integration-http.xsd">
<int:channel id="requestChannel"/>
<int:channel id="replyChannel">
<int:queue capacity='10'/>
</int:channel>
<int-http:outbound-gateway id="outboundGateway"
request-channel="requestChannel"
url="http://server/API.jsp?id=1"
http-method="GET"
expected-response-type="java.lang.String"
charset="UTF-8"
reply-channel="replyChannel"/>
</beans>
Main Application Class:
package test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
import ru.eco.products.waste.egr.Integration;
#SpringBootApplication
#ImportResource("/META-INF/spring/integration/http-outbound-config.xml")
public class Application {
public static void main(String[] args) {
Integration integration = new Integration();
integration.start();
SpringApplication.run(WasteWebClientApplication.class,
args
);
}
}
Integration class:
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
public void start() {
ClassPathXmlApplicationContext
context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml");
context.start();
MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class);
PollableChannel replyChannel = context.getBean("replyChannel", PollableChannel.class);
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
BUT, when I try to Autowire MessageChannel and PollableChannel, I receive a null pointer exception.
Integration class(not working example):
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
#Component
#Configuration
public class Integration {
#Autowired
#Qualifier("requestChannel")
MessageChannel requestChannel;
#Autowired
#Qualifier("replyChannel")
PollableChannel replyChannel;
public void start() {
Message<?> message = MessageBuilder.withPayload("").build();
requestChannel.send(message);
Message<?> receivedMsg = replyChannel.receive();
System.out.println("RESULT IS : " + receivedMsg.getPayload());
}
}
Question 1: Why Autowiring is not working?
Question 2: What is the best way to get data from dedicated server and save it into DB? Such config is ok? I will create a model class for the response and after will save it into DB via JPA.
Question 3: I need reading from server works in Async mode. How can I implement it in Spring Boot? So the main idea here is that I will receive a POST method from the UI, and will launch the integration with web-service. After integration will be finished, I need to notify user.
Question 4: Maybe Camel will be the best solution here?
Stack: Java 11, Spring Boot, thymeleaf + bootstrap.
Thank you for the answer in advance.
Since you do new Integration();, you definitely not going to have dependency injection since inversion of control container is not involved. Although it is fully not clear why do you need that new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/http-outbound-config.xml") if you already do Spring Boot and that proper #ImportResource.
The best way as you already do with the #ImportResource for that Spring Integration XML config. Then you need to get access to the ApplicationContext of the SpringApplication.run() and getBean(Integration.class) to call your start() method. However you fully need to forget about new Integratio(). Spring is going to manage a bean for that Integration and then dependency injection is going to work.
Async mode can be achieved with an ExecutorChannel in Spring Integration. So, when you send a message to this channel, the logic is going to be processed on a different thread. However it is not clear why you state such a requirement since you still going to block via that replyChannel.receive()... Although this should be addressed in a separate SO thread.
Camel VS Spring Integration question is out of StackOverflow policies.
Thymeleaf and Bootstrap are misleading in the context of this question.
Please, consider to learn how to ask properly here on SO: https://stackoverflow.com/help/how-to-ask
Also read some docs about dependency injection and inversion of control: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

Camel read properties file

How do I configure the use of a properties file using Java DSL and the Main object?
According to this page I should be able to call something like:
main.setPropertyPlaceholderLocations("example.properties");
However that simply doesn't work. It seems that option wasn't added until Camel 2.18 and I'm running 2.17.1.
What was the original way to set a properties file to use when letting the application run in a standalone form?
Some backstory:
I'm trying to convert from Spring to Java DSL. During that conversion I was attempting to have my Camel application run on its own. I know that is achieved using main.run();.
I had things "functioning" when using the CamelContext, but that cannot run on its own. So I know using the following will work in that case:
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("classpath:/myProperties.properties");
context.addComponent("properties", pc);
Is there some way I can tell the main to use that setup? Or is there something else needed?
You can use the following snippet:
PropertiesComponent pc = new PropertiesComponent();
pc.setLocation("classpath:/myProperties.properties");
main.getCamelContexts().get(0).addComponent("properties", pc);
Also, if you are using camel-spring, you could use org.apache.camel.spring.Main class, it should use the property placeholder from your application context.
Since you are mentioning you are in the process to move from Spring XML to Java Config here's a minimum application that is using properties and injecting it into a Camel route (it's really properties management in Spring injected into our Camel route bean):
my.properties:
something=hey!
Main class:
package camelspringjavaconfig;
import org.apache.camel.spring.javaconfig.CamelConfiguration;
import org.apache.camel.spring.javaconfig.Main;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Configuration
#ComponentScan("camelspringjavaconfig")
#PropertySource("classpath:my.properties")
public class MyApplication extends CamelConfiguration {
public static void main(String... args) throws Exception {
Main main = new Main();
main.setConfigClass(MyApplication.class); // <-- passing to the Camel Main the class serving as our #Configuration context
main.run(); // <-- never teminates
}
}
MyRoute class:
package camelspringjavaconfig;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
#Component
public class MyRoute extends RouteBuilder {
#Autowired
Environment env; //<-- we are wiring the Spring Env
#Override
public void configure() throws Exception {
System.out.println(env.getProperty("something")); //<-- so that we can extract our property
from("file://target/inbox")
.to("file://target/outbox");
}
}

How does spring construct and auto wire maps

I am using spring for quite some time but this morning I came across with some unexpected behavior.
As I could not decide by myself whether that was a desired functionality or a bug I am presenting it here with the hope I will get some good explanations about why would this be happening.
In short I have multiple beans defined in an application context and I create two map beans using utils:map name space with only part of my beans added to those maps. The two maps have exactly the same entries.
Then I auto wire those maps. One auto wire is done using #Autowired annotation and the other one is done using #Resource
To my surprise the bean annotated with #Autowired had got all beans in the context not only the ones I specifically put in the map. The other one auto wired using #Resource annotation had only the expected two entries in it.
I am mainly interested in:
1. Why all the beans declared in my context of that time appear in the #Autowired map and not the ones I added
2. Why #Resource and #Autowired would behave differently
Here is the working code that reproduces the scenario described above.
Some interface here:
package my.testing.pkg;
public interface Foo {
void doStuff();
}
And its implementation:
package my.testing.pkg;
public class FooImpl implements Foo {
#Override
public void doStuff() {}
}
The spring config file:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:utils="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="foo_1" class="my.testing.pkg.FooImpl"/>
<bean id="foo_2" class="my.testing.pkg.FooImpl"/>
<bean id="foo_3" class="my.testing.pkg.FooImpl"/>
<bean id="foo_4" class="my.testing.pkg.FooImpl"/>
<utils:map id="fooMap1" map-class="java.util.HashMap">
<entry key="foo_1" value-ref="foo_1"/>
<entry key="foo_2" value-ref="foo_2"/>
</utils:map>
<utils:map id="fooMap2" map-class="java.util.HashMap">
<entry key="foo_1" value-ref="foo_1"/>
<entry key="foo_2" value-ref="foo_2"/>
</utils:map>
</beans>
And the test case to reproduce the behavior:
package my.testing.pkg;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.util.Map;
import static org.junit.Assert.assertEquals;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "/test-context.xml")
public class SpringMapCreatingTest {
#Autowired
private Map<String, Foo> fooMap1;
#Resource
private Map<String, Foo> fooMap2;
#Test
public void shouldInjectATwoEntriesMap() throws Exception {
assertEquals("fooMap1 should have to entries", 2, fooMap1.size());
assertEquals("fooMap2 map should have to entries", 2, fooMap1.size());
}
}
Thank you in advance for your clarifications.
What is happening there is the following:
The #Autowired dependency will look for beans that match its type, and in this case it will create a map of your beans Foo mapped by their name. The same behavior will happen when you autowire on a list of beans, Spring will inject all the beans that implement the interface.
The #Resource dependency will look first for a bean that matches the dependency name, e.g fooMap2 and will inject it.
Take a look at the documentation http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers
Maps / List are not handled as "standard" beans injection.
Take a look to those questions on so: Can spring #Autowired Map?
https://stackoverflow.com/a/13914052/1517816
HIH
Check the spring docs, the tips section:
beans that are themselves defined as a collection or map type cannot
be injected through #Autowired, because type matching is not properly
applicable to them. Use #Resource for such beans
So it is better to use #Resource and not #Autowired for maps

Camel processor not autowiring Spring bean

Not really sure why this isn't working - I've tested the CustomerService autowiring outside of the camel route (HelloCamelClient) and it works fine, but once I put it in a camel Processor class, it does not autowire correctly. It gets as far as cs.getCustomer() in the process() function, then throws a NullPointerException.
Camel Context XML
<camelContext trace="false" xmlns="http://camel.apache.org/schema/spring">
<route id="HelloWorldRoute">
<from uri="jetty:http://0.0.0.0:8888/hellocamel"/>
<transform>
<language language="simple">Hello ${body}</language>
</transform>
</route>
<route id="HelloWorldRoute2">
<from uri="jetty:http://0.0.0.0:8888/hellocamel2"/>
<to uri="bean:myProcessor"/>
</route>
</camelContext>
<bean id="myProcessor" class="com.fusesource.byexample.hellocamel.impl.CamelHandler"/>
<bean id="customerService" class="com.fusesource.byexample.hellocamel.impl.CustomerService2" />
<bean id="HelloCamelClient" class="com.fusesource.byexample.hellocamel.impl.HelloCamelClient" />
CamelHandler.java
package com.fusesource.byexample.hellocamel.impl;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fusesource.byexample.hellocamel.impl.CustomerService2;
#Service
public class CamelHandler implements Processor {
#Autowired
private CustomerService2 cs;
public CamelHandler() {
//
}
public void test() throws SQLException {
}
#Override
public void process(Exchange arg0) throws Exception {
cs.getCustomer();
arg0.getOut().setBody(cs.getCustomer());
}
}
I changed my code to have setter methods for DataSource and CustomerService, and it seems to work fine now. Not sure why though.
2018 AND NOT A NOOB ANYMORE UPDATE:
This is because with an empty constructor provided, Spring only has the ability to use that to build the bean, and can't possibly set any #Autowired fields. Adding setters gave Spring the ability to populate those fields. The (preferred) alternative is to use Constructor Injection instead of the #Autowired annotation.

Configuring AOP with ZK to intercept methods

Before certain methods (or as of now all the methods) I have to call the method of an Aspect to log some messages. My application is functioning correctly otherwise but none of the methods of the Aspect class are called.
I have tried the same cutpoint in same folder structure in my local application but when I try to include it with ZK i am having issues. I have also modified my application-context.xml to support AOP.
This is my aspect class :
package com.mypckg.services.impl;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class MyIntercpeter {
#Pointcut("execution(* com.mypckg.services.impl.MyService.getStudents(..))")
public void performance() {
}
#Before("performance()")
public void doSomethingBeforeExecution() {
System.out.println("Before execution method called...");
}
#AfterReturning("performance()")
public void doSomethingAfterExecution() {
System.out.println("After execution method called...");
}
}
The modifications I made in the application-context.xml are
<beans .........
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
..........
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
.....
<aop:aspectj-autoproxy />
<context:annotation-config />
Am I missing something? Thanks in advance.
Looks like you missed an obvious thing : you forgot to declare beans in spring config?
From Spring docs:
Spring AOP only supports method execution join points for Spring beans, so you can think of a pointcut as matching the execution of methods on Spring beans.
http://static.springsource.org/spring/docs/2.0.x/reference/aop.html
You can declare your beans with annotations or by config.
Also would be better to put a version of spring you use (I supposed it was 2.0.x).

Categories