Java tutorial
/** * Copyright (C) 2013 Seajas, the Netherlands. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.seajas.search.profiler.service.task; import com.seajas.search.profiler.task.InjectionJobInterrupted; import com.seajas.search.utilities.logging.SearchLogger; import java.text.ParseException; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.quartz.CronScheduleBuilder; import org.quartz.CronTrigger; import org.quartz.JobDataMap; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobKey; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SimpleScheduleBuilder; import org.quartz.SimpleTrigger; import org.quartz.Trigger; import org.quartz.TriggerBuilder; import org.quartz.TriggerUtils; import org.quartz.impl.calendar.BaseCalendar; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.triggers.CronTriggerImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; /** * Task service. * * @author Jasper van Veghel <jasper@seajas.com> */ @Transactional @Service public class TaskService { /** * The static logger. */ private final Logger staticLogger = LoggerFactory.getLogger(TaskService.class); /** * The logger. */ @Autowired private SearchLogger logger; /** * Constants. */ private final static String JOB_FEED_INJECTION = "feedInjection"; private final static String JOB_ARCHIVE_INJECTION = "archiveInjection"; private final static String GROUP_FEED = "feed"; private final static String GROUP_ARCHIVE = "archive"; /** * The task scheduler. */ @Autowired @Qualifier("internalScheduler") private Scheduler taskScheduler; /** * The feed injection triggers. */ private final Map<String, String> feedInjectionTriggers = new LinkedHashMap<String, String>(); /** * The archive injection triggers. */ private final Map<String, String> archiveInjectionTriggers = new LinkedHashMap<String, String>(); /** * Default constructor. */ public TaskService() { } /** * Default constructor. */ @Autowired public TaskService(@Value("${profiler.project.feed.injection.triggers}") final String feedInjectionTriggers, @Value("${profiler.project.feed.injection.triggers.distributed}") final String feedInjectionTriggersDistributed, @Value("${profiler.project.archive.injection.triggers}") final String archiveInjectionTriggers, @Value("${profiler.project.archive.injection.triggers.distributed}") final String archiveInjectionTriggersDistributed, final Scheduler taskScheduler) { // Feed injection triggers injectTriggers(GROUP_FEED, feedInjectionTriggers, Arrays.asList(StringUtils.tokenizeToStringArray(feedInjectionTriggersDistributed, ",", true, true)), taskScheduler); // Archive injection triggers injectTriggers(GROUP_ARCHIVE, archiveInjectionTriggers, Arrays.asList( StringUtils.tokenizeToStringArray(archiveInjectionTriggersDistributed, ",", true, true)), taskScheduler); } /** * Inject the triggers for the given group - optionally distributing those that require it. * * @param groupName * @param triggers * @param triggersDistributed * @param taskScheduler */ private void injectTriggers(final String groupName, final String triggers, final List<String> triggersDistributed, final Scheduler taskScheduler) { String injectionJob; if (groupName.equals(GROUP_FEED)) injectionJob = JOB_FEED_INJECTION; else if (groupName.equals((GROUP_ARCHIVE))) injectionJob = JOB_ARCHIVE_INJECTION; else throw new IllegalStateException("Unknown group '" + groupName + "'"); for (String injectionTrigger : StringUtils.tokenizeToStringArray(triggers, ",", true, true)) { String[] keyValue = injectionTrigger.split(":"); if (keyValue.length != 2 || !StringUtils.hasText(keyValue[0]) || !StringUtils.hasText(keyValue[1])) throw new IllegalArgumentException("Invalid " + groupName + " injection trigger '" + injectionTrigger + "' - should be of type <key>:<cronExpression"); if (staticLogger.isInfoEnabled()) staticLogger.info("Creating " + groupName + " injection trigger '" + keyValue[0].trim() + "' using cron-pattern '" + keyValue[1].trim() + "'"); if (groupName.equals(GROUP_FEED)) this.feedInjectionTriggers.put(keyValue[0].trim(), keyValue[1].trim()); else if (groupName.equals((GROUP_ARCHIVE))) this.archiveInjectionTriggers.put(keyValue[0].trim(), keyValue[1].trim()); else throw new IllegalStateException("Unknown group '" + groupName + "'"); // Determine if this trigger should be distributed if (triggersDistributed.contains(keyValue[0])) { CronTriggerImpl trigger = new CronTriggerImpl(); List<Date> dates; try { trigger.setCronExpression(keyValue[1]); dates = TriggerUtils.computeFireTimes(trigger, new BaseCalendar(), 2); } catch (ParseException e) { throw new IllegalStateException(e); } if (dates.size() != 2) throw new IllegalStateException("The trigger '" + keyValue[1] + "' does not evaluate to at least two future fire times"); Long interval = dates.get(1).getTime() - dates.get(0).getTime(); if (staticLogger.isInfoEnabled()) staticLogger.info( "Distributed " + groupName + " injection trigger evaluated to a total interval of " + interval + " milliseconds - will fire every second instead"); SimpleTrigger perSecondTrigger = TriggerBuilder.newTrigger() .withIdentity(keyValue[0].trim(), groupName) .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever()) .forJob(new JobKey(injectionJob, groupName)).build(); perSecondTrigger.getJobDataMap().put("intervalTotal", interval / 1000); try { taskScheduler.scheduleJob(perSecondTrigger); } catch (SchedulerException e) { staticLogger.error("Unable to schedule distributed feedInjection job with cron-trigger '" + keyValue[0].trim() + "' / '" + keyValue[1].trim() + "'", e); } } else { // Now create a cron trigger and attach it to the appropriate job CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(keyValue[0].trim(), groupName) .withSchedule(CronScheduleBuilder.cronSchedule(keyValue[1].trim())) .forJob(new JobKey(injectionJob, groupName)).build(); try { taskScheduler.scheduleJob(trigger); } catch (SchedulerException e) { staticLogger.error("Unable to schedule feedInjection job with cron-trigger '" + keyValue[0].trim() + "' / '" + keyValue[1].trim() + "'", e); } } } } /** * Retrieve the list of scheduled jobs. * * @return Map<String, Map<String, TreeString>> */ public Map<String, LinkedHashMap<String, String>> getSchedulerJobs() { Map<String, LinkedHashMap<String, String>> schedulerJobs = new LinkedHashMap<String, LinkedHashMap<String, String>>(); // Make sure these are added in order schedulerJobs.put(JOB_FEED_INJECTION, new LinkedHashMap<String, String>()); schedulerJobs.put(JOB_ARCHIVE_INJECTION, new LinkedHashMap<String, String>()); for (Entry<String, String> feedInjectionTrigger : feedInjectionTriggers.entrySet()) { schedulerJobs.get(JOB_FEED_INJECTION).put(feedInjectionTrigger.getKey(), "inactive"); } for (Entry<String, String> archiveInjectionTrigger : archiveInjectionTriggers.entrySet()) { schedulerJobs.get(JOB_ARCHIVE_INJECTION).put(archiveInjectionTrigger.getKey(), "inactive"); } // Add all others in whichever order, and update those triggers that are not inactive try { for (String groupName : taskScheduler.getJobGroupNames()) { if (!groupName.equals(GROUP_FEED) && !groupName.equals(GROUP_ARCHIVE)) for (JobKey jobKey : taskScheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) { LinkedHashMap<String, String> triggers = new LinkedHashMap<String, String>(); for (Trigger trigger : taskScheduler.getTriggersOfJob(jobKey)) triggers.put(trigger.getKey().getName(), "inactive"); schedulerJobs.put(jobKey.getName(), triggers); } for (JobExecutionContext jobContext : taskScheduler.getCurrentlyExecutingJobs()) schedulerJobs.get(jobContext.getJobDetail().getKey().getName()) .put(jobContext.getTrigger().getKey().getName(), "active"); } } catch (SchedulerException e) { logger.error("Could not retrieve the list of currently running jobs"); } return schedulerJobs; } /** * Start a given scheduler job (optionally using the given trigger name.) * * @param jobName * @param triggerName */ public void startSchedulerJob(final String jobName, final String triggerName) { startSchedulerJob(jobName, triggerName, null); } /** * Start a given scheduler job (or even a specific trigger) using the given data. * * XXX: We've so far managed to avoid having to use this - as passing in data is difficult when starting jobs manually * * @param jobName * @param data */ private void startSchedulerJob(final String jobName, final String triggerName, final Map<String, Object> data) { try { JobDetail jobDetail = taskScheduler.getJobDetail(new JobKey(jobName, determineGroupName(jobName))); TriggerBuilder<Trigger> builder = TriggerBuilder.newTrigger().forJob(jobDetail).startNow(); if (triggerName != null) builder.withIdentity(triggerName); if (data != null) builder.usingJobData(new JobDataMap(data)); taskScheduler.scheduleJob(builder.build()); } catch (SchedulerException e) { logger.error("Could not inject the list of currently running jobs: " + e.getMessage()); } } /** * Stop a given scheduler job. * * @param jobName * @param triggerName */ public void stopSchedulerJob(final String jobName, final String triggerName) { try { boolean found = false; for (JobExecutionContext context : taskScheduler.getCurrentlyExecutingJobs()) if (context.getJobDetail().getKey().getName().equals(jobName) && context.getTrigger().getKey().getName().equals(triggerName)) { ((InjectionJobInterrupted) context.getTrigger().getJobDataMap().get("interrupted")) .setInterrupted(true); found = true; } if (!found) logger.warn("Job with name '" + jobName + "' and trigger '" + triggerName + "' was not found to be currently executing - not terminated"); } catch (SchedulerException e) { logger.error("Could not retrieve the list of currently running jobs: " + e.getMessage()); } } /** * The feed injection triggers. * * @return Map<String, String> */ public Map<String, String> getFeedInjectionTriggers() { return feedInjectionTriggers; } /** * The archive injection triggers. * * @return Map<String, String> */ public Map<String, String> getArchiveInjectionTriggers() { return archiveInjectionTriggers; } /** * Determine the group name based on the job name. * * @param jobName * @return String */ private static String determineGroupName(final String jobName) { if (jobName.equals(JOB_FEED_INJECTION)) return GROUP_FEED; else if (jobName.equals(JOB_ARCHIVE_INJECTION)) return GROUP_ARCHIVE; else return Scheduler.DEFAULT_GROUP; } }