com.almende.eve.agent.Agent.java Source code

Java tutorial

Introduction

Here is the source code for com.almende.eve.agent.Agent.java

Source

/**
 * @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, <jos@almende.org>
 * @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.annotation.JsonIgnore;
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<JSONResponse> 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<String> 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)
    @JsonIgnore
    public boolean isSelf(final String senderUrl) {
        if (senderUrl.startsWith("web://")) {
            return true;
        }
        final List<String> 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();
            final String myId = getId();
            host.getPool().submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        service.reconnect(myId);
                    } 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) {
            scheduler.cancelAllTasks();
        }
        if (callbacks != null) {
            callbacks.clear();
        }
        // 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)
    @JsonIgnore
    public final State getState() {
        return state;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.agent.AgentInterface#getScheduler()
     */
    @Override
    @Namespace("scheduler")
    @JsonIgnore
    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)
    @JsonIgnore
    public final AgentHost getAgentHost() {
        return host;

    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.agent.AgentInterface#getResultMonitorFactory()
     */
    @Override
    @Namespace("monitor")
    @JsonIgnore
    public final ResultMonitorFactoryInterface getResultMonitorFactory() {
        return monitorFactory;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.agent.AgentInterface#getEventsFactory()
     */
    @Override
    @Namespace("event")
    @JsonIgnore
    public final EventsInterface getEventsFactory() {
        return eventsFactory;
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.almende.eve.agent.AgentInterface#getFirstUrl()
     */
    @Override
    @Access(AccessType.PUBLIC)
    @JsonIgnore
    public URI getFirstUrl() {
        final List<String> 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)
    @JsonIgnore
    public List<Object> 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<JSONResponse> callback = new SyncCallback<JSONResponse>();
        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> T send(final URI url, final String method, final Object params, final Class<T> 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> 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> T send(final URI url, final String method, final Object params, final TypeUtil<T> 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> 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> 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> 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> T send(final URI url, final String method, final Class<T> 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> T send(final URI url, final String method, final TypeUtil<T> 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 extends AgentInterface> T createAgentProxy(final URI url, final Class<T> agentInterface) {
        final AgentProxyFactory pf = new AgentProxyFactory();
        return pf.genProxy(this, url, agentInterface);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.almende.eve.agent.AgentInterface#createAsyncAgentProxy(java.net.URI,
     * java.lang.Class)
     */
    @Override
    @Access(AccessType.UNAVAILABLE)
    @Deprecated
    public final <T extends AgentInterface> AsyncProxy<T> createAsyncAgentProxy(final URI url,
            final Class<T> 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 <T> void sendAsync(final URI url, final String method, final ObjectNode params,
            final AsyncCallback<T> callback, final Class<T> 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 <T> void sendAsync(final URI url, final String method, final ObjectNode params,
            final AsyncCallback<T> 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 <T> void sendAsync(final URI url, final String method, final ObjectNode params,
            final AsyncCallback<T> 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 <T> void sendAsync(final URI url, final JSONRequest request, final AsyncCallback<T> callback,
            final Class<T> 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 <T> void sendAsync(final URI url, final JSONRequest request, final AsyncCallback<T> 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 <T> void sendAsync(final URI url, final JSONRequest request, final AsyncCallback<T> 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<JSONResponse> responseCallback = new AsyncCallback<JSONResponse>() {
            @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)
    @JsonIgnore
    public List<String> getUrls() {
        final List<String> urls = new ArrayList<String>();
        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
    @JsonIgnore
    public <T> T getRef(final TypedKey<T> 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 <T> void putRef(final TypedKey<T> 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<String, Object> data = new HashMap<String, Object>();
        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) {
                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<Object[]>(AgentSignal.INVOKE, signalData));

                            final JSONResponse response = JSONRPC.invoke(me, request, params, me);

                            signalAgent(new AgentSignal<JSONResponse>(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<JSONResponse> callback = callbacks.pull(id);
                    if (callback != null) {
                        host.getPool().execute(new Runnable() {
                            @Override
                            public void run() {
                                signalAgent(new AgentSignal<JSONResponse>(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) {
            // 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);
            LOG.log(Level.WARNING, "Exception in receiving message", jsonError);

            final JSONResponse response = new JSONResponse(jsonError);
            response.setId(id);
            signalAgent(new AgentSignal<JSONResponse>(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<JSONResponse> callback,
            final String tag) throws IOException {
        if (msg instanceof JSONMessage) {
            signalAgent(new AgentSignal<JSONMessage>(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);
    }
}