Suddenly the scheduler stopped working, and it only works TriggerScheduler is injected into another component.
If it is not injected it doesn't even appear on IntelliJ Endpoints -> Beans -> Application, so the triggers aren't executed because i guess the bean is not instantiated. Neither in that case the ProjectFormTriggerExecutor, ProjectTriggerExecutor, DistrictTriggerExecutor and AreaMonitoringFragmentTriggerExecutor are instantiated.
The purpose of the bean is to asyncronously trigger PostgreSQL triggers, there is another trigger which just sets markedForUpdate = true when conditions are met.
Everything works fine when i dummy inject it into another Component.
Why is this happening? Is there some spring configuration somebody of my team changed to not instantiate uninjected beans or something?
#file:Suppress("JpaQlInspection")
package com.gilbertjolly.ulsi.mgmt.application
import com.gilbertjolly.lib.crud.CrudEntity
import com.gilbertjolly.lib.crud.RecalculationPeriod
import com.gilbertjolly.lib.crud.request_util.newTransaction
import com.gilbertjolly.ulsi.mgmt.feature.model.country.District
import com.gilbertjolly.ulsi.mgmt.feature.model.metrics.entities.AreaMonitoringFragment
import com.gilbertjolly.ulsi.mgmt.feature.model.monitoring.forms.ProjectForm
import com.gilbertjolly.ulsi.mgmt.feature.model.org.Project
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.env.Environment
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import org.springframework.transaction.PlatformTransactionManager
import java.time.Instant
import java.util.logging.Logger
import javax.persistence.EntityManager
import kotlin.reflect.KClass
#Component
abstract class ScheduledTriggerExecutor {
#Autowired
lateinit var txManager: PlatformTransactionManager
#Autowired
lateinit var entityManager: EntityManager
val recalculationPeriod: RecalculationPeriod get() = RecalculationPeriod.EVERY_MINUTE
val logger = Logger.getLogger("TriggerRecalculationLog")!!
abstract val entityClass: KClass<out CrudEntity>
fun executeTrigger() {
val startMilliseconds = Instant.now().toEpochMilli()
txManager.newTransaction {
entityManager.createQuery("UPDATE ${entityClass.simpleName} SET markedForUpdate = FALSE WHERE markedForUpdate = TRUE")
.executeUpdate()
}
val triggerExecutionMilliseconds = Instant.now().minusMillis(startMilliseconds).toEpochMilli()
if (triggerExecutionMilliseconds > 5000) logger.warning("Trigger execution of ${entityClass.simpleName} took ${triggerExecutionMilliseconds / 1000.0f}")
}
}
#Component
class TriggerScheduler(val env: Environment,
val txManager: PlatformTransactionManager,
val scheduledTriggerExecutors: List<ScheduledTriggerExecutor>) {
#Scheduled(cron = "0 1 1 * * ?")
fun everyNightAt101() {
performRecalculation(RecalculationPeriod.AT_NIGHT)
}
#Scheduled(cron = "0 1 * * * ?")
fun everyHour() {
performRecalculation(RecalculationPeriod.HOURLY)
}
#Scheduled(cron = "0 0/30 * * * ?")
fun every30Minutes() {
performRecalculation(RecalculationPeriod.EVERY_HALF_AN_HOUR)
}
#Scheduled(cron = "0 0/2 * * * ?")
fun every2Minutes() {
performRecalculation(RecalculationPeriod.EVERY_TWO_MINUTES)
}
#Scheduled(cron = "0 0/1 * * * ?")
fun everyMinute() {
performRecalculation(RecalculationPeriod.EVERY_MINUTE)
}
fun performRecalculation(recalculationPeriod: RecalculationPeriod) {
scheduledTriggerExecutors
.filter { it.recalculationPeriod == recalculationPeriod }
.forEach { it.executeTrigger() }
}
}
#Component
class ProjectFormTriggerExecutor : ScheduledTriggerExecutor() {
override val entityClass = ProjectForm::class
}
#Component
class ProjectTriggerExecutor : ScheduledTriggerExecutor() {
override val entityClass = Project::class
}
#Component
class DistrictTriggerExecutor : ScheduledTriggerExecutor() {
override val entityClass = District::class
}
#Component
class AreaMonitoringFragmentTriggerExecutor : ScheduledTriggerExecutor() {
override val entityClass = AreaMonitoringFragment::class
}
Maybe global lazy bean initialization is turned on somewhere. To quickly test if it is, try annotating TriggerScheduler with #Lazy(false). You can also look for spring.main.lazy-initialization=true property somewhere in Spring Environment.
Related
There is already a question asking for logging the active configuration, there is a correct answer but the problem is that the configuration is logged only if all beans are correctly instantiated. I would like to log all properties even (mainly) if the application crash at startup. My question is more specific:
How to log all active properties of a spring boot application before the beans instantiation?
For doing this you need to register an ApplicationListener. The event to catch is the ApplicationPreparedEvent, according to the documentation:
ApplicationPreparedEvent is an event published when a SpringApplication is starting up and the
ApplicationContext is fully prepared but not refreshed. The bean
definitions will be loaded and the Environment is ready for use at
this stage.
The main method would look like this:
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MyApplication.class);
springApplication.addListeners(new PropertiesLogger());
springApplication.run(args);
}
I've reused the code of the answer cited in the current question but I've modified it because the context you get is not already refreshed and the structure of the environment is not exactly the same as after the startup of the application. I've also printed the properties by property sources: one for the the system environment, one for the system properties, one for the application configuration properties, etc... Note also that the ApplicationPreparedEvent can be triggered multiple times, and that properties are printed only the first time. See Spring Boot issue #8899 for details.
package com.toto.myapp.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import java.util.LinkedList;
import java.util.List;
public class PropertiesLogger implements ApplicationListener<ApplicationPreparedEvent> {
private static final Logger log = LoggerFactory.getLogger(PropertiesLogger.class);
private ConfigurableEnvironment environment;
private boolean isFirstRun = true;
#Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
if (isFirstRun) {
environment = event.getApplicationContext().getEnvironment();
printProperties();
}
isFirstRun = false;
}
public void printProperties() {
for (EnumerablePropertySource propertySource : findPropertiesPropertySources()) {
log.info("******* " + propertySource.getName() + " *******");
String[] propertyNames = propertySource.getPropertyNames();
Arrays.sort(propertyNames);
for (String propertyName : propertyNames) {
String resolvedProperty = environment.getProperty(propertyName);
String sourceProperty = propertySource.getProperty(propertyName).toString();
if(resolvedProperty.equals(sourceProperty)) {
log.info("{}={}", propertyName, resolvedProperty);
}else {
log.info("{}={} OVERRIDDEN to {}", propertyName, sourceProperty, resolvedProperty);
}
}
}
}
private List<EnumerablePropertySource> findPropertiesPropertySources() {
List<EnumerablePropertySource> propertiesPropertySources = new LinkedList<>();
for (PropertySource<?> propertySource : environment.getPropertySources()) {
if (propertySource instanceof EnumerablePropertySource) {
propertiesPropertySources.add((EnumerablePropertySource) propertySource);
}
}
return propertiesPropertySources;
}
}
📝 Show the Properties BEFORE application is ready
In my case, I needed to show the properties before the context is loaded. While debugging the app, I would like to log all the properties so that I know what's going on...
☕ Kotlin Implementation
As described at https://www.baeldung.com/spring-boot-environmentpostprocessor, the properties can be collected before the context is loaded through the use of EnvironmentPostProcessor, which is instantiated as part of Spring Factories from the call ConfigFileApplicationListener.loadPostProcessors(). At this point, you can collect all the properties and show in any specific way.
NOTE: While loading properties during this event, the context isn't ready. So, are the loggers. For this reason, the properties can be loaded before the App Banner (if any)
Also, the entry for the spring factory must be present, so create it first
org.springframework.boot.env.EnvironmentPostProcessor=\
cash.app.PropertiesLoggerEnvironmentPostProcessor
Then, create the logger
package cash.app
import org.springframework.boot.SpringApplication
import org.springframework.boot.env.EnvironmentPostProcessor
import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.core.env.ConfigurableEnvironment
import org.springframework.core.env.EnumerablePropertySource
import java.util.*
/**
* This is to log the properties (config and system) before the app loads. This way, we know what will be loaded
* on the app.
* Note that we can't use the logger because the context hasn't built yet at the time it loads the properties twice.
*
* As an event consumer, the method ConfigFileApplicationListener.onApplicationEnvironmentPreparedEvent is called
* while the context is building. The process is described at https://www.baeldung.com/spring-boot-environmentpostprocessor
* and one important aspect is that this class is an EnvironmentPostProcessor, only loaded before the App is loaded
* with the assistance of the "src/main/resources/META-INF/spring.factories". It is loaded by the
* ConfigFileApplicationListener.loadPostProcessors(), which looks for the list of classses in the factories.
*
* https://www.baeldung.com/spring-boot-environmentpostprocessor explains how to create AutoConfiguration classes for
* shared libraries. For the case of config, the reload of properties is detailed and explained on the docs at
* https://www.baeldung.com/spring-reloading-properties
*
* TODO: We need to hide the secrets, if they are defined here.
*
* #author Marcello.DeSales#gmail.com
*/
#Order(Ordered.LOWEST_PRECEDENCE)
class PropertiesLoggerEnvironmentPostProcessor : EnvironmentPostProcessor {
companion object {
/**
* Sharing is started immediately and never stops.
*/
private var numberOfPasses: Int = 0
private var systemProperties: MutableMap<String, String> = mutableMapOf()
}
override fun postProcessEnvironment(environment: ConfigurableEnvironment, application: SpringApplication) {
for (propertySource in findPropertiesPropertySources(environment)) {
// Avoid printing the systemProperties twice
if (propertySource.name.equals("systemProperties")) {
numberOfPasses = numberOfPasses?.inc()
} else {
System.out.println("******* \" + ${propertySource.getName()} + \" *******" )
}
// Adaptation of https://stackoverflow.com/questions/48212761/how-to-log-all-active-properties-of-a-spring-boot-application-before-the-beans-i/48212783#48212783
val propertyNames = propertySource.propertyNames
Arrays.sort(propertyNames)
for (propertyName in propertyNames) {
val resolvedProperty = environment!!.getProperty(propertyName!!)
val sourceProperty = propertySource.getProperty(propertyName).toString()
if (resolvedProperty == sourceProperty) {
if (propertySource.name.equals("systemProperties")) {
systemProperties.put(propertyName, resolvedProperty)
} else {
System.out.println( "${propertyName}=${resolvedProperty}" )
}
} else {
if (propertySource.name.equals("systemProperties")) {
systemProperties.put(propertyName, resolvedProperty ?: "")
} else {
System.out.println( "${propertyName}=${sourceProperty} ----- OVERRIDDEN =>>>>>> ${propertyName}=${resolvedProperty}" )
}
}
}
}
// The system properties show up twice in the process... The class is called twice and we only print it in the end.
if (numberOfPasses == 2) {
System.out.println("******* \" System Properties \" *******")
val sysPropertyNames = systemProperties.keys.sorted()
for (sysPropertyName in sysPropertyNames) {
val sysPropertyValue = systemProperties!!.get(sysPropertyName!!)
System.out.println( "${sysPropertyName}=${sysPropertyValue}" )
}
}
}
private fun findPropertiesPropertySources(environment: ConfigurableEnvironment): List<EnumerablePropertySource<*>> {
val propertiesPropertySources: MutableList<EnumerablePropertySource<*>> = LinkedList()
for (propertySource in environment!!.propertySources) {
if (propertySource is EnumerablePropertySource<*>) {
if (propertySource.name.equals("systemProperties") || propertySource.name.contains("applicationConfig:")) {
propertiesPropertySources.add(propertySource)
}
}
}
return propertiesPropertySources.asReversed()
}
}
🔊 Example Logs
Here's the loggers during the bootstrap of one of my services
/Users/marcellodesales/.gradle/jdks/jdk-14.0.2+12/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always
....
....
2022-02-22T21:24:39 INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] o.s.b.devtools.restart.ChangeableUrls : The Class-Path manifest attribute in /Users/marcellodesales/.gradle/caches/modules-2/files-2.1/com.sun.xml.bind/jaxb-core/2.2.7s-codec-1.11.jar
2022-02-22T21:24:39 INFO [app=springAppName_IS_UNDEFINED,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
******* " + applicationConfig: [classpath:/application.yaml] + " *******
management.endpoint.health.show-details=always
management.endpoints.web.base-path=/actuator ==========>>>>>> OVERRIDDEN =========>>>>>> management.endpoints.web.base-path=/orchestrator/actuator
management.endpoints.web.exposure.include=*
management.metrics.web.server.request.autotime.enabled=true
spring.application.name=orchestrator-service
spring.boot.admin.client.enabled=false ==========>>>>>> OVERRIDDEN =========>>>>>> spring.boot.admin.client.enabled=true
spring.cloud.discovery.client.composite-indicator.enabled=false
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
******* " + applicationConfig: [classpath:/application-ppd_dev.yaml] + " *******
spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist=true ==========>>>>>> OVERRIDDEN
=========>>>>>> spring.datasource.url=jdbc:postgresql://localhost:6433/supercash?createDatabaseIfNotExist\=true
spring.devtools.livereload.enabled=true
spring.mail.properties.mail.smtp.starttls.required=true
spring.mail.test-connection=true
******* " System Properties " *******
LOG_LEVEL_PATTERN=%5p [,%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%X{X-Span-Export:-}]
PID=74720
com.sun.management.jmxremote=
file.encoding=UTF-8
ftion
java.vm.specification.version=14
java.vm.vendor=AdoptOpenJDK
java.vm.version=14.0.2+12
jboss.modules.system.pkgs=com.intellij.rt
jdk.debug=release
line.separator=
os.arch=x86_64
os.name=Mac OS X
os.version=10.16
user.name=marcellodesales
user.timezone=America/Los_Angeles
2022-02-22T21:25:16 DEBUG [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74720 --- [ restartedMain] o.s.b.c.c.ConfigFileApplicationListener : Activated activeProfiles observed,db,ppd_dev
_____ _____ _
/ ____| / ____| | |
| (___ _ _ _ __ ___ _ __| | __ _ ___| |__
2022-02-22T20:41:08 INFO [app=orchestrator-service,prof=observed,db,ppd_dev][tid=,sid=,sxp=][uid=] 74181 --- [ restartedMain]
I want to pass reqData form My Controller class to Step of my job,Is there any way to achieve the same any help will be appreciated. I have a Object of HttpRequestData which i have revived in controller. Thanks
HttpRequestController.java
package com.npst.imps.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.npst.imps.utils.HttpRequestData;
import com.npst.imps.utils.TransactionResponseData;
import javax.servlet.http.HttpSession;
#RestController
public class HttpRequestController {
TransactionResponseData transactionResponseData;
#Autowired
HttpSession session;
JobExecution jobExecution;
#Autowired
JobLauncher jobLauncher;
#Autowired
Job fundtrans;
String test;
#RequestMapping("/impsft")
public String handleHttpRequest(#RequestBody HttpRequestData reqData) throws Exception {
Logger logger = LoggerFactory.getLogger(this.getClass());
try {
JobParameters jobParameters = new JobParametersBuilder().addLong("time", System.currentTimeMillis()).toJobParameters();
jobExecution = jobLauncher.run(fundtrans, jobParameters);
ExecutionContext context= jobExecution.getExecutionContext();
//context.put("reqData", reqData);
transactionResponseData=(TransactionResponseData) context.get("transactionData");
//System.out.println(context.get("transactionResponseData"));
} catch (Exception e) {
logger.info(e.getMessage());
e.printStackTrace();
}
return reqData+" "+transactionResponseData.getMsg()+",Tid="+transactionResponseData.getTid();
}
}
Below is my step class
I want to get the same reqData in my step class and from here on wards i will put inside step Execution object of doAfter method.
PrepareTransactionId.java
package com.npst.imps.action;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.npst.imps.service.TransactionService;
import com.npst.imps.utils.GenericTicketKey;
import com.npst.imps.utils.HttpRequestData;
import com.npst.imps.utils.TicketGenerator;
import com.npst.imps.utils.TransactionResponseData;
#Service
public class PrepareTransactionId implements Tasklet,StepExecutionListener{
static Logger logger = LoggerFactory.getLogger(PrepareTransactionId.class);
String appId;
private static TicketGenerator ticketGenerator = null;
private static GenericTicketKey genericTicketKey = null;
#Autowired
HttpSession session;
#Autowired
TransactionService transactionService;
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
try {
DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
Date date = new Date();
String ticket;
System.out.println("transactionService:: PrepareTransactionId"+transactionService);
TransactionResponseData transactionData=new TransactionResponseData();
System.out.println("reqData::"+reqData);
long value=transactionService.getMaxTid(appId);
logger.info("Max id From db::"+value);
if (value == 0) {
value = System.currentTimeMillis() / 10000;
long l = value;
ticket=l+"";
}
long l = value + 1;
ticketGenerator = TicketGenerator.getInstance(9999999999L, 0, l);
genericTicketKey = new GenericTicketKey(0, false, 10);
ticket = ticketGenerator.getNextEdgeTicketFor(genericTicketKey);
stepExecution.getJobExecution().getExecutionContext().put("ticket", ticket);
ticket=appId+ticket;
System.out.println("tid::"+ticket);
stepExecution.getJobExecution().getExecutionContext().put("tid", ticket);
stepExecution.getJobExecution().getExecutionContext().put("reqData", reqData);
transactionData.setMsg("Request Recived...");
transactionData.setTid(ticket+"");
transactionData.setNodeId(appId);
transactionData.setReqtime(dateFormat.format(date));;
stepExecution.getJobExecution().getExecutionContext().put("transactionData", transactionData);
logger.info("Request Recived with tid::"+ticket);
ExitStatus exist=new ExitStatus("SUCCESS", "success");
return exist.replaceExitCode("SUCCESS");
}
catch(Exception e) {
e.printStackTrace();
return ExitStatus.FAILED;
}
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
#Override
public void beforeStep(StepExecution arg0) {
// TODO Auto-generated method stub
}
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
return null;
}
}
TL;DR -> You can't.
JobParameters instances can only hold values of types:
String
Long
Date
Double.
The reason behind it is primarily persistence. Remember that all spring batch metadata (including job parameters) goes to a datasource.
To use custom objects, you would need to make sure that your object is immutable and thread-safe.
JobParameters documentation states:
Value object representing runtime parameters to a batch job. Because
the parameters have no individual meaning outside of the JobParameters
they are contained within, it is a value object rather than an entity.
It is also extremely important that a parameters object can be
reliably compared to another for equality, in order to determine if
one JobParameters object equals another. Furthermore, because these
parameters will need to be persisted, it is vital that the types added
are restricted. This class is immutable and therefore thread-safe.
JobParametersBuilder documentation states as well:
Helper class for creating JobParameters. Useful because all
JobParameter objects are immutable, and must be instantiated
separately to ensure typesafety. Once created, it can be used in the
same was a java.lang.StringBuilder (except, order is irrelevant), by
adding various parameter types and creating a valid JobParameters once
finished.
But i promise my objects are ok. Can I use them?
You could, but Spring developers decide to not support this feature a long time ago.
This was discussed in spring forums and even a JIRA ticket was created - status Won't fix.
Related Links
Spring - JobParameters JavaDocs
Spring - JobParametersBuilder JavaDocs
Spring - JIRA Ticket
Spring - Forums Discussion
I will not suggest to pass complete HttpRequestData. Rather than pass only requires information to batch. You can pass this information using JobParameters.
sample code
JobParameters parameters = new JobParametersBuilder().addString("key1",HttpRequestData.gteData)
.addString("key2",HttpRequestData.gteData)
.addString("key3",HttpRequestData.gteData)
.toJobParameters();
now in step you can get JobParameters from StepExecution
putting custom object in JobParameters
HashMap<String, JobParameter>();
JobParameter myParameter = new JobParameter(your custom object);
map.put("myobject", myParameter);
JobParameters jobParameters = new JobParameters(map);
I have written a scheduler using OSGi R6 annotations but it doesn't seem to run :
package com.aem.sites.interfaces;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
#ObjectClassDefinition(name = "Scheduler Configuration for Weather", description = "Configuration file for Scheduler")
public #interface SchedulerConfiguration {
#AttributeDefinition(
name = "sample parameter",
description="Sample String parameter",
type = AttributeType.STRING
)
public String parameter() default "scheduler";
#AttributeDefinition(
name = "Concurrent",
description = "Schedule task concurrently",
type = AttributeType.BOOLEAN
)
boolean scheduler_concurrent() default true;
#AttributeDefinition(
name = "Expression",
description = "Cron-job expression. Default: run every minute.",
type = AttributeType.STRING
)
String scheduler_expression() default "0 * * * * ?";
}
and
package com.aem.sites.schedulers;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.aem.sites.interfaces.SchedulerConfiguration;
#Component(immediate = true,
configurationPid = "com.aem.sites.schedulers.WeatherServiceScheduler")
#Designate(ocd=SchedulerConfiguration.class)
public class WeatherServiceScheduler implements Runnable {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private String myParameter;
#Override
public void run() {
logger.info("*******************************************Sample OSGi Scheduler is now running", myParameter);
}
#Activate
public void activate(SchedulerConfiguration config) {
logger.info("*******************************************weather service scheduler"+ myParameter);
myParameter = config.parameter();
}
}
I am following this https://github.com/nateyolles/aem-osgi-annotation-demo/blob/master/core/src/main/java/com/nateyolles/aem/osgiannotationdemo/core/schedulers/SampleOsgiScheduledTask.java but looks like I am doing something wrong here. Not sure what though.
Thanks in advance
In your WeatherSchedulerService class, you are not registering it as a service. Instead of configurationPid, you can do like this service = Runnable.class.
The correct way to create a SlingScheduler using OSGi R6 annotations is as follows -
Create your OSGi configuration class
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
#ObjectClassDefinition(name = "Sling Scheduler Configuration", description = "This configuration is used to demonstrates a sling scheduler in action")
public #interface SchedulerConfiguration {
#AttributeDefinition(
name = "Scheduler name",
description = "Name of the scheduler",
type = AttributeType.STRING)
public String name() default "Custom Sling Scheduler";
#AttributeDefinition(
name = "Enabled",
description = "Flag to enable/disable a scheduler",
type = AttributeType.STRING)
public boolean enabled() default false;
#AttributeDefinition(
name = "Cron expression",
description = "Cron expression used by the scheduler",
type = AttributeType.STRING)
public String cronExpression() default "0 * * * * ?";
#AttributeDefinition(
name = "Custom parameter",
description = "Custom parameter to showcase the usage of a sling scheduler",
type = AttributeType.STRING)
public String customParameter();
}
Create your Scheduler class as a service. For creating an OSGi service using R6 annotations we use #Component(service=<your-interface>.class,...).
Thus, create a service as follows
import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.redquark.aem.learning.core.configurations.SchedulerConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Component(immediate = true, service = Runnable.class)
#Designate(ocd = SchedulerConfiguration.class)
public class CustomScheduler implements Runnable {
// Logger
private final Logger log = LoggerFactory.getLogger(this.getClass());
// Custom parameter that is to be read from the configuration
private String customParameter;
// Id of the scheduler based on its name
private int schedulerId;
// Scheduler instance injected
#Reference
private Scheduler scheduler;
/**
* Activate method to initialize stuff
*
* #param schedulerConfiguration
*/
#Activate
protected void activate(SchedulerConfiguration schedulerConfiguration) {
schedulerId = schedulerConfiguration.name().hashCode();
customParameter = schedulerConfiguration.customParameter();
}
/**
* Modifies the scheduler id on modification
*
* #param schedulerConfiguration
*/
#Modified
protected void modified(SchedulerConfiguration schedulerConfiguration) {
// Removing scheduler
removeScheduler();
// Updating the scheduler id
schedulerId = schedulerConfiguration.name().hashCode();
// Again adding the scheduler
addScheduler(schedulerConfiguration);
}
/**
* This method deactivates the scheduler and removes it
*
* #param schedulerConfiguration
*/
#Deactivate
protected void deactivate(SchedulerConfiguration schedulerConfiguration) {
// Removing the scheduler
removeScheduler();
}
/**
* This method removes the scheduler
*/
private void removeScheduler() {
log.info("Removing scheduler: {}", schedulerId);
// Unscheduling/removing the scheduler
scheduler.unschedule(String.valueOf(schedulerId));
}
/**
* This method adds the scheduler
*
* #param schedulerConfiguration
*/
private void addScheduler(SchedulerConfiguration schedulerConfiguration) {
// Check if the scheduler is enabled
if (schedulerConfiguration.enabled()) {
// Scheduler option takes the cron expression as a parameter and run accordingly
ScheduleOptions scheduleOptions = scheduler.EXPR(schedulerConfiguration.cronExpression());
// Adding some parameters
scheduleOptions.name(schedulerConfiguration.name());
scheduleOptions.canRunConcurrently(false);
// Scheduling the job
scheduler.schedule(this, scheduleOptions);
log.info("Scheduler added");
} else {
log.info("Scheduler is disabled");
}
}
/**
* Overridden run method to execute Job
*/
#Override
public void run() {
log.info("Custom Scheduler is now running using the passed custom paratmeter, customParameter {}",
customParameter);
}
In the activate() method, we are reading the required values. Then we are getting the schedulerId from the scheduler name.
The modified() method recalculates the schedulerId in case the OSGi configuration is modified.
In the addScheduler() method, we are registering the scheduler using the Scheduler API.
For more information and step by step execution, you can see my blog post as well - Day 13: Schedulers in AEM
I hope this helps. Happy coding!
There is no need for configurationPid in the class annotation, and also you are missing service=Runnable.class which should follow immediate=true, i.e. the class declaration should look like:
#Component(immediate = true, service=Runnable.class)
#Designate(ocd=SchedulerConfiguration.class)
public class WeatherServiceScheduler implements Runnable {
I want to cache a List of Category when level == 0, but keeping getting IllegalArgumentException. What am I missing?
In Service class:
#Override
#Transactional(readOnly = true)
#Cacheable(value="categories", condition="#level == 0")
public List<Category> findCategoryByLevel(int level) throws DataAccessException {
return categoryRepository.findCategoryByLevel(level);
}
Error:
java.lang.IllegalArgumentException: Cannot find cache named 'categories' for CacheableOperation[public java.util.List com.mySite.service.DidicityServiceImpl.findCategoryByLevel(int) throws org.springframework.dao.DataAccessException] caches=[categories] | key='' | condition='#level == 0' | unless=''
What caching provider are you using in Spring's Cache Abstraction? (I.e. ehcache, Guava, Hazelcast, etc)
It appears you are missing an explicit "Cache" definition and instance in your actual caching provider. For example, when using Pivotal GemFire as a caching provider in Spring's Cache Abstraction, you need to define a Region (a.k.a. Cache in the Spring Cache Abstraction), using your example above, like so...
<gfe:cache ...>
<gfe:replicated-region id="categories" persistent="false"...>
...
</gfe:replicated-region>
Spring Data GemFire goes onto lookup the "Cache" when the cached application service|repository method is invoked, and so the actual backing "Cache" (i.e. the GemFire Region) must exist, otherwise the Spring Cache Abstraction throws an IllegalArgumentException.
So, by way of a more explicit example, I wrote the following test...
/*
* Copyright 2014-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.spring.cache;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.spring.cache.CachingWithConcurrentMapUsingExplicitlyNamedCachesTest.ApplicationConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* The CachingWithConcurrentMapUsingExplicitlyNamedCachesTest class is a test suite of test cases testing the contract
* and functionality of Spring Cache Abstraction using the ConcurrentMap-based Cache Management Strategy
* with explicitly named "Caches".
*
* NOTE: when the Cache(s) [is|are] explicitly named using the ConcurrentMapCacheManager, then "dynamic" is disabled
* and corresponding the named Cache in the #Cacheable annotation of the cached service method must exist
* (or be declared). If no explicitly named Caches are provided to the ConcurrentMapManager constructor, then dynamic
* is enabled and the Cache will be created at runtime, on the fly.
*
* #author John Blum
* #see org.junit.Test
* #see org.junit.runner.RunWith
* #see org.springframework.cache.Cache
* #see org.springframework.cache.CacheManager
* #see org.springframework.cache.annotation.Cacheable
* #see org.springframework.cache.annotation.EnableCaching
* #see org.springframework.cache.concurrent.ConcurrentMapCacheManager
* #see org.springframework.context.annotation.Bean
* #see org.springframework.context.annotation.Configuration
* #see org.springframework.test.context.ContextConfiguration
* #see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* #since 1.0.0
*/
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ApplicationConfiguration.class)
#SuppressWarnings("unused")
public class CachingWithConcurrentMapUsingExplicitlyNamedCachesTest {
#Autowired
private NumberCategoryService numberCategoryService;
#Test
public void numberCategoryCaching() {
assertThat(numberCategoryService.isCacheMiss(), is(false));
List<NumberCategory> twoCategories = numberCategoryService.classify(2.0);
assertThat(twoCategories, is(notNullValue()));
assertThat(twoCategories.size(), is(equalTo(3)));
assertThat(twoCategories.containsAll(Arrays.asList(
NumberCategory.EVEN, NumberCategory.POSITIVE, NumberCategory.WHOLE)), is(true));
assertThat(numberCategoryService.isCacheMiss(), is(true));
List<NumberCategory> twoCategoriesAgain = numberCategoryService.classify(2.0);
assertThat(twoCategoriesAgain, is(sameInstance(twoCategories)));
assertThat(numberCategoryService.isCacheMiss(), is(false));
List<NumberCategory> negativeThreePointFiveCategories = numberCategoryService.classify(-3.5);
assertThat(negativeThreePointFiveCategories, is(notNullValue()));
assertThat(negativeThreePointFiveCategories.size(), is(equalTo(3)));
assertThat(negativeThreePointFiveCategories.containsAll(Arrays.asList(
NumberCategory.ODD, NumberCategory.NEGATIVE, NumberCategory.FLOATING)), is(true));
assertThat(numberCategoryService.isCacheMiss(), is(true));
}
#Configuration
#EnableCaching
public static class ApplicationConfiguration {
#Bean
public CacheManager cacheManager() {
//return new ConcurrentMapCacheManager("Categories");
return new ConcurrentMapCacheManager("Temporary");
}
#Bean
public NumberCategoryService numberCategoryService() {
return new NumberCategoryService();
}
}
#Service
public static class NumberCategoryService {
private volatile boolean cacheMiss;
public boolean isCacheMiss() {
boolean localCacheMiss = this.cacheMiss;
this.cacheMiss = false;
return localCacheMiss;
}
protected void setCacheMiss() {
this.cacheMiss = true;
}
#Cacheable("Categories")
public List<NumberCategory> classify(double number) {
setCacheMiss();
List<NumberCategory> categories = new ArrayList<>(3);
categories.add(isEven(number) ? NumberCategory.EVEN : NumberCategory.ODD);
categories.add(isPositive(number) ? NumberCategory.POSITIVE : NumberCategory.NEGATIVE);
categories.add(isWhole(number) ? NumberCategory.WHOLE : NumberCategory.FLOATING);
return categories;
}
protected boolean isEven(double number) {
return (isWhole(number) && Math.abs(number) % 2 == 0);
}
protected boolean isFloating(double number) {
return !isWhole(number);
}
protected boolean isNegative(double number) {
return (number < 0);
}
protected boolean isOdd(double number) {
return !isEven(number);
}
protected boolean isPositive(double number) {
return (number > 0);
}
protected boolean isWhole(double number) {
return (number == Math.floor(number));
}
}
public enum NumberCategory {
EVEN,
FLOATING,
NEGATIVE,
ODD,
POSITIVE,
WHOLE
}
}
This test example is currently setup to throw the IllegalArgumentException. If you change this...
return new ConcurrentMapCacheManager("Temporary");
To this...
return new ConcurrentMapCacheManager("Categories");
Then all is well.
Hopefully this adequately illustrates the problem you are having and how to fix it.
Cheers,
John
Looks like you want to use the key as a static hardcoded string.
Try the following "'categories'"
#Override
#Transactional(readOnly = true)
#Cacheable(value="'categories'", condition="#level == 0")
public List<Category> findCategoryByLevel(int level) throws DataAccessException {
return categoryRepository.findCategoryByLevel(level);
}
Accordinong to EJB 3.0 specification: While an instance is in a transaction, the instance must not attempt to use the resource-manager specific transaction demarcation API (e.g. it must not invoke the
commit or rollback method on the java.sql.Connection interface or on the
javax.jms.Session interface) In 13.3.3 of Specification.
I tried one example - where in BEAN managed transaction I included java.sql.Connection.commit() - created Stateless bean in NetBeans as EE5, deployed on Glassfish 3.1 and container did not complain? Bean method updates the database without any errors in Glassfish log. Is this expected behavior?
Also, there is no such restriction on using java.sql.Connection.commit() for beans with container transaction managed transactions mentioned in specification.
Thanks
Branislav
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package ejb;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.*;
import javax.persistence.Transient;
import javax.sql.DataSource;
import javax.transaction.*;
/**
*
* #author bane
*/
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
public class MySession implements MySessionRemote {
#Resource(name = "SAMPLE")
private DataSource SAMPLE;
//
#Resource UserTransaction utx;
//gore je novi kod
#Override
public String getResult() {
return "This is my Session Bean";
}
public void doSomething() {
try {
Connection conn = SAMPLE.getConnection();
Statement stmt = conn.createStatement();
String q = "select * from BOOK";
String up = "update BOOK set PRICE = PRICE + 1";
utx.begin();
int num = stmt.executeUpdate(up);
System.out.println("num: "+num);
ResultSet rs = stmt.executeQuery(q);
//is conn.commit() legal?
conn.commit();
String name = null;
int price = 0;
while (rs.next()) {
name = rs.getString(2);
price = rs.getInt(3);
System.err.println(name+" , "+price);
}
utx.commit();
} catch (SQLException ex) {
Logger.getLogger(MySession.class.getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(MySession.class.getName()).log(Level.SEVERE, null, ex);
}
}
// Add business logic below. (Right-click in editor and choose
// "Insert Code > Add Business Method")
}