Spring integration router configuration - java

I am struggling to find examples using the #Router annotation. If I am understanding the javadocs correctly:
#Service
public class AgentServiceImpl implements AgentService {
#Override
#Router(inputChannel = "agentLogin", defaultOutputChannel = "agentServiceResponse")
public AgentLoginResponse login(AgentLoginRequest request) {
}
}
In the xml examples with the router there was a service-activator, which is where I am getting hung up on trying to figure out how it will fit in.

Actually you do that wrong way. See #Router JavaDocs:
* Indicates that a method is capable of resolving to a channel or channel name
* based on a message, message header(s), or both.
So, your login method for the target router component must return a channel name or entire MessageChannel object.

Related

Change websocket scope (from application to session/view)

I created a basic web socket with a tutorial.
Here is a configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
}
And here is the message handling controller:
#MessageMapping("/chat")
#SendTo("/topic/messages")
public OutputMessage send(Message message) throws Exception {
return new OutputMessage("Hello World!");
}
Everything works, but from my investigation, it looks like the WebSockets by default has an application scope (by connecting to the channel I can see all calls, from all users).
What I want to do is to be able to see only calls from the current user session or current view only.
Any ideas on how to apply these configurations?
I was able to solve this puzzle, so I'm sharing with you my findings.
First, I found information that a simple in-memory message broker can not handle this:
/*
* This enables a simple (in-memory) message broker for our application.
* The `/topic` designates that any destination prefixed with `/topic`
* will be routed back to the client.
* It's important to keep in mind, this will not work with more than one
* application instance, and it does not support all of the features a
* full message broker like RabbitMQ, ActiveMQ, etc... provide.
*/
But this was misleading, as it can be easily achieved by #SendToUser annotation.
Also, the important thing that now on the client-side, you need to add an additional prefix /user/ while subscribing to the channel, so the solution would be:
On the server-side: change #SendTo("/topic/messages") into #SendToUser("/topic/messages").
On the client-side: /topic/messages into the /user/topic/messages.

Publish / Subscribe MQTT using SmallRye reactive messaging dynamically

We try to publish and subscribe to MQTT protocol using smallrye reactive messaging. We managed to actually publish a message into a specific topic/channel through the following simple code
import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import javax.enterprise.context.ApplicationScoped;
import java.time.Duration;
#ApplicationScoped
public class Publish {
#Outgoing("pao")
public Multi<String> generate() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.map(x -> "A Message in here");
}
}
What we want to do is to call whenever we want the generate() method somehow with a dynamic topic, where the user will define it. That one was our problem but then we found these classes from that repo in github. Package name io.smallrye.reactive.messaging.mqtt
For example we found that there is a class that says it makes a publish call to a MQTT broker(Mosquitto server up).
Here in that statement SendingMqttMessage<String> message = new SendingMqttMessage<String>("myTopic","A message in here",0,false);
We get the a red underline under the SendingMqttMessage<String> saying 'SendingMqttMessage(java.lang.String, java.lang.String, io.netty.handler.codec.mqtt.MqttQoS, boolean)' is not public in 'io.smallrye.reactive.messaging.mqtt.SendingMqttMessage'. Cannot be accessed from outside package
UPDATE(Publish done)
Finally made a Publish request to the mqtt broker(a mosquitto server) and all this with a dynamic topic configured from user. As we found out the previous Class SendingMqttMessage was not supposed to be used at all. And we found out that we also needed and emitter to actually make a publish request with a dynamic topic.
#Inject
#Channel("panatha")
Emitter<String> emitter;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createUser(Device device) {
System.out.println("New Publish request: message->"+device.getMessage()+" & topic->"+device.getTopic());
emitter.send(MqttMessage.of(device.getTopic(), device.getMessage()));
return Response.ok().status(Response.Status.CREATED).build();
}
Now we need to find out about making a Subscription to a topic dynamically.
first to sett us to the same page:
Reactive messaging does not work with topics, but with channels.
That is important to note, because you can exclusively read or write to a channel. So if you want to provide both, you need to configure two channels pointing at the same topic, one incoming and one outgoing
To answer your question:
You made a pretty good start with Emitters, but you still lack the dynamic nature you'd like.
In your example, you acquired that Emitter thru CDI.
Now that is all we need, to make this dynamic, since we cann dynamically inject Beans at runtime using CDI like this:
Sending Messages
private Emitter<byte[]> dynamicEmitter(String topic){
return CDI.current().select(new TypeLiteral<Emitter<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
please also note, that i am creating a Emitter of type byte[], as this is the only currently supportet type of the smallrye-mqtt connector (version 3.4.0) according to its documentation.
Receiving Messages
To read messages from a reactive messaging channel, you can use the counterpart of the Emitter, which is the Publisher.
It can be used analog:
private Publisher<byte[]> dynamicReceiver(String topic){
return CDI.current().select(new TypeLiteral<Publisher<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
You can then process these Date in any way you like.
As demo, it hung it on a simple REST Endpoint
#GET
#Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> stream(#QueryParam("topic") String topic) {
return Multi.createFrom().publisher(dynamicReceiver(topic)).onItem().transform(String::new);
}
#GET
#Path("/publish")
public boolean publish(#QueryParam("msg") String msg, #QueryParam("topic") String topic) {
dynamicEmitter(topic).send(msg.getBytes());
return true;
}
One more Thing
When creating this solution I hit a few pitfalls you should know about:
Quarkus removes any CDI-Beans that are "unused". So if you want to inject them dynamically, you need to exclude those, or turne off that feature.
All channels injected that way must be configured. Otherwise the injection will fail.
For some Reason, (even with removal completely disabled) I was unable to inject Emitters dynamically, unless they are ever injected elsewhere.

Using MarshallingWebServiceOutboundGateway with dynamic URI based on message header

I have the following configuration for sending SOAP requests as part of a integration flow, where uriDefinedInApplicationProperties is a fixed uri, defined in 'application.properties' file :
#Bean
public MarshallingWebServiceOutboundGateway outboundSOAPGateway()
{
final MarshallingWebServiceOutboundGateway outboundGateway = new MarshallingWebServiceOutboundGateway(
uriDefinedInApplicationProperties,
requestMarshaller,
responseMarshaller);
outboundGateway.setAdviceChain(Collections.singletonList(retryOutboundGatewayAdvice));
if (soapActionCallback!= null) {
outboundGateway.setRequestCallback(soapActionCallback);
}
return outboundGateway;
}
Now i have the requirement that the URI of the remote SOAP server should be dynamically generated ( i'm planning on using message headers to store the URI).
I wanted to do something like the following, but MarshallingWebServiceOutboundGateway does not seem to support it, and i haven't been able to find how to do something similar using spring integration dsl:
#Bean
public MarshallingWebServiceOutboundGateway outboundSOAPGateway()
{
final MarshallingWebServiceOutboundGateway outboundGateway = new MarshallingWebServiceOutboundGateway(
message -> (message -> message.getHeaders().get("remote.uri.header"),
requestMarshaller,
responseMarshaller);
outboundGateway.setAdviceChain(Collections.singletonList(retryOutboundGatewayAdvice));
if (soapActionCallback!= null) {
outboundGateway.setRequestCallback(soapActionCallback);
}
return outboundGateway;
}
I have noted that MarshallingWebServiceOutboundGateway has a setUriVariableExpressions(Map<String, Expression> uriVariableExpressions) method, but i didn't find any clear documentation on what it is supposed to do and how it works.
Also i tried to do something like the following to create the outbound gateway, but it does not seem to support requestCallbacks nor advice chain.
Http.outboundGateway(message -> message.getHeaders().get("remote.uri.header"))
.messageConverters(new MarshallingHttpMessageConverter(
remoteRequestMarshaller,
remoteResponseMarshaller));
What is the best way to create a SOAP outbound gateway with retry advice and dynamically generated uri?
The advice config is not a MessageHandler responsibility. If you use Java DSL, see a second argument (a GenericEndpointSpec lambda) of the handle() you use for that MarshallingWebServiceOutboundGateway:
/**
* Configure a list of {#link Advice} objects to be applied, in nested order, to the
* endpoint's handler. The advice objects are applied only to the handler.
* #param advice the advice chain.
* #return the endpoint spec.
*/
public S advice(Advice... advice) {
Yes, I agree that MarshallingWebServiceOutboundGateway (and its super class) doesn't support URI resolution against message at the moment. Feel free to raise a GH issue to fix a gap with a SpEL configuration like we have it for the mentioned Http.outboundGateway.
Meanwhile as a workaround you can consider to implement a DestinationProvider which reads an URI from a TheadLocal store. Before calling this gateway you should consult your message and store built URI into that ThreadLocal variable.

Using Spring Cloud Stream Source to send method results to stream

I'm trying to create a Spring Cloud Stream Source Bean inside a Spring Boot Application that simply sends the results of a method to a stream (underlying Kafka topic is bound to the stream).
Most of the Stream samples I've seen use #InboundChannelAdapter annotation to send data to the stream using a poller. But I don't want to use a poller. I've tried setting the poller to an empty array but the other problem is that when using #InboundChannelAdapter you are unable to have any method parameters.
The overall concept of what I am trying to do is read from an inbound stream. Do some async processing, then post the result to an outbound stream. So using a processor doesn't seem to be an option either. I am using #StreamListener with a Sink channel to read the inbound stream and that works.
Here is some code i've been trying but this doesn't work at all. I was hoping it would be this simple because my Sink was but maybe it isn't. Looking for someone to point me to an example of a source that isn't a Processor (i.e. doesn't require listening on an inbound channel) and doesn't use #InboundChannelAdapter or to give me some design tips to accomplish what I need to do in a different way. Thanks!
#EnableBinding(Source.class)
public class JobForwarder {
#ServiceActivator(outputChannel = Source.OUTPUT)
#SendTo(Source.OUTPUT)
public String forwardJob(String message) {
log.info(String.format("Forwarding a job message [%s] to queue [%s]", message, Source.OUTPUT));
return message;
}
}
Your orginal requirement can be achieved through the below steps.
Create your custom Bound Interface (you can use the default #EnableBinding(Source.class) as well)
public interface CustomSource {
String OUTPUT = "customoutput";
#Output(CustomSource.OUTPUT)
MessageChannel output();
}
Inject your bound channel
#Component
#EnableBinding(CustomSource.class)
public class CustomOutputEventSource {
#Autowired
private CustomSource customSource;
public void sendMessage(String message) {
customSource.output().send(MessageBuilder.withPayload(message).build());
}
}
Test it
#RunWith(SpringRunner.class)
#SpringBootTest
public class CustomOutputEventSourceTest {
#Autowired
CustomOutputEventSource output;
#Test
public void sendMessage() {
output.sendMessage("Test message from JUnit test");
}
}
So if you don't want to use a Poller, what causes the forwardJob() method to be called?
You can't just call the method and expect the result to go to the output channel.
With your current configuration, you need an inputChannel on the service containing your inbound message (and something to send a message to that channel). It doesn't have to be bound to a transport; it can be a simple MessageChannel #Bean.
Or, you could use a #Publisher to publish the result of the method invocation (as well as being returned to the caller) - docs here.
#Publisher(channel = Source.OUTPUT)
Thanks for the input. It took me a while to get back to the problem. I did try reading the documentation for #Publisher. It looked to be exactly what I needed but I just couldn't get the proper beans initialized to get it wired properly.
To answer your question the forwardJob() method is called after some async processing of the input.
Eventually I just implemented using spring-kafka library directly and that was much more explicit and felt easier to get going. I think we are going to stick to kafka as the only channel binding so I think we'll stick with that library.
However, we did eventually get the spring-cloud-stream library working quite simply. Here was the code for a single source without a poller.
#Component
#EnableBinding(Source.class)
public class JobForwarder {
private Source source;
#Autowired
public ScheduledJobForwarder(Source source) {
this.source = source;
}
public void forwardScheduledJob(String message) {
log.info(String.format("Forwarding a job message [%s] to queue [%s]", message, Source.OUTPUT));
source.output().send(MessageBuilder.withPayload(message).build());
}
}

Using Spring Integration to route domain objects to the appropriate method

I have recently started looking into Spring Integration as a result of this question and have a question about object-type based routing.
The application I am maintaining needs to process requests from an incoming ActiveMQ queue (request.queue) and send a response back to the caller on a topic (response.topic). At present the requests are structured as follows:
public abstract class Request {
// base class
}
public abstract class CustomerRequest extends Request {
// base class for customer-specific requests
}
public class FindCustomerByIdRequest extends CustomerRequest {
private int id;
}
public class FindAllCustomersRequest extends CustomerRequest {
private boolean includeArchivedCustomers;
}
public class AddCustomerRequest extends CustomerRequest {
private String name;
private Date signupDate;
private Address address;
}
I have a service for each high level domain object which provides the functionality to service these incoming requests:
#Service
public class CustomerService {
public CustomerResponse findCustomerById(FindCustomerByIdRequest request) {
// code snipped
return customerResponse;
}
public AddCustomerResponse addCustomer(AddCustomerRequest request) {
// code snipped
return addCustomerResponse;
}
}
I need to route each specific request to the approriate method in CustomerService via #ServiceActivator which I understand can be done by creating a separate channel for each request and implementing a PayloadTypeRouter to place requests on the correct channel based on type.
Over time the list of request types is going to grow, and I am questioning whether a one-channel-per-request setup is efficient/practical/scalable. For example, if there are 100 different request types in the future there are going to be 100 different channels.
What would be great is if I could route the high-level requests of superclass CustomerRequest to CustomerService and have Spring work out the approriate method to call via an annotation or some other mechanism. Does anyone know if this is possible, or have any comments regarding the many-channels approach?
If there is no ambiguity, use <service-activator ... reg="fooBean" /> (no method) and the framework will chose the target method based on the payload.
If there is ambiguity (more than one method for the same type), it will fail.
However, a single class with 100+ methods is probably not a good design.
It seems to be that your request types are app feature specific. This suggest that you have one queue for all the possible application features. That is horrible idea. You should have at least separate queue per feature.
I suggest to rethink the design of your app.

Categories