I'me using Spring Integration and java dsl specifications to implement my IntegrationFlow.
I want to use an custom header enricher to add some file names to the header, it will be something like :
public class FileHeaderNamingEnricher {
public Message<File> enrichHeader(Message<File> fileMessage) {
// getting some details fom the database ...
return messageBuilder
.setHeader("filename", "somestuff")
.build();
}
}
And my Integration flow will look like :
public IntegrationFlow myflow() {
return IntegrationFlows.from("input")
.enrich // here I want to enrich the header using my class
}
Can any one help me with this please ?
You can have your FileHeaderNamingEnricher extend AbstractReplyProducingMesageHandler (put your code in handleRequestMessage()).
Or, implement GenericHandler<T> (its handle method gets the payload and headers as parameters and can return a message).
Then use the .handle method...
...
.handle(myEnricher())
...
#Bean
public void MessageHandler myEnricher() {
return new FileHeaderNamingEnricher();
}
Related
I have a service, that receives AMQP messages. This service is bound to a queue, which receives all messages which match a set of routing keys.
My set up is as follows:
...
private SomeController controller;
#Autowired
private SimpleMessageListenerContainer receiverContainer;
#Bean
public IntegrationFlow inboundFlow(){
var adpater = Amqp.inboundAdapter(receiverContainer);
return IntegrationFlows.from(adapter)
// some transformations
.handle(controller, "processMessage")
.get();
}
This already works fine. However, now I want to handle a message with different controllers, depending on a header attribute. In this case I'd like to have a controller for each routing key. Is it also a good idea to use a single queue with multiple routing keys only to handle it differently for each key?
It is really legit to have several bindings between an exchange and a single queue.
See more info in this tutorial: https://www.rabbitmq.com/tutorials/tutorial-four-spring-amqp.html.
The Amqp.inboundAdapter() relies on the DefaultAmqpHeaderMapper.inboundMapper() by default which populates for us an AmqpHeaders.RECEIVED_ROUTING_KEY message header before producing. So, you indeed can use a route(Message.class, m -> m.getHeaders().get(AmqpHeaders.RECEIVED_ROUTING_KEY)) with appropriate channelMapping() for the routing key value.
I just wanted to add a code example, incorporating Artem Bilan's (correct) answer, because additionally to that, I had to incorporate a gateway (hinted by Artem Bilan with "appropriate channelMapping()).
More about why you need a gateway or in some cases a bridge, refer to this part of the documentation.
My initial code snippit becomes something like the following:
...
#Autowired
private FirstController firstController;
#Autowired
private SecondController secondController;
#Autowired
private SimpleMessageListenerContainer receiverContainer;
#Bean
public IntegrationFlow inboundFlow(){
var adpater = Amqp.inboundAdapter(receiverContainer);
return IntegrationFlows.from(adapter)
// some transformations
.route(Message.class, getMessageRoutingKey(m),
m -> m.subFlowMapping("routingKey1", firstFlow())
// after the first subFlow, all further integrationflows are wrapped in a gateway
.subFlowMapping("routingKey2", sf -> sf.gateway(secondFlow())))
.get();
}
#Bean
public IntegrationFlow firstFlow() {
return f -> f
// e.g. additional transformations
.handle(firstController, "processMessageInFirstFashion");
}
#Bean
public IntegrationFlow secondFlow() {
return f -> f
// e.g. additional transformations
.handle(secondController, "processMessageInSecondFashion");
}
private static String getMessageRoutingKey(final Message<?> message) {
return message.getHeaders().get(AmqpHeaders.RECEIVED_ROUTING_KEY).toString();
}
I have Java interface classes.
public interface ModelClient {
}
public interface DownstreamService1Client extends ModelClient {
public ContentData getContentData();
}
public interface DownstreamService2Client extends ModelClient {
public ContentData getContentData();
}
public interface DownstreamService3Client extends ModelClient {
public ContentData getContentData();
}
I have another spec builder method:
ModelClientSpec<DownstreamService1Client> spec = ModelClientSpec.builder(DownstreamService1Client.class);
Above spec can be used to create a client:
DownstreamService1Client client = context.getResourceClient(spec);
which can be used to call downstream client to get data:
ContentData data = client.getContentData(); // get the data from downstream service.
I have created following client spec static map:
"contentType" -> DownstreamClientSpec
"music" -> DownstreamClient1Spec
"books" -> DownstreamClient2Spec
...
Now I have a handler method:
public ContentData handle(String contentType) {
// need to get a client based on contentType
client = ???
return client.getContentData()
}
How do I get the client based on the contentType other than having the switch statement for contentType and specific client creation logic? Is it a clean way to dynamically bind the specific client using Guice?
Thanks!
I guess, Multibindings, and specifically MapBinder can be used to achieve what you are looking for. Bind your client to a map, inject it and get specific implementation from that map by key.
Please let me know if there is way to define #ReleaseStrategy with MessageGroup and associate it with #Aggregator.
I have POJO defined as below but not sure how would I associate a #Aggregator to it
public class FooReleaseStrategy {
#ReleaseStrategy
public boolean canRelease(MessageGroup group) {
return group.isComplete();
}
}
I have #Aggregator and #CorrelationStratgy defined part of configuration.
#Aggregator(inputChannel="sftpChannel" outputChannel="aggregateChannel")
public List<Message<?>> aggregateFiles(List<Message<?>> messages) {
return messages;
}
#CorrelationStrategy based on filename.
Would be very helpful if someone can shed some light on #ReleaseStrategy association with example if possible.
Based on the comments, I am planning on the create a aggregator factory bean to see if works for my use-case
#Bean
#ServiceActivator(inputChannel = "sftpChannel")
public FactoryBean<MessageHandler> aggregatorFactoryBean( ) {
AggregatorFactoryBean aggregatorBean = new AggregatorFactoryBean();
aggregatorBean.setProcessorBean(new CustomAggregator());
aggregatorBean.setMethodName("aggregate");
aggregatorBean.setMessageStore(new SimpleMessageStore());
aggregatorBean.setReleaseStrategy(messageGroup -> {
return messageGroup.isComplete();
});
aggregatorBean.setOutputChannel(aggregatorFileChannel());
aggregatorBean.setExpireGroupsUponTimeout(true);
aggregatorBean.setGroupTimeoutExpression(new ValueExpression<>(1000L));
aggregatorBean.setSendPartialResultOnExpiry(false);
aggregatorBean.setExpireGroupsUponCompletion(true);
return aggregatorBean;
}
If you want to use an #Aggregator, #ReleaseStrategy and #CorrelationStrategy, consider to configure an AggregatorFactoryBean as a #Bean and apply a #SerivceActivator annotation on it for those inputChannel and outputChannel.
See docs for more info: https://docs.spring.io/spring-integration/docs/5.4.0-M2/reference/html/message-routing.html#aggregator-annotations
When using that style of configuration, #Aggregator, #CorrelationStrategy and #ReleasStrategy are usually in the same bean.
You can, however, define a ReleaseStrategyFactoryBean bean that will provide an implementation of ReleaseStrategy based on your POJO method.
setTarget(myRSBean);
It will find the annotation.
I am using pub sub integration with spring boot, for which my configuration class look like this:
#Configuration
public class PubSubConfiguration {
#Value("${spring.pubsub.topic.name}")
private String topicName;
#Bean
#ServiceActivator(inputChannel = "MyOutputChannel")
public PubSubMessageHandler messageSender(PubSubTemplate pubsubTemplate) {
return new PubSubMessageHandler(pubsubTemplate, topicName);
}
#MessagingGateway(defaultRequestChannel = "MyOutputChannel")
public interface PubsubOutboundGateway {
void sendToPubsub(String attribute);
}
}
So now, I was calling only sendToPubSub method which add payload into topic from my app, like this:
#Autowired
private PubSubConfiguration.PubsubOutboundGateway outboundGateway;
// used line in my code wherever is needed.
outboundGateway.sendToPubsub(jsonInString);
The above code is just meant for one topic which i loaded from application property file.
But now I wanted to make my topic name is dynamically added into messageSender, how to do that.
To override the default topic you can use the GcpPubSubHeaders.TOPIC header.
final Message<?> message = MessageBuilder
.withPayload(msg.getPayload())
.setHeader(GcpPubSubHeaders.TOPIC, "newTopic").build();
and modify your sendToPubsub(Message<byte[]> message) to use message as input.
Refer for more information
Consider creating a BeanFactory to generate a PubSubMessageHandler Bean given a topic name. PubSubMessageHandler also has a setTopic() method, which may be of use.
How can I send GET request for entity with custom MediaType?
For example I want to retrieve MyUserDTO and set MediaType to application/user+yml.
For now I have two separated actions. I can retrieve entity:
resource.get(MyUserDTO.class);
and can retrieve string:
resource.get(new MediaType("application", "user+yml");
But how to combine them? Or maybe there is some trick to configure Restlet to teach him how to work with custom MediaTypes.
In fact, you have the right approach but you don't use the right constructor of the class MediaType (new MediaType(name, description)).
To make your code work, you need to change it to this:
resource.get(new MediaType("application/user+yml"));
On the server side, you will get this:
#Get
public Representation getSomething() {
System.out.println(">> media types = " +
getRequest().getClientInfo().getAcceptedMediaTypes());
// Display this: media types = [application/user+yml:1.0]
(...)
}
You can leverage the extension support of Restlet by adding a value within the annotation Get. In your case, you need to add a custom extension as described below:
public class MyApplication extends Application {
public MyApplication() {
getMetadataService().addExtension(
"myextension", new MediaType("application/user+yml"));
(...)
}
#Override
public Restlet createInboundRoot() {
(...)
}
}
Now you can use the extension within your server resource:
#Get("myextension")
public Representation getSomething() {
(...)
}
This method will be used with the expected media type is application/user+yml.
Hope it helps you,
Thierry