All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almende.eve.scheduler.ClockScheduler Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/*
 * 
 */
package com.almende.eve.scheduler;

import java.io.Serializable;
import java.net.URI;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.joda.time.DateTime;

import com.almende.eve.agent.AgentHost;
import com.almende.eve.agent.AgentInterface;
import com.almende.eve.rpc.jsonrpc.JSONRequest;
import com.almende.eve.rpc.jsonrpc.jackson.JOM;
import com.almende.eve.scheduler.clock.Clock;
import com.almende.eve.scheduler.clock.RunnableClock;
import com.almende.eve.state.TypedKey;
import com.almende.util.uuid.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * The Class ClockScheduler.
 */
public class ClockScheduler extends AbstractScheduler implements Runnable {
	private static final Logger									LOG			= Logger.getLogger("ClockScheduler");
	private final AgentInterface								myAgent;
	private final Clock											myClock;
	private final ClockScheduler								_this		= this;
	private static final TypedKey>	TYPEDKEY	= new TypedKey>(
																					"_taskList") {
																			};
	private static final int									MAXCOUNT	= 100;
	
	/**
	 * Instantiates a new clock scheduler.
	 * 
	 * @param myAgent
	 *            the my agent
	 * @param host
	 *            the host
	 */
	public ClockScheduler(final AgentInterface myAgent, final AgentHost host) {
		if (myAgent == null) {
			throw new IllegalArgumentException("MyAgent should not be null!");
		}
		this.myAgent = myAgent;
		myClock = new RunnableClock();
	}
	
	/**
	 * Gets the first task.
	 * 
	 * @return the first task
	 */
	public TaskEntry getFirstTask() {
		if (myAgent.getState() == null) {
			return null;
		}
		final TreeMap timeline = myAgent.getState().get(
				TYPEDKEY);
		if (timeline != null && !timeline.isEmpty()) {
			TaskEntry task = timeline.firstEntry().getValue();
			int count = 0;
			while (task != null && task.isActive() && count < MAXCOUNT) {
				count++;
				final Entry entry = timeline
						.higherEntry(task.getTaskId());
				task = null;
				if (entry != null) {
					task = entry.getValue();
				}
			}
			if (count >= MAXCOUNT) {
				LOG.warning("Oops: more than 100 tasks active at the same time:"
						+ myAgent.getId()
						+ " : "
						+ timeline.size()
						+ "/"
						+ count);
			} else {
				return task;
			}
		}
		return null;
	}
	
	/**
	 * Put task.
	 * 
	 * @param task
	 *            the task
	 */
	public void putTask(final TaskEntry task) {
		putTask(task, false);
	}
	
	/**
	 * Put task.
	 * 
	 * @param task
	 *            the task
	 * @param onlyIfExists
	 *            the only if exists
	 */
	public void putTask(final TaskEntry task, final boolean onlyIfExists) {
		if (task == null || myAgent.getState() == null) {
			LOG.warning("Trying to save task to non-existing state or task is null");
			return;
		}
		final TreeMap oldTimeline = myAgent.getState().get(
				TYPEDKEY);
		TreeMap timeline = null;
		
		if (oldTimeline != null) {
			timeline = new TreeMap(oldTimeline);
		} else {
			timeline = new TreeMap();
		}
		
		if (onlyIfExists) {
			if (timeline.containsKey(task.getTaskId())) {
				timeline.put(task.getTaskId(), task);
			}
		} else {
			timeline.put(task.getTaskId(), task);
		}
		
		if (!myAgent.getState().putIfUnchanged(TYPEDKEY.getKey(), timeline,
				oldTimeline)) {
			// recursive retry....
			putTask(task, onlyIfExists);
			return;
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.scheduler.Scheduler#cancelTask(java.lang.String)
	 */
	@Override
	public void cancelTask(final String id) {
		if (myAgent.getState() == null) {
			return;
		}
		
		final TreeMap oldTimeline = myAgent.getState().get(
				TYPEDKEY);
		TreeMap timeline = null;
		
		if (oldTimeline != null) {
			timeline = new TreeMap(oldTimeline);
			timeline.remove(id);
		} else {
			timeline = new TreeMap();
		}
		
		if (timeline != null
				&& !myAgent.getState().putIfUnchanged(TYPEDKEY.getKey(),
						timeline, oldTimeline)) {
			// recursive retry....
			cancelTask(id);
			return;
		}
	}
	
	/**
	 * Run task.
	 * 
	 * @param task
	 *            the task
	 */
	public void runTask(final TaskEntry task) {
		if (task == null || task.isActive()) {
			return;
		}
		task.setActive(true);
		_this.putTask(task, true);
		
		try {
			// TODO: fix sequential calls, needs callback and guaranteed
			// replies, also in the case of void? (This holds for all methods?)
			final String receiverUrl = "local:" + myAgent.getId();
			// Next call is always short/asynchronous
			myAgent.send(task.getRequest(), URI.create(receiverUrl), null, null);
			
			if (task.getInterval() <= 0) {
				// Remove from list
				_this.cancelTask(task.getTaskId());
			} else {
				task.setDue(DateTime.now().plus(task.getInterval()));
				task.setActive(false);
				_this.putTask(task, true);
				_this.run();
			}
		} catch (final Exception e) {
			LOG.log(Level.SEVERE, myAgent.getId()
					+ ": Failed to run scheduled task:" + task.toString(), e);
		}
		
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.scheduler.Scheduler#createTask(com.almende.eve.rpc.jsonrpc
	 * .JSONRequest, long)
	 */
	@Override
	public String createTask(final JSONRequest request, final long delay) {
		return createTask(request, delay, false, false);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.scheduler.Scheduler#createTask(com.almende.eve.rpc.jsonrpc
	 * .JSONRequest, long, boolean, boolean)
	 */
	@Override
	public String createTask(final JSONRequest request, final long delay,
			final boolean repeat, final boolean sequential) {
		final TaskEntry task = new TaskEntry(DateTime.now().plus(delay),
				request, (repeat ? delay : 0), sequential);
		putTask(task);
		if (repeat || delay <= 0) {
			runTask(task);
		} else {
			run();
		}
		return task.getTaskId();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.scheduler.Scheduler#getTasks()
	 */
	@Override
	public Set getTasks() {
		if (myAgent.getState() == null) {
			return null;
		}
		
		final Set result = new HashSet();
		final TreeMap timeline = myAgent.getState().get(
				TYPEDKEY);
		if (timeline == null || timeline.size() == 0) {
			return result;
		}
		return timeline.keySet();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.scheduler.Scheduler#getDetailedTasks()
	 */
	@Override
	public Set getDetailedTasks() {
		if (myAgent.getState() == null) {
			return null;
		}
		
		final Set result = new HashSet();
		final TreeMap timeline = myAgent.getState().get(
				TYPEDKEY);
		if (timeline == null || timeline.size() == 0) {
			return result;
		}
		for (final TaskEntry entry : timeline.values()) {
			result.add(entry.toString());
		}
		return result;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		final TaskEntry task = getFirstTask();
		if (task != null) {
			if (task.getDue().isBeforeNow()) {
				runTask(task);
				// recursive call next task
				run();
				return;
			}
			myClock.requestTrigger(myAgent.getId(), task.getDue(), this);
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		if (myAgent.getState() == null) {
			return null;
		}
		
		final TreeMap timeline = myAgent.getState().get(
				TYPEDKEY);
		return (timeline != null) ? timeline.toString() : "[]";
	}
}

/**
 * @author Almende
 *
 */
class TaskEntry implements Comparable, Serializable {
	private static final Logger	LOG					= Logger.getLogger(TaskEntry.class
															.getCanonicalName());
	private static final long	serialVersionUID	= -2402975617148459433L;
	// TODO, make JSONRequest.equals() state something about real equal tasks,
	// use it as deduplication!
	private String				taskId				= null;
	private JSONRequest			request;
	private DateTime			due;
	private long				interval			= 0;
	private boolean				sequential			= true;
	private boolean				active				= false;
	
	/**
	 * Instantiates a new task entry.
	 */
	public TaskEntry() {
	};
	
	/**
	 * Instantiates a new task entry.
	 * 
	 * @param due
	 *            the due
	 * @param request
	 *            the request
	 * @param interval
	 *            the interval
	 * @param sequential
	 *            the sequential
	 */
	public TaskEntry(final DateTime due, final JSONRequest request,
			final long interval, final boolean sequential) {
		taskId = new UUID().toString();
		this.request = request;
		this.due = due;
		this.interval = interval;
		this.sequential = sequential;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object o) {
		if (this == o) {
			return true;
		}
		if (!(o instanceof TaskEntry)) {
			return false;
		}
		final TaskEntry other = (TaskEntry) o;
		return taskId.equals(other.taskId);
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return taskId.hashCode();
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(final TaskEntry o) {
		if (equals(o)) {
			return 0;
		}
		if (due.equals(o.due)) {
			return taskId.compareTo(o.taskId);
		}
		return due.compareTo(o.due);
	}
	
	/**
	 * Gets the task id.
	 * 
	 * @return the task id
	 */
	public String getTaskId() {
		return taskId;
	}
	
	/**
	 * Gets the request.
	 * 
	 * @return the request
	 */
	public JSONRequest getRequest() {
		return request;
	}
	
	/**
	 * Gets the due as string.
	 * 
	 * @return the due as string
	 */
	public String getDueAsString() {
		return due.toString();
	}
	
	/**
	 * Gets the due.
	 * 
	 * @return the due
	 */
	@JsonIgnore
	public DateTime getDue() {
		return due;
	}
	
	/**
	 * Gets the interval.
	 * 
	 * @return the interval
	 */
	public long getInterval() {
		return interval;
	}
	
	/**
	 * Sets the task id.
	 * 
	 * @param taskId
	 *            the new task id
	 */
	public void setTaskId(final String taskId) {
		this.taskId = taskId;
	}
	
	/**
	 * Sets the request.
	 * 
	 * @param request
	 *            the new request
	 */
	public void setRequest(final JSONRequest request) {
		this.request = request;
	}
	
	/**
	 * Sets the due as string.
	 * 
	 * @param due
	 *            the new due as string
	 */
	public void setDueAsString(final String due) {
		this.due = new DateTime(due);
	}
	
	/**
	 * Sets the due.
	 * 
	 * @param due
	 *            the new due
	 */
	public void setDue(final DateTime due) {
		this.due = due;
	}
	
	/**
	 * Sets the interval.
	 * 
	 * @param interval
	 *            the new interval
	 */
	public void setInterval(final long interval) {
		this.interval = interval;
	}
	
	/**
	 * Sets the sequential.
	 * 
	 * @param sequential
	 *            the new sequential
	 */
	public void setSequential(final boolean sequential) {
		this.sequential = sequential;
	}
	
	/**
	 * Sets the active.
	 * 
	 * @param active
	 *            the new active
	 */
	public void setActive(final boolean active) {
		this.active = active;
	}
	
	/**
	 * Checks if is sequential.
	 * 
	 * @return true, if is sequential
	 */
	public boolean isSequential() {
		return sequential;
	}
	
	/**
	 * Checks if is active.
	 * 
	 * @return true, if is active
	 */
	public boolean isActive() {
		return active;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		try {
			return JOM.getInstance().writeValueAsString(this);
		} catch (final Exception e) {
			LOG.log(Level.WARNING, "Couldn't use Jackson to print task.", e);
			return "{\"taskId\":" + taskId + ",\"due\":" + due + "}";
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy