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

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

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

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.joda.time.DateTime;
import org.joda.time.Interval;

import com.almende.eve.agent.AgentFactory;
import com.almende.eve.agent.annotation.Sender;
import com.almende.eve.rpc.RequestParams;
import com.almende.eve.rpc.jsonrpc.JSONRPCException;
import com.almende.eve.rpc.jsonrpc.JSONRequest;
import com.almende.eve.state.State;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

/**
 * Documentation on Scheduling:
 * http://docs.oracle.com/javase/1.5.0/docs/api/java
 * /util/concurrent/ScheduledExecutorService.html
 * http://www.javapractices.com/topic/TopicAction.do?Id=54
 */
public class RunnableSchedulerFactory implements SchedulerFactory {
	private State state = null;
	private String stateId = null;
	private AgentFactory agentFactory = null;
	private long count = 0;
	private ScheduledExecutorService scheduler = Executors
			.newScheduledThreadPool(10);

	// {agentId: {taskId: task}}
	private final Map> allTasks = new ConcurrentHashMap>();

	private Logger logger = Logger.getLogger(this.getClass().getSimpleName());

	/**
	 * This constructor is called when constructed by the AgentFactory
	 * 
	 * @param agentFactory
	 * @param params
	 */
	public RunnableSchedulerFactory(AgentFactory agentFactory,
			Map params) {
		this(agentFactory, (params != null) ? (String) params.get("id") : null);
	}

	public RunnableSchedulerFactory(AgentFactory agentFactory, String id) {
		this.agentFactory = agentFactory;
		this.stateId = id;

		init();
	}

	/**
	 * Perform initialization tasks.
	 */
	private void init() {
		initState();
		initTasks();
	}

	/**
	 * Initialize a state for the service, to persist the parameters of all open
	 * connections.
	 * 
	 * @param id
	 */
	private void initState() {
		// set a state for the service, where the service can
		// persist its state.
		if (stateId == null) {
			stateId = "_runnablescheduler";
			logger.info("No id specified for RunnableSchedulerFactory. "
					+ "Using '" + stateId + "' as id.");
		}
		try {
			// TODO: dangerous to use a generic state (can possibly conflict
			// with the id a regular agent)
			state = agentFactory.getStateFactory().get(stateId);
			if (state == null) {
				state = agentFactory.getStateFactory().create(stateId);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Get a scheduler for a specific agent
	 * 
	 * @param agentId
	 */
	@Override
	public Scheduler getScheduler(String agentId) {
		return new RunnableScheduler(agentId);
	}


	@Override
	public void destroyScheduler(String agentId) {
		allTasks.remove(agentId);
	}
	
	/**
	 * Create a new unique taskId
	 * 
	 * @return taskId
	 */
	private synchronized String createTaskId() {
		count++;
		long id = count;
		return Long.toString(id);
	}

	// TODO: make the class Task serializable (and auto-restart when
	// initializing again?)
	private class Task {
		private String agentId = null;
		private String taskId = null;
		private JSONRequest request = null;
		private DateTime timestamp = null;
		private ScheduledFuture future = null;

		/**
		 * Schedule a task
		 * 
		 * @param agentId
		 *            Id of the agent to be requested
		 * @param request
		 *            A JSONRequest with method and params
		 * @param delay
		 *            The delay in milliseconds
		 */
		Task(final String agentId, final JSONRequest request, long delay) {
			// TODO: throw exceptions when agentId, request are null or delay <
			// 0
			this.agentId = agentId;
			this.request = request;

			start(delay);
		}

		/**
		 * Schedule a task
		 * 
		 * @param params
		 *            A Map with parameters: agentId, request (stringified
		 *            JSONRequest), and timestamp (ISOdate)
		 * @throws IOException
		 * @throws JSONRPCException
		 * @throws JsonMappingException
		 * @throws JsonParseException
		 */
		Task(Map params) throws JsonParseException,
				JsonMappingException, JSONRPCException, IOException {
			// TODO: throw exceptions when agentId, request are null or delay <
			// 0

			agentId = params.get("agentId");
			request = new JSONRequest(params.get("request"));
			timestamp = new DateTime(params.get("timestamp"));

			long delay = 0;
			if (timestamp.isAfterNow()) {
				delay = new Interval(DateTime.now(), timestamp)
						.toDurationMillis();
			}

			start(delay);
		}

		/**
		 * Start task
		 * 
		 * @param delay
		 *            delay in milliseconds
		 */
		private void start(long delay) {
			// create the task
			timestamp = DateTime.now().plus(delay);
			taskId = createTaskId();
			future = scheduler.schedule(new Runnable() {
				@Override
				public void run() {
					try {
						RequestParams params = new RequestParams();
						params.put(Sender.class, null); // TODO: provide itself

						agentFactory.invoke(agentId, request, params);
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						remove();
					}
				}
			}, delay, TimeUnit.MILLISECONDS);

			// persist the task
			store();
		}

		public String getTaskId() {
			return taskId;
		}

		public void cancel() {
			if (future != null) {
				boolean mayInterruptIfRunning = false;
				future.cancel(mayInterruptIfRunning);
			}
			remove();
		}

		/**
		 * Store this task in the global task list
		 */
		private void store() {
			Map tasks = allTasks.get(agentId);
			if (tasks == null) {
				synchronized (allTasks) {
					tasks = allTasks.get(agentId);
					if (tasks == null) {
						tasks = new ConcurrentHashMap();
						allTasks.put(agentId, tasks);
					}
				}
			}
			tasks.put(taskId, this);
			storeTasks();
		}

		/**
		 * Remove this task from the global task list
		 */
		private void remove() {
			Map tasks = allTasks.get(agentId);
			if (tasks != null) {
				tasks.remove(taskId);

				if (tasks.size() == 0) {
					synchronized (tasks) {
						synchronized (allTasks) {
							if (tasks.size() == 0) {
								allTasks.remove(agentId);
							}
						}
					}
				}
				storeTasks();
			}
		}

		public Map getParams() {
			Map params = new HashMap();
			params.put("agentId", agentId);
			params.put("request", request.toString());
			params.put("timestamp", timestamp.toString());
			return params;
		}

	}

	/**
	 * The RunnableSchedular class is the interface which the agents can
	 * interact with. It can only be instantiated by the factory using the
	 * method getSchedular(agentId).
	 */
	public class RunnableScheduler implements Scheduler {
		private RunnableScheduler(String agentId) {
			this.agentId = agentId;
		}

		/**
		 * Schedule a task
		 * 
		 * @param request
		 *            A JSONRequest with method and params
		 * @param delay
		 *            The delay in milliseconds
		 * @return taskId
		 */
		public String createTask(JSONRequest request, long delay) {
			Task task = new Task(agentId, request, delay);
			return task.getTaskId();
		}

		/**
		 * Cancel a scheduled task by its id
		 * 
		 * @param taskId
		 */
		public void cancelTask(String taskId) {
			Map tasks = allTasks.get(agentId);
			if (tasks != null) {
				Task task = tasks.get(taskId);
				if (task != null) {
					task.cancel();
				}
			}
		}
		
		/**
		 * Retrieve a list with all scheduled tasks
		 * 
		 * @return taskIds
		 */
		@Override
		public synchronized Set getTasks() {
			Map tasks = allTasks.get(agentId);
			if (tasks != null) {
				return tasks.keySet();
			}
			return new HashSet();
		}

		private String agentId = null;
	}

	/**
	 * load scheduled, persisted tasks
	 */
	// TODO: storing all running tasks in one state file is quite a bottleneck
	// and not scalable!
	private void initTasks() {
		int taskCount = 0;
		int failedTaskCount = 0;

		try {
			@SuppressWarnings("unchecked")
			List> serializedTasks = (List>) state
					.get("tasks");

			if (serializedTasks != null) {
				for (Map taskParams : serializedTasks) {
					taskCount++;
					try {
						// start the task
						new Task(taskParams);
						// TODO: optimize: when a new Task is created, it will
						// automatically
						// store and persist allTasks again. That is inefficient
					} catch (Exception e) {
						e.printStackTrace();
						failedTaskCount++;
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		logger.info("Initialized "
				+ taskCount
				+ " tasks"
				+ ((failedTaskCount > 0) ? (" " + failedTaskCount + " tasks failed to start.")
						: ""));
	}

	/**
	 * Persist all currently running tasks
	 */
	private void storeTasks() {
		List> serializedTasks = new ArrayList>();

		for (Entry> allEntry : allTasks.entrySet()) {
			Map tasks = allEntry.getValue();
			for (Entry entry : tasks.entrySet()) {
				Task task = entry.getValue();
				serializedTasks.add(task.getParams());
			}
		}

		state.put("tasks", serializedTasks);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy