/*
* Copyright: Almende B.V. (2014), Rotterdam, The Netherlands
* License: The Apache Software License, Version 2.0
*/
package com.almende.eve.agent;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.DateTime;
import com.almende.eve.protocol.jsonrpc.JSONRpcProtocol;
import com.almende.eve.protocol.jsonrpc.annotation.Access;
import com.almende.eve.protocol.jsonrpc.annotation.AccessType;
import com.almende.eve.protocol.jsonrpc.annotation.Name;
import com.almende.eve.protocol.jsonrpc.annotation.Optional;
import com.almende.eve.protocol.jsonrpc.annotation.RequestId;
import com.almende.eve.protocol.jsonrpc.annotation.Sender;
import com.almende.eve.protocol.jsonrpc.formats.JSONRequest;
import com.almende.eve.protocol.jsonrpc.formats.Params;
import com.almende.eve.scheduling.Scheduler;
import com.almende.util.TypeUtil;
import com.almende.util.callback.AsyncCallback;
import com.almende.util.callback.SyncCallback;
import com.almende.util.jackson.JOM;
import com.almende.util.uuid.UUID;
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 Class Agent, wraps various convenience methods around AgentCore.
*/
@Access(AccessType.UNAVAILABLE)
public class Agent extends AgentCore implements AgentInterface {
private static final Logger LOG = Logger.getLogger(Agent.class.getName());
/**
* Instantiates a new agent.
*/
public Agent() {
super();
}
/**
* Instantiates a new agent with given configuration. For configuration
* options check AgentConfig.
*
* @see AgentConfig
* @param config
* A JSON tree containing the configuration for this agent
*/
public Agent(final ObjectNode config) {
super(config);
}
/**
* Instantiates a new agent, with given id and configuration.
*
* @see AgentConfig
* @param agentId
* the new agent id
* @param config
* A JSON tree containing the configuration for this agent
*/
public Agent(final String agentId, ObjectNode config) {
super(agentId, config);
}
@Access(AccessType.PUBLIC)
@Override
public String getType() {
return this.getClass().getName();
}
@Access(AccessType.PUBLIC)
@Override
@JsonIgnore
public List<URI> getUrls() {
return caller.getSenderUrls();
}
@Access(AccessType.PUBLIC)
@Override
@JsonIgnore
public URI getUrlByScheme(final @Name("scheme") String scheme) {
return caller.getSenderUrlByScheme(scheme);
}
@Access(AccessType.PUBLIC)
@Override
@JsonIgnore
public ObjectNode getMethods() {
// TODO: find a different way to get this list. (maybe loop over all
// protocols, let each JSONRpcProtocol add methods.
return ((JSONRpcProtocol) getProtocolStack().getLast()).getMethods();
}
/**
* Send JSON-RPC notification, expecting no response.
*
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected void call(final URI url, final String method,
final ObjectNode params) throws IOException {
caller.call(url, method, params);
}
/**
* Send JSON-RPC notification, expecting no response.
*
* @param url
* the address of the other agent
* @param request
* the actual RPC request
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected void call(final URI url, final JSONRequest request)
throws IOException {
caller.call(url, request);
}
/**
* Send async, expecting a response through the given callback.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param callback
* A callback with the expected result type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> void call(final URI url, final String method,
final ObjectNode params, final AsyncCallback<T> callback)
throws IOException {
caller.call(url, method, params, callback);
}
/**
* Send async, expecting a response through the given callback.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param callback
* A callback with the expected result type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> void call(final URI url, final Method method,
final Object[] params, final AsyncCallback<T> callback)
throws IOException {
caller.call(url, method, params, callback);
}
/**
* Send an asynchronous request to multiple agents. This method calls the
* given callback with a map of results, after the final agent
* returns or reaches its timeout.
*
* @param <T>
* the generic type of the result, controlled by the return
* value.
* @param urls
* the addresses of the other agents
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param callback
* After the final request returns, the callback's onSuccess is
* called with an unmodifiable map, mapping the remote address to
* its results. Failure to get a result from a peer will lead to
* null values for that peer.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> void callMulti(final List<URI> urls, final String method,
final ObjectNode params, final AsyncCallback<Map<URI, T>> callback)
throws IOException {
final Map<URI, T> result = new ConcurrentHashMap<URI, T>(urls.size());
final Boolean[] done = new Boolean[] { false };
for (final URI peer : urls) {
final JSONRequest request = new JSONRequest(method, params,
new AsyncCallback<T>() {
private void checkRes() {
synchronized (done) {
if (!done[0] && result.size() == urls.size()) {
callback.onSuccess(Collections
.unmodifiableMap(result));
done[0] = true;
}
}
}
@Override
public void onSuccess(T res) {
result.put(peer, res);
checkRes();
}
@Override
public void onFailure(Exception exception) {
LOG.log(Level.WARNING,
"CallMulti: Failed to call peer:" + peer,
exception);
result.put(peer, null);
checkRes();
}
});
caller.call(peer, request);
}
}
/**
* Send synchronous request, waiting for a response.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param type
* the expected result type, in the form of a Java Type.
* @return the result, cast/converted to the given type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> T callSync(final URI url, final String method,
final ObjectNode params, final Type type) throws IOException {
return caller.callSync(url, method, params, type);
}
/**
* Send synchronous request, waiting for a response.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param type
* the expected result type, in the form of a Jackson JavaType.
* @return the result, cast/converted to the given type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> T callSync(final URI url, final String method,
final ObjectNode params, final JavaType type) throws IOException {
return caller.callSync(url, method, params, type);
}
/**
* Send synchronous request, waiting for a response.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param clazz
* the expected result type, in the form of a class.
* @return the result, cast/converted to the given type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> T callSync(final URI url, final String method,
final ObjectNode params, final Class<T> clazz) throws IOException {
return caller.callSync(url, method, params, clazz);
}
/**
* Send synchronous request, waiting for a response.
*
* @param <T>
* the generic type of the result, controlled by the TypeUtil
* injector.
* @param url
* the address of the other agent
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @param type
* the expected result type, in the form of a TypeUtil injector.
* @return the result, cast/converted to the given type.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> T callSync(final URI url, final String method,
final ObjectNode params, final TypeUtil<T> type) throws IOException {
return caller.callSync(url, method, params, type);
}
/**
* Send synchronous request to multiple agents, waiting for a response from
* each. This method returns with a map of results, after the final agent
* return or timeouts. <br>
* <br>
* <i>Note: this method will block the calling thread, in such a way that
* protocols that depend on single threaded agents (e.g. inbox
* protocol and Simulation inbox protocol) will not be able to detect this.
* In those cases you will have to sequentially run multiple synchronous
* calls in some loop, this method will not work in that case.</i>
*
* @param <T>
* the generic type of the result, controlled by the return
* value.
* @param urls
* the addresses of the other agents
* @param method
* the remote RPC method
* @param params
* the remote RPC method's params
* @return An unmodifiable map, mapping the remote address to its results.
* Failure to get a result will lead to null values for the given
* peer.
* @throws IOException
* Signals that an I/O exception has occurred.
*/
protected <T> Map<URI, T> callMultiSync(final List<URI> urls,
final String method, final ObjectNode params) throws IOException {
final SyncCallback<Map<URI, T>> callback = new SyncCallback<Map<URI, T>>();
callMulti(urls, method, params, callback);
try {
return callback.get();
} catch (final Exception e) {
throw new IOException(e);
}
}
/**
* Schedule a local RPC call after the specified delay in milliseconds.
*
* @param request
* the RPC request
* @param delay
* the delay
* @return the triggerId of this scheduled task (for cancelling)
*/
@Access(AccessType.UNAVAILABLE)
protected String schedule(final JSONRequest request, final long delay) {
return schedule(request, getScheduler().nowDateTime().plus(delay));
}
/**
* Schedule a local RPC call after the specified delay in milliseconds.
*
* @param request
* the RPC request
* @param delay
* the delay
* @return the triggerId of this scheduled task (for cancelling)
*/
@Access(AccessType.UNAVAILABLE)
protected String schedule(final JSONRequest request, final int delay) {
return schedule(request, getScheduler().nowDateTime().plus(delay));
}
/**
* Schedule an local RPC call at a specified due time.
*
* @param method
* the local RPC method
* @param params
* the local RPC method's params
* @param due
* the due time
* @return the string
*/
@Access(AccessType.UNAVAILABLE)
protected String schedule(final String method, final ObjectNode params,
final DateTime due) {
return schedule(new JSONRequest(method, params), due);
}
/**
* Schedule a local RPC call after the specified delay in milliseconds.
*
* @param method
* the local RPC method
* @param params
* the local RPC method's params
* @param delay
* the delay
* @return the triggerId of this scheduled task (for cancelling)
*/
@Access(AccessType.UNAVAILABLE)
protected String schedule(final String method, final ObjectNode params,
final int delay) {
return schedule(new JSONRequest(method, params), getScheduler()
.nowDateTime().plus(delay));
}
/**
* Schedule a local RPC call after the specified delay in milliseconds.
*
* @param method
* the local RPC method
* @param params
* the local RPC method's params
* @param delay
* the delay
* @return the triggerId of this scheduled task (for cancelling)
*/
@Access(AccessType.UNAVAILABLE)
protected String schedule(final String method, final ObjectNode params,
final long delay) {
return schedule(new JSONRequest(method, params), getScheduler()
.nowDateTime().plus(delay));
}
/**
* _schedule next.
*
* @param request
* the request
* @param interval
* the interval
* @param type
* the type
* @param timestamp
* the timestamp
* @param id
* the id
* @param senderUrl
* the sender url
*/
@Access(AccessType.SELF)
public void _scheduleNext(final @Name("request") JSONRequest request,
final @Name("interval") long interval,
final @Name("type") String type,
final @Optional @Name("timestamp") DateTime timestamp,
@RequestId JsonNode id, @Sender URI senderUrl) {
final Scheduler scheduler = getScheduler();
if (scheduler == null) {
return;
}
DateTime nextDue = scheduler.nowDateTime().plus(interval);
final Params params = new Params();
params.add("request", request);
params.add("interval", interval);
params.add("type", type);
if (timestamp != null) {
nextDue = timestamp.plus(interval);
params.add("timestamp", nextDue);
}
switch (type) {
case "sequential":
receive(request, senderUrl, null);
schedule(new JSONRequest(id, "_scheduleNext", params, null),
nextDue);
break;
default:
schedule(new JSONRequest(id, "_scheduleNext", params, null),
nextDue);
receive(request, senderUrl, null);
}
}
private String scheduleInt(final String method, final ObjectNode params,
final long interval, final String type) {
final Params parms = new Params();
parms.add("request", new JSONRequest(method, params));
parms.add("interval", interval);
parms.add("type", type);
return schedule(
new JSONRequest(JOM.createObjectNode().textNode(
new UUID().toString()), "_scheduleNext", parms, null),
getScheduler().nowDateTime().plus(interval));
}
/**
* Repetitive schedule a local RPC call at the specified interval in
* milliseconds.
*
* @param method
* the local RPC method
* @param params
* the local RPC method's params
* @param interval
* the interval in milliseconds
* @return the triggerID for cancellation
*/
@Access(AccessType.UNAVAILABLE)
protected String scheduleInterval(final String method,
final ObjectNode params, final long interval) {
return scheduleInt(method, params, interval, "parallel");
}
/**
* Repetitive schedule a local RPC call at the specified interval in
* milliseconds. This version only schedules the next interval after the
* earlier run has
* finished, preventing overlap between calls.
*
* @param method
* the method
* @param params
* the params
* @param interval
* the interval
* @return the triggerID for cancellation
*/
@Access(AccessType.UNAVAILABLE)
protected String scheduleIntervalSequential(final String method,
final ObjectNode params, final long interval) {
return scheduleInt(method, params, interval, "sequential");
}
/**
* Repetitive schedule a local RPC call at the specified interval in
* milliseconds.
* This version schedules the next interval without allowing drift, the next
* scheduled due time is an exact interval after the former.
* <b>If the start timestamp is further in the past than one interval, this
* will run for each interval, quickly after each other to catch up!</b>
*
* @param method
* the method
* @param params
* the params
* @param interval
* the interval
* @param start
* the start timestamp, on which the first interval is based.
* @return the triggerID for cancellation
*/
@Access(AccessType.UNAVAILABLE)
protected String scheduleIntervalPrecize(final String method,
final ObjectNode params, final long interval, final DateTime start) {
final Params parms = new Params();
parms.add("request", new JSONRequest(method, params));
parms.add("interval", interval);
parms.add("timestamp", start);
parms.add("type", "precize");
return schedule(
new JSONRequest(JOM.createObjectNode().textNode(
new UUID().toString()), "_scheduleNext", parms, null),
getScheduler().nowDateTime().plus(interval));
}
}