I'm dwelling with Quartz from days..
I need to create, the app starts, some triggers and job details..
So, this is my Job
#DisallowConcurrentExecution
public class TimeoutJob extends QuartzJobBean{
public final String ID = "idInterruttore";
private final Logger logger = Logger.getLogger(TimeoutJob.class);
#Autowired InterruttoreService interruttoreService;
#Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
int idInterruttore = dataMap.getIntFromString(ID);
Interruttore interruttore = interruttoreService.findById(idInterruttore);
logger.debug("Job reached for " + interruttore.getNomeInterruttore());
}
}
Then i configure some bean in QuartzConfiguration.java
#Configuration
#ComponentScan("it.besmart")
public class QuartzConfiguration {
#Autowired
ApplicationContext applicationContext;
#Bean
public SchedulerFactoryBean scheduler() {
SchedulerFactoryBean schedulerFactory = new SchedulerFactoryBean();
schedulerFactory.setJobFactory(springBeanJobFactory());
return schedulerFactory;
}
#Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
}
}
Now, I have a JobManager.class which manage jobDetails and Triggers
#Service("jobManager")
public class JobManager {
private final Logger logger = Logger.getLogger(JobManager.class);
#Autowired
SchedulerFactoryBean scheduler;
#Autowired
InterruttoreService interruttoreService;
#PostConstruct
public void createInitialJobs() {
logger.debug("Start ut jobs to create");
List<Interruttore> interruttori = interruttoreService.findAllSwitches();
Date now = new Date();
for (int i = 0; i < interruttori.size(); i++) {
Interruttore interruttore = interruttori.get(i);
if (interruttore.getTimeoutDate().after(now) && interruttore.isStato()) {
// JobDetail and Trigger creation
createJob(interruttore, interruttore.getTimeoutDate());
}
}
}
public void createJob(Interruttore interruttore, Date richiesta) {
JobDetailFactoryBean jobDetail = new JobDetailFactoryBean();
jobDetail.setJobClass(TimeoutJob.class);
jobDetail.setName("Job detail for " + interruttore.getNomeInterruttore());
jobDetail.setDescription("Job Description");
jobDetail.setDurability(true);
Map<String, Integer> map = new HashMap<String,Integer>();
map.put("idInterruttore", interruttore.getIdInterruttore());
jobDetail.setJobDataAsMap(map);
long future = richiesta.getTime() - new Date().getTime();
logger.debug("next timeout is " + future / 1000 / 60 + " minuti for " + interruttore.getNomeInterruttore());
//trigger creation
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setName("myTrigger"+interruttore.getNomeInterruttore());
trigger.setGroup("timeoutTriggers");
trigger.setJobDetail(jobDetail.getObject());
trigger.setStartDelay(0);
trigger.setRepeatCount(1);
trigger.setRepeatInterval(future);
trigger.afterPropertiesSet();
logger.debug("Trigger for " + interruttore.getNomeInterruttore());
logger.debug("Trigger object is :" + trigger.getObject());
logger.debug("Next Trigger date " + trigger.getObject().getFinalFireTime());
try {
scheduler.getScheduler().scheduleJob(jobDetail.getObject(), trigger.getObject());
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
When launching the app, the #PostConstruct method tries to create the triggers, but i'm getting an exception when creating jobManager
Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobManager': Invocation of init method failed; nested exception is java.lang.NullPointerException
caused by
Caused by: java.lang.NullPointerException
at org.springframework.scheduling.quartz.SimpleTriggerFactoryBean.afterPropertiesSet(SimpleTriggerFactoryBean.java:231)
at it.besmart.quartz.JobManager.createJob(JobManager.java:85)
at it.besmart.quartz.JobManager.createInitialJobs(JobManager.java:54)
which is
trigger.afterPropertiesSet();
as my triggers are not created...
There is a bug in the spring-context-support jar 4.2.5 version.
sti.setJobKey(this.jobDetail.getKey());
i.e. jobDetail can be null.
In the new versions it is fixed. I checked 4.3.2 version.
You can use 4.3.2 or later.
In 4.3.2 version
if (this.jobDetail != null) {
sti.setJobKey(this.jobDetail.getKey());
}
Related
I am using Spring Boot 2.6.6 and I want to configure scheduled tasks at runtime. The task configs are saved in a database. Here is my code:
#Configuration
#EnableScheduling
public class SchedulerConfig implements SchedulingConfigurer, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(SchedulerConfig.class);
private BackupJobsService backupJobsService;
private BackupService backupService;
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public SchedulerConfig(#Autowired BackupJobsService backupJobsService, #Autowired BackupService backupService) {
this.backupJobsService = backupJobsService;
this.backupService = backupService;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
logger.info("Register all custom cron jobs...");
logger.debug("====> SchedulerConfig: configureTasks <====");
List<BackupJob> listOfJobs = backupJobsService.findAll();
for (BackupJob backupJob : listOfJobs) {
BackupTask backupTask = new BackupTask(backupJob, backupService);
Trigger trigger = new Trigger() {
#Override
public Date nextExecutionTime(TriggerContext triggerContext) {
logger.debug("====> SchedulerConfig: nextExecutionTime (" + backupJob.getJobName() + ") <====");
CronTrigger crontrigger = new CronTrigger(backupJob.getJobConfig());
return crontrigger.nextExecutionTime(triggerContext);
}
};
taskRegistrar.addTriggerTask(backupTask, trigger);
}
}
#Override
public void destroy() throws Exception {
logger.debug("====> SchedulerConfig: destroy <====");
if (executor != null) {
executor.shutdownNow();
}
}
}
The object backupJob has all Infos about the job. In the field JobConfig save the cron string for the job.
This class work perfect if I start the spring boot application. How can I reconfigure the tasks without a restart of the spring boot application ?
I have created messaging component which will be called by other service for consuming and sending message from kafka, producer part is working fine, I am not sure what wrong with the below consumer listner part why it not printing messages or in debug mode control also not going inside the #kafkaListner method, but GUI based kafkamanager app shows offset is got committed even thought its mannual offset commit.
Here is my Message listner class code , I have checked topic and groupid is setting and fetched properly
#Component
public class SpringKafkaMessageListner {
public CountDownLatch latch = new CountDownLatch(1);
#KafkaListener(topics = "#{consumerFactory.getConfigurationProperties().get(\"topic-name\")}",
groupId = "#{consumerFactory.getConfigurationProperties().get(\"group.id\")}",
containerFactory = "springKafkaListenerContainerFactory")
public void listen(ConsumerRecord<?, ?> consumerRecord, Acknowledgment ack) {
System.out.println("listening...");
System.out.println("Received Message in group : "
+ " and message: " + consumerRecord.value());
System.out.println("current offsetId : " + consumerRecord.offset());
ack.acknowledge();
latch.countDown();
}
}
Consumer config class-
#Configuration
#EnableKafka
public class KafkaConsumerBeanConfig<T> {
#Autowired
#Lazy
private KafkaConsumerConfigDTO kafkaConsumerConfigDTO;
#Bean
public ConsumerFactory<Object, T> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(kafkaConsumerConfigDTO.getConfigs());
}
//for spring kafka with manual offset commit
#Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<Object,
springKafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Object, T> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
//manual commit
factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
return factory;
}
#Bean
SpringKafkaMessageListner consumerListner(){
return new SpringKafkaMessageListner();
}
}
Below code snippet is consumer interface implementation which expose subscribe() method and all other bean creation is done thru ConfigurableApplicationContext.
public class SpringKafkaConsumer<T> implements Consumer<T> {
public SpringKafkaConsumer(ConsumerConfig<T> consumerConfig,
ConfigurableApplicationContext context) {
this.consumerConfig = consumerConfig;
this.context = context;
this.consumerFactory = context.getBean("consumerFactory", ConsumerFactory.class);
this.springKafkaContainer = context.getBean("springKafkaListenerContainerFactory",
ConcurrentKafkaListenerContainerFactory.class);
}
// here is it just simple code to initialize SpringKafkaMessageListner class and invoking
listening part
#Override
public void subscribe() {
consumerListner = context.getBean("consumerListner", SpringKafkaMessageListner.class);
try {
consumerListner.latch.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Test class with my local docker kafka setup
#RunWith(SpringRunner.class)
#DirtiesContext
#ContextConfiguration(classes = QueueManagerSpringConfig.class)
public class SpringKafkaTest extends AbstractJUnit4SpringContextTests {
#Autowired
private QueueManager queueManager;
private Consumer<KafkaMessage> consumer;`
// test method
#Test
public void testSubscribeWithLocalBroker() {
String topicName = "topic1";
String brokerServer = "127.0.0.1:9092";
String groupId = "grp1";
Map<String, String> additionalProp = new HashMap<>();
additionalProp.put(KafkaConsumerConfig.GROUP_ID, groupId);
additionalProp.put(KafkaConsumerConfig.AUTO_COMMIT, "false");
additionalProp.put(KafkaConsumerConfig.AUTO_COMMIT_INTERVAL, "100");
ConsumerConfig<KafkaMessage> consumerConfig =
new ConsumerConfig.Builder<>(topicName, new KafkaSuccessMessageHandler(new
KafkaMessageSerializerTest()),
new KafkaMessageDeserializerTest())
.additionalProperties(additionalProp)
.enableSpringKafka(true)
.offsetPositionStrategy(new EarliestPositionStrategy())
.build();
consumer = queueManager.getConsumer(consumerConfig);
System.out.println("start subscriber");
// calling subcribe method of consumer that will invoke kafkalistner
consumer.subscribe();
}
#Configuration
public class QueueManagerSpringConfig {
#Bean
public QueueManager queueManager() {
Map<String, String> kafkaProperties = new HashMap<>();
kafkaProperties.put(KafkaPropertyNamespace.NS_PREFIX +
KafkaPropertyNamespace.BOOTSTRAP_SERVERS,
"127.0.0.1:9092");
return QueueManagerFactory.getInstance(new KafkaPropertyNamespace(kafkaProperties)); } }
I have the class below in which I want to configure my Camunda process engine.
I want to make the engine deployment-aware and set the backoff-time-in-millis.
I can do the former using config.setJobExecutorDeploymentAware(true);.
How can I set the backoff-time-in-millis and other parameters of the job executor?
The obvious solution (config.getJobExecutor().setBackoffTimeInMillis(100);) is not applicable because when the method org.example.Config#processEngineConfiguration is called, config.getJobExecutor() is equal to null.
#Configuration
#Import( SpringProcessEngineServicesConfiguration.class )
public class Config {
private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
#Autowired
#Qualifier("camundaBpmDataSource")
private DataSource dataSource;
#Autowired
#Qualifier("camundaTxManager")
private PlatformTransactionManager txManager;
#Autowired
private ResourcePatternResolver resourceLoader;
#Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
final SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setJobExecutorDeploymentAware(true);
config.setIdGenerator(new StrongUuidGenerator());
config.setDataSource(dataSource);
config.setTransactionManager(txManager);
config.setDatabaseSchemaUpdate("true");
config.getProcessEnginePlugins().add(new SpinProcessEnginePlugin());
config.setHistory(HistoryLevel.HISTORY_LEVEL_FULL.getName());
config.setJobExecutorActivate(true);
config.setMetricsEnabled(false);
final Logger logger = LoggerFactory.getLogger("History Event Handler");
final HistoryEventHandler testHistoryEventHandler = new HistoryEventHandler() {
#Override
public void handleEvent(final HistoryEvent evt) {
LOGGER.debug("handleEvent | " + evt.getProcessInstanceId() + " | "
+ evt.toString());
}
#Override
public void handleEvents(final List<HistoryEvent> events) {
for (final HistoryEvent curEvent : events) {
handleEvent(curEvent);
}
}
};
config.setHistoryEventHandler(new CompositeHistoryEventHandler(Collections.singletonList(testHistoryEventHandler)));
try {
final Resource[] bpmnResources = resourceLoader.getResources("classpath:*.bpmn");
final Resource[] dmnResources = resourceLoader.getResources("classpath:*.dmn");
config.setDeploymentResources(addAll(bpmnResources, dmnResources));
} catch (final IOException exception) {
exception.printStackTrace();
LOGGER.error("An error occurred while trying to deploy BPMN and DMN files", exception);
}
return config;
}
}
Add following code to the method processEngineConfiguration() above:
final ThreadPoolJobExecutor executor = new DefaultJobExecutor();
executor.setMaxJobsPerAcquisition(3);
executor.setWaitTimeInMillis(500);
executor.setLockTimeInMillis(300000);
executor.setMaxBackoff(150);
executor.setMaxWait(50);
final SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setJobExecutor(executor);
config.setJobExecutorDeploymentAware(true);
I am trying to deploy a HASingleton on a JBoss6.4. I have followed this tutorial to come up with the following:
I create a Service which is supposed to start a timer (own timer interface), by injecting the timer bean through JNDI.
public class HATimerService implements Service<String> {
private Logger logger = Logger.getLogger(HATimerService.class);
private final AtomicBoolean started = new AtomicBoolean(false);
private ServiceName serviceName;
private final InjectedValue<ServerEnvironment> env = new InjectedValue();
private String JNDI = "java:global/my-ear/my-module/MyTimer"
public HATimerService() {
serviceName = ServiceName.JBOSS.append(new String[]{"my", "ha", "singleton", "MyHaService"});
}
public String getValue() throws IllegalStateException, IllegalArgumentException {
return "";
}
public void start(StartContext context) throws StartException {
if(!started.compareAndSet(false, true)) {
throw new StartException("The service is still started!");
} else {
try {
InitialContext e = new InitialContext();
TimerScheduler myTimer = (TimerScheduler)e.lookup(JNDI);
timer.startTimer();
} catch (NamingException var6) {
throw new StartException("Could not initialize timer", var6);
}
}
}
public void stop(StopContext context) {
if(started.compareAndSet(true, false)) {
try {
InitialContext e = new InitialContext();
((TimerScheduler)e.lookup(JNDI)).stopTimer();
} catch (NamingException var4) {
logger.error("Could not stop timer", var4);
}
}
}
public ServiceName getServiceName() {
return serviceName;
}
public InjectedValue<ServerEnvironment> getEnvironment() {
return env;
}
}
I also have an activator which activates the service.
public class HATimerServiceActivator implements ServiceActivator {
private final Logger log = Logger.getLogger(this.getClass());
public HATimerServiceActivator() {
}
public void activate(ServiceActivatorContext context) {
HATimerService service = new HATimerService();
this.log.info(service.getServiceName() + "HATimerService will be installed");
SingletonService singleton = new SingletonService(service, service.getServiceName());
singleton.build(new DelegatingServiceContainer(context.getServiceTarget(), context.getServiceRegistry()))
.addDependency(ServerEnvironmentService.SERVICE_NAME, ServerEnvironment.class, service.getEnvironment())
.setInitialMode(Mode.ACTIVE)
.install();
}
}
The timer bean, HATimerService, and the HATimerServiceActivator are all deployed in an ear called my-ear. In the log files I can see:
JNDI bindings for session bean named MyTimer.... :
java:global/my-ear/my-module/MyTimer
However, every once in a while (approx. 1/3 of all deploys), this setup fails due to a NameNotFoundException where the JNDI lookup fails. The full exception is: Caused by: javax.naming.NameNotFoundException: Error looking up my-ear/my-module/MyTimer, service service jboss.naming.context.java.global.my-ear.my-module.MyTimer is not started
My guess is that this can be some sort of race condition where the bean isn't registered in the JNDI-tree yet. How can I make the service wait with the lookup until the bean is available?
It seems that there exists a possibility to create dependencies on deployment units. When creating the SingletonService, the following dependency can be added:
ServiceName ejbDependency = ServiceName.of("jboss", "deployment", "subunit", "my-ear.ear", "my-module.jar", "component", "MyTimerBean", "START");
singleton.build(new DelegatingServiceContainer(context.getServiceTarget(), context.getServiceRegistry()))
.addDependency(ServerEnvironmentService.SERVICE_NAME, ServerEnvironment.class, service.getEnvironment())
.setInitialMode(Mode.ACTIVE)
.addDependency(ejbDependency)
.install();
As long as ejbDependency is a correct dependency, the lookup will be performed after bean start.
In our REST-Service we want to implement a job that checks something every 10 seconds. So we thought we could use Quartz to make a Job that cover this. But the problem is, that we need to inject a singleton, because it is used in the job and the job seems to be not in the context of our service, so the injected class is always null (NullPointerException).
So is there another possible solution to achieve such a job without using Quartz? Already tried to write our own JobFactory that connects the job with the BeanManager, but it didnt work at all.
This is the code for the job that is not working:
#Stateless
public class GCEStatusJob implements Job, Serializable{
private Logger log = LoggerFactory.getLogger(GCEStatusJob.class);
#Inject
SharedMemory sharedMemory;
#Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
GoogleComputeEngineFactory googleComputeEngineFactory = new GoogleComputeEngineFactory();
List<HeartbeatModel> heartbeatList = new ArrayList<>(sharedMemory.getAllHeartbeats());
List<GCE> gceList = googleComputeEngineFactory.listGCEs();
List<String> ipAddressList = gceList.stream().map(GCE::getIp).collect(Collectors.toList());
for(HeartbeatModel heartbeat : heartbeatList){
if(ipAddressList.contains(heartbeat.getIpAddress())){
long systemTime = System.currentTimeMillis();
if(systemTime-heartbeat.getSystemTime()>10000){
log.info("Compute Engine mit IP "+heartbeat.getIpAddress()+" antwortet nicht mehr. Wird neu gestartet!");
String name = gceList.stream().filter((i) -> i.getIp().equals(heartbeat.getIpAddress())).findFirst().get().getName();
googleComputeEngineFactory.resetGCE(name);
}
}
}
}
}
SharedMemory is always null.
I have used Scheduler context map to achive this. You can try this.
In REST API when we create a Scheduler we can use the Context map to pass the parameters to Job
#Path("job")
public class RESTApi {
private String _userID;
public String get_userID() {
return _userID;
}
public void set_userID(String _userID) {
this._userID = _userID;
}
#GET
#Path("/start/{userId}")
public void startJob(#PathParam("userId") String userID) {
_userID = userID;
try {
SimpleTrigger trigger = new SimpleTrigger();
trigger.setName("updateTrigger");
trigger.setStartTime(new Date(System.currentTimeMillis() + 1000));
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setRepeatInterval(1000);
JobDetail job = new JobDetail();
job.setName("updateJob");
job.setJobClass(GCEStatusJob.class);
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getContext().put("apiClass", this);
scheduler.start();
scheduler.scheduleJob(job, trigger);
} catch (Exception e) {
e.printStackTrace();
}
}
}
JOB implementation
public class GCEStatusJob implements Job {
#Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
RESTApi apiClass;
try {
apiClass = ((RESTApi) arg0.getScheduler().getContext().get("apiClass"));
System.out.println("User name is" + apiClass.get_userID());
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
Correct me, if my understanding is wrong.