/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions copyright [year] [name of copyright owner]". * * Portions copyright 2012-2015 ForgeRock AS. */ package org.forgerock.openidm.scheduler; import static org.forgerock.json.JsonValue.array; import static org.forgerock.json.JsonValue.field; import static org.forgerock.json.JsonValue.json; import static org.forgerock.json.JsonValue.object; import static org.forgerock.json.resource.QueryResponse.FIELD_RESULT; import static org.forgerock.json.resource.Responses.newActionResponse; import static org.forgerock.json.resource.Responses.newQueryResponse; import static org.forgerock.json.resource.Responses.newResourceResponse; import static org.forgerock.openidm.util.ResourceUtil.notSupported; import static org.forgerock.util.promise.Promises.newResultPromise; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.UUID; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.felix.scr.annotations.Service; import org.forgerock.json.JsonValueException; import org.forgerock.json.resource.PreconditionFailedException; import org.forgerock.services.context.Context; import org.forgerock.json.JsonException; import org.forgerock.json.JsonValue; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.ActionResponse; import org.forgerock.json.resource.BadRequestException; import org.forgerock.json.resource.ConflictException; import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.NotFoundException; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; import org.forgerock.json.resource.QueryResourceHandler; import org.forgerock.json.resource.QueryResponse; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.RequestHandler; import org.forgerock.json.resource.Requests; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceResponse; import org.forgerock.json.resource.UpdateRequest; import org.forgerock.openidm.cluster.ClusterManagementService; import org.forgerock.openidm.config.enhanced.EnhancedConfig; import org.forgerock.openidm.core.ServerConstants; import org.forgerock.openidm.quartz.impl.ScheduledService; import org.forgerock.openidm.quartz.impl.SchedulerServiceJob; import org.forgerock.openidm.quartz.impl.StatefulSchedulerServiceJob; import org.forgerock.openidm.router.RouteService; import org.forgerock.util.promise.Promise; import org.osgi.framework.Constants; import org.osgi.service.component.ComponentContext; import org.osgi.util.tracker.ServiceTracker; import org.quartz.CronTrigger; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.ObjectAlreadyExistsException; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.Trigger; import org.quartz.impl.DirectSchedulerFactory; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.CascadingClassLoadHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; /** * Scheduler service using Quartz. */ @Component(name = "org.forgerock.openidm.scheduler", immediate=true, policy=ConfigurationPolicy.REQUIRE) @Service(value = {SchedulerService.class, RequestHandler.class}, serviceFactory=false) @Properties({ @Property(name = Constants.SERVICE_VENDOR, value = ServerConstants.SERVER_VENDOR_NAME), @Property(name = Constants.SERVICE_DESCRIPTION, value = "Scheduler Service using Quartz"), @Property(name = ServerConstants.ROUTER_PREFIX, value = "/scheduler*") }) public class SchedulerService implements RequestHandler { final static Logger logger = LoggerFactory.getLogger(SchedulerService.class); // Keys in the OSGi configuration public final static String SCHEDULE_ENABLED = "enabled"; public final static String SCHEDULE_TYPE = "type"; public final static String SCHEDULE_START_TIME = "startTime"; public final static String SCHEDULE_END_TIME = "endTime"; public final static String SCHEDULE_CRON_SCHEDULE = "schedule"; public final static String SCHEDULE_TIME_ZONE = "timeZone"; public final static String SCHEDULE_INVOKE_SERVICE = "invokeService"; public final static String SCHEDULE_INVOKE_CONTEXT = "invokeContext"; public final static String SCHEDULE_INVOKE_LOG_LEVEL = "invokeLogLevel"; public final static String SCHEDULE_PERSISTED = "persisted"; public final static String SCHEDULE_MISFIRE_POLICY = "misfirePolicy"; public final static String SCHEDULE_CONCURRENT_EXECUTION = "concurrentExecution"; // Valid configuration values public final static String SCHEDULE_TYPE_CRON = "cron"; // Default service PID prefix to use if the invokeService name is a fragment public final static String SERVICE_RDN_PREFIX = "org.forgerock.openidm."; // Misfire Policies public final static String MISFIRE_POLICY_DO_NOTHING = "doNothing"; public final static String MISFIRE_POLICY_FIRE_AND_PROCEED = "fireAndProceed"; // Internal service tracker final static String SERVICE_TRACKER = "scheduler.service-tracker"; final static String SERVICE_PID = "scheduler.service-pid"; final static String GROUP_NAME = "scheduler-service-group"; final static String CONFIG = "schedule.config"; /** * Supported actions on the scheduler service. */ protected enum SchedulerAction { create, listCurrentlyExecutingJobs, pauseJobs, resumeJobs }; private static Scheduler inMemoryScheduler; private static Scheduler persistentScheduler = null; private static SchedulerConfig schedulerConfig = null; private Map<String, ScheduleConfigService> configMap = new HashMap<String, ScheduleConfigService>(); private static Object CONFIG_SERVICE_LOCK = new Object(); private static Object LOCK = new Object(); private boolean executePersistentSchedules = false; private boolean started = false; private final ObjectMapper mapper = new ObjectMapper(); // Optional user defined name for this instance, derived from the file install name String configFactoryPID; // Tracks OSGi services that match the configured service PID ServiceTracker scheduledServiceTracker; @Reference ClusterManagementService clusterManager; @Reference(name = "ref_SchedulerService_PolicyService", target = "(" + ServerConstants.ROUTER_PREFIX + "=/policy*)") protected RouteService policy; /** Enhanced configuration service. */ @Reference(policy = ReferencePolicy.DYNAMIC) private EnhancedConfig enhancedConfig; protected void bindEnhancedConfig(EnhancedConfig enhancedConfig) { this.enhancedConfig = enhancedConfig; } @Activate void activate(ComponentContext compContext) throws SchedulerException, ParseException { logger.debug("Activating Service with configuration {}", compContext.getProperties()); String pid = (String)compContext.getProperties().get("config.factory-pid"); if (pid != null) { logger.warn("Please rename the schedule configuration file for " + pid + " to conform to the 'schedule-<name>.json' format"); return; } synchronized (CONFIG_SERVICE_LOCK) { // TODO: This should be reworked to start after all "core" services are available logger.info("Starting Scheduler Service"); started = true; // Initialize the schedulers (if they haven't been already) initInMemoryScheduler(); initPersistentScheduler(compContext); // Start processing schedules logger.info("Starting Volatile Scheduler"); inMemoryScheduler.start(); if (executePersistentSchedules) { logger.info("Starting Persistent Scheduler"); persistentScheduler.start(); } else { logger.info("Persistent Schedules will not be executed on this node"); } logger.info("There are {} jobs waiting to be scheduled", configMap.size()); Set<String> keys = configMap.keySet(); for (String key : keys) { ScheduleConfigService service = configMap.get(key); try { addSchedule(service.getScheduleConfig(), service.getJobName(), false); } catch (ObjectAlreadyExistsException e) { logger.debug("Job {} already scheduled", service.getJobName()); } } logger.info("Scheduling waiting schedules"); } } /** * Registers a {@link ScheduleConfigService} and adds the scheduler if the scheduler has been started. * * @param service the {@link ScheduleConfigService} to register. * @throws SchedulerException * @throws ParseException */ public void registerConfigService(ScheduleConfigService service) throws SchedulerException, ParseException { synchronized (CONFIG_SERVICE_LOCK) { logger.debug("Registering new ScheduleConfigService"); configMap.put(service.getJobName(), service); if (!started) { logger.debug("The Scheduler Service has not been started yet, storing new Schedule {}", service.getJobName()); } else { try { logger.debug("Adding schedule {}", service.getJobName()); addSchedule(service.getScheduleConfig(), service.getJobName(), true); } catch (ObjectAlreadyExistsException e) { logger.debug("Job {} already scheduled", service.getJobName()); } } } } /** * Unregisters a {@link ScheduleConfigService} and deletes the schedule if the scheduler has been started. * * @param service the {@link ScheduleConfigService} to register. * @throws SchedulerException * @throws ParseException */ public void unregisterConfigService(ScheduleConfigService service) { synchronized (CONFIG_SERVICE_LOCK) { logger.debug("Unregistering ScheduleConfigService"); configMap.remove(service.getJobName()); if (started) { try { logger.debug("Deleting schedule {}", service.getJobName()); deleteSchedule(service.getJobName()); } catch (SchedulerException e) { logger.warn("Error deleting schedule {}: {}", service.getJobName(), e.getMessage()); } } } } @Deactivate void deactivate(ComponentContext compContext) { logger.debug("Deactivating Scheduler Service {}", compContext); try { if (inMemoryScheduler != null && inMemoryScheduler.isStarted()) { inMemoryScheduler.shutdown(); } } catch (SchedulerException e) { logger.error("Error shutting down in-memory scheduler", e); } finally { inMemoryScheduler = null; } try { if (persistentScheduler != null && persistentScheduler.isStarted()) { persistentScheduler.shutdown(); } } catch (SchedulerException e) { logger.error("Error shutting down persistent scheduler", e); } } /** * Schedules a job. * * @param scheduleConfig The schedule configuration * @param jobName The job name * @param update Whether to delete the old job if present * @return true if the job was scheduled, false otherwise * @throws SchedulerException * @throws ParseException * @throws ObjectAlreadyExistsException */ public boolean addSchedule(ScheduleConfig scheduleConfig, String jobName, boolean update) throws SchedulerException, ParseException, ObjectAlreadyExistsException { try { // Lock access to the scheduler so that a schedule is not added during a config update synchronized (LOCK) { // Determine the schedule class based on whether the job has concurrent execution enabled/disabled Class scheduleClass = null; if (scheduleConfig.getConcurrentExecution()) { scheduleClass = SchedulerServiceJob.class; } else { scheduleClass = StatefulSchedulerServiceJob.class; } // Attempt to add the schedule if (scheduleConfig.getCronSchedule() != null && scheduleConfig.getCronSchedule().length() > 0) { JobDetail job = new JobDetail(jobName, GROUP_NAME, scheduleClass); job.setVolatility(scheduleConfig.isPersisted()); job.setJobDataMap(createJobDataMap(scheduleConfig)); Trigger trigger = createTrigger(scheduleConfig, jobName); final Scheduler scheduler = scheduleConfig.isPersisted() ? persistentScheduler : inMemoryScheduler; if (update) { // Update the job by first deleting it, then scheduling the new version deleteSchedule(jobName); } // check if it is enabled if (scheduleConfig.isEnabled()) { // Set to non-durable so that jobs won't persist after last firing job.setDurability(false); // Schedule the Job (with trigger) scheduler.scheduleJob(job, trigger); logger.info("Job {} scheduled with schedule {}, timezone {}, start time {}, end time {}.", new Object[] { jobName, scheduleConfig.getCronSchedule(), scheduleConfig.getTimeZone(), scheduleConfig.getStartTime(), scheduleConfig.getEndTime() }); } else { // Set the job to durable so that it can exist without a trigger (since the job is "disabled") job.setDurability(true); // Add the job (no trigger) scheduler.addJob(job, false); logger.info("Job {} added with schedule {}, timezone {}, start time {}, end time {}.", new Object[] { jobName, scheduleConfig.getCronSchedule(), scheduleConfig.getTimeZone(), scheduleConfig.getStartTime(), scheduleConfig.getEndTime() }); } } } } catch (ParseException ex) { logger.warn("Parsing of scheduler configuration failed, can not create scheduler service for " + jobName + ": " + ex.getMessage(), ex); throw ex; } catch (ObjectAlreadyExistsException ex) { throw ex; } catch (SchedulerException ex) { logger.warn("Failed to create scheduler service for " + jobName + ": " + ex.getMessage(), ex); throw ex; } return true; } /** * Deletes a schedule from the scheduler * * @param jobName the job name associated with this schedule. * @throws SchedulerException */ public void deleteSchedule(String jobName) throws SchedulerException { if (inMemoryScheduler.getJobDetail(jobName, GROUP_NAME) != null) { inMemoryScheduler.deleteJob(jobName, GROUP_NAME); } if (persistentScheduler.getJobDetail(jobName, GROUP_NAME) != null) { persistentScheduler.deleteJob(jobName, GROUP_NAME); } } /** * Creates and returns a CronTrigger using the supplied schedule configuration. * * @param scheduleConfig The schedule configuration * @param jobName The name of the job to associate the trigger with * @return The created Trigger * @throws ParseException */ private CronTrigger createTrigger(ScheduleConfig scheduleConfig, String jobName) throws ParseException { String cronSchedule = scheduleConfig.getCronSchedule(); Date startTime = scheduleConfig.getStartTime(); Date endTime = scheduleConfig.getEndTime(); String misfirePolicy = scheduleConfig.getMisfirePolicy(); TimeZone timeZone = scheduleConfig.getTimeZone(); CronTrigger trigger = new CronTrigger("trigger-" + jobName, GROUP_NAME, cronSchedule); trigger.setJobName(jobName); trigger.setJobGroup(GROUP_NAME); if (startTime != null) { trigger.setStartTime(startTime); // TODO: review time zone consistency with cron trigger timezone } if (endTime != null) { trigger.setEndTime(endTime); } if (timeZone != null) { trigger.setTimeZone(timeZone); } if (misfirePolicy.equals(MISFIRE_POLICY_FIRE_AND_PROCEED)) { trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW); } else if (misfirePolicy.equals(MISFIRE_POLICY_DO_NOTHING)) { trigger.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING); } return trigger; } /** * Creates and returns a JobDataMap using the supplied schedule configuration. * * @param scheduleConfig The schedule configuration * @return The created JobDataMap */ private JobDataMap createJobDataMap(ScheduleConfig scheduleConfig) { String invokeService = scheduleConfig.getInvokeService(); Object invokeContext = scheduleConfig.getInvokeContext(); String invokeLogLevel = scheduleConfig.getInvokeLogLevel(); JobDataMap map = new JobDataMap(); map.put(ScheduledService.CONFIG_NAME, "scheduler"+ (configFactoryPID != null ? "-" + configFactoryPID : "")); map.put(ScheduledService.CONFIGURED_INVOKE_SERVICE, invokeService); map.put(ScheduledService.CONFIGURED_INVOKE_CONTEXT, invokeContext); map.put(ScheduledService.CONFIGURED_INVOKE_LOG_LEVEL, invokeLogLevel); map.put(CONFIG, scheduleConfig.getConfig().toString()); return map; } /** * Determines if a job already exists. * * @param jobName The name of the job * @return True if the job exists, false otherwise * @throws SchedulerException */ private boolean jobExists(String jobName) throws SchedulerException { final boolean existsInMemory = inMemoryScheduler.getJobDetail(jobName, GROUP_NAME) != null; final boolean existsInPersistent = persistentScheduler.getJobDetail(jobName, GROUP_NAME) != null; return existsInMemory || existsInPersistent; } @Override public Promise<ResourceResponse, ResourceException> handleCreate(Context context, CreateRequest request) { try { String id = request.getNewResourceId() == null ? UUID.randomUUID().toString() : request.getNewResourceId(); Map<String, Object> object = request.getContent().asMap(); object.put("_id", id); if (jobExists(id)) { throw new PreconditionFailedException("Schedule already exists"); } ScheduleConfig scheduleConfig = new ScheduleConfig(new JsonValue(object)); // Check defaults if (scheduleConfig.isEnabled() == null) { scheduleConfig.setEnabled(true); } if (scheduleConfig.isPersisted() == null) { scheduleConfig.setPersisted(true); } addSchedule(scheduleConfig, id, false); return newResourceResponse(id, null, getSchedule(id)).asPromise(); } catch (ParseException e) { return new BadRequestException(e.getMessage(), e).asPromise(); } catch (ObjectAlreadyExistsException e) { return new PreconditionFailedException(e.getMessage(), e).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (JsonException e) { return new BadRequestException("Error creating schedule", e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> handleRead(Context context, ReadRequest request) { try { if (request.getResourcePathObject().isEmpty()) { throw new BadRequestException("Empty resourceId"); } // Get the schedule JsonValue schedule = getSchedule(request.getResourcePath()); // Return the result return newResourceResponse(request.getResourcePath(), null, schedule).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (JsonValueException e) { return new BadRequestException(e.getMessage(), e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> handleUpdate(Context context, UpdateRequest request) { try { if (request.getResourcePathObject().isEmpty()) { throw new BadRequestException("Empty resourceId"); } Map<String, Object> object = request.getContent().asMap(); object.put("_id", request.getResourcePath()); // Default incoming config to "persisted" if not specified Object persistedValue = object.get(SchedulerService.SCHEDULE_PERSISTED); if (persistedValue == null) { object.put(SchedulerService.SCHEDULE_PERSISTED, new Boolean(true)); } ScheduleConfig scheduleConfig = new ScheduleConfig(new JsonValue(object)); if (!jobExists(request.getResourcePath())) { throw new NotFoundException(); } else { // Update the Job addSchedule(scheduleConfig, request.getResourcePath(), true); return newResultPromise(newResourceResponse(request.getResourcePath(), null, getSchedule(request.getResourcePath()))); } } catch (ParseException e) { return new BadRequestException(e.getMessage(), e).asPromise(); } catch (ObjectAlreadyExistsException e) { return new ConflictException(e.getMessage(), e).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (JsonException e) { return new BadRequestException("Error updating schedule", e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> handleDelete(Context context, DeleteRequest request) { try { if (request.getResourcePathObject().isEmpty()) { throw new BadRequestException("Empty resourceId"); } if (!jobExists(request.getResourcePath())) { throw new NotFoundException(); } // Get the schedule JsonValue schedule = getSchedule(request.getResourcePath()); // Delete the schedule deleteSchedule(request.getResourcePath()); // Return the deleted resource return newResourceResponse(request.getResourcePath(), null, schedule).asPromise(); } catch (JsonException e) { return new BadRequestException("Error deleting schedule", e).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ResourceResponse, ResourceException> handlePatch(Context context, PatchRequest request) { return notSupported(request).asPromise(); } @Override public Promise<QueryResponse, ResourceException> handleQuery(Context context, QueryRequest request, QueryResourceHandler handler) { try { String queryId = request.getQueryId(); if (queryId == null) { throw new BadRequestException( "query-id parameters"); } JsonValue result = null; if (queryId.equals("query-all-ids")) { // Query all the Job IDs in both schedulers String[] persistentJobNames = null; persistentJobNames = persistentScheduler.getJobNames(GROUP_NAME); String[] inMemoryJobNames = inMemoryScheduler.getJobNames(GROUP_NAME); List<Map<String, String>> resultList = new ArrayList<Map<String, String>>(); if (persistentJobNames != null) { for (String job : persistentJobNames) { Map<String, String> idMap = new HashMap<String, String>(); idMap.put("_id", job); resultList.add(idMap); } } if (inMemoryJobNames != null) { for (String job : inMemoryJobNames) { Map<String, String> idMap = new HashMap<String, String>(); idMap.put("_id", job); resultList.add(idMap); } } result = json(object()); result.put(FIELD_RESULT, resultList); } else { throw new BadRequestException( "Unsupported query-id: " + queryId); } for (JsonValue r: result.get(FIELD_RESULT)){ handler.handleResource(newResourceResponse(r.get("_id").asString(), null, new JsonValue(r))); } return newQueryResponse().asPromise(); } catch (JsonException e) { return new BadRequestException("Error performing query", e).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } @Override public Promise<ActionResponse, ResourceException> handleAction(Context context, ActionRequest request) { try { Map<String, String> params = request.getAdditionalParameters(); String action = request.getAction(); switch (request.getActionAsEnum(SchedulerAction.class)) { case create: String id = UUID.randomUUID().toString(); params.put("_id", id); if (jobExists(id)) { throw new BadRequestException("Schedule already exists"); } CreateRequest createRequest = Requests.newCreateRequest(id, new JsonValue(params)); ResourceResponse response = handleCreate(context, createRequest).getOrThrow(); return newActionResponse(response.getContent()).asPromise(); case listCurrentlyExecutingJobs: JsonValue currentlyExecutingJobs = json(array()); List<?> jobs = persistentScheduler.getCurrentlyExecutingJobs(); for (Object job : jobs) { JsonValue config = parseStringified((String)((JobExecutionContext)job).getJobDetail().getJobDataMap().get(CONFIG)); currentlyExecutingJobs.add(new ScheduleConfig(config).getConfig().getObject()); } jobs = inMemoryScheduler.getCurrentlyExecutingJobs(); for (Object job : jobs) { JsonValue config = parseStringified((String)((JobExecutionContext)job).getJobDetail().getJobDataMap().get(CONFIG)); currentlyExecutingJobs.add(new ScheduleConfig(config).getConfig().getObject()); } return newActionResponse(currentlyExecutingJobs).asPromise(); case pauseJobs: persistentScheduler.pauseAll(); inMemoryScheduler.pauseAll(); return newActionResponse(json(object(field("success",true)))).asPromise(); case resumeJobs: persistentScheduler.resumeAll(); inMemoryScheduler.resumeAll(); return newActionResponse(json(object(field("success",true)))).asPromise(); default: throw new BadRequestException("Unknown action: " + action); } } catch (JsonException e) { return new BadRequestException("Error performing action " + request.getAction(), e).asPromise(); } catch (SchedulerException e) { return new InternalServerErrorException(e.getMessage(), e).asPromise(); } catch (ResourceException e) { return e.asPromise(); } catch (Exception e) { return new InternalServerErrorException(e).asPromise(); } } public void initPersistentScheduler(ComponentContext compContext) throws SchedulerException { boolean alreadyConfigured = (schedulerConfig != null); JsonValue configValue = enhancedConfig.getConfigurationAsJson(compContext); schedulerConfig = new SchedulerConfig(configValue, clusterManager.getInstanceId()); if (alreadyConfigured) { // Close current scheduler if (persistentScheduler != null && persistentScheduler.isStarted()) { persistentScheduler.shutdown(); } } if (schedulerConfig.executePersistentSchedulesEnabled()) { executePersistentSchedules = true; } else { executePersistentSchedules = false; } createPersistentScheduler(); } private void createPersistentScheduler() throws SchedulerException { // Get the persistent scheduler using our custom JobStore implementation logger.info("Creating Persistent Scheduler"); StdSchedulerFactory sf = new StdSchedulerFactory(); sf.initialize(schedulerConfig.toProps()); persistentScheduler = sf.getScheduler(); } private void initInMemoryScheduler() throws SchedulerException { try { if (inMemoryScheduler == null) { // Quartz tries to be too smart about classloading, // but relies on the thread context classloader to load classload helpers // That is not a good idea in OSGi, // hence, hand it the OSGi classloader for the ClassLoadHelper we want it to find ClassLoader original = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(CascadingClassLoadHelper.class.getClassLoader()); // Get the in-memory scheduler // Must use DirectSchedulerFactory instance so that it does not confict with // the StdSchedulerFactory (used to create the persistent schedulers). logger.info("Creating In-Memory Scheduler"); DirectSchedulerFactory.getInstance().createVolatileScheduler(10); inMemoryScheduler = DirectSchedulerFactory.getInstance().getScheduler(); // Set back to the original thread context classloader Thread.currentThread().setContextClassLoader(original); } } catch (SchedulerException ex) { logger.warn("Failure in initializing the scheduler facility " + ex.getMessage(), ex); throw ex; } logger.info("Scheduler facility started"); } /** * Returns a JsonValue representation of a schedule with the supplied name from the supplied scheduler. * * @param scheduleName the name of the scheduler * @return the JsonValue representation of the schedule * @throws SchedulerException * @throws ResourceException */ private JsonValue getSchedule(String scheduleName) throws SchedulerException, ResourceException { Scheduler scheduler = null; if (inMemoryScheduler.getJobDetail(scheduleName, GROUP_NAME) != null) { scheduler = inMemoryScheduler; } else if (persistentScheduler.getJobDetail(scheduleName, GROUP_NAME) != null) { scheduler = persistentScheduler; } else { throw new NotFoundException("Schedule does not exist"); } JobDetail job = scheduler.getJobDetail(scheduleName, GROUP_NAME); JobDataMap dataMap = job.getJobDataMap(); JsonValue resultMap = new ScheduleConfig(parseStringified((String) dataMap.get(CONFIG))).getConfig(); resultMap.put("_id", scheduleName); return resultMap; } private JsonValue parseStringified(String stringified) { JsonValue jsonValue = null; try { Map parsedValue = (Map) mapper.readValue(stringified, Map.class); jsonValue = new JsonValue(parsedValue); } catch (IOException ex) { throw new JsonException("String passed into parsing is not valid JSON", ex); } return jsonValue; } }