001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.schedule; 017 018import java.time.Duration; 019import java.time.Instant; 020import java.util.Map; 021 022import org.apache.avalon.framework.context.Context; 023import org.apache.avalon.framework.context.ContextException; 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.cocoon.Constants; 027import org.apache.cocoon.environment.ObjectModelHelper; 028import org.apache.cocoon.environment.Request; 029import org.apache.cocoon.environment.background.BackgroundEnvironment; 030import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 031import org.quartz.DisallowConcurrentExecution; 032import org.quartz.Job; 033import org.quartz.JobDataMap; 034import org.quartz.JobDetail; 035import org.quartz.JobExecutionContext; 036import org.quartz.JobExecutionException; 037import org.quartz.JobKey; 038import org.quartz.PersistJobDataAfterExecution; 039import org.quartz.SchedulerException; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import org.ametys.core.authentication.AuthenticateAction; 044import org.ametys.core.engine.BackgroundEngineHelper; 045import org.ametys.core.schedule.Runnable.FireProcess; 046import org.ametys.core.user.population.UserPopulationDAO; 047import org.ametys.plugins.core.schedule.Scheduler; 048import org.ametys.plugins.core.user.UserDAO; 049 050/** 051 * Ametys implementation of a {@link Job} which delegates the execution of the task to the right {@link Schedulable} 052 */ 053@PersistJobDataAfterExecution 054@DisallowConcurrentExecution 055public class AmetysJob implements Job 056{ 057 /** The key for the last duration of the {@link #execute(org.quartz.JobExecutionContext)} method which is stored in the {@link JobDataMap} */ 058 public static final String KEY_LAST_DURATION = "duration"; 059 /** The key for the previous fire time of this job which is stored in the {@link JobDataMap} */ 060 public static final String KEY_PREVIOUS_FIRE_TIME = "previousFireTime"; 061 /** The key for the success state of the last execution of the job */ 062 public static final String KEY_SUCCESS = "success"; 063 064 /** The service manager */ 065 protected static ServiceManager _serviceManager; 066 /** The extension point for {@link Schedulable}s */ 067 protected static SchedulableExtensionPoint _schedulableEP; 068 /** The scheduler component */ 069 protected static Scheduler _scheduler; 070 /** The cocoon environment context. */ 071 protected static org.apache.cocoon.environment.Context _environmentContext; 072 073 /** 074 * Initialize the static fields. 075 * @param serviceManager The service manager 076 * @param context The context 077 * @throws ServiceException if an error occurs during the lookup of the {@link SchedulableExtensionPoint} 078 * @throws ContextException if environment context object not found 079 */ 080 public static void initialize(ServiceManager serviceManager, Context context) throws ServiceException, ContextException 081 { 082 _serviceManager = serviceManager; 083 _schedulableEP = (SchedulableExtensionPoint) serviceManager.lookup(SchedulableExtensionPoint.ROLE); 084 _scheduler = (Scheduler) serviceManager.lookup(Scheduler.ROLE); 085 _environmentContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 086 } 087 088 /** 089 * Releases and destroys used resources 090 */ 091 public static void dispose() 092 { 093 _serviceManager = null; 094 _schedulableEP = null; 095 _scheduler = null; 096 _environmentContext = null; 097 } 098 099 @Override 100 public void execute(JobExecutionContext context) throws JobExecutionException 101 { 102 JobDetail detail = context.getJobDetail(); 103 JobDataMap jobDataMap = detail.getJobDataMap(); 104 JobKey jobKey = detail.getKey(); 105 String runnableId = jobDataMap.getString(Scheduler.KEY_RUNNABLE_ID); 106 String schedulableId = jobDataMap.getString(Scheduler.KEY_SCHEDULABLE_ID); 107 Schedulable schedulable = _schedulableEP.getExtension(schedulableId); 108 Logger logger = LoggerFactory.getLogger(AmetysJob.class.getName() + "$" + schedulableId); 109 110 // Concurrency allowed ? 111 if (!schedulable.acceptConcurrentExecution() && _checkConcurrency(schedulableId, runnableId, jobKey, logger)) 112 { 113 logger.warn("The Runnable '{}' of the Schedulable '{}' cannot be executed now because concurrent execution is not allowed for this Schedulable", runnableId, schedulableId); 114 return; 115 } 116 117 boolean success = true; 118 119 // Set the previous (which is actually the current) fire time in the map (because the trigger may no longer exist in the future) 120 jobDataMap.put(KEY_PREVIOUS_FIRE_TIME, context.getTrigger().getPreviousFireTime().getTime()); // possible with @PersistJobDataAfterExecution annotation 121 122 Map<String, Object> environmentInformation = BackgroundEngineHelper.createAndEnterEngineEnvironment(_serviceManager, _environmentContext, new SLF4JLoggerAdapter(logger)); 123 124 _setUserSystemInSession(environmentInformation); 125 126 logger.info("Executing the Runnable '{}' of the Schedulable '{}' with jobDataMap:\n '{}'", runnableId, schedulableId, jobDataMap.getWrappedMap().toString()); 127 Instant start = Instant.now(); 128 try 129 { 130 schedulable.execute(context); 131 } 132 catch (Exception e) 133 { 134 success = false; 135 logger.error(String.format("An exception occured during the execution of the Schedulable '%s'", schedulableId), e); 136 throw new JobExecutionException(String.format("An exception occured during the execution of the job '%s'", schedulableId), e); 137 } 138 catch (Throwable t) 139 { 140 success = false; 141 logger.error(String.format("An error occured during the execution of the Schedulable '%s'", schedulableId), t); 142 throw t; 143 } 144 finally 145 { 146 // Set the duration in the map 147 Instant end = Instant.now(); 148 long duration = Duration.between(start, end).toMillis(); 149 jobDataMap.put(KEY_LAST_DURATION, duration); // possible with @PersistJobDataAfterExecution annotation 150 151 // Leave the Engine Environment 152 if (environmentInformation != null) 153 { 154 BackgroundEngineHelper.leaveEngineEnvironment(environmentInformation); 155 } 156 157 // Success ? 158 jobDataMap.put(KEY_SUCCESS, success); 159 160 // Run at startup tasks are one-shot tasks => if so, indicates it is completed for never refiring it 161 if (FireProcess.STARTUP.toString().equals(jobDataMap.getString(Scheduler.KEY_RUNNABLE_FIRE_PROCESS))) 162 { 163 jobDataMap.put(Scheduler.KEY_RUNNABLE_STARTUP_COMPLETED, true); 164 } 165 } 166 } 167 168 private void _setUserSystemInSession (Map<String, Object> environmentInformation) 169 { 170 BackgroundEnvironment bgEnv = (BackgroundEnvironment) environmentInformation.get("environment"); 171 Request request = ObjectModelHelper.getRequest(bgEnv.getObjectModel()); 172 173 AuthenticateAction.setUserIdentityInSession(request, UserPopulationDAO.SYSTEM_USER_IDENTITY, new UserDAO.ImpersonateCredentialProvider(), true); 174 } 175 176 private boolean _checkConcurrency(String schedulableId, String runnableId, JobKey currentJobKey, Logger logger) throws JobExecutionException 177 { 178 try 179 { 180 return _scheduler.getScheduler().getCurrentlyExecutingJobs().stream() 181 .map(JobExecutionContext::getJobDetail) 182 .filter(detail -> !detail.getKey().equals(currentJobKey)) // currently executing without this 183 .map(detail -> detail.getJobDataMap().getString(Scheduler.KEY_SCHEDULABLE_ID)) 184 .filter(id -> id.equals(schedulableId)) 185 .findFirst() 186 .isPresent(); 187 } 188 catch (SchedulerException e) 189 { 190 logger.error(String.format("An error occured during the concurrency check of the Runnable '%s'", runnableId), e); 191 throw new JobExecutionException(String.format("An error occured during the concurrency check of the Runnable '%s'", runnableId), e); 192 } 193 } 194}