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.
Related
I'm trying to achieve something like this:
#Controller
public SomeController {
#CustomConfig("var.a")
private String varA;
#CustomConfig("var.b")
private String varB;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String get() {
return varA;
}
}
CustomConfig would be an #Interface class that accepts one value parameter. The reason why we are not using #Value is because this will not come from config file but from API (such as https://getconfig.com/get?key=var.a). So we are going to make HTTP request to inject it.
So far I've only manage to make something work if the varA and varB is inside get() method as parameter, by using below in a class that extends WebMvcConfigurerAdapter:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
CustomConfigResolver resolver = new CustomConfigResolver();
argumentResolvers.add(resolver);
}
And inside CustomComfigResolver.resolveArgument() we would do the HTTP query, but that's not really what we wanted, we need it to be injected as class variable.
Does anyone have experience in resolving it at class variable level?
Thank you
This could work if you use #Value instead of your own custom annotation. This uses the built in environment:
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class TcpIpPropertySourceConfig implements InitializingBean {
#Autowired
private ConfigurableEnvironment env;
#Autowired
private RestTemplate rest;
public void afterPropertiesSet() {
// Call your api using Resttemplate
RemoteProperties props = //Rest Call here;
// Add your source to the environment.
MutablePropertySources sources = env.getPropertySources();
sources.addFirst(new PropertiesPropertySource("customSourceName", props)
}
}
What you are trying to achieve is difficult when you start to consider "unhappy" scenarios. Server down / not reachable. You need to account for all of that in the method above.
I would highly recommend to instead use Spring Cloud Config. Great guide on that is here: https://www.baeldung.com/spring-cloud-configuration
This provides:
- Reloading of your #Value() properties, so no custom annotation needed.
- A more stable server and great Spring integration out of the box.
Best of all, it is easy to apply Retries and Backoffs if the configuration server goes down (see https://stackoverflow.com/a/44203216/2082699). This will make sure your app doesn't just crash when the server is not available.
I'd like to use queue names using a specific pattern, like project.{queue-name}.queue. And to keep this pattern solid, I wrote a helper class to generate this name from a simple identifier. So, foo would generate a queue called project.foo.queue. Simple.
But, the annotation RabbitListener demands a constant string and gives me an error using my helper class. How can I achieve this (or maybe another approach) using RabbitListener annotation?
#Component
public class FooListener {
// it doesn't work
#RabbitListener(queues = QueueName.for("foo"))
// it works
#RabbitListener(queues = "project.foo.queue")
void receive(final FooMessage message) {
// ...
}
}
To create and listen to a queue name constructed from a dynamic UUID, you could use random.uuid.
The problem is that this must be captured to a Java variable in only one place because a new random value would be generated each time the property is referenced.
The solution is to use Spring Expression Language (SpEL) to call a function that provides the configured value, something like:
#RabbitListener(queues = "#{configureAMQP.getControlQueueName()}")
void receive(final FooMessage message) {
// ...
}
Create the queue with something like this:
#Configuration
public class ConfigureAMQP {
#Value("${controlQueuePrefix}-${random.uuid}")
private String controlQueueName;
public String getControlQueueName() {
return controlQueueName;
}
#Bean
public Queue controlQueue() {
System.out.println("controlQueue(): controlQueueName=" + controlQueueName);
return new Queue(controlQueueName, true, true, true);
}
}
Notice that the necessary bean used in the SpEL was created implicitly based on the #Configuration class (with a slight alteration of the spelling ConfigureAMQP -> configureAMQP).
Declare a magic bean, in this case implicitly named queueName:
#Component
public class QueueName {
public String buildFor(String name) {
return "project."+name+".queue";
}
}
Access this using a "constant string" that will be evaluated at runtime:
#RabbitListener(queues = "#{queueName.buildFor(\"foo\")}")
If {queue-name} would came from yml file - it should work:
#RabbitListener(queues = "${queue-name}")
public void receiveMessage(FooMessage message) {
}
Spring will inject value from application.yml.
I currently have a JMSListener as shown below. It uses a selector of a value in a properties file. This works fine.
#JmsListener(destination = "myQueueDest",
selector = MyHeaders.SELECTOR_KEY + " = '${myapp.selector_val}'")
private void consumeData(MyCustomObj mycustomObj) { }
I have a need now to use a dynamic selector with a value in memory, rather than the spring property. Is there a way to use JMSListener (or some other listener mechnaism) to do a selection off the ActiveMQ queue?
Update:
It may be possible to assign an ID to my #JMSListener, and then retrieve it from my JmsListenerEndpointRegistry bean. Get the listener container by ID, cast it to DefaultMessageListenerContainer, and call setMessageSelector(), although I'm not entirely sure if this will work yet.
This requires setting my DefaultJmsListenerContainerFactory bean to have the cache level of CACHE_SESSION.
But this doesn't seem to work, as the listener picks up all messages, regardless of what I set the message selector to be.
JMS specification says the selection string must be provided while creating a consumer. So the answer is NO. Consumer must be closed and recreated with a different selection string to receive messages that match a different selection criteria.
If using JMS API is not a must for your project, then you could explore using Active MQ's native APIs. I am sure the API will have a way to specify a different selection string every time a receive is called. IBM MQ's native API provides such a functionality.
As stated in one of the comments:
the javadoc for setMessageSelector says it can be set at runtime. http://docs.spring.io/spring-framework/docs/2.5.x/api/org/springframework/jms/listener/AbstractMessageListenerContainer.html#setMessageSelector(java.lang.String)
This example explains how to setup at startup but doing it dynamically should be possible with a few more tricks:
#EnableJms
#Configuration
public class JmsConfiguration {
#Value("${my.int.param:100}")
private int config;
#Bean
public MessageConverter messageConverter() {
final MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
#Bean
public JmsListenerContainerFactory<?> specialQueueListenerFactory() {
final String selector = "foo > " + config;
final DefaultJmsListenerContainerFactory factory = new CustomJmsListenerContainerFactory(selector);
factory.setMessageConverter(messageConverter());
return factory;
}
}
And the CustomJmsListenerContainerFactory
public class CustomJmsListenerContainerFactory extends DefaultJmsListenerContainerFactory {
private final String selector;
public CustomJmsListenerContainerFactory(String jmsSelector) {
this.selector = jmsSelector;
}
#Override
public DefaultMessageListenerContainer createListenerContainer(JmsListenerEndpoint endpoint) {
final DefaultMessageListenerContainer instance = super.createListenerContainer(endpoint);
instance.setMessageSelector(selector);
return instance;
}
}
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();
}
I found this approach from Camel's website which shows how to use #Produce annotation to create an pseudo-method call for sending message to JMS queue:
public interface MyListener {
String sayHello(String name);
}
public class MyBean {
#Produce(uri = "activemq:foo")
protected MyListener producer;
public void doSomething() {
// lets send a message
String response = producer.sayHello("James");
}
}
However, in my scenario, I need the ability to set different JMS queue for different environment. Therefore the JMS queue in:
#Produce(uri = "activemq:foo")
needs to come from a property file rather than hardcoded.
How can I achieve this? Is there any other ways I can use to achieve without using annotation?
Thank you very much.
Read the documentation about using property placeholders
http://camel.apache.org/using-propertyplaceholder.html
When you setup this, then you can use placeholders in the uri string you define with the annotation
#Produce(uri = "activemq:{{myQueue}}")
Use the ProducerTemplate described here:
http://camel.apache.org/producertemplate.html
#Bean
public class MyBean {
#Autowired
ProducerTemplate template
public void doSomething() {
// lets send a message
template.sendBody("your_mq_address", "James");
}
}
Remember to define the template in the camel context:
<camelContext xmlns="http://camel.apache.org/schema/spring" id="camelContext">
<contextScan/>
<template id="template"/>
</camelContext>