/** * Copyright 2010 JBoss Inc * * Licensed 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.drools.time.impl; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; import java.util.Date; import java.util.PriorityQueue; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.drools.SystemEventListenerFactory; import org.drools.common.DroolsObjectInputStream; import org.drools.common.InternalWorkingMemory; import org.drools.time.Job; import org.drools.time.JobContext; import org.drools.time.JobHandle; import org.drools.time.SessionPseudoClock; import org.drools.time.TimerService; import org.drools.time.Trigger; /** * A PseudoClockScheduler is a scheduler based on a user controlled clock * that allows the user to explicitly control current time. * * @author etirelli * */ public class PseudoClockScheduler implements TimerService, SessionPseudoClock, Externalizable { private volatile long timer; private PriorityQueue<ScheduledJob> queue; private transient InternalWorkingMemory session; public PseudoClockScheduler() { this( null ); } public PseudoClockScheduler(InternalWorkingMemory session) { this.timer = 0; this.queue = new PriorityQueue<ScheduledJob>(); this.session = session; } @SuppressWarnings("unchecked") public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { timer = in.readLong(); PriorityQueue<ScheduledJob> tmp = (PriorityQueue<ScheduledJob>) in.readObject(); if ( tmp != null ) { queue = tmp; } session = ((DroolsObjectInputStream) in).getWorkingMemory(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeLong( timer ); // this is a work around to a bug in the object stream code, where it raises exceptions // when trying to de-serialize an empty priority queue. out.writeObject( queue.isEmpty() ? null : queue ); } /** * @inheritDoc * * @see org.drools.temporal.SessionClock#getCurrentTime() */ public synchronized long getCurrentTime() { return this.timer; } /** * @inheritDoc * * @see org.drools.time.TimerService#scheduleJob(org.drools.time.Job, org.drools.time.JobContext, org.drools.time.Trigger) */ public JobHandle scheduleJob(Job job, JobContext ctx, Trigger trigger) { Date date = trigger.hasNextFireTime(); if ( date != null ) { ScheduledJob callableJob = new ScheduledJob( job, ctx, trigger ); queue.add( callableJob ); return callableJob.getHandle(); } return null; } /** * @inheritDoc * * @see org.drools.time.TimerService#removeJob(org.drools.time.JobHandle) */ public boolean removeJob(JobHandle jobHandle) { return this.queue.remove( ((DefaultJobHandle) jobHandle).getScheduledJob() ); } /** * @inheritDoc */ public synchronized long advanceTime(long amount, TimeUnit unit) { this.timer += unit.toMillis( amount ); this.runCallBacks(); return this.timer; } public synchronized void setStartupTime(long i) { this.timer = i; } /** * @return the session */ public synchronized InternalWorkingMemory getSession() { return session; } /** * @param session the session to set */ public synchronized void setSession(InternalWorkingMemory session) { this.session = session; } /** * {@inheritDoc} */ public void shutdown() { // nothing to do } private void runCallBacks() { ScheduledJob item = queue.peek(); long fireTime; while ( item != null && ((fireTime = item.getTrigger().hasNextFireTime().getTime()) <= this.timer) ) { // remove the head queue.remove(); // updates the trigger item.getTrigger().nextFireTime(); if ( item.getTrigger().hasNextFireTime() != null ) { // reschedule for the next fire time, if one exists queue.add( item ); } // save the current timer because we are going to override to to the job's trigger time long savedTimer = this.timer; try { // set the clock back to the trigger's fire time this.timer = fireTime; // execute the call item.call(); } catch ( Exception e ) { SystemEventListenerFactory.getSystemEventListener().exception( e ); } finally { this.timer = savedTimer; } // get next head item = queue.peek(); } } public synchronized long getTimeToNextJob() { ScheduledJob item = queue.peek(); return ( item != null ) ? item.getTrigger().hasNextFireTime().getTime() - this.timer : -1; } /** * An Scheduled Job class with all fields final to make it * multi-thread safe. * * @author etirelli */ public static final class ScheduledJob implements Comparable<ScheduledJob>, Callable<Void>, Serializable { private static final long serialVersionUID = 510l; private final Job job; private final Trigger trigger; private final JobContext ctx; /** * @param timestamp * @param behavior * @param behaviorContext */ public ScheduledJob(final Job job, final JobContext context, final Trigger trigger) { super(); this.job = job; this.ctx = context; this.trigger = trigger; } public int compareTo(ScheduledJob o) { return this.trigger.hasNextFireTime().compareTo( o.getTrigger().hasNextFireTime() ); } public Void call() throws Exception { this.job.execute( this.ctx ); return null; } public Job getJob() { return job; } public Trigger getTrigger() { return trigger; } public JobContext getCtx() { return ctx; } public JobHandle getHandle() { return new DefaultJobHandle( this ); } public String toString() { return "ScheduledJob( job=" + job + " trigger=" + trigger + " context=" + ctx + " )"; } } }