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

com.almende.eve.agent.Agent Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/**
 * @file Agent.java
 * 
 * @brief
 *        Agent is the abstract base class for all Eve agents.
 *        It provides basic functionality such as id, url, getting methods,
 *        subscribing to events, etc.
 * 
 * @license
 *          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.
 * 
 *          Copyright © 2010-2012 Almende B.V.
 * 
 * @author Jos de Jong, 
 * @date 2012-12-12
 */

package com.almende.eve.agent;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.almende.eve.agent.annotation.Namespace;
import com.almende.eve.agent.callback.AsyncCallback;
import com.almende.eve.agent.callback.AsyncCallbackQueue;
import com.almende.eve.agent.callback.SyncCallback;
import com.almende.eve.event.EventsInterface;
import com.almende.eve.monitor.ResultMonitorFactoryInterface;
import com.almende.eve.rpc.RequestParams;
import com.almende.eve.rpc.annotation.Access;
import com.almende.eve.rpc.annotation.AccessType;
import com.almende.eve.rpc.annotation.Sender;
import com.almende.eve.rpc.jsonrpc.JSONMessage;
import com.almende.eve.rpc.jsonrpc.JSONRPC;
import com.almende.eve.rpc.jsonrpc.JSONRPCException;
import com.almende.eve.rpc.jsonrpc.JSONRPCException.CODE;
import com.almende.eve.rpc.jsonrpc.JSONRequest;
import com.almende.eve.rpc.jsonrpc.JSONResponse;
import com.almende.eve.rpc.jsonrpc.jackson.JOM;
import com.almende.eve.scheduler.Scheduler;
import com.almende.eve.state.State;
import com.almende.eve.state.TypedKey;
import com.almende.eve.transport.TransportService;
import com.almende.util.AnnotationUtil;
import com.almende.util.AnnotationUtil.AnnotatedClass;
import com.almende.util.TypeUtil;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

/**
 * The base class for all Eve agents.
 * 
 * @author Almende
 */
@Access(AccessType.UNAVAILABLE)
public abstract class Agent implements AgentInterface {
	
	private static final Logger				LOG					= Logger.getLogger(Agent.class
																		.getCanonicalName());
	private AgentHost						host				= null;
	private State							state				= null;
	private Scheduler						scheduler			= null;
	private ResultMonitorFactoryInterface	monitorFactory		= null;
	private EventsInterface					eventsFactory		= null;
	private AsyncCallbackQueue	callbacks			= null;
	private static final RequestParams		EVEREQUESTPARAMS	= new RequestParams();
	static {
		EVEREQUESTPARAMS.put(Sender.class, null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getDescription()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public String getDescription() {
		return "Base agent.";
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getVersion()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public String getVersion() {
		return "1.0";
	}
	
	/**
	 * Instantiates a new agent.
	 */
	public Agent() {
	}
	
	/**
	 * This method is called during construction of the agent object.
	 * This is not a constructor itself, because else all implementing
	 * subclasses (=all agents) need to explicitly create this constructor.
	 * 
	 * @param agentHost
	 *            the agent host
	 * @param state
	 *            the state
	 */
	public void constr(final AgentHost agentHost, final State state) {
		if (this.state == null) {
			host = agentHost;
			this.state = state;
			monitorFactory = agentHost.getResultMonitorFactory(this);
			eventsFactory = agentHost.getEventsFactory(this);
			callbacks = agentHost.getCallbackQueue(getId(),
					JSONResponse.class);
			
			// validate the Eve agent and output as warnings
			final List errors = JSONRPC.validate(this.getClass(),
					EVEREQUESTPARAMS);
			for (final String error : errors) {
				LOG.warning("Validation error class: "
						+ this.getClass().getName() + ", message: " + error);
			}
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#hasPrivate()
	 */
	@Override
	public boolean hasPrivate() {
		try {
			final Class clazz = this.getClass();
			final AnnotatedClass annotated = AnnotationUtil.get(clazz);
			for (final Annotation anno : annotated.getAnnotations()) {
				if (anno.annotationType().equals(Access.class)
						&& ((Access) anno).value() == AccessType.PRIVATE) {
					return true;
				}
				if (anno.annotationType().equals(Sender.class)) {
					return true;
				}
			}
		} catch (final Exception e) {
			LOG.log(Level.WARNING,
					" Couldn't determine private annotations of agent "
							+ getId(), e);
		}
		return false;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.rpc.jsonrpc.JSONAuthorizor#onAccess(java.lang.String,
	 * java.lang.String)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public boolean onAccess(final String senderUrl, final String functionTag) {
		return true;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.rpc.jsonrpc.JSONAuthorizor#onAccess(java.lang.String)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public boolean onAccess(final String senderUrl) {
		return onAccess(senderUrl, null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.rpc.jsonrpc.JSONAuthorizor#isSelf(java.lang.String)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public boolean isSelf(final String senderUrl) {
		if (senderUrl.startsWith("web://")) {
			return true;
		}
		final List urls = getUrls();
		return urls.contains(senderUrl);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.agent.AgentInterface#signalAgent(com.almende.eve.agent
	 * .AgentSignal)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	// TODO: Replace this by some form of publish/subscribe model!
	public void signalAgent(final AgentSignal event) {
		if (AgentSignal.INVOKE.equals(event.getEvent())) {
			onInvoke((Object[]) event.getData());
		} else if (AgentSignal.RESPOND.equals(event.getEvent())) {
			onRespond((JSONResponse) event.getData());
		} else if (AgentSignal.RESPONSE.equals(event.getEvent())) {
			onResponse((JSONResponse) event.getData());
		} else if (AgentSignal.SEND.equals(event.getEvent())) {
			onSend((JSONMessage) event.getData());
		} else if (AgentSignal.EXCEPTION.equals(event.getEvent())) {
			onException((JSONResponse) event.getData());
		} else if (AgentSignal.CREATE.equals(event.getEvent())) {
			onCreate();
		} else if (AgentSignal.INIT.equals(event.getEvent())) {
			onInit();
		} else if (AgentSignal.DELETE.equals(event.getEvent())) {
			onDelete();
		} else if (AgentSignal.DESTROY.equals(event.getEvent())) {
			onDestroy();
		} else if (AgentSignal.SETSCHEDULERFACTORY.equals(event.getEvent())) {
			// init scheduler tasks
			scheduler = host.getScheduler(this);
		} else if (AgentSignal.ADDTRANSPORTSERVICE.equals(event.getEvent())) {
			final TransportService service = (TransportService) event.getData();
			try {
				service.reconnect(getId());
			} catch (final IOException e) {
				LOG.log(Level.WARNING,
						"Failed to reconnect agent on new transport.", e);
			}
		}
	}
	
	/**
	 * This method is called once in the life time of an agent, at the moment
	 * the agent is being created by the AgentHost.
	 * It can be overridden and used to perform some action when the agent
	 * is create, in that case super.sigCreate() should be called in
	 * the overridden sigCreate().
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onCreate() {
		for (final TransportService service : host.getTransportServices()) {
			try {
				service.reconnect(getId());
			} catch (final Exception e) {
				LOG.log(Level.WARNING, "Couldn't reconnect transport:"
						+ service + " for agent:" + getId(), e);
			}
		}
	}
	
	/**
	 * This method is called on each incoming RPC call.
	 * It can be overridden and used to perform some action when the agent
	 * is invoked.
	 * 
	 * @param signalData
	 *            the signal data
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onInvoke(final Object[] signalData) {
	}
	
	/**
	 * This method is called after handling each incoming RPC call.
	 * It can be overridden and used to perform some action when the agent
	 * has been invoked.
	 * 
	 * @param response
	 *            the response
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onRespond(final JSONResponse response) {
	}
	
	/**
	 * This method is called when handling any outgoing RPC messages.
	 * It can be overridden and used to perform some action when the agent
	 * is sending a message.
	 * 
	 * @param message
	 *            the message
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onSend(final JSONMessage message) {
	}
	
	/**
	 * This method is called when an agent encounters any exception handling RPC
	 * messages.
	 * It can be overridden and used to perform some action when the agent
	 * encounters an exception.
	 * 
	 * @param response
	 *            the response
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onException(final JSONResponse response) {
	}
	
	/**
	 * This method is called when handling an incoming RPC response.
	 * It can be overridden and used to perform some action when the agent
	 * has received a response.
	 * 
	 * @param response
	 *            the response
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onResponse(final JSONResponse response) {
	}
	
	/**
	 * This method is called directly after the agent and its state is
	 * initiated.
	 * It can be overridden and used to perform some action when the agent
	 * is initialized, in that case super.sigInit() should be called in
	 * the overridden sigInit().
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onInit() {
	}
	
	/**
	 * This method is called by the finalize method (GC) upon unloading of the
	 * agent from memory.
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onDestroy() {
	}
	
	/**
	 * This method is called once in the life time of an agent, at the moment
	 * the agent is being deleted by the AgentHost.
	 * It can be overridden and used to perform some action when the agent
	 * is deleted, in that case super.sigDelete() should be called in
	 * the overridden sigDelete().
	 */
	@Access(AccessType.UNAVAILABLE)
	protected void onDelete() {
		// TODO: unsubscribe from all subscriptions
		
		// cancel all scheduled tasks.
		if (scheduler == null) {
			scheduler = host.getScheduler(this);
		}
		if (scheduler != null) {
			for (final String taskId : scheduler.getTasks()) {
				scheduler.cancelTask(taskId);
			}
		}
		// remove all keys from the state
		// Note: the state itself will be deleted by the AgentHost
		state.clear();
		
		// save the agents class again in the state
		state.put(State.KEY_AGENT_TYPE, getClass().getName());
		state = null;
		// forget local reference, as it can keep the State alive
		// even if the AgentHost removes the file.
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#finalize()
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	protected void finalize() throws Throwable {
		// ensure the state is cleanup when the agent's method destroy is not
		// called.
		onDestroy();
		getState().destroy();
		super.finalize();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getState()
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final State getState() {
		return state;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getScheduler()
	 */
	@Override
	@Namespace("scheduler")
	public final Scheduler getScheduler() {
		if (scheduler == null) {
			scheduler = host.getScheduler(this);
		}
		return scheduler;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getAgentHost()
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final AgentHost getAgentHost() {
		return host;
		
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getResultMonitorFactory()
	 */
	@Override
	@Namespace("monitor")
	public final ResultMonitorFactoryInterface getResultMonitorFactory() {
		return monitorFactory;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getEventsFactory()
	 */
	@Override
	@Namespace("event")
	public final EventsInterface getEventsFactory() {
		return eventsFactory;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getFirstUrl()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public URI getFirstUrl() {
		final List urls = getUrls();
		if (urls.size() > 0) {
			return URI.create(urls.get(0));
		}
		return URI.create("local:" + getId());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getMethods()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public List getMethods() {
		return JSONRPC.describe(this, EVEREQUESTPARAMS);
	}
	
	// TODO: only allow ObjectNode as params?
	/**
	 * Loc send.
	 * 
	 * @param url
	 *            the url
	 * @param method
	 *            the method
	 * @param params
	 *            the params
	 * @return the jSON response
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @throws JSONRPCException
	 *             the jSONRPC exception
	 */
	private JSONResponse locSend(final URI url, final String method,
			final Object params) throws IOException, JSONRPCException {
		
		ObjectNode jsonParams;
		if (params instanceof ObjectNode) {
			jsonParams = (ObjectNode) params;
		} else {
			jsonParams = JOM.getInstance().valueToTree(params);
		}
		
		// invoke the other agent via the AgentHost, allowing the factory
		// to route the request internally or externally
		final JSONRequest request = new JSONRequest(method, jsonParams);
		final SyncCallback callback = new SyncCallback();
		send(request, url, callback, null);
		JSONResponse response;
		try {
			response = callback.get();
		} catch (final Exception e) {
			throw new JSONRPCException(CODE.REMOTE_EXCEPTION, "", e);
		}
		final JSONRPCException err = response.getError();
		if (err != null) {
			throw err;
		}
		return response;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Object, java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final Object params, final Class type) throws IOException,
			JSONRPCException {
		return TypeUtil.inject(locSend(url, method, params).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Object, java.lang.reflect.Type)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final Object params, final Type type) throws IOException,
			JSONRPCException {
		return TypeUtil.inject(locSend(url, method, params).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Object, com.almende.util.TypeUtil)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final Object params, final TypeUtil type) throws IOException,
			JSONRPCException {
		return type.inject(locSend(url, method, params).getResult());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Object,
	 * com.fasterxml.jackson.databind.JavaType)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final Object params, final JavaType type) throws IOException,
			JSONRPCException {
		return TypeUtil.inject(locSend(url, method, params).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.reflect.Type)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method, final Type type)
			throws IOException, JSONRPCException {
		return TypeUtil.inject(locSend(url, method, null).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, com.fasterxml.jackson.databind.JavaType)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final JavaType type) throws IOException, JSONRPCException {
		return TypeUtil.inject(locSend(url, method, null).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final Class type) throws IOException, JSONRPCException {
		
		return TypeUtil.inject(locSend(url, method, null).getResult(), type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, com.almende.util.TypeUtil)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T send(final URI url, final String method,
			final TypeUtil type) throws IOException, JSONRPCException {
		
		return type.inject(locSend(url, method, null).getResult());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String, java.lang.Object)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final void send(final URI url, final String method,
			final Object params) throws IOException, JSONRPCException {
		locSend(url, method, params);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.net.URI,
	 * java.lang.String)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final void send(final URI url, final String method)
			throws IOException, JSONRPCException {
		locSend(url, method, null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#createAgentProxy(java.net.URI,
	 * java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  T createAgentProxy(final URI url,
			final Class agentInterface) {
		return getAgentHost().createAgentProxy(this, url, agentInterface);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.agent.AgentInterface#createAsyncAgentProxy(java.net.URI,
	 * java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  AsyncProxy createAsyncAgentProxy(
			final URI url, final Class agentInterface) {
		return getAgentHost().createAsyncAgentProxy(this, url, agentInterface);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * java.lang.String, com.fasterxml.jackson.databind.node.ObjectNode)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final void sendAsync(final URI url, final String method,
			final ObjectNode params) throws IOException {
		sendAsync(url, method, params, null, Void.class);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * java.lang.String)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final void sendAsync(final URI url, final String method)
			throws IOException {
		sendAsync(url, method, null, null, Void.class);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * java.lang.String, com.fasterxml.jackson.databind.node.ObjectNode,
	 * com.almende.eve.agent.callback.AsyncCallback, java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final String method,
			final ObjectNode params, final AsyncCallback callback,
			final Class type) throws IOException {
		final JSONRequest request = new JSONRequest(method, params);
		sendAsync(url, request, callback, JOM.getTypeFactory()
				.uncheckedSimpleType(type));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * java.lang.String, com.fasterxml.jackson.databind.node.ObjectNode,
	 * com.almende.eve.agent.callback.AsyncCallback, java.lang.reflect.Type)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final String method,
			final ObjectNode params, final AsyncCallback callback,
			final Type type) throws IOException {
		final JSONRequest request = new JSONRequest(method, params);
		sendAsync(url, request, callback,
				JOM.getTypeFactory().constructType(type));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * java.lang.String, com.fasterxml.jackson.databind.node.ObjectNode,
	 * com.almende.eve.agent.callback.AsyncCallback,
	 * com.fasterxml.jackson.databind.JavaType)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final String method,
			final ObjectNode params, final AsyncCallback callback,
			final JavaType type) throws IOException {
		final JSONRequest request = new JSONRequest(method, params);
		sendAsync(url, request, callback, type);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * com.almende.eve.rpc.jsonrpc.JSONRequest,
	 * com.almende.eve.agent.callback.AsyncCallback, java.lang.Class)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final JSONRequest request,
			final AsyncCallback callback, final Class type)
			throws IOException {
		sendAsync(url, request, callback, JOM.getTypeFactory()
				.uncheckedSimpleType(type));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * com.almende.eve.rpc.jsonrpc.JSONRequest,
	 * com.almende.eve.agent.callback.AsyncCallback, java.lang.reflect.Type)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final JSONRequest request,
			final AsyncCallback callback, final Type type)
			throws IOException {
		sendAsync(url, request, callback,
				JOM.getTypeFactory().constructType(type));
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#sendAsync(java.net.URI,
	 * com.almende.eve.rpc.jsonrpc.JSONRequest,
	 * com.almende.eve.agent.callback.AsyncCallback,
	 * com.fasterxml.jackson.databind.JavaType)
	 */
	@Override
	@Access(AccessType.UNAVAILABLE)
	public final  void sendAsync(final URI url, final JSONRequest request,
			final AsyncCallback callback, final JavaType type)
			throws IOException {
		
		// Create a callback to retrieve a JSONResponse and extract the result
		// or error from this. This is double nested, mostly because of the type
		// conversions required on the result.
		final AsyncCallback responseCallback = new AsyncCallback() {
			@SuppressWarnings("unchecked")
			@Override
			public void onSuccess(final JSONResponse response) {
				if (callback == null) {
					final Exception err = response.getError();
					if (err != null) {
						LOG.warning("async RPC call failed, and no callback handler available:"
								+ err.getLocalizedMessage());
					}
				} else {
					final Exception err = response.getError();
					if (err != null) {
						callback.onFailure(err);
					}
					if (type != null && !type.hasRawClass(Void.class)) {
						try {
							final T res = (T) TypeUtil.inject(
									response.getResult(), type);
							callback.onSuccess(res);
						} catch (final ClassCastException cce) {
							callback.onFailure(new JSONRPCException(
									"Incorrect return type received for JSON-RPC call:"
											+ request.getMethod() + "@" + url,
									cce));
						}
						
					} else {
						callback.onSuccess(null);
					}
				}
			}
			
			@Override
			public void onFailure(final Exception exception) {
				if (callback == null) {
					LOG.warning("async RPC call failed and no callback handler available:"
							+ exception.getLocalizedMessage());
				} else {
					callback.onFailure(exception);
				}
			}
		};
		
		send(request, url, responseCallback, null);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getUrls()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public List getUrls() {
		final List urls = new ArrayList();
		if (host != null) {
			final String agentId = getId();
			for (final TransportService service : host.getTransportServices()) {
				final URI url = service.getAgentUrl(agentId);
				if (url != null) {
					urls.add(url.toString());
				}
			}
			urls.add("local:" + agentId);
		} else {
			LOG.severe("AgentHost not initialized?!?");
		}
		return urls;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.agent.AgentInterface#getRef(com.almende.eve.state.TypedKey
	 * )
	 */
	@Override
	public  T getRef(final TypedKey key) {
		return host.getRef(getId(), key);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.almende.eve.agent.AgentInterface#putRef(com.almende.eve.state.TypedKey
	 * , java.lang.Object)
	 */
	@Override
	public  void putRef(final TypedKey key, final T value) {
		host.putRef(getId(), key, value);
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getId()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public String getId() {
		return state.getAgentId();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#getType()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public String getType() {
		return getClass().getSimpleName();
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	@Access(AccessType.PUBLIC)
	public String toString() {
		final Map data = new HashMap();
		data.put("class", this.getClass().getName());
		data.put("id", getId());
		return data.toString();
	}
	
	// TODO: This should be abstracted to a generic "Translation service"?
	/**
	 * This message tries to convert/parse the given object to a JSONMessage.
	 * Return null if it fails to convert the message.
	 * 
	 * @param msg
	 *            the msg
	 * @return JSONMessage
	 */
	public static JSONMessage jsonConvert(final Object msg) {
		JSONMessage jsonMsg = null;
		try {
			if (msg instanceof JSONMessage) {
				jsonMsg = (JSONMessage) msg;
			} else {
				ObjectNode json = null;
				if (msg instanceof String) {
					final String message = (String) msg;
					if (message.startsWith("{")
							|| message.trim().startsWith("{")) {
						
						json = JOM.getInstance().readValue(message,
								ObjectNode.class);
					}
				} else if (msg instanceof ObjectNode) {
					json = (ObjectNode) msg;
				} else if (msg == null) {
					LOG.warning("Message null!");
				} else {
					LOG.warning("Message unknown type:" + msg.getClass());
				}
				if (json != null) {
					if (JSONRPC.isResponse(json)) {
						final JSONResponse response = new JSONResponse(json);
						jsonMsg = response;
					} else if (JSONRPC.isRequest(json)) {
						final JSONRequest request = new JSONRequest(json);
						jsonMsg = request;
					} else {
						LOG.warning("Message contains valid JSON, but is not JSON-RPC:"
								+ json);
					}
				}
			}
		} catch (Exception e) {
			LOG.log(Level.WARNING,
					"Message triggered exception in trying to convert it to a JSONMessage.",
					e);
		}
		return jsonMsg;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#receive(java.lang.Object,
	 * java.net.URI, java.lang.String)
	 */
	@Override
	public void receive(final Object msg, final URI senderUrl, final String tag) {
		JsonNode id = null;
		try {
			final JSONMessage jsonMsg = jsonConvert(msg);
			if (jsonMsg != null) {
				if (jsonMsg.getId() != null) {
					id = jsonMsg.getId();
				}
				if (jsonMsg instanceof JSONRequest) {
					final RequestParams params = new RequestParams();
					params.put(Sender.class, senderUrl.toASCIIString());
					
					final JSONRequest request = (JSONRequest) jsonMsg;
					final AgentInterface me = this;
					host.getPool().execute(new Runnable() {
						@Override
						public void run() {
							final Object[] signalData = new Object[2];
							signalData[0] = request;
							signalData[1] = params;
							signalAgent(new AgentSignal(
									AgentSignal.INVOKE, signalData));
							
							final JSONResponse response = JSONRPC.invoke(me,
									request, params, me);
							
							signalAgent(new AgentSignal(
									AgentSignal.RESPOND, response));
							try {
								send(response, senderUrl, null, tag);
							} catch (final IOException e) {
								LOG.log(Level.WARNING, getId()
										+ ": Failed to send response.", e);
							}
						}
					});
					
				} else if (jsonMsg instanceof JSONResponse && callbacks != null
						&& id != null && !id.isNull()) {
					final JSONResponse response = (JSONResponse) jsonMsg;
					final AsyncCallback callback = callbacks.pull(id);
					if (callback != null) {
						host.getPool().execute(new Runnable() {
							@Override
							public void run() {
								signalAgent(new AgentSignal(
										AgentSignal.RESPONSE, response));
								if (response.getError() != null) {
									callback.onFailure(response.getError());
								} else {
									callback.onSuccess(response);
								}
							}
						});
					}
				}
			} else {
				LOG.log(Level.WARNING, getId()
						+ ": Received non-JSON message:'" + msg + "'");
			}
		} catch (final Exception e) {
			LOG.log(Level.WARNING, "Exception in receiving message", e);
			// generate JSON error response, skipped if it was an incoming
			// notification i.s.o. request.
			final JSONRPCException jsonError = new JSONRPCException(
					JSONRPCException.CODE.INTERNAL_ERROR, e.getMessage(), e);
			final JSONResponse response = new JSONResponse(jsonError);
			response.setId(id);
			signalAgent(new AgentSignal(AgentSignal.EXCEPTION,
					response));
			try {
				send(response, senderUrl, null, tag);
			} catch (final Exception e1) {
				LOG.log(Level.WARNING,
						getId() + ": failed to send '"
								+ e.getLocalizedMessage()
								+ "' error to remote agent.", e1);
			}
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see com.almende.eve.agent.AgentInterface#send(java.lang.Object,
	 * java.net.URI, com.almende.eve.agent.callback.AsyncCallback,
	 * java.lang.String)
	 */
	@Override
	public void send(final Object msg, final URI receiverUrl,
			final AsyncCallback callback, final String tag)
			throws IOException {
		if (msg instanceof JSONMessage) {
			signalAgent(new AgentSignal(AgentSignal.SEND,
					(JSONMessage) msg));
			if (callback != null && callbacks != null) {
				callbacks.push(((JSONMessage) msg).getId(),msg.toString(), callback);
			}
		}
		// This should already been done!
		if (msg instanceof JSONRPCException) {
			LOG.log(Level.WARNING,
					"Send has been called to send an JSONRPCException i.s.o. a JSONMessage...");
			host.sendAsync(receiverUrl,
					new JSONResponse((JSONRPCException) msg), this, tag);
			return;
		}
		host.sendAsync(receiverUrl, msg, this, tag);
	}
}