com.seajas.search.profiler.service.task.TaskService.java Source code

Java tutorial

Introduction

Here is the source code for com.seajas.search.profiler.service.task.TaskService.java

Source

/**
 * 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;
    }
}