Schedule a method dinamically using cron of the annotation #Scheduled - spring

I would Like to schedule a method using The annotation #Scheduled using cron, For example I want that the method should be executed everyday in the time specified by the client.
So I would like to get the cron value from the DB, in order to give the client the possibility of executing the method whenever he wants.
Here is my method, it sends emails automatically at 10:00 am to the given addresses, so my goal is to make the 10:00 dynamic.
Thanks for your help.
#Scheduled(cron = "0 00 10* * ?")
public void periodicNotification() {
JavaMailSenderImpl jms = (JavaMailSenderImpl) sender;
MimeMessage message = jms.createMimeMessage();
MimeMessageHelper helper;
try {
helper = new MimeMessageHelper(message, MimeMessageHelper.MULTIPART_MODE_MIXED_RELATED, StandardCharsets.UTF_8.name());
List<EmailNotification> emailNotifs = enr.findAll();
for (EmailNotification i : emailNotifs)
{
helper.setFrom("smsender4#gmail.com");
List<String> recipients = fileRepo.findWantedEmails(i.getDaysNum());
//List<String> emails = recipientsRepository.getScheduledEmails();
String[] to = recipients.stream().toArray(String[]::new);
helper.setTo(to);
helper.setText(i.getMessage());
helper.setSubject(i.getSubject());
sender.send(message);
System.out.println("Email successfully sent to: " + Arrays.toString(to));
}
}
catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

So I'm thinking at the next solution. ( + using the answer accepted here )
Let's say you have a class that imlpements Runnable interface -> this will be your job that gets executed. Let's call it MyJob
Also assume that we have a map that hold the id of the job and it's execution reference ( you'll see in a sec what i'm talking about). Call it something like currentExecutingJobs
Assume you have an endpoint that gets the name of the job and a cron expression from the client
When that endpoints gets called:
You'll look in the map above to see if there is any entry with that job id. If it exists, you cancel the job.
After that, you'll create an instance of that job ( You can do that by using reflection and having a custom annotation on your job classes in which you can provide an id. For example #MyJob("myCustomJobId" )
And from the link provided, you'll schedule the job using
// Schedule a task with the given cron expression
ScheduledFuture myJobScheduledFutere = executor.schedule(myJob, new CronTrigger(cronExpression));
And put the result in the above map currentExecutingJobs.put("myCustomJobId", myJobScheduledFutere)
ScheduledFuture docs

In case you want to read property from database you can implement the EnvironmentPostProcessor and read the necessary values from DB and add it to Environment object, more details available at https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-spring-boot-application

Related

Spring synchronize saving to database with another instances

I have 2 instances of my Spring boot application, I am using spring JPA.
Cron scheduler run method every one hour which
first, check if record is already updated, if it's updated it should skip and don't update, but it's updated anyway on both instances.
How to implement, something like synchronized, allow to read only when something is done?
#Scheduled(cron = "0 0 * * * ?", zone = "Europe/Paris")
public void updateServers() {
applicationServerRepository.findAll().forEach(this::updateServerInfo);
}
Please explain the problem more detailed&exact.
But as I understand, you should:
Or run the cron jobs only in one instance. (Back in 90ies we did it "manually", but spring-boot-profiles offer here great opportunity: decorate "all #scheduled beans" (re-factoring!?) with #Profile("cronFooBar"), and activate/add it only on one instance.)
Or, if you also want cron jobs "load balanced", then you should find a way to synchronize/lock. (probably best in updateServerInfo or updateServers. (more details!? ..I am sure both instances will find "some object to lock on", at least the database (table, row, ..)))
As proposed by Erkan/found on internet, with "little setup" (and discarding quartz!), you can have a handy annotation for this, like:
#SchedulerLock(name = "...",
lockAtLeastForString = "...", lockAtMostForString = "...")
But I suppose, it is also possible with spring-data-jpa (only & quartz) resources, like:
Add (pessimistic) locking:
interface ApplicationServerRepository ... {
#Lock(LockModeType.PESSIMISTIC_READ)
#Query("select as from ApplicationService ...")
findAllForCron();
...
}
Catch it:
#Scheduled(cron = "0 0 * * * ?", zone = "Europe/Paris")
public void updateServers() {
try {
applicationServerRepository
.findAllForCron()
.forEach(this::updateServerInfo);
​}
catch​ (javax.persistence.PessimisticLockException plex) {
logger.info("i get some coffee");
return;
}
}

Consuming from Camel queue every x minutes

Attempting to implement a way to time my consumer to receive messages from a queue every 30 minutes or so.
For context, I have 20 messages in my error queue until x minutes have passed, then my route consumes all messages on queue and proceeds to 'sleep' until another 30 minutes has passed.
Not sure the best way to implement this, I've tried spring #Scheduled, camel timer, etc and none of it is doing what I'm hoping for. I've been trying to get this to work with route policy but no dice in the correct functionality. It just seems to immediately consume from queue.
Is route policy the correct path or is there something else to use?
The route that reads from the queue will always read any message as quickly as it can.
One thing you could do is start / stop or suspend the route that consumes the messages, so have this sort of set up:
route 1: error_q_reader, which goes from('jms').
route 2: a timed route that fires every 20 mins
route 2 can use a control bus component to start the route.
from('timer?20mins') // or whatever the timer syntax is...
.to("controlbus:route?routeId=route1&action=start")
The tricky part here is knowing when to stop the route. Do you leave it run for 5 mins? Do you want to stop it once the messages are all consumed? There's probably a way to run another route that can check the queue depth (say every 1 min or so), and if it's 0 then shutdown route 1, you might get it to work, but I can assure you this will get messy as you try to deal with a number of async operations.
You could also try something more exotic, like a custom QueueBrowseStrategy which can fire an event to shutdown route 1 when there are no messages on the queue.
I built a customer bean to drain a queue and close, but it's not a very elegant solution, and I'd love to find a better one.
public class TriggeredPollingConsumer {
private ConsumerTemplate consumer;
private Endpoint consumerEndpoint;
private String endpointUri;
private ProducerTemplate producer;
private static final Logger logger = Logger.getLogger( TriggeredPollingConsumer.class );
public TriggeredPollingConsumer() {};
public TriggeredPollingConsumer( ConsumerTemplate consumer, String endpoint, ProducerTemplate producer ) {
this.consumer = consumer;
this.endpointUri = endpoint;
this.producer = producer;
}
public void setConsumer( ConsumerTemplate consumer) {
this.consumer = consumer;
}
public void setProducer( ProducerTemplate producer ) {
this.producer = producer;
}
public void setConsumerEndpoint( Endpoint endpoint ) {
consumerEndpoint = endpoint;
}
public void pollConsumer() throws Exception {
long count = 0;
try {
if ( consumerEndpoint == null ) consumerEndpoint = consumer.getCamelContext().getEndpoint( endpointUri );
logger.debug( "Consuming: " + consumerEndpoint.getEndpointUri() );
consumer.start();
producer.start();
while ( true ) {
logger.trace("Awaiting message: " + ++count );
Exchange exchange = consumer.receive( consumerEndpoint, 60000 );
if ( exchange == null ) break;
logger.trace("Processing message: " + count );
producer.send( exchange );
consumer.doneUoW( exchange );
logger.trace("Processed message: " + count );
}
producer.stop();
consumer.stop();
logger.debug( "Consumed " + (count - 1) + " message" + ( count == 2 ? "." : "s." ) );
} catch ( Throwable t ) {
logger.error("Something went wrong!", t );
throw t;
}
}
}
You configure the bean, and then call the bean method from your timer, and configure a direct route to process the entries from the queue.
from("timer:...")
.beanRef("consumerBean", "pollConsumer");
from("direct:myRoute")
.to(...);
It will then read everything in the queue, and stop as soon as no entries arrive within a minute. You might want to reduce the minute, but I found a second meant that if JMS as a bit slow, it would time out halfway through draining the queue.
I've also been looking at the sjms-batch component, and how it might be used with with a pollEnrich pattern, but so far I haven't been able to get that to work.
I have solved that by using my application as a CronJob in a MicroServices approach, and to give it the power of gracefully shutting itself down, we may set the property camel.springboot.duration-max-idle-seconds. Thus, your JMS consumer route keeps simple.
Another approach would be to declare a route to control the "lifecycle" (start, sleep and resume) of your JMS consumer route.
I would strongly suggest that you use the first approach.
If you use ActiveMQ you can leverage the Scheduler feature of it.
You can delay the delivery of a message on the broker by simply set the JMS property AMQ_SCHEDULED_DELAY to the number of milliseconds of the delay. Very easy in the Camel route
.setHeader("AMQ_SCHEDULED_DELAY", 60000)
It is not exactly what you look for because it does not drain a queue every 30 minutes, but instead delays every individual message for 30 minutes.
Notice that you have to enable the schedulerSupport in your broker configuration. Otherwise the delay properties are ignored.
<broker brokerName="localhost" dataDirectory="${activemq.data}" schedulerSupport="true">
...
</broker>
You should consider Aggregation EIP
from(URI_WAITING_QUEUE)
.aggregate(new GroupedExchangeAggregationStrategy())
.constant(true)
.completionInterval(TIMEOUT)
.to(URI_PROCESSING_BATCH_OF_EXCEPTIONS);
This example describes the following rules: all incoming in URI_WAITING_QUEUE objects will be grouped into List. constant(true) is a grouping condition (wihout any). And every TIMEOUT period (in millis) all grouped objects will be passed into URI_PROCESSING_BATCH_OF_EXCEPTIONS queue.
So the URI_PROCESSING_BATCH_OF_EXCEPTIONS queue will deal with List of objects to process. You can apply Split EIP to split them and to process one by one.

Long running Spring Service is locking DB table

I have a Spring Service that is going through multiple items in a list and for each one it is making an extra WS call to external services. The Service is called by a Job on a fixed time interval.
As a first step, the Service is saving in a JOB_CONTROL table the status of the Job (STARTED), then it iterates through the list and at the end it saves it to (FINISHED).
There are 2 issues:
the JOB_CONTROL table doesn't get saved gradually - only the
"FINISHED" value is saved and never "STARTED"
if using flush method in order to force the commit, the table gets locked, eg. no other select can be made on it until the Service finishes
#Service
public class PromotionSchedulerService implements Runnable {
#Autowired
GeofencingAreaDAO storeDao;
#Autowired
promotionsWSClient promotionsWSClient;
#Autowired
private JobControlDAO jobControlDAO;
public void run() {
JobControl job = jobControlDAO.findByClassName(this.getClass().getSimpleName());
job.setState(JobControlStateTypes.RUNNING.getStateType());
job.setLastRunDate(new Date());
// LINE BELLOW DOES NOT GET COMMITED IN DB
jobControlDAO.save(job);
List < GeofencingArea > stores = storeDao.findAllStores();
for (GeofencingArea store: stores) {
/** Call WS **/
GetActivePromotionsResponse rsp = null;
try {
rsp = promotionsWSClient.getpromotions();
} catch (Exception e) {
e.printStackTrace();
job.setState(JobControlStateTypes.FAILED.getStateType());
job.setLastRunStatus("There was an error calling promagic promotions");
jobControlDAO.save(job);
return;
}
List < PromotionBean > promos = rsp.getReturn();
for (PromotionBean promo: promos) {
BackendPromotionPOJO backendPromotionsPOJO = new BackendPromotionPOJO();
backendPromotionsPOJO.setDescription(promo.getDescription());
}
}
// ONLY THIS JOB STATE GOES TO DB. IT ACTUALLY SEEM TO OVERWRITE PREVIOUS SET VALUE ("RUNNING") from line 16
job.setLastRunStatus("COMPLETED");
job.setState(JobControlStateTypes.SUCCESS.getStateType());
jobControlDAO.save(job);
}
}
I would like to force the commit after changing job state and not locking the table when doing this.

#TransactionalEventListener method not fired

I have a code where event is published in a method annotated with Spring #Transactional annotation.
#Override
#Transactional
public Task updateStatus(Integer taskId, ExecutionStatus newStatus) {
Task task = Task.builder().executionStatus(newStatus).build();
return updateStatusInternal(taskId, rteWithMetadata);
}
private TaskExecution updateStatusInternal(Integer taskId,
Task newStatus) {
Task task = taskService.findById(taskId);
TaskExecution te = task.getFirstExecution();
TaskExecution.ExecutionStatus oldStatus = te.getExecutionStatus();
TaskExecution.ExecutionStatus newStatus = newStatus.getExecutionStatus();
log.info(
"Task Execution status changed. Task id={}, from={}, to={}. Manual override : {}",
task.getId(), oldStatus, newStatus,
newStatus.isManualOverrideInitiated());
te.setExecutionStatus(newStatus);
if (te.getExecutionStatus() == ExecutionStatus.COMPLETED
|| te.getExecutionStatus() == ExecutionStatus.FAILED) {
te.setEndDate(DateTimeHelper.getUtcNow());
if (rte.isManualOverrideInitiated()) {
rte.setManualOverrideEndDate(DateTimeHelper.getUtcNow());
}
}
publisher.publishEvent(TaskStatusChanged.of(task, oldStatus, newStatus));
log.info("Published TaskStatusChanged event. task Id={}", task.getId());
// Send STOMP message
final Object payload = StompMessageHelper.getTaskExecutionUpdateMessage(task);
messageTemplate.convertAndSend(taskDestination(task), payload);
log.info("STOMP message for task status update sent. task Id={}",
task.getId());
return te;
}
There is a corresponding listener method for the application event which is annotated with #TransactionalEventListener.
#Async("changeEventExecutor")
#TransactionalEventListener(phase=TransactionPhase.AFTER_COMMIT)
public void taskStatusChanged(final TaskStatusChanged e) {
log.info("taskStatusChanged called");
}
Problem is listener is not fired on one of our production boxes. It works fine consistently on local dev environment but fails consistently in production.
Did somebody face this issue earlier? Only solution I can think of is to manually fire the application event.
Note: I have checked the existing similar posting. My scenario does not match with any of the existing posting.
The only thing I can think of comes from Spring's javadoc:
If the event is not published within the boundaries of a managed
transaction, the event is discarded unless the fallbackExecution()
flag is explicitly set. If a transaction is running, the event is
processed according to its TransactionPhase.
Could there be no transaction running? I assume your code sample isn't complete, so perhaps the transaction is being rolled-back when the event is fired or something along those lines.
In any case, you could try with the following (I know you are referring to a production box, so I'm not sure what are your options in trying things out):
#TransactionalEventListener(fallbackExecution=true, phase=TransactionPhase.AFTER_COMMIT)

Quartz Integration with Spring

I have a web application and I am trying to start Quartz scheduler programmatically in spring. I have a service class where I create an instance of SchedulerFactory and then get a scheduler.
The code is as follows.
#Service("auctionWinnerService")
public class NormalAuctionWinnerServiceImpl implements AuctionWinnerService {
public static final String NORMAL_AUCTION = "NORMAL AUCTION";
public static int NORMAL_AUCTION_COUNTER = 0;
private SchedulerFactory schedulerFactory;
private Scheduler scheduler;
public void declareWinner(int auctionId, Map<String, Object> parameterMap) {
System.out.println("INSIDE declareWinner of NormalAuctionWinner");
schedulerFactory = new StdSchedulerFactory();
try {
scheduler = schedulerFactory.getScheduler();
System.out.println("GOT SCHEDULER : "+scheduler);
} catch (SchedulerException e1) {
e1.printStackTrace();
}
JobDetail jd = new JobDetail();
jd.setName(NORMAL_AUCTION+" JOB "+NORMAL_AUCTION_COUNTER);
jd.setJobClass(NormalAuctionWinnerJob.class);
/** CREATE CRON TRIGGER INSTANCE **/
CronTrigger t = new CronTrigger();
t.setName(NORMAL_AUCTION + ++NORMAL_AUCTION_COUNTER);
t.setGroup("Normal Auction");
Date d = new Date();
Date d1 = new Date();
d1.setMinutes(d.getMinutes()+5);
t.setStartTime(d);
t.setEndTime(d1);
try {
t.setCronExpression("10 * * * * ? *");
scheduler.scheduleJob(jd, t);
} catch (SchedulerException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
The schedulerFactory and scheduler are instantiated but my jobs do not run.
Could someone point out what am I missing here?
Also I need only one instance of Factory and one scheduler instance. I tried making the static but it didn't work. Any pointers in this direction will be helpful.
Thanks
Unless you have a specific requirement on Quartz's proprietary functionality, I recommend getting rid of it and using Spring's internal scheduling capability. As of Spring 3, this includes support for cron-type expressions, very similar to Quartz's cron trigger.
As well as bringing simplicity to your application and its config, it's inherently more reliable than Quartz, and provides an easier API for programmatic usage, via the TaskScheduler interface.
First of all, how well do you know Quartz or cron trigger expressions? I may be mistaken, but 10 * * * * ? * will make the trigger fire every 10th second of every minute, but I've never seen such expression, it may not fire at all.
Are you trying to create a trigger to fire every 10 seconds? In this case, use a simple trigger like this:
new SimpleTrigger((NORMAL_AUCTION + ++NORMAL_AUCTION_COUNTER),
"Normal Auction",
d,
d1,
SimpleTrigger.REPEAT_INDEFINITELY,
10000L);
Edit:
Ok, so if that's you requirement, you need a trigger that fill fire just once, at the end time of the auction. For that, use a SimpleTrigger like this:
new SimpleTrigger((NORMAL_AUCTION + ++NORMAL_AUCTION_COUNTER),
"Normal Auction",
d1,
null,
0,
0L);
The start date in this case does not matter, as long as you set it to fire on the appropriate time (the end time) and just once.
And as an additional note, do not calculate dates like that. I suggest you to try Joda Time library. Really simple and well known replacement for the clumsy standard Date/Calendar API.
You have forgotten to start the scheduler! scheduler.start();
...
try {
t.setCronExpression("10 * * * * ? *");
scheduler.scheduleJob(jd, t);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
I have proved this, after adding the missing statement, (and replacing the job with an dummy) it worked for me/

Resources