I like to have an implementation of one #Scheduled job using different configuration properties of .ymlfile.
Means in my yaml file I describe the cron expression as a list:
job:
schedules:
- 10 * * * * *
- 20 * * * * *
I read those values out using Configuration and created a #Bean named scheduled:
#Configuration
#ConfigurationProperties(prefix="job", locations = "classpath:cronjob.yml")
public class CronConfig {
private List<String> schedules;
#Bean
public List<String> schedules() {
return this.schedules;
}
public List<String> getSchedules() {
return schedules;
}
public void setSchedules(List<String> schedules) {
this.schedules = schedules;
}
}
In my Job class I want to start the execution of one method but for both of the schedules in my configuration.
#Scheduled(cron = "#{#schedules}")
public String execute() {
System.out.println(converterService.test());
return "success";
}
With this solution the application creates an error: (more or less clear)
Encountered invalid #Scheduled method 'execute': Cron expression must consist of 6 fields (found 12 in "[10 * * * * *, 20 * * * * *]")
Is there a way to configure the same scheduled job method with multiple declarations of cron expressions?
EDIT 1
After some try I just used a second annotation on the executer method.
#Scheduled(cron = "#{#schedules[0]}")
#Scheduled(cron = "#{#schedules[1]}")
public String execute() {
System.out.println(converterService.test());
return "success";
}
This solution works but is not really dynamic. Is there also a way to make this dynamic?
(edit since I found a way to perform this)
You can actually do this. Below I'm showcasing a working example:
cronjob.yaml
job:
schedules:
- 10 * * * * *
- 20 * * * * *
the actual task to perform MyTask:
package hello;
import org.springframework.stereotype.Component;
#Component
public class MyTask implements Runnable {
#Override
public void run() {
//complicated stuff
}
}
Your CronConfig as is:
package hello;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
#Configuration
#ConfigurationProperties(prefix="job", locations = "classpath:cronjob.yml")
public class CronConfig {
private List<String> schedules;
#Bean
public List<String> schedules() {
return this.schedules;
}
public List<String> getSchedules() {
return schedules;
}
public void setSchedules(List<String> schedules) {
this.schedules = schedules;
}
}
The ScheduledTask bean that is responsible to schedule all crons:
package hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
#Component
public class ScheduledTasks {
#Autowired
private TaskScheduler taskScheduler;
#Autowired
private CronConfig cronConfig;
#Autowired
private MyTask myTask;
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
public void scheduleAllCrons() {
cronConfig.getSchedules().forEach( cron -> taskScheduler.schedule(myTask, new CronTrigger(cron)) );
}
}
The context/main class Application:
package hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler;
#SpringBootApplication
#EnableScheduling
#EnableAsync
public class Application {
#Bean
public TaskScheduler taskScheduler() {
return new ConcurrentTaskScheduler();
}
public static void main(String[] args) throws Exception {
ApplicationContext ctx = SpringApplication.run(Application.class);
ScheduledTasks scheduledTasks = ctx.getBean(ScheduledTasks.class);
scheduledTasks.scheduleAllCrons();
}
}
application.yaml:
crontab:
submitSubtask: "0 * * * * *"
updateBacktrace: "2/30 * * * * *"
java code:
#EnableScheduling
public class Demo{
#Scheduled(cron = "${crontab.updateBacktrace}")
private void updateBacktrace() {
...
}
#Scheduled(cron = "${crontab.submitSubtask}")
private void submitSubtask() {
...
}
}
it works for springboot 2.3.7.
A trick related to it:
while defining the corn job timing, attribute name should be in lower case
for example: if it is in Camel case, spring did not kick the job :(
application.yml:
common:
scheduler:
feedeErrorLogCleanUp: 0 0/5 * ? * *
WHEREAS BELOW STUFF WORKS
common:
scheduler:
feedeerrorlogcleanUp: 0 0/5 * ? * *
Related
I want to create a daily background job to be executed by AEM.
I read an aem document and apache sling official site, and I thought I need two classes.
a service class that register the job to JobManager.
a consumer class that do the job.
So I tried these code, but my job was not executed.
service class
import org.apache.sling.event.jobs.JobManager;
import org.apache.sling.event.jobs.JobBuilder.ScheduleBuilder;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component
public class MyJobService {
private static final Logger logger = LoggerFactory.getLogger(MyJobService.class);
#Reference
private JobManager jobManager;
public static final String JOB_TOPIC = "my/sample/jobtopic";
public void startScheduledJob() {
ScheduleBuilder scheduleBuilder = jobManager.createJob(JOB_TOPIC).schedule();
scheduleBuilder.hourly(9, 0); // execute daily at AM9:00
if (scheduleBuilder.add() == null) {
logger.error("myjobservice error");
}
}
}
job consumer class
import org.apache.sling.event.jobs.Job;
import org.apache.sling.event.jobs.consumer.JobConsumer;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component(
immediate = true,
service = JobConsumer.class,
property = {
JobConsumer.PROPERTY_TOPICS + "=my/sample/jobtopic"
}
)
public class MyJobConsumer implements JobConsumer {
private static final Logger logger = LoggerFactory.getLogger(MyJobConsumer.class);
#Override
public JobResult process(Job job) {
String topic = job.getTopic();
logger.info("this message is from myjobconsumer. topic is " + topic);
return JobResult.OK;
}
}
Do I need another class or some configurations? Does My code have something wrong?
If you annotate a method with #Activate it will be called when the component starts.
#Activate
public void startScheduledJob()
I guess you want your job to run on startup.
Another option would be to let MyJobService be a servlet and call it from outside.
I created the following service interface:
import javax.validation.constraints.NotBlank;
import org.springframework.lang.NonNull;
import org.springframework.validation.annotation.Validated;
#Validated
public interface UserService {
User create(#NonNull Long telegramId, #NotBlank String name, #NonNull Boolean isBot);
}
but the following invocation:
userService.create(telegramId, "Mike", null);
passes the #NotNull validation for isBot parameter. How to correctly configure Spring Boot and my service in order to take into account #NonNull annotation and prevent method execution in case of null parameter?
I played around with this problem for a bit.
Your code looks fine to me: Make sure that the implementation of UserService also has the validation annotations present.
Ensure that you allow Spring to create the Bean; it should work as you expect.
Example
Service Definition
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Validated
public interface GreetingService {
String greet(#NotNull #NotBlank String greeting);
}
Service Implementation
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Service
public class HelloGreetingService implements GreetingService {
public String greet(#NotNull #NotBlank String greeting) {
return "hello " + greeting;
}
}
Testcase
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import javax.validation.ConstraintViolationException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
#SpringBootTest
class HelloGreetingServiceTest {
#Autowired
private GreetingService helloGreetingService;
#Test
void whenGreetWithStringInput_shouldDisplayGreeting() {
String input = "john doe";
assertEquals("hello john doe", helloGreetingService.greet(input));
}
#Test
void whenGreetWithNullInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(null));
}
#Test
void whenGreetWithBlankInput_shouldThrowException() {
assertThrows(ConstraintViolationException.class, () -> helloGreetingService.greet(""));
}
}
Testcases are green for me.
Github: https://github.com/almac777/spring-validation-playground
Source: https://www.baeldung.com/javax-validation-method-constraints
HTH!
Use the same thing in Implementation class instead interface.
Also can write one global exception like:
#Order(Ordered.HIGHEST_PRECEDENCE)
#RestControllerAdvice
public class GlobalRestException extends ResponseEntityExceptionHandler {
...
...
/**
* Handle MethodArgumentNotValidException. Triggered when an object fails #Valid
* validation.
*
* #param ex the MethodArgumentNotValidException that is thrown when #Valid
* validation fails
* #param headers HttpHeaders
* #param status HttpStatus
* #param request WebRequest
* #return the ApiException object
*/
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getBindingResult().getFieldErrors());
apiError.addValidationError(ex.getBindingResult().getGlobalErrors());
return buildResponseEntity(apiError);
}
}
There are more method that can be override to handle different kind of exception like :
/**
* Handles javax.validation.ConstraintViolationException. Thrown when #Validated
* fails.
*
* #param ex the ConstraintViolationException
* #return the ApiException object
*/
#ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
Error apiError = new Error(BAD_REQUEST);
apiError.setMessage("Validation error");
apiError.addValidationErrors(ex.getConstraintViolations());
return buildResponseEntity(apiError);
}
You need to make sure that #Validated annotation is used on 'class' which method arguments will need to be validated and Spring configuration need to be added
#Configuration
public class MethodValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
}
I created a Spring Boot app and have trouble with some endpoints that can be triggered manually or via #Scheduled annotation.
The issue I get is the one below:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException:
An Authentication object was not found in the SecurityContext
Is there a way to trigger SecurityContext if the process called via #Scheduled?
I am new to Spring Security and it is very hard for me to understand the reference guide. I found some similar questions but still cannot understand how to apply answers to my case.
Example of my MyController:
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Scheduled(cron = "* * * * * *")
#GetMapping(path="/data")
public void getData() {
// Do some operations
}
}
The #Scheduled method should not be #Secured. Scheduled methods are already in trusted code.
Refactor your code, e.g.
#Secured("ROLE_ADMIN")
#RestController
#RequestMapping(value = "/api")
public class MyController {
#Autowired
private MyService myService;
#PostMapping(path="/run")
public void runJobs() {
myService.runJobs();
}
}
#Service
public class MyService {
#Scheduled(cron = "* * * * * *")
public void runJobs() {
// Do some operations
}
}
Basically you have to configure your scheduler with the necessary authentication. Something in the lines of the following:
import com.google.common.collect.ImmutableList;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.concurrent.DelegatingSecurityContextScheduledExecutorService;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import java.util.List;
import java.util.concurrent.Executor;
import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
import static org.springframework.security.core.context.SecurityContextHolder.getContext;
#Configuration
public class SchedulerConfiguration implements SchedulingConfigurer {
private static final String KEY = "spring";
private static final String PRINCIPAL = "spring";
private static final List<SimpleGrantedAuthority> AUTHORITIES = ImmutableList.of(new SimpleGrantedAuthority("ADMIN"));
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean
public Executor taskExecutor() {
final AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(KEY, PRINCIPAL, AUTHORITIES);
final SecurityContext securityContext = getContext();
securityContext.setAuthentication(token);
return new DelegatingSecurityContextScheduledExecutorService(newSingleThreadScheduledExecutor(), securityContext);
}
}
Then you can annotate your controller with #Secured("hasAuthority('ADMIN')").
I'm studying Aspect Oriented Programming in Spring with #AspectJ
I write an example but when I run it I've an error
Here are the class that I write
package concert;
/**
*
* #author phate
*/
public interface Performance {
public void perform();
}
Class PerformanceImpl implements the Performance interface and implements perform method
package concert;
import org.springframework.stereotype.Service;
/**
*
* #author Dennis A. Boanini
*/
#Service
public class PerformanceImpl implements Performance{
#Override
public void perform() {
System.out.println("perform");
}
}
Class Audience is annotated with #Aspect and declare some Advice
package concert;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
*
* #author Dennis A. Boanini
*/
#Component
#Aspect
public class Audience {
#Before("execution(* concert.Performance.perform(..))")
public void silenceCellPhones(){
System.out.println("Silencing cell phones");
}
#Before("execution(* concert.Performance.perform(..))")
public void takeSeats(){
System.out.println("Taking seats");
}
#AfterReturning("execution(* concert.Performance.perform(..))")
public void applause(){
System.out.println("CLAP CLAP CLAP");
}
#AfterThrowing("execution(* concert.Performance.perform(..))")
public void demandRefund(){
System.out.println("Demanding a refund");
}
}
Class ConcertConfig is the bean that autoproxy
package concert;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
*
* #author Dennis A. Boanini
*/
#Configuration
#EnableAspectJAutoProxy
#ComponentScan
public class ConcertConfig{
#Bean
public Audience audience() {
return new Audience();
}
}
And this is the main class
public class Turing {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConcertConfig.class);
ctx.refresh();
Performance userService = ctx.getBean(PerformanceImpl.class);
userService.perform();
}
}
If I run I receive this error
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [concert.PerformanceImpl] is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:374)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1088)
at com.phate.spring.aop.example.Turing.main(Turing.java:32)
But if I eliminate the interface everything work correctly, why?
I have a scheduled job configured in an XML file:
<task:scheduled-tasks scheduler="taskScheduler">
<task:scheduled ref="scheduledJobs" method="doSomething" cron="30 * * * * ?"/>
</task:scheduled-tasks>
<bean id="scheduledJobs" class="com.xxx.ScheduledJobs"/>
<task:scheduler id="taskScheduler" pool-size="2" />
Is it possible to query the cron expression (to display to the user) and update it (not necessarily in the XML file but in the bean instance) with a new cron expression?
Or is there a better way of achieving this type of functionality?
I believe the answer is this is not possible using Spring/JDK scheduling but is possible if you use Quartz scheduling instead:
There is another thread covering a similar area:
Rescheduling a CronTriggerBean dynamically with same job details in Spring
You can achieve it in a programmatic way, shortened version:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.support.CronTrigger;
public class ChangelogDeleterTask implements InitializingBean, Runnable {
#Autowired
private TaskScheduler scheduler;
private ScheduledFuture<?> scheduledTask;
private CronTrigger trigger;
private Date nextExecutionTime;
#Override
public void afterPropertiesSet() throws Exception {
schedule("0/5 * * * * *");
}
#Override
public void run() {/*do scheduled job*/}
public synchronized void schedule(String cronExpression) {
scheduledTask.cancel(false);
trigger = new MyCronTrigger(cronExpression);
scheduledTask = scheduler.schedule(this, trigger);
}
String getNextExecutionTime() {
return nextExecutionTime.toString();
}
String getExpression() {
return trigger.getExpression();
}
/**
* Class allows to remember nextExecutionTime.
*/
private class MyCronTrigger extends CronTrigger {
public ChangelogCronTrigger(String expression) {
super(expression);
}
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
Date date = super.nextExecutionTime(triggerContext);
nextExecutionTime = new Date(date.getTime());
return date;
}
}
}