How to restart scheduled task on runtime with EnableScheduling annotation in spring? - java

I have been investigating how to change the frequency of a job on runtime with Java 8 and spring. This question was very useful but it did not totally solve my issue.
I can now configure the date when to job should be executed next. But If set the delay to 1 year, then I need to wait 1 year before the new configuration in taken into account.
My idea would be to stop the scheduled task if the configuration value is changed (so from another class). Then recalculate the next time the task should be executed. Perhaps there is an easier way of doing this.
Here is the code I have so far.
#Configuration
#EnableScheduling
public class RequestSchedulerConfig implements SchedulingConfigurer {
#Autowired
SchedulerConfigService schedulerConfigService;
#Bean
public RequestScheduler myBean() {
return new RequestScheduler();
}
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
taskRegistrar.addTriggerTask(
new Runnable() {
#Override public void run() {
myBean().startReplenishmentComputation();
}
},
new Trigger() {
#Override public Date nextExecutionTime(TriggerContext triggerContext) {
Duration d = schedulerConfigService.getIntervalFromDB();
return DateTime.now().plus(d).toDate();
}
}
);
}
}
This would be what I would like to do.
#RestController
#RequestMapping("/api/config/scheduler")
public class RequestSchedulerController {
#Autowired
ApplicationConfigWrapper applicationConfigWrapper;
#RequestMapping("/set/")
#ResponseBody
public String setRequestSchedulerConfig(#RequestParam(value = "frequency", defaultValue = "") final String frequencyInSeconds){
changeValueInDb(frequencyInSeconds);
myJob.restart();
return "Yeah";
}
}

Create a singleton bean that gets an injected TaskScheduler. This will hold as state variables all ScheduledFutures, like private ScheduledFuture job1;
On deployment, load from databases all schedule data and start the jobs, filling in all state variables like job1.
On change of scheduling data, cancel the corresponding Future (e.g job1) and then start it again with the new scheduling data.
The key idea here is to get control on the Futures as they are created, so to save them in some state variables, so that when something in scheduling data changes, you can cancel them.
Here is the working code:
applicationContext.xml
<task:annotation-driven />
<task:scheduler id="infScheduler" pool-size="10"/>
The singleton bean, that holds the Futures
#Component
public class SchedulerServiceImpl implements SchedulerService {
private static final Logger logger = LoggerFactory.getLogger(SchedulerServiceImpl.class);
#Autowired
#Qualifier(value="infScheduler")
private TaskScheduler taskScheduler;
#Autowired
private MyService myService;
private ScheduledFuture job1;//for other jobs you can add new private state variables
//Call this on deployment from the ScheduleDataRepository and everytime when schedule data changes.
#Override
public synchronized void scheduleJob(int jobNr, long newRate) {//you are free to change/add new scheduling data, but suppose for now you only want to change the rate
if (jobNr == 1) {//instead of if/else you could use a map with all job data
if (job1 != null) {//job was already scheduled, we have to cancel it
job1.cancel(true);
}
//reschedule the same method with a new rate
job1 = taskScheduler.scheduleAtFixedRate(new ScheduledMethodRunnable(myService, "methodInMyServiceToReschedule"), newRate);
}
}
}

What about using Set<ScheduledTask> ScheduledTaskRegistrar.getScheduledTasks() to get all schedules tasks and calling ScheduledTask::cancel() ?
or maybe executing ThreadPoolTaskScheduler::shutdown()
and recreating ThreadPoolTaskScheduler and setting it again in ScheduledTaskRegistrar ?

The following, an improved version of this code, seems a working POC based on Spring Boot. You can start and stop the scheduled tasks any number of times based on a table configuration. But you can't start a stopped job from where it was stopped.
1) In the main class, make sure scheduling is enabled, and perhaps configure a ThreadPoolTaskScheduler with size more than one so scheduled tasks may run in parallel.
#SpringBootApplication
#EnableScheduling
#Bean
public TaskScheduler poolScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
scheduler.setPoolSize(10);
scheduler.initialize();
return scheduler;
}
2) an object that contains the schedule configuration, e.g. a cron like configuration in this case:
public class ScheduleConfigVo {
//some constructors, getter/setters
private String taskName;
private String configValue; // like */10 * * * * * for cron
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScheduleConfigVo that = (ScheduleConfigVo) o;
return taskName.equals(that.taskName) &&
configValue.equals(that.configValue) ;
}
#Override
public int hashCode() {
return Objects.hash(taskName, configValue);
}
}
equals and hashCode are needed since object comparison will be conducted.
3) I use mybatis, so the sheduled selection is something like:
#Mapper
public interface ScheduleConfigMapper {
List<ScheduleConfigVo> getAllConfigure();
}
and
public class ScheduleConfigMapperImpl implements ScheduleConfigMapper {
#Override
public List<ScheduleConfigVo>getAllConfigure() {
return getAllConfigure();
}
}
with a simple companion mybatis xml configuration (not shown here but can find it anywhere in the internet).
4) create a table and populate it with a record
CREATE TABLE "SCHEDULER"
( "CLASS_NAME" VARCHAR2(100), --PK
"VALUE" VARCHAR2(20 BYTE) --not null
)
and populated it with a record class_name=Task1, value=*/10 * * * * * etc. => run like a cron every ten seconds
5) the scheduler part:
#Service
public class DynamicScheduler implements SchedulingConfigurer {
#Autowired
private ScheduleConfigMapper repo;
#Autowired
private Runnable [] tsks;
#Autowired
private TaskScheduler tsch;
private ScheduledTaskRegistrar scheduledTaskRegistrar;
private ScheduledFuture future;
private Map<String, ScheduledFuture> futureMap = new ConcurrentHashMap<>(); // for the moment it has only class name
List<ScheduleConfigVo> oldList = new ArrayList<>();
List<ScheduleConfigVo> newList;
List<ScheduleConfigVo> addList = new ArrayList<>();
List<ScheduleConfigVo> removeList = new ArrayList<>();
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
if (scheduledTaskRegistrar == null) {
scheduledTaskRegistrar = taskRegistrar;
}
if (taskRegistrar.getScheduler() == null) {
taskRegistrar.setScheduler(tsch);
}
updateJobList();
}
#Scheduled(fixedDelay = 5000)
public void updateJobList() {
newList = repo.getConfigure()== null ? new ArrayList<>() : repo.getConfigure();
addList.clear();
removeList.clear();
if (!newList.isEmpty()) {
//compare new List with oldList
if (!oldList.isEmpty()) {
addList = newList.stream().filter(e -> !oldList.contains(e)).collect(Collectors.toList());
removeList = oldList.stream().filter(e -> !newList.contains(e)).collect(Collectors.toList());
} else {
addList = new ArrayList<>(newList); // nothing to remove
}
} else { // nothing to add
if (!oldList.isEmpty()) {
removeList = new ArrayList<>(oldList);
} // else removeList = 0
}
log.info("addList="+ addList.toString());
log.info("removeList="+ removeList.toString());
//re-schedule here
for ( ScheduleConfigVo conf : removeList ) {
if ( !futureMap.isEmpty()){
future = futureMap.get(conf.getTaskName());
if (future != null) {
log.info("cancelling task "+conf.getTaskName() +" ...");
future.cancel(true);
log.info(conf.getTaskName() + " isCancelled = " + future.isCancelled());
futureMap.remove(conf.getTaskName());
}
}
}
for ( ScheduleConfigVo conf : addList ) {
for (Runnable o: tsks) {
if (o.getClass().getName().contains(conf.getTaskName())) { // o has fqn whereas conf has class name only
log.info("find " + o.getClass().getName() + " to add to scheduler");
future = scheduledTaskRegistrar.getScheduler().schedule(o, (TriggerContext a) -> {
CronTrigger crontrigger = new CronTrigger(conf.getConfigValue());
return crontrigger.nextExecutionTime(a);
});
futureMap.put(o.getClass().getName().substring(o.getClass().getName().lastIndexOf('.')+1), future);
}
}
}
oldList.clear();
oldList= newList;
}
6) one or more Runnable tasks that actually does the cron work, for instance:
#Slf4j
#Service
public class Task1 implements Runnable {
#Override
public void run() {
log.info("Task1 is running...");
}
}
Once the application is started, the cron job will run. The running interval changes as the value in the table changes, and the job stops as the table entry is removed.
Note that if the job runs longer than the cron interval, the next run is after the previous job finishes. You can simulate this situation by adding, for instance, sleep 15 seconds in Task1 above to test it. Sometimes after being cancelled, a job maybe still run till it's done.
***Just edit to add that if folks like lambda to save some lines, the above removeList and addList can be modified as:
removeList.stream().filter(conf -> {
future = futureMap.get(conf.getTaskName());
return future != null;
}).forEach((conf) -> {
log.info("cancelling task " + conf.getTaskName() + " ...");
future.cancel(true);
log.info(conf.getTaskName() + " isCancelled = " + future.isCancelled());
});
and
Arrays.stream(tsks).forEach(task -> {
addList.stream().filter(conf -> task.getClass().getName().contains(conf.getTaskName())).forEach(conf -> {
log.info("find " + task.getClass().getName() + " to add to scheduler");
future = scheduledTaskRegistrar.getScheduler().schedule(task, (TriggerContext a) -> {
CronTrigger crontrigger = new CronTrigger(conf.getConfigValue());
return crontrigger.nextExecutionTime(a);
});
futureMap.put(task.getClass().getName().substring(task.getClass().getName().lastIndexOf('.') + 1), future);
});
});

One simple approach is to only ever add new tasks, not to try and cancel or restart the scheduler.
Each time the configuration changes, just add a new task with its new configuration.
Then, whenever a task runs, it must first check some state (by querying database, or lookup up in a concurrent map, or whatever) to decide if it is the latest version. If it is, then it should proceed. Otherwise, it should end immediately.
The only downside is that if you are changing job configuration frequently compared to how often they run, then of course the list of scheduled tasks will keep growing in memory.

Related

How to set an expiration time for the cache?

#Override
#Transactional
public UserDetails loadUserByUsername(String username) {
log.debug("Load user {}", username);
UserDetails userDetails = userService.findUserDetailsByName(username).get();
return userDetails;
}
When the user authorizes, the loadUserByUsername method calls the findUserDetailsByName method from the userService. In this method, when authorizing a user, Spring writes the user's data to cache and does not contact the database the next time.
My task is to delete this cache within 10 minutes, so that Spring can re-enter the database, since roles can be changed. I know a solution using #Scheduled, but I think the given approach is wrong. Can you suggest an acceptable option for clearing the cache every 10 minutes?
#Component
#Slf4j
public class Customizer implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
#Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(List.of(Caches.UD_CACHE));
}
#CacheEvict(allEntries = true, value = {Caches.UD_CACHE})
#Scheduled(fixedDelay = 10 * 60000)
public void reportCacheEvict() {
log.info("Flush cache " + Caches.UD_CACHE + " at " + new Date());
}
}
private final Cache userDetailsCache;
public UserService(CacheManager cacheManager) {
this.userDetailsCache = cacheManager.getCache(Caches.USER_DETAILS_CACHE);
}
public Optional<UserDetails> findUserDetailsByName(String username) {
ValueWrapper v= userDetailsCache.get(username);
if (v== null) {
Optional<UserDetails> userDetailsOpt =
userRepository.findOne(QUser.user.userName.eq(username)).map(UserService::createFrom);
userDetailsOpt.ifPresent(u -> userDetailsCache.put(username, u));
return userDetailsOpt.map(u -> withUserDetails(u).build());
} else {
return Optional.ofNullable((UserDetails) v.get()).map(u -> withUserDetails(u).build());
}
}
public final class Caches {
public static final String UD_CACHE = "udCache";
}
In you example you use fixedDelay, but since you want to clear the cache every 10 mins you should use a fixedRate.
public class Customizer implements CacheManagerCustomizer<ConcurrentMapCacheManager>{
#Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(List.of(Caches.UD_CACHE));
}
#CacheEvict(allEntries = true, value = {Caches.UD_CACHE})
#Scheduled(fixedRate = 10 * 60000)
public void evictUDCache() {
log.info("Flush cache " + Caches.UD_CACHE + " at " + new Date());
}
}
EDIT
thanks. but I'm interested in the option without #Scheduled
You can inject the TaskScheduler and then use it when you need it.
TaskScheduler taskScheduler = ....;
Cache udCache = cacheManager.getCache(Caches.UD_CACHE);
Duration clearCacheInterval = Duration.of(10L, ChronoUnit.MINUTES);
taskScheduler.scheduleAtFixedRate(udCache::clear, clearCacheInterval);
or schedule with a fixed delay
taskScheduler.scheduleWithFixedDelay(udCache::clear, clearCacheInterval);

rxJava Ordered (by key) task execution

I have a bunch of objects representing some data. These objects can be written to their corresponding files. User may request some changes to be made quicker than previous changes written to the file.
Say, I make changes to File A, File B and File C and submit them for execution. Then, while they are being written, I make changes to File A and post it. For instance, there are 3 threads operating. Once first changes to A, B and C executed (written to files), 1st and 2nd changes to A will be executed almost simultaneously. However, I want the 2nd change to be applied after the 1st one is done.
How can I do that in rxJava?
Another point. In a different place I want to run action with the latest changes. One option is to wait until all tasks finished.
Is there appropriate RxJava primitive/approach that would hopefully cover these 2 use cases?
I am new to RxJava, but I hope this makes sense. Subjects come to my mind as relevant, but there gonna be hundreds of files.
I already have the implementation using custom Executor.
public class OrderingExecutor
implements Executor
{
#Delegate
private final Executor delegate;
private final Map<Object, Queue<Runnable>> keyedTasks = new HashMap<>();
public OrderingExecutor(
Executor delegate)
{
this.delegate = delegate;
}
public void execute(
Runnable task,
Object key)
{
Objects.requireNonNull(key);
boolean first;
Runnable wrappedTask;
synchronized (keyedTasks)
{
Queue<Runnable> dependencyQueue = keyedTasks.get(key);
first = (dependencyQueue == null);
if (dependencyQueue == null)
{
dependencyQueue = new LinkedList<>();
keyedTasks.put(key, dependencyQueue);
}
wrappedTask = wrap(task, dependencyQueue, key);
if (!first)
{
dependencyQueue.add(wrappedTask);
}
}
// execute method can block, call it outside synchronize block
if (first)
{
delegate.execute(wrappedTask);
}
}
private Runnable wrap(
Runnable task,
Queue<Runnable> dependencyQueue,
Object key)
{
return new OrderedTask(task, dependencyQueue, key);
}
class OrderedTask
implements Runnable
{
private final Queue<Runnable> dependencyQueue;
private final Runnable task;
private final Object key;
public OrderedTask(
Runnable task,
Queue<Runnable> dependencyQueue,
Object key)
{
this.task = task;
this.dependencyQueue = dependencyQueue;
this.key = key;
}
#Override
public void run()
{
try
{
task.run();
}
finally
{
Runnable nextTask = null;
synchronized (keyedTasks)
{
if (dependencyQueue.isEmpty())
{
keyedTasks.remove(key);
}
else
{
nextTask = dependencyQueue.poll();
}
}
if (nextTask != null)
{
delegate.execute(nextTask);
}
}
}
}
}
Maybe some sensible way to plug it into rxJava?
It's not fully clear what you try to achieve here, but you can layer a priority queue on
top of RxJava.
class OrderedTask implements Comparable<OrderedTask> { ... }
PriorityBlockingQueue<OrderedTask> queue = new PriorityBlockingQueue<>();
PublishSubject<Integer> trigger = PublishSubject.create();
trigger.flatMap(v -> {
OrderedTask t = queue.poll();
return someAPI.workWith(t);
}, 1)
.subscribe(result -> { }, error -> { });
queue.offer(new SomeOrderedTask(1));
trigger.onNext(1);
queue.offer(new SomeOrderedTask(2));
trigger.onNext(2);

Task execution in Java web application

I'm developing Spring MVC web application. One of it's functionalities is file converting (uploading file -> converting -> storing on server).
Some files could be too big for converting on-the-fly so I decided to put them in shared queue after upload.
Files will be converted with priority based on upload time, i.e. FIFO.
My idea is to add task to queue in controller after upload.
There would also be service executing all tasks in queue, and if empty, then wait until new task is added. I don't need scheduling - tasks should be executing always when queue is not empty.
I've read about ExecutorService but I didn't find any example that fit to my case.
I'd appreciate any suggestions.
EDIT
Thanks for answers, I need to clarify my problem:
Basically, I know how to execute tasks, I need to manage with handling the queue of tasks. User should be able to view the queue and pause, resume or remove task from queue.
My task class:
public class ConvertTask implements Callable<String> {
private Converter converter;
private File source;
private File target;
private State state;
private User user;
public ConvertTask(Converter converter, File source, File target, User user) {
this.converter = converter;
this.source = source;
this.target = target;
this.user = user;
this.state = State.READY;
}
#Override
public String call() throws Exception {
if (this.state == State.READY) {
BaseConverterService converterService = ConverterUtils.getConverterService(this.converter);
converterService.convert(this.source, this.target);
MailSendServiceUtil.send(user.getEmail(), target.getName());
return "success";
}
return "task not ready";
}
}
I also created class responsible for managing queue/tasks followed by your suggestions:
#Component
public class MyExecutorService {
private LinkedBlockingQueue<ConvertTask> converterQueue = new LinkedBlockingQueue<>();
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public void add(ConvertTask task) throws InterruptedException {
converterQueue.put(task);
}
public void execute() throws InterruptedException, ExecutionException {
while (!converterQueue.isEmpty()) {
ConvertTask task = converterQueue.peek();
Future<String> statusFuture = executorService.submit(task);
String status = statusFuture.get();
converterQueue.take();
}
}
}
My point is, how to execute tasks if queue is not empty and resume when new task is added and queue was previously empty. I think of some code that fits in add(ConvertTask task) method.
Edited after question updates
You don't need to create any queue for the tasks since the ThreadPoolExecutor implementation has its own queue. Here's the source code of Oracle's Java 8 implementation of newSingleThreadExecutor() method:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
So you just submit a new task directly and it's getting queued by the ThreadPoolExecutor
#Component
public class MyExecutorService {
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public void add(ConvertTask task) throws InterruptedException {
Future<String> statusFuture = executorService.submit(task);
}
}
If you're worried about the bounds of your queue, you can create a queue instance explicitly and supply it to a ThreadPoolExecutor constructor.
private executorService = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(MAX_SIZE));
Please note that I have removed the line
String status = statusFuture.get();
because get() call is blocking. If you have this line in the same thread where you submit, your code is not asynchronous anymore. You should store the Future objects and check them asynchronously in a different thread. Or you can consider using CompletableFuture introduced in Java 8. Check out this post.
After upload you should return response immediately. The client can't wait for resource too long. However you can change it in the client settings. Anyway if you are running a background task you can do it without interacting with the client or notify the client while execution is in progress. This is an example of callable demo used by the executor service
/**
* Created by Roma on 17.02.2015.
*/
class SumTask implements Callable<Integer> {
private int num = 0;
public SumTask(int num){
this.num = num;
}
#Override
public Integer call() throws Exception {
int result = 0;
for(int i=1;i<=num;i++){
result+=i;
}
return result;
}
}
public class CallableDemo {
Integer result;
Integer num;
public Integer getNumValue() {
return 123;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public Integer getResult() {
return result;
}
public void setResult(Integer result) {
this.result = result;
}
ExecutorService service = Executors.newSingleThreadExecutor();
public String execute() {
try{
Future<Integer> future = service.submit(new SumTask(num));
result = future.get();
//System.out.println(result);
service.shutdown();
}
catch(Exception e)
{
e.printStackTrace();
}
return "showlinks";
}
}

Better way to implement synchronous repeatable task using Spring/Spring-Batch

I have scenario when I need to poll database for specific result. I cant go on within my code until I get the expected result(except the case of passing the timeout interval)
Step A -> Steb B -> Step C
Simple way of doing this(but doesnt feel right for me) was:
numOfRetry=0;
invokeStepA();
while(true)
{
numOfRetry++
boolen result=invokeStepB();
if(result || numOfRetry==3)
{
break;
}
else
{
Thread.sleep(100000)
}
invokeStepC();
Assume the database polling is occurring on Step B.
It doesnt feel right having this while loop on my Spring bean service while calling those jobs.
Maybe I could implement this better?
Thank you.
Farther explanation about my process:
Step A is Invoking external service to do some logic.
Step B need to poll another service which checking if Step A has finished it's work(In case it has finished I can proceed to StepC else I need to try again in X seconds and to check again)
StepC - another logic which must be accomplished only after StepB returned true.
The logic which Step A is doing happens on external service.
In the asynchronous way it happens like
int count = Runtime.getRuntime().availableProcessors();
ExecutorService threadPool = Executors.newFixedThreadPool(count);
invokeStepA();
for (int i = 0; i < RETRY_COUNT; i++) {
Future f = threadPool.submit(new Callable() {
#Override
public Object call() {
return invokeStepB();
}
}
result = (YOUR_DATA_STRUCTURE) f.get();
if (resultIsOK(result)) {
break;
}
}
However, I think since your task is ordered and assuming you cannot go to do something else, using asynchronous isn't really that effective. Please tell me more about your background in case you have special requirements.
EDIT: I think your new requirement looks like you need a proper way to tell if step A is finished fine. So you can use CountDownLatch to check if A has finished properly. I.e.
private final int count = Runtime.getRuntime().availableProcessors();
private final ExecutorService threadPool = Executors.newFixedThreadPool(count);
// invoke step A
invokeStepA();
// submit step B
final CountDownLatch latch = new CountDownLatch(1);
threadPool.submit(new Runnable() {
#Override
public void run() {
invokeStepB();
latch.countDown();
}
});
// wait for step B
boolean result;
try {
result = latch.await(TIME_OUT_IN_MILLISECONDS, TimeUnit.MILLISECOND);
} catch (InterruptedException e) {
}
// Check result
if (result) {
invokeStepC();
} else {
LOG.error("Timeout waiting for step A.");
}
This assumes your invokeStepA() is a blocking method.
Here's another idea by using an event driven approach. This is just out of my mind and not tested ;)
import org.springframework.context.ApplicationEventPublisher;
#Service
public class JobA {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Scheduled(cron = "0 0 * * * ?")
public void doStepA() {
log.debug("some heavy lifting");
Object someData = ....;
applicationEventPublisher.publishEvent(new JobAEvent("Jo, I'm finished", someData));
}
}
#Service
public class JobB implements ApplicationListener<JobAEvent> {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Override
public void onApplicationEvent(final JobAEvent event) {
log.debug("do something more based on the event data");
Object someMoreData = ....;
applicationEventPublisher.publishEvent(new JobBEvent("Dude, me too", event.getSomeData(), someMoreData));
}
}
#Service
public class JobC implements ApplicationListener<JobBEvent> {
#Autowired
private ApplicationEventPublisher applicationEventPublisher;
#Override
public void onApplicationEvent(final JobBEvent event) {
log.debug("do even more work");
}
}
EDIT:
You can also call the method directly but then it runs synchronosly. Another possibilty is using '#Async'

Persisting in #PostConstruct: javax.persistence.TransactionRequiredException

My Entitymanager has no transaction when persisting an object in #PostConstruct.
I have no clue why and how to fix this, can anyone help me?
PS. If you need any other data, please ask
TestmachineManager
#Singleton
public class TestmachineManager {
#PersistenceContext
private EntityManager em;
private TimerTask handler = new TimerTask() {
#Override
public void run() {
DayPlanning planning = getPlanning();
Order order = planning.getNextInLine();
if(order instanceof Order) {
em.merge(planning);
List<String> tests = new ArrayList();
for(Test test : order.getTests()) {
tests.add(test.getName());
}
TestmachineSender.orderTests(order.getId(), order.getDomain(), tests);
timer.schedule(checker, safetycheckAt());
}
else {
timer.schedule(handler, postponeTo());
}
}
};
#PostConstruct
public void init() {
if(getPlanning().hasActiveTest()) {
handler.run();
}
}
private DayPlanning getPlanning() {
LocalDate today = new LocalDate(
Calendar.getInstance().getTime());
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<DayPlanning> query = cb.createQuery(DayPlanning.class);
Root dayPlanning = query.from(DayPlanning.class);
Predicate predicateDate = cb.equal(dayPlanning.get("dateOfPlanning"), today.toDate());
query.select(dayPlanning).where(predicateDate);
return em.createQuery(query).getSingleResult();
} catch(NoResultException ex) {
DayPlanning newPlanning = new DayPlanning(today);
em.persist(newPlanning);
return newPlanning;
}
}
}
Stacktrace
Caused by: javax.persistence.TransactionRequiredException
at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTxRequiredCheck(EntityManagerWrapper.java:163)
at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTransactionScopedTxCheck(EntityManagerWrapper.java:145)
at com.sun.enterprise.container.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:263)
at TestmachineManager.getPlanning(TestmachineManager.java:130)
at TestmachineManager.init(TestmachineManager.java:78)
You have a transaction in #PostConstruct for the Singleton bean. The moment you are loosing your transaction is when you launch a new timer using timer.schedule(checker, safetycheckAt()); or timer.schedule(handler, postponeTo());. I think that the timer object that you are using is actually java.util.Timer, looking at the signature.
You should try using the EJB TimerService resource. For example:
#Resource
private TimerService timerService;
#Timeout
public void run() {
DayPlanning planning = getPlanning();
Order order = planning.getNextInLine();
if(order instanceof Order) {
em.merge(planning);
List<String> tests = new ArrayList();
for(Test test : order.getTests()) {
tests.add(test.getName());
}
TestmachineSender.orderTests(order.getId(), order.getDomain(), tests);
// if the checker handler doesn't need to run transactionally you could leave it like it was before
timer.schedule(checker, safetycheckAt());
// otherwise you could create a checker EJB that uses #Timeout in the same manner or #Scheduled from EJB3.1
} else {
// postpone timer
timerService.createTimer(postponeTo(), "postponed timer information");
}
}
#PostConstruct
public void init() {
if(getPlanning().hasActiveTest()) {
timerService.createTimer(computeDelay(), "timer information");
// or you could use the other create methods of the timerService, those that fits your needs better
}
}
I didn't tested the code, it's just rough code. Hope it helps.

Categories

Resources