Transform Header and payload to Json nodes - spring-integration

I am trying to enrich payload with some Headers keys and convert to a json structure like that:
{
"Header": { ["key" : "value", "key2": "value"]}
"Payload": { "attribute" : "value" }
}
My gateway is configured like this:
#MessagingGateway
public static interface MailService {
#Gateway(requestChannel = "mail.input")
void sendMail(String body, #Headers Map<String,String> headers);
}
Here is my flow:
#Bean
public IntegrationFlow errorFlow(){
return IntegrationFlows.from(recoveryChannel())
.transform("payload.failedMessage")
.enrichHeaders(c -> c.header(FileHeaders.FILENAME, "emailErrors.json"))
.handle(this.fileOutboundAdapter())
.get();
}
How could I solve this issue?
Thanks.

To convert the whole message to the JSON, you should do something like this:
.handle((p, h) -> MessageBuilder.withPayload(new GenericMessage<>(p, h)))
.transform(Transformers.toJson())
The trick is like Transformers.toJson() doesn't care about headers and transforms only payload. So, we have to hack it a bit placing the whole message to the payload.
Since ServiceActivator (ground floor of the .handle()) returns message as is if the result is Message<?> , we don't have choice unless provide MessageBuilder and Transformers.toJson() will have all the info for your use-case.

#SpringBootApplication
public class So41223173Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So41223173Application.class, args);
context.getBean("flow.input", MessageChannel.class)
.send(new ErrorMessage(new MessagingException(new GenericMessage<>(new Foo()))));
context.close();
}
#Bean
public IntegrationFlow flow() {
return f -> f.transform("payload.failedMessage")
.enrichHeaders(c -> c.header("foo", "bar"))
.transform(toMap(), "transform")
.transform(Transformers.toJson())
.handle(m -> System.out.println(m.getPayload()));
}
#Bean
public Transformer toMap() {
return new AbstractTransformer() {
#Override
protected Object doTransform(Message<?> message) throws Exception {
Map<String, Object> map = new LinkedHashMap<>();
Map<String, Object> headers = new LinkedHashMap<>(message.getHeaders());
headers.remove(MessageHeaders.ID);
headers.remove(MessageHeaders.TIMESTAMP);
map.put("headers", headers);
map.put("payload", message.getPayload());
return map;
}
};
}
public static class Foo {
String bar = "bar";
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
}
}
result
{"headers":{"foo":"bar"},"payload":{"bar":"bar"}}

Related

readStartDocument can only be called when CurrentBSONType is DOCUMENT, not when CurrentBSONType is STRING Error?

My code is like below, what should I do to avoid this error?
#RequestMapping(method = RequestMethod.POST, path = "/deneme", consumes = "application/json")
public void logout(#RequestBody Map<String, Object> userId) {
tokensService.logout(userId.get("userId").toString());
}
public void logout(String userId){
try {
tokensRepository.logout(userId,"");
}
catch (Exception e) {
System.out.println(e.getMessage());
}`public interface TokensRepository extends MongoRepository<Tokens, String> {
#Query("Update tokens t set t.token=:token where t._id=:userId")
void logout(#Param("userId") String userId, #Param("token") String token);
}`

How to config tcp server to receive data from multiple client using spring boot?

I would like to configure TCP server to receive and reply data from multiple clients. I searched many other thread but could not found exact way to do. I'm using spring integration first time and have no experience.
Server requirement
should be able to receive and reply data to specific client (can have multiple client, each client should processed separately)
should be able to send data to client and wait for response for specific timeout.
Should be able to detect client is disconnect or not. if Client is disconnect then connection should be closed to save memory. (In earlier method without spring integration I was able to do it by ping client and see sending is failed or not but don't know how to do with spring integration)
I tried below code, In which I'm able to send data to client but could achieve my above requirements
TCP Server Configuration:
#Configuration
public class TcpServerConfig {
private List<TcpConnectionOpenEvent> clientList = new ArrayList<>();
public List<TcpConnectionOpenEvent> getClientList() {
return clientList;
}
#Bean
public TcpReceivingChannelAdapter server(TcpNetServerConnectionFactory cf) {
TcpReceivingChannelAdapter adapter = new TcpReceivingChannelAdapter();
adapter.setConnectionFactory(cf);
adapter.setOutputChannel(inputChannel());
return adapter;
}
#Bean
public MessageChannel inputChannel() {
return new QueueChannel();
}
#Bean
public MessageChannel outputChannel() {
return new DirectChannel();
}
#Bean
public TcpNetServerConnectionFactory cf() {
return new TcpNetServerConnectionFactory(1001);
}
#Bean
public IntegrationFlow outbound() {
return IntegrationFlows.from(outputChannel())
.handle(sender())
.get();
}
#Bean
public MessageHandler sender() {
TcpSendingMessageHandler tcpSendingMessageHandler = new TcpSendingMessageHandler();
tcpSendingMessageHandler.setConnectionFactory(cf());
return tcpSendingMessageHandler;
}
#Bean
public ApplicationListener<TcpConnectionOpenEvent> listener() {
return new ApplicationListener<TcpConnectionOpenEvent>() {
#Override
public void onApplicationEvent(TcpConnectionOpenEvent event) {
outputChannel().send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, event.getConnectionId())
.build());
clientList.add(event);
}
};
}
}
Test Code:
#Service
public class Test {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class);
#Autowired
TcpServerConfig tcpServerConfig;
#Autowired
private MessageChannel outputChannel;
#Autowired
private MessageChannel inputChannel;
#Scheduled(fixedRate = 1000)
void task() {
LOGGER.info("Client count: " + tcpServerConfig.getClientList().size());
for (TcpConnectionOpenEvent client : tcpServerConfig.getClientList()) {
outputChannel.send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, client.getConnectionId())
.build());
}
}
}
Any help would be appreciated.
Here is one solution:
#SpringBootApplication
#EnableScheduling
public class So62877512ServerApplication {
public static void main(String[] args) {
SpringApplication.run(So62877512ServerApplication.class, args);
}
#Bean
public IntegrationFlow serverIn(Handler handler) {
return IntegrationFlows.from(Tcp.inboundAdapter(server()))
.transform(Transformers.objectToString())
.filter(handler, "existingConnection", spec -> spec
.discardFlow(f -> f
.handle(handler, "sendInitialReply")))
.handle(handler, "reply")
.get();
}
#Bean
public IntegrationFlow serverOut() {
return f -> f.handle(Tcp.outboundAdapter(server()));
}
#Bean
public TcpServerConnectionFactorySpec server() {
return Tcp.netServer(1234)
.serializer(TcpCodecs.lf())
.deserializer(TcpCodecs.lf()); // compatible with netcat
}
}
#Component
#DependsOn("serverOut")
class Handler {
private static final Logger LOG = LoggerFactory.getLogger(Handler.class);
private final ConcurrentMap<String, BlockingQueue<Message<?>>> clients = new ConcurrentHashMap<>();
private final MessageChannel out;
private final TcpNetServerConnectionFactory server;
public Handler(#Qualifier("serverOut.input") MessageChannel out, TcpNetServerConnectionFactory server) {
this.out = out;
this.server = server;
}
public boolean existingConnection(Message<?> message) {
String connectionId = message.getHeaders().get(IpHeaders.CONNECTION_ID, String.class);
boolean containsKey = this.clients.containsKey(connectionId);
if (!containsKey) {
this.clients.put(connectionId, new LinkedBlockingQueue<Message<?>>());
}
return containsKey;
}
public void sendInitialReply(Message<String> message) {
LOG.info("Replying to " + message.getPayload());
this.out.send(MessageBuilder.withPayload(message.getPayload().toUpperCase())
.copyHeaders(message.getHeaders()).build());
}
#Scheduled(fixedDelay = 5000)
public void sender() {
this.clients.forEach((key, queue) -> {
try {
this.out.send(MessageBuilder.withPayload("foo")
.setHeader(IpHeaders.CONNECTION_ID, key).build());
Message<?> reply = queue.poll(10, TimeUnit.SECONDS);
if (reply == null) {
LOG.error("Timeout waiting for " + key);
this.server.closeConnection(key);
}
else {
LOG.info("Reply " + reply.getPayload() + " from " + key);
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOG.error("Interrupted");
}
catch (Exception e) {
LOG.error("Failed to send to " + key, e);
}
});
}
public void reply(Message<String> in) {
BlockingQueue<Message<?>> queue = this.clients.get(in.getHeaders().get(IpHeaders.CONNECTION_ID, String.class));
if (queue != null) {
queue.add(in);
}
}
#EventListener
public void closed(TcpConnectionCloseEvent event) {
this.clients.remove(event.getConnectionId());
LOG.info(event.getConnectionId() + " closed");
}
}
$ nc localhost 1234
foo <- typed
FOO
foo
bar <- typed
foo
bar <- typed
foo
$ <- closed by server - timeout
2020-07-14 14:41:04.906 INFO 64763 --- [pool-1-thread-2] com.example.demo.Handler : Replying to foo
2020-07-14 14:41:13.841 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : Reply bar from localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:21.465 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : Reply bar from localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:36.473 ERROR 64763 --- [ scheduling-1] com.example.demo.Handler : Timeout waiting for localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153
2020-07-14 14:41:36.474 INFO 64763 --- [ scheduling-1] com.example.demo.Handler : localhost:65115:1234:a9fc7e3d-4dda-4627-b765-4f0bb0835153 closed

RxJava subscribe onNext is not called when adding element asynchronously

I have a Observable like this
Observable<String> gitHubRepoModelObservable;
I have this code
repoNames = new ArrayList<String>();
gitHubRepoModelObservable = Observable.fromIterable(repoNames);
repoNames.add("Hello");
gitHubRepoModelObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String s) {
System.out.println(s);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
repoNames is just a list of string. When I am adding a string "hello" manually the onNext is getting called but when I am adding string from a API call like bellow
call.enqueue(new Callback<List<GitHubRepoModel>>() {
#Override
public void onResponse(Call<List<GitHubRepoModel>> call, Response<List<GitHubRepoModel>> response) {
for (GitHubRepoModel repo : response.body()) {
repoNames.add(repo.getName());
}
}
#Override
public void onFailure(Call<List<GitHubRepoModel>> call, Throwable t) {
}
});
I am adding strings from the API into the repoNames the "onNext" is not getting called.
I have seen
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
can be added while initializing retrofit but I want to better understand the rxjava so in this experiment it is not working.
Please help!
It can't not be work.
When you create you api request and try subscribe you list is emty, so Observable does not work.
You need to create Observable such, that your subcribe will run your request!
Observable<String> gitHubRepoModelObservable = Observable.create(
new Observable.OnSubscribe<String>() {
#Override
public void call(final Subscriber<? super String> sub) {
call.enqueue(new Callback<List<GitHubRepoModel>>() {
#Override
public void onResponse(Call<List<GitHubRepoModel>> call, Response<List<GitHubRepoModel>> response) {
for (GitHubRepoModel repo : response.body()) {
sub.onNext(repo.getName()); //send result to rx
}
sub.onCompleted();
}
#Override
public void onFailure(Call<List<GitHubRepoModel>> call, Throwable t) {
}
});
}
}
);
gitHubRepoModelObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<String>() {
#Override
public void onNext(String s) {
System.out.println(s);
}
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
});
Why would onNext get called if you are just adding element to plain List?
In the first example you are seeing onNext being called because modified list is passed through the stream during subscribe.
Create Subject ex. PublishSubject and pass list to Subject.onNext in onResponse, subscribe to it and you will get what you want.
Second option is adding RxJava2CallAdapterFactory and return Observable<Response<List<GithubRepoModel>>>. This way you don't need to create stream yourself.

Spring Integration Load Balance to JMS Queues

I would like to take JMS msgs from a single input queue and fan them out onto N output queues.
I have a simple flow that will forward messages to a single destination but can not figure out how to apply LoadBalancer to allow for multiple destinations in round-robin fashion.
Any ideas how to do this?
#Configuration
public class TestLoadBalance {
public static final String INPUT_QUEUE = "_dev.lb.input";
public static final String OUTPUT_QUEUE_PREFIX = "_dev.lb.output-";
#Bean
public IntegrationFlow testLoadBalanceFlow(
ConnectionFactory jmsConnectionFactory) {
IntegrationFlow flow = IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
.destination(INPUT_QUEUE)
)
.handle(buildOutput(jmsConnectionFactory, 1))
// cant have 2nd handle. gets warn & flow end:
// The 'currentComponent' (org.springframework.integration.jms.JmsSendingMessageHandler#516462cc)
// is a one-way 'MessageHandler' and it isn't appropriate to configure 'outputChannel'
//.handle(buildOutput(jmsConnectionFactory, 2))
.get();
return flow;
}
private JmsSendingMessageHandler buildOutput(ConnectionFactory jmsConnectionFactory, int i){
return Jms.outboundAdapter(jmsConnectionFactory)
.destination(OUTPUT_QUEUE_PREFIX + i).get();
}
}
There are a couple of ways to do it; you can either have multiple subscribers on the channel...
#Bean
public IntegrationFlow inbound(ConnectionFactory cf) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(cf)
.destination("foo"))
.channel(roundRobin())
.get();
}
#Bean
public DirectChannel roundRobin() {
return new DirectChannel();
}
#Bean
public IntegrationFlow outbound1(ConnectionFactory cf) {
return IntegrationFlows.from(roundRobin())
.bridge() // otherwise log() will wire tap the roundRobin channel
.log()
.log(new LiteralExpression("Sending to bar"))
.handle(Jms.outboundAdapter(cf)
.destination("bar"))
.get();
}
#Bean
public IntegrationFlow outbound2(ConnectionFactory cf) {
return IntegrationFlows.from(roundRobin())
.bridge() // otherwise log() will wire tap the roundRobin channel
.log()
.log(new LiteralExpression("Sending to baz"))
.handle(Jms.outboundAdapter(cf)
.destination("baz"))
.get();
}
Or, you can use a destination expression:
#Bean
public AtomicInteger toggle() {
return new AtomicInteger();
}
#Bean
public IntegrationFlow inbound(ConnectionFactory cf) {
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(cf)
.destination("foo"))
.handle(Jms.outboundAdapter(cf)
.destinationExpression("#toggle.getAndIncrement() % 2 == 0 ? 'bar' : 'baz'"))
.get();
}
#JmsListener(destination = "bar")
public void bar(String in) {
System.out.println("received " + in + " from bar");
}
#JmsListener(destination = "baz")
public void baz(String in) {
System.out.println("received " + in + " from baz");
}
Result:
received test1 from bar
received test2 from baz
Based on Gary's examples I went with the destinationExpression approach as:
#Configuration
public class TestLoadBalance {
public static final String INPUT_QUEUE = "_dev.lb.input";
public static final String OUTPUT_QUEUE_PREFIX = "_dev.lb.output-";
#Bean
public JmsDestinationPartitioner partitioner() {
return new JmsDestinationPartitioner(OUTPUT_QUEUE_PREFIX,1,3);
}
#Bean
public IntegrationFlow testLoadBalanceFlow(
ConnectionFactory jmsConnectionFactory) {
IntegrationFlow flow = IntegrationFlows.from(
Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
.destination(INPUT_QUEUE)
)
.handle(Jms.outboundAdapter((jmsConnectionFactory))
.destinationExpression("#partitioner.nextDestination()"))
.get();
return flow;
}
}
With a wrapper around AtomicInt to handling naming with a prefix:
public class JmsDestinationPartitioner {
private int min;
private int max;
private String prefix;
private AtomicInteger current;
public JmsDestinationPartitioner(String prefix, int min, int max){
this.prefix = prefix;
this.min = min;
this.max = max;
current = new AtomicInteger(min);
}
public int getAndIncrement(){
int i = current.get();
current.getAndIncrement();
if (current.get() > max){
current.set(min);
}
return i;
}
public String nextDestination(){
return prefix + getAndIncrement();
}
}

HashMap is empty after deserialization with Jersey and Jackson

I have a REST web service using Jersey 1.17.1 and Jackson 1.9.2.
The API looks like this:
public class PlayerRequest {
private String language;
private String playerId;
private Map<String, String> params;
}
When this service is called by other component, the params map is received empty:
PlayerRequest [language=null, playerId=100036343, params={}]
Original request from other component:
PlayerRequest [language=null, playerId=100036343, params={context=mobile, countrycode=SE, partnerskin=8, locale=en_GB, ipaddress=62.209.186.13}]
Why is the HashMap empty after the deserialization?
In your PlayerRequest class, create a method annotated with #JsonAnySetter, as following:
#JsonAnySetter
public void set(String key, String value) {
params.put(key, value);
}
This method works as a fallback handler for all unrecognized properties found in the JSON content.
To use the above mentioned approach, ensure the params field is being initialized:
private Map<String, String> params = new HashMap<String, String>();
So, your PlayerRequest class would be like:
public class PlayerRequest {
private String language;
private String playerId;
private Map<String, String> params = new HashMap<String, String>();
public PlayerRequest() { }
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getPlayerId() {
return playerId;
}
public void setPlayerId(String playerId) {
this.playerId = playerId;
}
public Map<String, String> getParams() {
return params;
}
public void setParams(Map<String, String> params) {
this.params = params;
}
#JsonAnySetter
public void set(String key, String value) {
params.put(key, value);
}
}
Fixed by implementing and adaptor(javax.xml.bind.annotation.adapters.XmlAdapter) and annotated the map in api with #XmlJavaTypeAdapter

Resources