How to inject ProducerTemplate - spring

I'm wondering how to properly use the #Produce annotation for a ProducerTemplate I have defined in one of my model beans.
If I add an #Autowired and this #Bean definition everything is peachy:
#Bean
ProducerTemplate producerTemplate() throws Exception {
ProducerTemplate producerTemplate = camelContext().createProducerTemplate();
producerTemplate.setDefaultEndpointUri("seda:workflowEntryPoint");
return producerTemplate;
}
But if I don't and only do
#Produce(uri = "seda:workflowEntryPoint")
private ProducerTemplate producer;
I get an NPE when trying to use it to call sendMessage(). So, what's the correct usage of the annotation?
Best,
Edoardo

As per camel's documentation , it creates a proxy implementing the interface that has been annotated with #Produce. Can you try to have a very simple interface with just one method as suggested in the documentation. Although, your code should work but I am suspecting that the ProducerTemplate has plenty of methods and bcoz of that the proxy creation does not happen

Related

Configure a Jackson's DeserializationProblemHandler in Spring environment [duplicate]

This question already has answers here:
Can't set ProblemHandler to ObjectMapper in Spring Boot
(2 answers)
Closed 4 years ago.
As I understood, Spring is already providing a bean for Jackson ObjectMapper. Therefore, instead of creating a new bean, I'm trying to customize this bean.
From this blog post, and then this Github project I used Jackson2ObjectMapperBuilder bean to achieve this customization.
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
Then, I was trying to customize the deserializer in order to make it lenient: if an exception is raised when deserializing a property, I want the result object's property to be null and let the deserialization continue (default is to fail on first property that cannot be deserialized).
I've been able to achieve that with a class NullableFieldsDeserializationProblemHandler that extends DeserializationProblemHandler (I do not think the code is relevant but if needed, I can share it).
The simplest way to register this handler is to use the .addHandler() method of ObjectMapper. But of course, doing like this, I would need to set that every time I inject and use the ObjectMapper. I'd like to be able to configure handler so that every time the ObjectMapper is auto-wired, the handler is already present.
The best solution I came up with so far is to use a #PostConstruct annotation only to register the problem handler.
#Configuration
public class JacksonConfiguration implements InitializingBean {
#Autowired private ObjectMapper objectMapper;
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
#Override
public void afterPropertiesSet() {
objectMapper.addHandler(new NullableFieldsDeserializationProblemHandler());
}
}
But the problem of this solution is that it seems I can still access an autowired ObjectMapper that doesn't have yet registered the problem handler (I can see it happening after when I need it in debug mode).
Any idea how I should register this handler? I've noticed Jackson2ObjectMapperBuilder has a .handlerInstantiator() but I couldn't figure out how to use it.
Note I've also tried with Jackson2ObjectMapperBuilderCustomizer since I'm using Spring Boot but had no better results.
It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. The handlerInstanciator() method is for something else.
However, it's possible to do so by registering a Jackson module:
the builder has a modules() method
the module has access via setupModule() to a SetupContext instance, which has a addDeserializationProblemHandler() method
This works:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.modules(new MyModule());
}
};
}
private static class MyModule extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
// Required, as documented in the Javadoc of SimpleModule
super.setupModule(context);
context.addDeserializationProblemHandler(new NullableFieldsDeserializationProblemHandler());
}
}
What about writing a bean like this:
#Configuration
public class ObjectMapperConfiguration {
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// jackson 1.9 and before
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// or jackson 2.0
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This is for global configuration. If, instead, what you want to do is to configure the feature for specific a class, use this annotation above the class definition:
#JsonIgnoreProperties(ignoreUnknown = true)

not able to replace spring bean with mock in camel route

using #Profile I am able to mock the spring bean, however in the camel route which mock bean method is not invoked. I am using SpringJUnit4ClassRunner.class and using #ActiveProfile
Below is the route in which I want to replace, cancelSubscriptionTransformer, myBeanClient, extendedClient beans with my mock beans in unit testing.
from("{{cancelSubscriptionFromRMQUri}}").routeId("cancelSubscriptionRoute")
.unmarshal().json(JsonLibrary.Jackson, Subscription.class)
.bean("cancelSubscriptionTransformer", "toKbCancelSubscription")
.choice()
.when().simple("${body.serviceType} == 'subscriptions'")
.bean("myBeanClient", "cancelSubscription(${body.subscriptionId}, ${body.createdBy}, ${body.reason}, ${body.comment})")
.bean("extendedClient", "retrieveSubscription(${body.subscriptionId}, ${body.externalKey})")
.marshal(json)
.to("{{cancelSubscriptionTORMQUri}}")
.when().simple("${body.serviceType} == 'usage'")
.bean("myBeanClient", "cancelSubscription(${body.subscriptionId}, ${body.dateTime},null, null, -1, ${body.createdBy}, ${body.reason}," +
" ${body.comment})")
.endChoice();
Below is how I define my ExtendedClientMock, I use the same approach for the rest of the mock beans
#Profile("test")
#Primary
#Repository
public class ExtendedClientMock extends ExtendedClient {
public Subscription retrieveSubscription(UUID subscriptionid, String sdpSubscriptionId) throws MyClientException {
Subscription subs=new Subscription();
subs.setProductName("test");
return subs;
}
}
Below is the code for unit testing:
#ActiveProfiles({"test", "aop"})
#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = CancelSubscriptionRouteTest.class)
#EnableAutoConfiguration
#ComponentScan
#ContextConfiguration(classes = { BillingServicesApplication.class })
#UseAdviceWith
public class CancelSubscriptionRouteTest {
#Autowired
protected CamelContext camelContext;
#Autowired
private CancelSubscriptionTransformer cancelSubscriptionTransformer;
#Autowired
private ExtendedClient extendedClient;
#Autowired
private MyBeanClient myBeanClient;
#EndpointInject(uri = "{{cancelSubscriptionTORMQUri}}")
private MockEndpoint cancelSubscriptionTORMQUriEndpoint;
#EndpointInject(uri = "{{cancelSubscriptionFromRMQUri}}")
private ProducerTemplate cancelSubscriptionFromRMQUriEndpoint;
#Inject
private ObjectMapperContextResolver objectMapperContextResolver;
#Test
#DirtiesContext
public void testCancelSubscriptionRoute() throws Exception {
cancelSubscriptionTORMQUriEndpoint.expectedMessageCount(1);
ObjectMapper objectMapper= objectMapperContextResolver.getContext(ObjectMapperContextResolver.class);
String jsonString=objectMapper.writeValueAsString(subscription);
CancelSubscription cancelSubscription=cancelSubscriptionTransformer.toKbCancelSubscription(subscription);
Assert.assertEquals("mock auto created by amel",cancelSubscription.getComment());
cancelSubscriptionFromRMQUriEndpoint.sendBody(" {{cancelSubscriptionFromRMQUri}}",jsonString);
cancelSubscriptionTORMQUriEndpoint.assertIsSatisfied();
}
}
The Assert.assertEquals("mock auto created by amel",cancelSubscription.getComment()); gets statisfied by calling cancelSubscriptionTransformer.toKbCancelSubscription which is invoked on the mock bean. however when message is sent to cancelSubscriptionFromRMQUriEndpoint.sendBody, the route is invoked and the actual beans in the route are not being replaced by mock beans
#MickaƫlB looks like the issue was I was not extending the correct bean and also I had to use #Inject in my route builder spring bean and use bean name instead of string format of bean name
This is very old but I ran into this issue.
The answer is that instead of .Bean(MyBean.class, "myMethod"), you should use .to("bean:myBean?method=myMethod"). The reason is that the first way, Camel will instantiate the bean. The 2nd way, Spring has control of the bean and camel will look it up. Therefore you can use Spring mockBean to change it.
I'm using Camel version 3 now by the way, and beanRef is removed. If you used beanRef, replace it with .to("bean:myBean?method=myMethod).

How to use #Autowired in manually new instance in Spring Boot

As we know, #Autowired can be used only in instances managed by spring container, If you new an instance, and #Autowired member in it will not effect.
But I think In some situation, new an instance can't be avoid.
Such as a RunnableTask. which contains the DAOService, which managed by spring. Because the task is manually new. So I can't use the DAOService in the ThreadTask.
So I want to know how to get the ApplicationContext in Spring Boot, and so I can get the bean by context.getBean().
I knew in main() I can Autowired the ApplicationContext. But I can't pass the context as a parameter everywhere!
I want to get the ApplicationContext anywhere.
Any help would be greatly appreciated.
How about using a factory object managed by spring?
class TheBeanYouWant {
private Integer beanSupposeToAutowired;
public TheBeanYouWant(Integer bean) {
this.beanSupposeToAutowired = bean;
}
}
#Component
class TheBeanFactory {
#Autowired
private Integer beanAutowired;
public TheBeanYouWant newBean() {
return new TheBeanYouWant(beanAutowired);
}
}
I want to get the ApplicationContext anywhere.
That's an anti-pattern. Try to avoid it.
Why can't you inject your DAOService into the thing that creates the RunnableTask?

Spring 4 Using #Lazy and #Resource annotations together

I would like to lazy load a resource within a lazy loaded configuration class. it seems that it is not currently possible with Spring 4.1.
#Configuration
#Lazy
public class ModificationConfiguration {
#Resource(name="modProps")
#Lazy
Map<String, String> props;
#Bean
Map<String, String> modProps(){
...
}
}
from the Spring documentation
In addition to its role for component initialization, the #Lazy annotation may also be placed on
injection points marked with #Autowired or #Inject. In this context, it leads to the injection
of a lazy-resolution proxy.
It seems natural to also want this available with #Resource as I can't use #Autowired on a Map<String, String>. Without the #Lazy being available on #Resource the modProps() bean is created straight away.
Is there a technical reason why it is not currently possible to do this?

Set ConnectionFactory for Camel JMS Producer: camel-jms Vs camel-sjms

Ciao, my basic requirement is to have a route where I can send a message and this is put on a JMS Queue. The camel context run in a JavaEE 6 container namely JBoss AS 7.1.1 so it's HornetQ for JMS which ships with it; I start the context via bootstrap singleton but I don't use the camel-cdi. So far I've been using camel-jms component, but now I'm looking to migrate to the camel-sjms if possible because springless.
My question is: what is the proper way to configure the ConnectionFactory for camel-sjms in this JavaEE scenario, please?
With the camel-jms I could put this in the endpoint URL, as simple as .to("jms:myQueue?connectionFactory=#ConnectionFactory"). With the camel-sjms instead it seems to me that I need to create an instance of the SJMSComponent myself, set the connectionFactory, and set this instance in the camel context before starting it.
I have code below for the camel-jms Vs camel-sjms case, and I would like to know if I "migrated" the setting of the ConnectionFactory correctly. Thanks.
For camel-jms this was done as:
#Singleton
#Startup
public class CamelBootstrap {
private CamelContext camelContext;
private ProducerTemplate producerTemplate;
public CamelContext getCamelContext() {
return camelContext;
}
public ProducerTemplate getProducerTemplate() {
return producerTemplate;
}
#PostConstruct
public void init() throws Exception {
camelContext = new DefaultCamelContext();
camelContext.addRoutes(new MyCamelRoutes());
camelContext.start();
producerTemplate = camelContext.createProducerTemplate();
}
}
Nothing special, and in the MyCamelRoutes I could do route configuration using:
.to("jms:myQueue?connectionFactory=#ConnectionFactory")
For camel-sjms now I have to modify the bootstrap singleton with:
#Singleton
#Startup
public class CamelBootstrap {
#Resource(mappedName="java:/ConnectionFactory")
private ConnectionFactory connectionFactory;
private CamelContext camelContext;
private ProducerTemplate producerTemplate;
public CamelContext getCamelContext() {
return camelContext;
}
public ProducerTemplate getProducerTemplate() {
return producerTemplate;
}
#PostConstruct
public void init() throws Exception {
camelContext = new DefaultCamelContext();
SjmsComponent sjms = new SjmsComponent();
sjms.setConnectionFactory(connectionFactory);
camelContext.addComponent("sjms", sjms);
camelContext.addRoutes(new MyCamelRoutes());
camelContext.start();
producerTemplate = camelContext.createProducerTemplate();
}
}
and please notice #Resource for the connectionFactory this is passed as a reference to the SjmsComponent instance, which is passed to the camelContext. And then in the MyCamelRoutes I could use the sjms while do route configuration using:
.to("sjms:myQueue")
The code seems to work correctly in both scenario, but as I understand the configuration of the ConnectionFactory is quite susceptible of performance issue if not done correctly, therefore I prefer to ask if I migrated to the camel-sjms correctly for my JavaEE scenario. Thanks again
Performance issues are likely to happend if you don't do caching/pooling of JMS resources. Caching is typically configured by wrapping a ConnectionFactory in some Caching ConnectionFactory library - or by handing over the control to the application server.
Camel SJMS includes built-in pooling. However, if you have a container managed resource to handle JMS connections, you should probably consider using it. SJMS has some facilities to deal with that, ConncetionResource instead of ConnectionFactory.

Resources