org.eclipse.gyrex.jobs.internal.scheduler.Schedule.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.gyrex.jobs.internal.scheduler.Schedule.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2013 AGETO Service GmbH and others.
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 *
 * Contributors:
 *     Gunnar Wagenknecht - initial API and implementation
 *     Mike Tschierschke - improvements due working on https://bugs.eclipse.org/bugs/show_bug.cgi?id=344467
 *******************************************************************************/
package org.eclipse.gyrex.jobs.internal.scheduler;

import java.text.ParseException;
import java.util.List;

import org.eclipse.gyrex.jobs.internal.JobsActivator;
import org.eclipse.gyrex.jobs.internal.JobsDebug;
import org.eclipse.gyrex.jobs.internal.schedules.ScheduleImpl;
import org.eclipse.gyrex.jobs.internal.schedules.ScheduleManagerImpl;
import org.eclipse.gyrex.jobs.internal.schedules.ScheduleStore;
import org.eclipse.gyrex.jobs.schedules.IScheduleEntry;
import org.eclipse.gyrex.monitoring.metrics.MetricSet;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;

import org.osgi.framework.ServiceRegistration;
import org.osgi.service.prefs.BackingStoreException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.SchedulerException;
import org.quartz.impl.DirectSchedulerFactory;
import org.quartz.impl.SchedulerRepository;
import org.quartz.simpl.RAMJobStore;
import org.quartz.simpl.SimpleThreadPool;
import org.quartz.spi.JobStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A quarz schedule
 */
public class Schedule implements IPreferenceChangeListener {

    private class DeferredActivationJob extends Job {

        public DeferredActivationJob() {
            super("Deferred activation for schedule " + getScheduleStoreStorageKey());
            setSystem(true);
        }

        @Override
        protected IStatus run(final IProgressMonitor monitor) {
            try {
                // Force re-load of schedule data
                final ScheduleImpl schedule = ensureScheduleData(Boolean.TRUE);
                if (schedule.isEnabled()) {
                    if (monitor.isCanceled())
                        return Status.CANCEL_STATUS;
                    activateEngine();
                } else {
                    deactivateEngine();
                }
                return Status.OK_STATUS;
            } catch (final RuntimeException e) {
                LOG.error("Error activating schedule {}: {}",
                        new Object[] { getScheduleStoreStorageKey(), e.getMessage(), e });
                quietShutdown();
                return Status.CANCEL_STATUS;
            }
        }

    }

    private static final Logger LOG = LoggerFactory.getLogger(Schedule.class);
    private static final long DEFERRED_ACTIVATION_DELAY = 10000L; // 10 seconds

    public static String asQuartzCronExpression(final String cronExpression) {
        // quartz allows for seconds but Unix cron does not support this
        // thus, we always force the second to be '0'
        return "0 " + cronExpression;
    }

    private final DeferredActivationJob deferredActivationJob = new DeferredActivationJob();
    private final String scheduleStoreStorageKey;

    private final ScheduleMetrics metrics;
    private ServiceRegistration<MetricSet> metricsRegistration;
    private ScheduleImpl scheduleData;

    private org.quartz.Scheduler quartzScheduler;

    /**
     * Creates a new instance.
     * 
     * @param scheduleStoreStorageKey
     * @param scheduler
     * @throws BackingStoreException
     */
    public Schedule(final String scheduleStoreStorageKey, final Scheduler scheduler) throws Exception {
        this.scheduleStoreStorageKey = scheduleStoreStorageKey;
        metrics = new ScheduleMetrics(scheduleStoreStorageKey);
    }

    synchronized void activateEngine() {
        if (JobsDebug.schedulerEngine) {
            LOG.debug("Activating schedule {}...", scheduleStoreStorageKey);
        }
        if (null != quartzScheduler)
            return;

        try {
            // make sure that Quartz does not check for updates
            System.setProperty("org.terracotta.quartz.skipUpdateCheck", "true");

            // prepare scheduler 
            final DirectSchedulerFactory factory = DirectSchedulerFactory.getInstance();
            final SimpleThreadPool threadPool = new SimpleThreadPool(1, Thread.NORM_PRIORITY);
            threadPool.setInstanceId(scheduleStoreStorageKey);
            threadPool.setInstanceName(scheduleStoreStorageKey);
            final JobStore jobStore = new RAMJobStore();

            // create scheduler
            // (make sure that only a single thread manipulates the SchedulerRepository)
            final SchedulerRepository repository = SchedulerRepository.getInstance();
            synchronized (repository) {
                factory.createScheduler(scheduleStoreStorageKey, scheduleStoreStorageKey, threadPool, jobStore);
                quartzScheduler = factory.getScheduler(scheduleStoreStorageKey);
                if (null == quartzScheduler) {
                    quartzScheduler = repository.lookup(scheduleStoreStorageKey);
                }
            }

            // double check to ensure that Quartz did not fail
            if (null == quartzScheduler)
                throw new SchedulerException(
                        "Unabled to retrieve created scheduler from Quartz SchedulerRepository. It looks like the creation failed but the Quartz framework did not report it as such!");

            // TODO add support for calendars (we likely should support global calendars)

            // refresh the schedule
            refreshSchedule();

            // start
            quartzScheduler.start();
            metrics.setStatus("QUARTZRUNNING", "Quartz schedule started successfully");

            // log success message
            LOG.info("Activated schedule {}.", getScheduleStoreStorageKey());
        } catch (final SchedulerException e) {
            LOG.error("Unable to activate Quarz scheduler. {}", ExceptionUtils.getRootCauseMessage(e));
            metrics.error("error activating schedule", e);

            // cleanup
            quietShutdown();
        }

    }

    synchronized void deactivateEngine() {
        if (JobsDebug.schedulerEngine) {
            LOG.debug("Deactivating Quartz engine {}...", getScheduleStoreStorageKey());
        }
        quietShutdown();
        metrics.setStatus("DEACTIVATED", "schedule deactivated");
    }

    /**
     * Returns the scheduleData.
     * 
     * @return the scheduleData
     */
    private ScheduleImpl ensureScheduleData(final boolean forceReload) {
        if ((null == scheduleData) || forceReload) {
            try {
                return scheduleData = ScheduleStore.load(scheduleStoreStorageKey,
                        ScheduleManagerImpl.getExternalId(scheduleStoreStorageKey), true);
            } catch (final Exception e) {
                throw new IllegalStateException(
                        String.format("Unable to load schedule '%s'. %s", scheduleStoreStorageKey, e.getMessage()),
                        e);
            }
        }

        return scheduleData;
    }

    public String getScheduleStoreStorageKey() {
        return scheduleStoreStorageKey;
    }

    synchronized boolean isActive() {
        try {
            return (null != quartzScheduler) && quartzScheduler.isStarted();
        } catch (final SchedulerException e) {
            return false;
        }
    }

    @Override
    public void preferenceChange(final PreferenceChangeEvent event) {
        // cancel any ongoing activation
        deferredActivationJob.cancel();

        if (ScheduleImpl.ENABLED.equals(event.getKey())) {
            final boolean activate = StringUtils.equals(Boolean.toString(Boolean.TRUE),
                    (String) event.getNewValue());
            try {
                if (activate) {
                    deferredActivationJob.schedule(DEFERRED_ACTIVATION_DELAY);
                } else {
                    deactivateEngine();
                }
            } catch (final Exception e) {
                if (activate) {
                    LOG.error("Error activating schedule '{}'. {}",
                            new Object[] { event.getNode().name(), ExceptionUtils.getRootCauseMessage(e), e });
                } else {
                    LOG.error("Error deactivating schedule '{}'. {}",
                            new Object[] { event.getNode().name(), ExceptionUtils.getRootCauseMessage(e), e });
                }
                quietShutdown();
            }
        } else {
            // a schedule might have been incomplete previously and not activated properly;
            // check if now is a good time to activate it and try to activate it
            if (!isActive()) {
                try {
                    deferredActivationJob.schedule(DEFERRED_ACTIVATION_DELAY);
                } catch (final Exception e) {
                    // ignore; still not ready
                    quietShutdown();
                }
            }
        }
    }

    private synchronized void quietShutdown() {
        // cancel any ongoing activation
        deferredActivationJob.cancel();

        // make sure that there is no Quartz schedule left in the Quartz scheduler repo
        final SchedulerRepository repository = SchedulerRepository.getInstance();
        if (null == quartzScheduler) {
            synchronized (repository) {
                quartzScheduler = repository.lookup(scheduleStoreStorageKey);
            }
        }

        // shutdown Quartz scheduler
        if (null != quartzScheduler) {
            try {
                // remove from SchedulerRepository
                boolean removed;
                synchronized (repository) {
                    removed = repository.remove(scheduleStoreStorageKey);
                }
                if (!removed) {
                    LOG.error(
                            "Quartz eninge for schedule {} could not be removed from the Quartz scheduler repository. Please monitor the process memory and scheduling closely for anomalies. A restart of the node may be necessary.",
                            scheduleStoreStorageKey);
                } else if (JobsDebug.schedulerEngine) {
                    LOG.debug("Successful removal of Quartz engine {} from scheduler repo.",
                            scheduleStoreStorageKey);
                }
            } catch (final Exception ignored) {
                // ignore
            } finally {
                try {
                    // shutdown
                    quartzScheduler.shutdown();
                    metrics.setStatus("QUARTZSTOPPED", "quiet shutdown triggered");

                    // log success message
                    LOG.info("Deactivated schedule {}.", scheduleStoreStorageKey);
                } catch (final Exception ignored) {
                    // ignore
                }
            }
            quartzScheduler = null;
        }
    }

    private synchronized void refreshSchedule() throws SchedulerException {

        // delete all existing jobs
        final String[] jobGroupNames = quartzScheduler.getJobGroupNames();
        if (null != jobGroupNames) {
            for (final String groupName : jobGroupNames) {
                for (final String jobName : quartzScheduler.getJobNames(groupName)) {
                    if (JobsDebug.schedulerEngine) {
                        LOG.debug("Removing job {} from Quartz engine {}...", jobName,
                                getScheduleStoreStorageKey());
                    }
                    quartzScheduler.deleteJob(jobName, groupName);
                }
            }
        }

        // get configured jobs
        // (note, it's important that we pass false here to ensureScheduleData in order to prevent the #sync call on the preference node)
        // (otherwise a in-flight preference change event will be reverted)
        // FIXME: this is another case where the preference store is problematic
        final ScheduleImpl schedule = ensureScheduleData(false);
        final List<IScheduleEntry> entries = schedule.getEntries();

        // schedule entries with cron expression if available
        for (final IScheduleEntry entry : entries) {
            final String cronExpression = entry.getCronExpression();
            if (StringUtils.isBlank(cronExpression)) {
                continue;
            }

            final JobDetail detail = new JobDetail(entry.getId(), SchedulingJob.class);
            SchedulingJob.populateJobDataMap(detail.getJobDataMap(), entry, schedule);

            final CronTrigger trigger = new CronTrigger(entry.getId());
            trigger.setTimeZone(schedule.getTimeZone());
            try {
                trigger.setCronExpression(asQuartzCronExpression(cronExpression));
            } catch (final ParseException e) {
                LOG.error("Unable to schedule entry {}. Invalid cron expression. {}", entry,
                        ExceptionUtils.getRootCauseMessage(e));
                continue;
            }

            if (JobsDebug.schedulerEngine) {
                LOG.debug("Adding job {} to Quartz engine {}...", entry, getScheduleStoreStorageKey());
            }
            quartzScheduler.scheduleJob(detail, trigger);
        }
    }

    /**
     * Starts the schedule
     */
    public void start() throws Exception {
        if (JobsDebug.schedulerEngine) {
            LOG.debug("Starting schedule {}...", getScheduleStoreStorageKey());
        }

        metricsRegistration = JobsActivator.registerMetrics(metrics);

        if (!ScheduleStore.getSchedulesNode().nodeExists(scheduleStoreStorageKey)) {
            metrics.setStatus("NOTFOUND", "not found during start");
            throw new IllegalStateException(String.format("Schedule '%s' not found", scheduleStoreStorageKey));
        }

        // get preference node
        final IEclipsePreferences node = (IEclipsePreferences) ScheduleStore.getSchedulesNode()
                .node(scheduleStoreStorageKey);

        // add listener
        node.addPreferenceChangeListener(this);

        // load the schedule
        // (note, use ensureScheduleData to populate with fresh data)
        ScheduleImpl schedule;
        try {
            schedule = ensureScheduleData(Boolean.TRUE);
        } catch (final IllegalStateException e) {
            // the schedule might be in an incomplete "in-creation" phase
            // don't activate the engine right now and wait for the complete creation
            if (JobsDebug.schedulerEngine) {
                LOG.debug(
                        "Schedule {} will not be activated due to loading errors. It looks like its creation is still in progress.",
                        scheduleStoreStorageKey, e);
            }
            metrics.setStatus("ERROR", "exception during start");
            metrics.error("loading error", e);
            return;
        }

        // check if enabled
        if (schedule.isEnabled()) {
            metrics.setStatus("ACTIVATING", "schedule started");
            activateEngine();
        } else {
            if (JobsDebug.schedulerEngine) {
                LOG.debug("Schedule {} is disabled.", getScheduleStoreStorageKey());
            }
            metrics.setStatus("DISABLED", "schedule is disabled");
        }
    }

    /**
     * Stops the schedule
     */
    public void stop() {
        if (JobsDebug.schedulerEngine) {
            LOG.debug("Stopping schedule {}...", getScheduleStoreStorageKey());
        }

        // bring down engine
        deactivateEngine();

        try {
            if (ScheduleStore.getSchedulesNode().nodeExists(scheduleStoreStorageKey)) {
                // get preference node
                final IEclipsePreferences node = (IEclipsePreferences) ScheduleStore.getSchedulesNode()
                        .node(scheduleStoreStorageKey);

                // remove listener
                node.removePreferenceChangeListener(this);
            }
        } catch (final Exception e) {
            // might have been removed
        } finally {
            final ServiceRegistration<MetricSet> registration = metricsRegistration;
            if (registration != null) {
                try {
                    registration.unregister();
                } catch (final IllegalStateException e) {
                    // ignore
                } finally {
                    metricsRegistration = null;
                }
            }
        }

        metrics.setStatus("STOPPED", "schedule stopped");
    }
}