Using property parameter in Camel annotation - java

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>

Related

Spring boot custom resolver for class variable

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.

Select data from table using apache camel

I want to be able to continuously poll database to select data from my table using Camel. I have configured Camel in my spring boot application. Here are the configurations that I am using
build.gradle:
implementation 'org.apache.camel:camel-jdbc-starter:2.24.0'
implementation 'org.apache.camel:camel-sql-starter:2.24.0'
RouteBuilder class:
#Component
public class CustomCamelConfig extends RouteBuilder {
Logger log = LoggerFactory.getLogger(getClass());
#Autowired
RouteDataMapper dataMapper;
#Override
public void configure() throws Exception {
from("timer://timer1?period=2s").log("Called every 2 seconds")
.setBody(constant("select * from tenders"))
.bean(dataMapper,"generateSalesData")
.noDelayer();
}
}
Bean:
#Component
public class RouteDataMapper {
Logger log = LoggerFactory.getLogger(getClass());
public void generateSalesData(String payload) {
log.info("RouteDataMapper - [generateSalesData]");
log.info("payload : {}", payload);
}
}
application.properties
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:#xxx:xxx/zzz
spring.datasource.username=zzz
spring.datasource.password=zzz
Issue I am facing is that, when I print the bean method parameter (generateSalesData(String payload)), I get the query string itself ("select * from tenders") and not the value from the table. The setBody() in configure method doesn't accept a sql:select .. statement, shows "The method setBody(Expression) in the type ProcessorDefinition is not applicable for the arguments (String)".
I am new to camel. Could anyone please let me know what is that I am missing to do.
The route you have, as written, is simply setting the body of the message to a string which happens to look like a SQL. Camel has no idea, since you haven't use the right component.
Instead of
.setBody(constant("select * from tenders"))
you need to tell Camel to use the sql component
.to("sql:select * from tenders")
The result that's passed on to RouteDataMapper will be a List<Map<String, Object>>, as described in the documentation. You'd need to adjust your method parameter accordingly.

Spring boot: push message to specific topic for each request

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.

Dynamic Queues on RabbitListener Annotation

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.

Camel REST Bean Chaining

I currently have a REST route builder that looks as follows:
rest("/v1")
.post("/create")
.to("bean:myAssembler?method=assemble(${in.header.content})")
.to("bean:myService?method=create(?)");
The bean myAssembler takes raw JSON and transforms this into MyObject. This object is then returned and I want it forwarded onto myService as a parameter for its create method.
How can I do this using Camel?
Your beans will bind automatically to specific parameters like Exchange if you put it as a parameter to a method (see complete list Parameter binding).
One solution would be to define your route and beans like this:
restConfiguration()
.component("restlet")
.bindingMode(RestBindingMode.json)
.skipBindingOnErrorCode(false)
.port(port);
rest("/v1")
.post("/create")
.route()
.to("bean:myAssembler?method=assemble")
.to("bean:myService?method=create");
with beans like this
public class MyAssembler {
public void assemble(Exchange exchange) {
String content = exchange.getIn().getHeader("content", String.class);
// Create MyObject here.
MyObject object; // ...transformation here.
exchange.getOut().setBody(object);
}
}
and this
public class MyService {
public void create(MyObject body) {
// Do what ever you want with the content.
// Here it's just log.
LOG.info("MyObject is: " + body.toString());
}
}
The dependencies for shown configuration are
org.apache.camel/camel-core/2.15.3
org.apache.camel/camel-spring/2.15.3
org.apache.camel/camel-restlet/2.15.3
javax.servlet/javax.servlet-api/3.1.0
org.apache.camel/camel-jackson/2.15.3
org.apache.camel/camel-xmljson/2.15.3
xom/xom/1.2.5
Actually, if last bean returns MyObject, next bean can accept and bind MyObject as first arg. You don't need to put it into Exchange body or anything.

Categories

Resources