org.apache.ode.bpel.engine.cron.CronScheduler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ode.bpel.engine.cron.CronScheduler.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.ode.bpel.engine.cron;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.engine.Contexts;
import org.apache.ode.bpel.engine.BpelServerImpl.ContextsAware;
import org.apache.ode.bpel.iapi.ClusterAware;
import org.apache.ode.bpel.iapi.ProcessConf;
import org.apache.ode.bpel.iapi.ProcessConf.CronJob;
import org.apache.ode.bpel.iapi.Scheduler.JobDetails;
import org.apache.ode.bpel.iapi.Scheduler.MapSerializableRunnable;
import org.apache.ode.utils.CronExpression;

public class CronScheduler {
    static final Log __log = LogFactory.getLog(CronScheduler.class);

    // minimum interval of the cron job(1 second)
    private final long MIN_INTERVAL = 0;
    // if the work is schedule too late from the due time, skip it
    private final long TOLERABLE_SCHEDULE_DELAY = 0;

    private ExecutorService _scheduledTaskExec;

    private Contexts _contexts;

    private final Timer _schedulerTimer = new Timer("CronScheduler", true);

    private final Collection<TerminationListener> _systemTerminationListeners = new ArrayList<TerminationListener>();

    private final Map<QName, Collection<TerminationListener>> _terminationListenersByPid = new HashMap<QName, Collection<CronScheduler.TerminationListener>>();

    private volatile boolean _shuttingDown = false;

    public void setScheduledTaskExec(ExecutorService taskExec) {
        _scheduledTaskExec = taskExec;
    }

    public void setContexts(Contexts _contexts) {
        this._contexts = _contexts;
    }

    public void shutdown() {
        _shuttingDown = true;
        _schedulerTimer.cancel();

        for (TerminationListener listener : _systemTerminationListeners) {
            listener.terminate();
        }
        _systemTerminationListeners.clear();

        for (Collection<TerminationListener> listeners : _terminationListenersByPid.values()) {
            for (TerminationListener listener : listeners) {
                listener.terminate();
            }
        }
        _terminationListenersByPid.clear();
    }

    public void cancelProcessCronJobs(QName pid, boolean undeployed) {
        assert pid != null;

        if (__log.isDebugEnabled())
            __log.debug("Cancelling PROCESS CRON jobs for: " + pid);
        Collection<TerminationListener> listenersToTerminate = new ArrayList<TerminationListener>();

        synchronized (_terminationListenersByPid) {
            Collection<TerminationListener> listeners = _terminationListenersByPid.get(pid);
            if (listeners != null) {
                listenersToTerminate.addAll(listeners);
                listeners.clear();
            }
            if (undeployed) {
                _terminationListenersByPid.remove(pid);
            }
        }

        // terminate existing cron jobs if there are
        synchronized (pid) {
            for (TerminationListener listener : listenersToTerminate) {
                listener.terminate();
            }
        }
    }

    public void scheduleProcessCronJobs(QName pid, ProcessConf pconf) {
        if (_shuttingDown) {
            return;
        }
        assert pid != null;

        cancelProcessCronJobs(pid, false);
        Collection<TerminationListener> newListeners = new ArrayList<TerminationListener>();

        synchronized (pid) {
            if (__log.isDebugEnabled())
                __log.debug("Scheduling PROCESS CRON jobs for: " + pid);

            // start new cron jobs
            for (final CronJob job : pconf.getCronJobs()) {
                if (__log.isDebugEnabled())
                    __log.debug("Scheduling PROCESS CRON job: " + job.getCronExpression() + " for: " + pid);
                // for each different scheduled time
                Runnable runnable = new Runnable() {
                    public void run() {
                        if (__log.isDebugEnabled())
                            __log.debug("Running cron cleanup with details list size: "
                                    + job.getRunnableDetailList().size());
                        for (JobDetails details : job.getRunnableDetailList()) {
                            try {
                                // for each clean up for the scheduled time
                                RuntimeDataCleanupRunnable cleanup = new RuntimeDataCleanupRunnable();
                                cleanup.restoreFromDetails(details);
                                cleanup.setContexts(_contexts);
                                cleanup.run();
                                if (__log.isDebugEnabled())
                                    __log.debug("Finished running runtime data cleanup from a PROCESS CRON job: "
                                            + cleanup);
                            } catch (Exception re) {
                                __log.error("Error during runtime data cleanup from a PROCESS CRON: " + details
                                        + "; check your cron settings in deploy.xml.", re);
                                // don't sweat.. the rest of the system and other cron jobs still should work
                            }
                        }
                    }
                };
                newListeners.add(schedule(job.getCronExpression(), runnable, null, null));
            }
        }

        // make sure the pid does not get into the terminationListener map if no cron is setup
        if (!newListeners.isEmpty()) {
            synchronized (_terminationListenersByPid) {
                Collection<TerminationListener> oldListeners = _terminationListenersByPid.get(pid);
                if (oldListeners == null) {
                    _terminationListenersByPid.put(pid, newListeners);
                } else {
                    oldListeners.addAll(newListeners);
                }
            }
        }
    }

    public void refreshSystemCronJobs(SystemSchedulesConfig systemSchedulesConf) {
        if (_shuttingDown) {
            return;
        }

        synchronized (_systemTerminationListeners) {
            if (__log.isDebugEnabled())
                __log.debug("Refreshing SYSTEM CRON jobs.");

            try {
                // if error thrown on reading the schedules.xml, do not cancel existing cron jobs
                List<CronJob> systemCronJobs = systemSchedulesConf.getSystemCronJobs();

                // cancel cron jobs
                for (TerminationListener listener : _systemTerminationListeners) {
                    listener.terminate();
                }
                _systemTerminationListeners.clear();

                // start new cron jobs
                for (final CronJob job : systemCronJobs) {
                    if (__log.isDebugEnabled())
                        __log.debug("Scheduling SYSTEM CRON job:" + job);
                    // for each different scheduled time
                    Runnable runnable = new Runnable() {
                        public void run() {
                            for (JobDetails details : job.getRunnableDetailList()) {
                                try {
                                    // for now, we have only runtime data cleanup cron job defined
                                    // for each clean up for the scheduled time
                                    RuntimeDataCleanupRunnable cleanup = new RuntimeDataCleanupRunnable();
                                    synchronized (_terminationListenersByPid) {
                                        if (!_terminationListenersByPid.isEmpty()) {
                                            details.getDetailsExt().put("pidsToExclude",
                                                    _terminationListenersByPid.keySet());
                                        }
                                    }
                                    cleanup.restoreFromDetails(details);
                                    cleanup.setContexts(_contexts);
                                    cleanup.run();
                                    if (__log.isDebugEnabled())
                                        __log.debug("Finished running runtime data cleanup from a SYSTEM CRON job:"
                                                + cleanup);
                                } catch (Exception e) {
                                    __log.error("Error running a runtime data cleanup from a SYSTEM CRON job: "
                                            + details + "; check your system cron setup.", e);
                                }
                            }
                        }
                    };
                    _systemTerminationListeners.add(schedule(job.getCronExpression(), runnable, null, null));
                }
            } catch (Exception e) {
                __log.error("Error during refreshing SYSTEM CRON schedules: ", e);
            }
        }
    }

    public TerminationListener schedule(final CronExpression cronExpression, final Runnable runnable,
            final JobDetails runnableDetails, TerminationListener terminationListener) {
        if (_shuttingDown) {
            __log.info("CRON Scheduler is being shut down. This new scheduling request is ignored.");
            return new TerminationListener() {
                public void terminate() {
                    // do nothing
                }
            };
        }

        assert cronExpression != null;
        assert runnable != null;

        final Date nextScheduleTime = cronExpression
                .getNextValidTimeAfter(new Date(System.currentTimeMillis() + MIN_INTERVAL));
        final CronScheduledJob job = new CronScheduledJob(nextScheduleTime, runnable, runnableDetails,
                cronExpression, terminationListener);
        if (__log.isDebugEnabled())
            __log.debug("CRON will run in " + (nextScheduleTime.getTime() - System.currentTimeMillis()) + "ms.");

        try {
            _schedulerTimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    if (__log.isDebugEnabled()) {
                        __log.debug("Cron scheduling timer kicked in: " + cronExpression);
                    }
                    // run only if the current node is the coordinator,
                    // with the SimpleScheduler, the node is always the coordinator
                    if (!(_contexts.scheduler instanceof ClusterAware)
                            || ((ClusterAware) _contexts.scheduler).amICoordinator()) {
                        // do not hold the timer thread too long, submit the work to an executorService
                        _scheduledTaskExec.submit(job);
                        if (__log.isDebugEnabled()) {
                            __log.debug("CRON job scheduled " + runnable);
                        }
                    }
                }
            }, nextScheduleTime);
        } catch (IllegalStateException ise) {
            if (_shuttingDown) {
                __log.info("CRON Scheduler is being shut down. This new scheduling request is ignored.");
            } else {
                throw ise;
            }
        }

        return job.terminationListener;
    }

    public interface TerminationListener {
        void terminate();
    }

    private class CronScheduledJob implements Callable<TerminationListener> {
        private volatile boolean terminated = false;
        private Date nextScheduleTime;
        private Runnable runnable;
        private JobDetails runnableDetails;
        private CronExpression cronExpression;
        private TerminationListener terminationListener;

        public CronScheduledJob(Date nextScheduleTime, Runnable runnable, JobDetails runnableDetails,
                CronExpression cronExpression, TerminationListener terminationListener) {
            this.nextScheduleTime = nextScheduleTime;
            this.runnable = runnable;
            this.runnableDetails = runnableDetails;
            this.cronExpression = cronExpression;
            if (terminationListener == null) {
                terminationListener = new TerminationListener() {
                    public void terminate() {
                        terminated = true;
                    }
                };
            }
            this.terminationListener = terminationListener;
        }

        public TerminationListener call() throws Exception {
            try {
                if (TOLERABLE_SCHEDULE_DELAY == 0
                        || nextScheduleTime.getTime() < System.currentTimeMillis() + TOLERABLE_SCHEDULE_DELAY) {
                    if (runnableDetails != null && runnable instanceof MapSerializableRunnable) {
                        ((MapSerializableRunnable) runnable).restoreFromDetails(runnableDetails);
                    }
                    if (runnable instanceof ContextsAware) {
                        ((ContextsAware) runnable).setContexts(_contexts);
                    }
                    if (!_shuttingDown && !terminated) {
                        if (__log.isDebugEnabled()) {
                            __log.debug("Running CRON job: " + runnable + " for " + nextScheduleTime.getTime());
                        }
                        runnable.run();
                    }
                } else {
                    // ignore the scheduling.. it will be scheduled later
                }
            } catch (Exception e) {
                if (_shuttingDown) {
                    __log.info("A cron job threw an Exception during ODE shutdown: " + e.getMessage()
                            + ", you can ignore the error.");
                } else if (e instanceof RuntimeException) {
                    throw e;
                } else {
                    throw new RuntimeException("Exception during running cron scheduled job: " + runnable, e);
                }
            } finally {
                if (!_shuttingDown && !terminated) {
                    schedule(cronExpression, runnable, runnableDetails, terminationListener);
                }
            }

            return terminationListener;
        }
    }
}