Startup race-condition spring-batch with spring-integration - java

For an application I'm using spring-boot, spring-batch and spring-integration.
My problem is that, on startup, when everything is auto configured and auto wired, my spring-integration #MessageEndpoints, connected to RabbitMQ, are starting to process available messages on its queues. Based on the received messages, these #MessageEndpoints are trying to start specific spring-batch Jobs, looked up, through it's auto wired JobRegistery.
Because of all the auto configuration not all jobs are yet registered to the used JobRegistery! (A few seconds later they will be).
After all spring-batch jobs are registered to the JobRegistery then the #MessageEndpoints should start. Is this possible? Maybe thought the ContextRefreshEvent?

I just looked at the code and the problem appears to be that the AutomaticJobRegistrar uses the context refreshed event to load the jobs; it should really implement SmartLifecycle and start in an "early-ish" phase.
The spring integration components implement SmartLifecycle and inbound endpoints (such as the rabbit endpoints) start in a late phase.
I suggest you open a JIRA against Batch - there's a TODO in the AutomaticJobRegistrar code:
// TODO: With Spring 3 a SmartLifecycle is started automatically
As a work-around, you could set autoStartup to false on the inbound adapter(s) and use your own event listener to start them on the context refreshed event.
The listener should implement Ordered; the AutomaticJobRegistrar is Ordered.LOWEST_PRECEDENCE so you want to run with a higher precedence (lower priority).

Gary Russell thank you for your push in the right direction! I have solved it as follow:
Disable autostart for the inbound channels in my integration XML.
After SpringApplication.run() if start manually these inbound channels, found through getBeansOftype().
.
public static void main(final String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBatchApplication.class, args);
startInboundChannelAdapters(context);
}
private static void startInboundChannelAdapters(ConfigurableApplicationContext context) {
Map<String, AmqpInboundChannelAdapter> adapters = context.getBeansOfType(AmqpInboundChannelAdapter.class);
adapters.forEach((name, adapter) -> {
if (!adapter.isRunning()) {
adapter.start();
}
});
}

Related

Too many active consumers for ActiveMQ queue

My Spring app consumes ActiveMQ queue. There are two approaches possible. Initial part of ActiveMQ integration is the same for the both approaches:
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory();
}
#Bean
public Queue notificationQueue() {
return resolveAvcQueueByJNDIName("java:comp/env/jms/name.not.important.queue");
}
Single thread approach:
#Bean
public IntegrationFlow orderNotify() {
return IntegrationFlows.from(Jms.inboundAdapter(connectionFactory()).destination(notificationQueue()),
c -> c.poller(Pollers.fixedDelay(QUEUE_POLLING_INTERVAL_MS)
.errorHandler(e -> logger.error("Can't handle incoming message", e))))
.handle(...).get();
}
But I want to consume messages using the several worker threads, so I refactored the code from Inbound adapter to Message driven channel adapter:
#Bean
public IntegrationFlow orderNotify() {
return IntegrationFlows.from(Jms.messageDriverChannelAdapter(connectionFactory()).configureListenerContainer(c -> {
final DefaultMessageListenerContainer container = c.get();
container.setMaxConcurrentConsumers(notifyThreadPoolSize);
}).destination(notificationQueue()))
.handle(...).get();
}
The problem is that the app doesn't stop ActiveMQ's consumer when it being redeployed into Tomcat or being restarted for the second approach. It creates new consumer during it's startup. But all new messages are being routed to the old "dead" consumer so they're sat in the "Pending messages" section and never being dequeued.
What can be the problem here?
You have to stop Tomcat fully, I believe. Typically during redeploy for the application, the Spring container should be stopped and clear properly, but looks like that's not your case: there is something missed for the Tomcat redeploy hook. Therefore I suggest to stop it fully.
Another option is to forget external Tomcat and just migrate to Spring Boot with its ability to start embedded servlet container. This way there is not going to be any leaks after rebuilding and restarting application.

Configuring Order of JmsListener Execution

If I have more than one #JmsListener method in a Spring Boot application is there a way to explicitly declare either listener to consume all its messages from its queue before the other?
In other words, if I have
#JmsListener(destination = "queueOne")
public void processOrder1(String message) {. . .}
#JmsListener(destination = "queueTwo")
public void processOrder2(String message) {. . .}
is there configuration available to have processOrder1() run to completion, shut it down, then have processOrder2() run? Or will processOrder2() always execute after processOrder1()(which is what the debugger suggests)? Or are they somehow running separately independent of each other?
If they cannot be configured in such a way I'd like to know why not.
Not with configuration - each listener gets its own independent asynchronous listener container.
You could configure the container factory with autoStartup set to false and start each container programmatically - get a reference to the container from the container registry using the listener's id attribute and start()/stop() it.
One difficulty is how you would "know" that the first one has completed its work.

Apache Camel: Notification on CamelContext stop

How can I get a notification when a CamelContext is stopped, the API has addStartupListener but nothing similar for when the context is stopped?
This question was also asked on the Camel mailing list
http://camel.465427.n5.nabble.com/Notification-on-CamelContext-stop-tp5729300.html
There is several ways. One as Evgeniy Dorofeev shows with the lifecycle strategy.
http://camel.apache.org/maven/current/camel-core/apidocs/org/apache/camel/spi/LifecycleStrategy.html
And another is the event notifier which is using Event objects and has more events than the lifecycle strategy. For example as shown in this example: http://camel.apache.org/eventnotifier-to-log-details-about-all-sent-exchanges.html
And if you use Spring or Blueprint XML then you can configure them by defining a bean and Camel will pick it up as documented here: http://camel.apache.org/advanced-configuration-of-camelcontext-using-spring.html
this is one of the ways
camelContext.addLifecycleStrategy(new LifecycleStrategySupport() {
#Override
public void onContextStop(CamelContext context) {
}
});
see more in Claus Ibsen's answer, he is the author of Camel in Action http://www.manning.com/ibsen/

java ee background service

You'll have to excuse me if I'm describing this incorrectly, but essentially I'm trying to get a service-like class to be instantiated just once at server start and to sort of "exist" in the background until it is killed off at server stop. At least from what I can tell, this is not exactly the same as a typical servlet (though I may be wrong about this). What's even more important is that I need to also be able to access this service/object later down the line.
As an example, in another project I've worked on, we used the Spring Framework to accomplish something similar. Essentially, we used the configuration XML file along with the built-in annotations to let Spring know to instantiate instances of some of our services. Later down the line, we used the annotation #Autowired to sort of "grab" the object reference of this pre-instantiated service/object.
So, though it may seem against some of the major concepts of Java itself, I'm just trying to figure out how to reinvent this wheel here. I guess sometimes I feel like these big app frameworks do too much "black-box magic" behind the scenes that I'd really like to be able to fine-tune.
Thanks for any help and/or suggestions!
Oh and I'm trying to run this all from JBoss 6
Here's one way to do it. Add a servlet context listener to your web.xml, e.g.:
<listener>
<listener-class>com.example.BackgroundServletContextListener</listener-class>
</listener>
Then create that class to manage your background service. In this example I use a single-threaded ScheduledExecutorService to schedule it to run every 5 minutes:
public class BackgroundServletContextListener implements ServletContextListener {
private ScheduledExecutorService executor;
private BackgroundService service;
public void contextInitialized(ServletContextEvent sce) {
service = new BackgroundService();
// setup single thread to run background service every 5 minutes
executor = Executors.newSingleThreadScheduledExecutor();
executor.scheduleAtFixedRate(service, 0, 5, TimeUnit.MINUTES);
// make the background service available to the servlet context
sce.getServletContext().setAttribute("service", service);
}
public void contextDestroyed(ServletContextEvent sce) {
executor.shutdown();
}
}
public class BackgroundService implements Runnable {
public void run() {
// do your background processing here
}
}
If you need to access the BackgroundService from web requests, you can access it through the ServletContext. E.g.:
ServletContext context = request.getSession().getServletContext();
BackgroundService service = (BackgroundService) context.getAttribute("service");
Have you considered using an EJB 3.1 Session bean? These can be deployed in a war file, and can be annotated with #Singleton and #Startup.
A number of annotations available with EJB 3.1 are designed to bring Spring goodies into the Java EE framework. It may be the re-invention you're considering has been done for you.
If you must roll your own, you can create a servlet and configure it start up when the application does using load-on-startup. I built a system like that a few years ago. We then used the new(ish) java.util.concurrent stuff like ExecutorService to have it process work from other servlets.
More information about what you're trying to do, and why the existing ways of doing things is insufficient, would be helpful.
You can use messaging for that. Just send message to the queue, and let the message listener do the processing asynchronously in the background.
You can use JMS for the implementation, and ActiveMQ for the message broker.
Spring has JMSTemplate, JMSGateWaySupport API to make JMS Implementation simple
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/jms.html

Message Driven Bean Selectors (JMS)

I have recently discovered message selectors
#ActivationConfigProperty(
propertyName="messageSelector",
propertyValue="Fragile IS TRUE")
My Question is: How can I make the selector dynamic at runtime?
Lets say a consumer decided they wanted only messages with the property "Fragile IS FALSE"
Could the consumer change the selector somehow without redeploying the MDB?
Note: I am using Glassfish v2.1
To my knowledge, this is not possible. There may be implementations that will allow it via some custom server hooks, but it would be implementation dependent. For one, it requires a change to the deployment descriptor, which is not read after the EAR is deployed.
JMS (Jakarta Messaging) is designed to provide simple means to do simple things and more complicated things to do more complicated but less frequently needed things. Message-driven beans are an example of the first case. To do some dynamic reconfiguration, you need to stop using MDBs and start consuming messages using the programmatic API, using an injected JMSContext and topic or queue. For example:
#Inject
private JMSContext context;
#Resource(lookup="jms/queue/thumbnail")
Queue thumbnailQueue;
JMSConsumer connectListener(String messageSelector) {
JMSConsumer consumer = context.createConsumer(logTopic, messageSelector);
consumer.setMessageListener(message -> {
// process message
});
return consumer;
}
You can call connectListener during startup, e.g. in a CDI bean:
public void start(#Observes #Initialized(ApplicationScoped.class) Object startEvent) {
connectListener("Fragile IS TRUE");
}
Then you can easily reconfigure it by closing the returned consumer and creating it again with a new selector string:
consumer.close();
consumer = connectListener("Fragile IS FALSE");

Categories

Resources