Intercepting Camel #Consume - spring-boot

I have an existing application which is using Apache Camel to send messages to SEDA endpoints for Async processing and would like to intercept calls to these methods for instrumentation.
Example code:
#Component
public class CamelMessageService {
private static final Logger log = LoggerFactory.getLogger(CamelMessageService.class);
public static final String QUEUE = "seda:message";
#Resource
private ProducerTemplate producerTemplate;
public void send() {
producerTemplate.sendBody(QUEUE, "Hello World");
}
#Consume(uri = QUEUE)
public void receive(#Body String payload) {
log.info("Received message {}", payload);
}
}
Is there a way to intercept all methods annotated with #Consume before invoking. I looked at an AOP based approach but this seemed to fall over due to existing Spring/Camel proxying of these classes.
I have also tried using various Camel Intercept routes and adding a custom InterceptStrategy but it seems that the example above does not create a Camel route so is not intercepted.
EDIT: On further investigation in seems that these endpoints can be Intercepted using camel but only if there is at least 1 other route defined in the Camel Context?
#Component
class MyRouteBuilder extends RouteBuilder {
private static final Logger log = LoggerFactory.getLogger(MyRouteBuilder.class);
public void configure() {
interceptSendToEndpoint(CamelMessageService.QUEUE)
.process(exchange -> log.info("intercepted exchange {}", exchange));
from("timer:hello?period={{timer.period}}").routeId("hello").routeGroup("hello-group")
.transform().simple("yo")
.filter(simple("${body} contains 'foo'"))
.to("log:foo")
.end()
.to("stream:out");
}
}
If I run this app with the Route Builder above then my interceptor is triggered if however I comment out the hello route it is not?
Any help would be greatly appreciated.

Related

Spring Data Rest - How to receive Headers in #RepositoryEventHandler

I'm using the latest Spring Data Rest and I'm handling the event "before create". The requirement I have is to capture also the HTTP Headers submitted to the POST endpoint for the model "Client". However, the interface for the RepositoryEventHandler does not expose that.
#Component
#RepositoryEventHandler
public class ClientEventHandler {
#Autowired
private ClientService clientService;
#HandleBeforeCreate
public void handleClientSave(Client client) {
...
...
}
}
How can we handle events and capture the HTTP Headers? I'd like to have access to the parameter like Spring MVC that uses the #RequestHeader HttpHeaders headers.
You can simply autowire the request to a field of your EventHandler
#Component
#RepositoryEventHandler
public class ClientEventHandler {
private HttpServletRequest request;
public ClientEventHandler(HttpServletRequest request) {
this.request = request;
}
#HandleBeforeCreate
public void handleClientSave(Client client) {
System.out.println("handling events like a pro");
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements())
System.out.println(names.nextElement());
}
}
In the code given I used Constructor Injection, which I think is the cleanest, but Field or Setter injection should work just as well.
I actually found the solution on stackoverflow: Spring: how do I inject an HttpServletRequest into a request-scoped bean?
Oh, and I just noticed #Marc proposed this in thecomments ... but I actually tried it :)

Spring Microservices issue with #HystrixCommand

We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}

Apache Camel Spring Javaconfig Unit Test No consumers available on endpoint

I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!

How to do integration test with activemq and spring3?

I have a code like this, but I'm not sure how would I test this piece of of code that I extracted from my project. I'm using Spring3 and ActiveMQ. And I'm using spring to do remote HTTPInvoker that's why I have the GateWay. So, when I call method submit in my Gateway, it's going to send a JMS message via JMSDispatcher. How would you inject JmsTemplate to Gateway? As far as I know, if I want to test JMS I have to configure it in application-context.xml in Spring and inject overridden JmsTemplate. So, I could test the message inside the queue? But I can't inject JmsTemplate to Gateway since Mockito will complain about not having that field inside Gateway.
public class Gateway {
#Autowired
private ProcessController processController;
public void submit() {
processControllerFactory.submit();
}
}
public ProcessController {
#Autowired
private JMSDispatcher jmsDispatcher;
public void submit() {
// do something
jmsDispatcher.send(message);
}
}
public JMSDispatcher {
#Autowired
#Qualifier("someJmsTemplate")
private JmsTemplate jmsTemplate;
public void send(MessageCreator message) {
jmsTemplate.send(message);
}
}

Problems injecting a BayeuxService into another class with annotations

I have a web app that is using Bayeux to handle Comet connections. I initialize a BayeuxServer and tie it into Spring annotations and it all works fine, listening on selected channels and responding.
I have a Jersey annotated class and an annotated Bayeux service as shown below. The idea is I wanted to be able to control resources via Rest from an individual web app, and then right after the resource is changed, do a server push via Comet to all other applicable clients to tell them to update their information.
Here is the problem: A Bayeux Service is created when the webapp is deployed, setting up proper channels to listen on and monitoring clients. There should only be one instance of this. When Jersey attempts to use the Bayeux service it creates a whole new service, when it should be using the original one. This new service doesn't have the BayeuxServer properly injected so I can't access client information through it.
It makes since that this should be doable, but I don't seem to understand how to inject these things properly via annotations. Can anyone point me in the right direction?
Jersey Annotated Class:
#Path("JsonTest")
public class JsonTest {
#Context
Request request;
#Context
UriInfo uriInfo;
#Context
ResourceContext resourceContext;
protected final Logger log = Logger.getLogger(getClass());
public JsonTest() {
}
#DELETE
#Path("{id}")
public void deleteJson(#PathParam("id") String id) {
JsonTestDao.instance.getModel().remove(id);
log.info("Deleted Json..." + id);
log.info("New json: " + JsonTestDao.instance.getModel().toString());
JsonTestService jsonTestService = resourceContext.getResource(JsonTestService.class);
jsonTestService.sendUpdate();
}
}
BayeuxService:
#Named
// Singleton here didn't seem to make a difference
#Service
public class JsonTestService {
protected final Logger log = Logger.getLogger(getClass());
#Inject
private BayeuxServer bayeux;
#Session
private ServerSession serverSession;
#PostConstruct
public void init() {
log.info("Initializing JsonTest Bayeux HelloService...");
log.info("Current sessions are: " + bayeux.getSessions().toString());
}
#Listener("/cometd/JsonTest")
public void jsonTestHandler(ServerSession remote, ServerMessage.Mutable message) {
}
public void sendUpdate() {
//bayeux.newMessage(); // Need a method that the Jersey class can call to notify changes
log.info("Bayeux server should be sending an update now...");
}
#PreDestroy
public void destroy() {
log.info("Destroying JsonTest Bayeux HelloService...");
}
}
See Jersey and spring integration - bean Injections are null at runtime.
Another question I asked. Both of these stem from the same problem involving properly setting the Jersey dependency and integrating it with spring.

Resources