/**
*
* @author greg (at) myrobotlab.org
*
* This file is part of MyRobotLab (http://myrobotlab.org).
*
* MyRobotLab is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version (subject to the "Classpath" exception
* as provided in the LICENSE.txt file that accompanied this code).
*
* MyRobotLab is distributed in the hope that it will be useful or fun,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* All libraries in thirdParty bundle are subject to their own license
* requirements - please refer to http://myrobotlab.org/libraries for
* details.
*
* Enjoy !
*
* */
package org.myrobotlab.framework;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import org.myrobotlab.cache.LRUMethodCache;
import org.myrobotlab.codec.CodecUtils;
import org.myrobotlab.codec.Recorder;
import org.myrobotlab.io.FileIO;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.math.MathUtils;
import org.myrobotlab.net.CommunicationManager;
import org.myrobotlab.net.Heartbeat;
import org.myrobotlab.service.Runtime;
import org.myrobotlab.service.interfaces.AuthorizationProvider;
import org.myrobotlab.service.interfaces.CommunicationInterface;
import org.myrobotlab.service.interfaces.Invoker;
import org.myrobotlab.service.interfaces.NameProvider;
import org.myrobotlab.service.interfaces.QueueReporter;
import org.myrobotlab.service.interfaces.ServiceInterface;
import org.slf4j.Logger;
/**
*
* Service is the base of the MyRobotLab Service Oriented Architecture. All
* meaningful Services derive from the Service class. There is a
* _TemplateService.java in the org.myrobotlab.service package. This can be used
* as a very fast template for creating new Services. Each Service begins with
* two threads One is for the "OutBox" this delivers messages out of the
* Service. The other is the "InBox" thread which processes all incoming
* messages.
*
*/
public abstract class Service extends MessageService implements Runnable, Serializable, ServiceInterface, Invoker, QueueReporter {
// FIXME upgrade to ScheduledExecutorService
// http://howtodoinjava.com/2015/03/25/task-scheduling-with-executors-scheduledthreadpoolexecutor-example/
protected class Task extends TimerTask {
String taskName;
Message msg;
long interval = 0;
public Task(String taskName, long interval, String name, String method, Object... data) {
this.msg = createMessage(name, method, data);
this.interval = interval;
this.taskName = taskName;
}
public Task(Task s) {
this.msg = s.msg;
this.interval = s.interval;
this.taskName = s.taskName;
}
@Override
public void run() {
info("task %s running - next run %s", taskName, MathUtils.msToString(interval));
getInbox().add(msg);
if (interval > 0) {
Task t = new Task(this);
// clear history list - becomes "new" message
t.msg.historyList.clear();
Timer timer = tasks.get(taskName);
if (timer != null) {
//timer = new Timer(String.format("%s.timer", getName()));
timer.schedule(t, interval);
}
}
}
}
/**
* contains all the meta data about the service - pulled from the static
* method getMetaData() each instance will call the method and populate the
* data for an instance
*
*/
ServiceType serviceType;
/**
* a radix-tree of data -"DNA" Description of Neighboring Automata ;)
*/
// transient static public final Index<ServiceReservation> dna = new
// Index<ServiceReservation>();
transient static public final TreeMap<String, ServiceReservation> dna = new TreeMap<String, ServiceReservation>();
private static final long serialVersionUID = 1L;
transient public final static Logger log = LoggerFactory.getLogger(Service.class);
/**
* key into Runtime's hosts of ServiceEnvironments mrlscheme://[gateway
* name]/scheme://key for gateway mrl://gateway/xmpp://incubator incubator if
* host == null the service is local
*/
private URI instanceId = null;
private String name;
private String simpleName; // used in gson encoding for getSimpleName()
private String serviceClass;
private boolean isRunning = false;
transient protected Thread thisThread = null;
transient protected Inbox inbox = null;
transient Timer timer = null;
/**
* a more capable task handler
*/
transient HashMap<String, Timer> tasks = new HashMap<String, Timer>();
protected boolean allowDisplay = true;
public final static String cfgDir = FileIO.getCfgDir();
// transient private Serializer serializer = new Persister();
transient protected Set<String> methodSet;
transient protected SimpleDateFormat TSFormatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");
transient protected Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
// recordings
// static private boolean isRecording = false;
static private Recorder recorder = null;
transient public final String MESSAGE_RECORDING_FORMAT_XML = "MESSAGE_RECORDING_FORMAT_XML";
transient public final String MESSAGE_RECORDING_FORMAT_BINARY = "MESSAGE_RECORDING_FORMAT_BINARY";
// FIXME SecurityProvider
protected static AuthorizationProvider security = null;
private Status lastError = null;
// FIXME - remove out of Peers, have Peers use this logic & pass in its
// index
/**
* This method is used to add new dna to dna which is passed in. Typically
* this method is called by mergePeerDNA - which sends the global static dna
* reference in plus a class name of a class which is currently being
* constructed
*
* @param myDNA
* - dna which information will be added to
* @param myKey
* - key (name) instance of the class currently under construction
* @param serviceClass
* - type of class being constructed
* @param comment
* - added comment
*/
static public void buildDNA(String myKey, String serviceClass, String comment) {
String fullClassName = CodecUtils.getServiceType(serviceClass);
try {
/// PUSH PEER KEYS IN - IF SOMETHING ALREADY EXISTS LEAVE IT
//// ------- this is static data which will never change
//// ----------------------
// - the 'key' structure will never change - however the service
//// reservations within
// - the dna CAN change - so the order of operations
// get the static keys
// query on keys
// if reservations exist then merge in data
Class<?> theClass = Class.forName(fullClassName);
// getPeers
Method method = theClass.getMethod("getMetaData");
// Peers peers = (Peers) method.invoke(null, new Object[] { myKey
// });
// ServiceType st = (ServiceType) method.invoke(null, new Object[] {
// myKey });
ServiceType st = (ServiceType) method.invoke(null);
// Index<ServiceReservation> peerDNA = peers.getDNA();
// Index<ServiceReservation> peerDNA =
// st.getPeers();//peers.getDNA();
TreeMap<String, ServiceReservation> peers = st.getPeers();
// getMetaData
log.info(String.format("processing %s.getPeers(%s) will process %d peers", serviceClass, myKey, peers.size()));
// Two loops are necessary - because recursion should not start
// until the entire level
// of peers has been entered into the tree - this will build the
// index level by level
// versus depth first - necessary because the "upper" levels need to
// process first
// to influence the lower levels
for (ServiceReservation sr : peers.values()) {
// FIXME A BIT LAME - THE Index.crawlForData should be returning
// Set<Map.Entry<?>>
String peerKey = sr.key;
String fullKey = String.format("%s.%s", myKey, peerKey);
ServiceReservation reservation = dna.get(fullKey);
log.info(String.format("(%s) - [%s]", fullKey, sr.actualName));
if (reservation == null) {
// NO PREVIOUS DEFINITION - reservation is null !!
// so we set actualName to the key - which is
// (currentContext).(actualName)
sr.actualName = fullKey;
log.info(String.format("dna adding new key %s %s %s %s", fullKey, sr.actualName, sr.fullTypeName, comment));
dna.put(fullKey, sr);
} else {
log.info(String.format("dna collision - replacing null values !!! %s", fullKey));
StringBuffer sb = new StringBuffer();
if (reservation.actualName == null) {
sb.append(String.format(" updating actualName to %s ", sr.actualName));
reservation.actualName = sr.actualName;
}
if (reservation.fullTypeName == null) {
// FIXME check for dot ?
sb.append(String.format("updating peerType to %s ", sr.fullTypeName));
reservation.fullTypeName = sr.fullTypeName;
}
if (reservation.comment == null) {
sb.append(String.format(" updating comment to %s ", comment));
reservation.comment = sr.comment;
}
log.info(sb.toString());
buildDNA(Peers.getPeerKey(myKey, sr.key), sr.fullTypeName, sr.comment);
}
}
// recursion loop
/*
* for (int x = 0; x < peers.size(); ++x) { ServiceReservation peersr =
* peers.get(x); buildDNA(myDNA, Peers.getPeerKey(myKey, peersr.key),
* peersr.fullTypeName, peersr.comment); }
*/
} catch (Exception e) {
Logging.logError(e);
log.debug(String.format("%s does not have a getMetaData ", fullClassName));
}
}
static public String getDnaString() {
StringBuffer sb = new StringBuffer();
for (Map.Entry<String, ServiceReservation> entry : dna.entrySet()) {
String key = entry.getKey();
ServiceReservation value = entry.getValue();
sb.append(String.format("%s=%s", key, value.toString()));
}
return sb.toString();
}
/**
* Recursively builds Peer type information - which is not instance specific.
* Which means it will not prefix any of the branches with a instance name
*
* @param serviceClass
* @return
*/
static public TreeMap<String, ServiceReservation> buildDNA(String serviceClass) {
return buildDNA("", serviceClass);
}
static public TreeMap<String, ServiceReservation> buildDNA(String myKey, String serviceClass) {
buildDNA(myKey, serviceClass, null);
log.info("{}", dna);
return dna;
}
/**
* copyShallowFrom is used to help maintain state information with
*/
public static Object copyShallowFrom(Object target, Object source) {
if (target == source) { // data is myself - operating on local copy
return target;
}
Class<?> sourceClass = source.getClass();
Class<?> targetClass = target.getClass();
Field fields[] = sourceClass.getDeclaredFields();
for (int j = 0, m = fields.length; j < m; j++) {
try {
Field f = fields[j];
int modifiers = f.getModifiers();
// if (Modifier.isPublic(mod)
// !(Modifier.isPublic(f.getModifiers())
// Hmmm JSON mappers do hacks to get by
// IllegalAccessExceptions.... Hmmmmm
if (!Modifier.isPublic(f.getModifiers()) || f.getName().equals("log") || Modifier.isTransient(f.getModifiers()) || Modifier.isStatic(f.getModifiers())
|| Modifier.isFinal(f.getModifiers())) {
log.debug(String.format("skipping %s", f.getName()));
continue;
}
Type t = f.getType();
log.info(String.format("setting %s", f.getName()));
/*
* if (Modifier.isStatic(f.getModifiers()) ||
* Modifier.isFinal(f.getModifiers())) { continue; }
*/
if (t.equals(java.lang.Boolean.TYPE)) {
targetClass.getDeclaredField(f.getName()).setBoolean(target, f.getBoolean(source));
} else if (t.equals(java.lang.Character.TYPE)) {
targetClass.getDeclaredField(f.getName()).setChar(target, f.getChar(source));
} else if (t.equals(java.lang.Byte.TYPE)) {
targetClass.getDeclaredField(f.getName()).setByte(target, f.getByte(source));
} else if (t.equals(java.lang.Short.TYPE)) {
targetClass.getDeclaredField(f.getName()).setShort(target, f.getShort(source));
} else if (t.equals(java.lang.Integer.TYPE)) {
targetClass.getDeclaredField(f.getName()).setInt(target, f.getInt(source));
} else if (t.equals(java.lang.Long.TYPE)) {
targetClass.getDeclaredField(f.getName()).setLong(target, f.getLong(source));
} else if (t.equals(java.lang.Float.TYPE)) {
targetClass.getDeclaredField(f.getName()).setFloat(target, f.getFloat(source));
} else if (t.equals(java.lang.Double.TYPE)) {
targetClass.getDeclaredField(f.getName()).setDouble(target, f.getDouble(source));
} else {
log.info(String.format("setting reference to remote object %s", f.getName()));
targetClass.getDeclaredField(f.getName()).set(target, f.get(source));
}
} catch (Exception e) {
Logging.logError(e);
}
}
return target;
}
/**
* Create the reserved peer service if it has not already been created
*
* @param key
* unique identification of the peer service used by the composite
* @return true if successfully created
*/
static public ServiceInterface createRootReserved(String key) {
log.info(String.format("createReserved %s ", key));
ServiceReservation node = dna.get(key);
if (node != null) {
ServiceReservation r = dna.get(key);
return Runtime.create(r.actualName, r.fullTypeName);
}
log.error(String.format("createRootReserved can not create %s", key));
return null;
}
/**
*
* @return
*/
public static String getCfgDir() {
return cfgDir;
}
static public TreeMap<String, ServiceReservation> getDNA() {
return dna;
}
/**
*
* @param inHost
* @return
*/
public static String getHostName(final String inHost) {
if (inHost != null)
return inHost;
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
log.error("could not find host, host is null or empty !");
}
return "localhost"; // no network - still can't be null // chumby
}
/**
*
* @param className
* @param methodName
* @param params
* @return
*/
public static String getMethodToolTip(String className, String methodName, Class<?>[] params) {
Class<?> c;
Method m;
ToolTip tip = null;
try {
c = Class.forName(className);
m = c.getMethod(methodName, params);
tip = m.getAnnotation(ToolTip.class);
} catch (SecurityException e) {
logException(e);
} catch (NoSuchMethodException e) {
logException(e);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
logException(e);
}
if (tip == null) {
return null;
}
return tip.value();
}
/**
*
* @param e
*/
public final static void logException(final Throwable e) {
log.error(stackToString(e));
}
static public void logTimeEnable(Boolean b) {
Logging.logTimeEnable(b);
}
/**
* This method will merge in the requested peer dna into the final global dna
* - from which it will be accessible for create methods
*
* template merge with existing dna
*
* @param myKey
* @param className
*/
public void mergePeerDNA(String myKey, String className) {
if (myKey.equals("c01")) {
log.info("blah");
}
if (serviceType != null) {
TreeMap<String, ServiceReservation> peers = serviceType.getPeers();
for (Entry<String, ServiceReservation> entry : peers.entrySet()) {
String templateKey = entry.getKey();
ServiceReservation template = entry.getValue();
// build full key with our instance key + the peer template
// defined in getMetaData
String fullKey = String.format("%s.%s", myKey, templateKey);
// test dna - if something already exists then LEAVE IT !!!
// if it does not exist then inject it
if (!dna.containsKey(fullKey)) {
// full key does not exist - so we put this reservation in
// for further definition
// since there was no previous definition of this service -
// we will modify
// the actual name so it is correct with the fullKey (prefix
// of the context)
// this is a template being merged in
// if actualName == key then there is no re-mapping and both
// get prefixed !
// if actualName != key then there is a re-map
// create new service reservation with fullkey to put into
// dna
// do we prefix the actual name !?!?!?!?!?
ServiceReservation sr = null;
if (template.key.equals(template.actualName) && !template.isRoot) {
sr = new ServiceReservation(fullKey, template.fullTypeName, template.comment);
} else {
// COLLISION WITH CUSTOM KEY - WE ARE MOVING DNA !!!
String actualName = null;
if (template.isRoot) {
// moving to root
actualName = template.actualName;
} else {
// We Prefix it if its not a root !
actualName = String.format("%s.%s", myKey, template.actualName);
}
sr = new ServiceReservation(fullKey, actualName, template.fullTypeName, template.comment, template.isRoot);
// we have to recursively move things if we moved a root
// of some complex peer
movePeerDNA(fullKey, actualName, template.fullTypeName, sr.comment);
}
dna.put(fullKey, sr);
} else {
log.info("found reservation {} {}", fullKey, entry.getValue());
}
}
}
// buildDNA(myKey, className, "merged dna");
log.debug("merged dna \n{}", dna);
}
/**
* a method to recursively move all peer children of this server
*
* @param myKey
* @param actualName
* @param className
* @param comment
*/
public void movePeerDNA(String myKey, String actualName, String fullTypeName, String comment) {
ServiceType meta = getMetaData(fullTypeName);
if (meta != null) {
TreeMap<String, ServiceReservation> peers = meta.getPeers();
for (Entry<String, ServiceReservation> reservation : peers.entrySet()) {
String templateKey = reservation.getKey();
// build full key with our instance key + the peer template
// defined in getMetaData
String fullKey = String.format("%s.%s", myKey, templateKey);
String movedActual = String.format("%s.%s", actualName, templateKey);
ServiceReservation templateSr = reservation.getValue();
ServiceReservation sr = new ServiceReservation(movedActual, movedActual, templateSr.fullTypeName, templateSr.comment);
dna.put(movedActual, sr);
// recurse to process children
movePeerDNA(fullKey, movedActual, templateSr.fullTypeName, templateSr.comment);
}
}
}
/**
* Reserves a name for a root level Service. allows modifications to the
* reservation map at the highest level
*
* @param key
* @param simpleTypeName
* @param comment
*/
static public void reserveRoot(String key, String simpleTypeName, String comment) {
// strip delimeter out if put in by key
// String actualName = key.replace(".", "");
reserveRoot(key, key, simpleTypeName, comment);
}
static public void reserveRoot(String key, String actualName, String simpleTypeName, String comment) {
log.info(String.format("reserved key %s -> %s %s %s", key, actualName, simpleTypeName, comment));
dna.put(key, new ServiceReservation(key, actualName, simpleTypeName, comment));
}
/**
* basic useful reset of a peer before service is created
*
* @param string
* @param string2
*/
public void setPeer(String peerName, String peerType) {
String fullKey = String.format("%s.%s", getName(), peerName);
ServiceReservation sr = new ServiceReservation(fullKey, peerName, peerType, null);
dna.put(fullKey, sr);
}
/**
* This method re-binds the key to another name. An example of where this
* would be used is within Tracking there is an Servo service named "x",
* however it may be desired to bind this to an already existing service named
* "pan" in a pan/tilt system
*
* @param key
* key internal name
* @param newName
* new name of bound peer service
* @return true if re-binding took place
*/
static public boolean reserveRootAs(String key, String newName) {
ServiceReservation genome = dna.get(key);
if (genome == null) {
// FIXME - this is a BAD KEY !!! into the ServiceReservation (I
// think :P) - another
// reason to get rid of it !!
dna.put(key, new ServiceReservation(key, newName, null, null));
} else {
genome.actualName = newName;
}
return true;
}
public static boolean setSecurityProvider(AuthorizationProvider provider) {
if (security != null) {
log.error("security provider is already set - it can not be unset .. THAT IS THE LAW !!!");
return false;
}
security = provider;
return true;
}
/**
* sleep without the throw
*
* @param millis
*/
public static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
logException(e);
}
}
/**
*
* @param e
* @return
*/
public final static String stackToString(final Throwable e) {
StringWriter sw;
try {
sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
} catch (Exception e2) {
return "bad stackToString";
}
return "------\r\n" + sw.toString() + "------\r\n";
}
// FIXME - make a static initialization part !!!
/**
*
* @param reservedKey
* @param serviceClass
* @param inHost
*/
public Service(String reservedKey) {
super(reservedKey);
serviceClass = this.getClass().getCanonicalName();
simpleName = this.getClass().getSimpleName();
try {
serviceType = getMetaData(this.getClass().getCanonicalName());
} catch (Exception e) {
Logging.logError(e);
}
// FIXME - this is 'sort-of' static :P
if (methodSet == null) {
methodSet = getMessageSet();
}
// a "safety" if Service was created by new Service(name)
// we still want the local Runtime running
if (!Runtime.isRuntime(this)) {
Runtime.getInstance();
}
// merge all our peer keys into the dna
// so that reservations are set with actual names if
// necessary
mergePeerDNA(reservedKey, serviceClass);
// see if incoming key is my "actual" name
ServiceReservation sr = dna.get(reservedKey);
if (sr != null) {
log.info(String.format("found reservation exchanging reservedKey %s for actual name %s", reservedKey, sr.actualName));
name = sr.actualName;
} else {
name = reservedKey;
}
// keep MessageService name in sync
// this.timer = new Timer(String.format("%s_timer", name)); FIXME -
// re-implement but only create if there is a task!!
this.inbox = new Inbox(name);
this.outbox = new Outbox(this);
cm = new CommunicationManager(name);
this.outbox.setCommunicationManager(cm);
TSFormatter.setCalendar(cal);
load();
Runtime.register(this, null);
}
public void addListener(MRLListener listener) {
addListener(listener.topicMethod, listener.callbackName, listener.callbackMethod);
}
/**
* adds a MRL message listener to this service this is the result of a
* "subscribe" from a different service FIXME !! - implement with HashMap or
* HashSet .. WHY ArrayList ???
*
* @param topicMethod
* - method when called, it's return will be sent to the
* callbackName/calbackMethod
* @param callbackName
* - name of the service to send return message to
* @param callbackMethod
* - name of the method to send return data to
*/
public void addListener(String topicMethod, String callbackName, String callbackMethod) {
MRLListener listener = new MRLListener(topicMethod, callbackName, callbackMethod);
if (outbox.notifyList.containsKey(listener.topicMethod.toString())) {
// iterate through all looking for duplicate
boolean found = false;
ArrayList<MRLListener> nes = outbox.notifyList.get(listener.topicMethod.toString());
for (int i = 0; i < nes.size(); ++i) {
MRLListener entry = nes.get(i);
if (entry.equals(listener)) {
log.debug(String.format("attempting to add duplicate MRLListener %s", listener));
found = true;
break;
}
}
if (!found) {
log.debug(String.format("adding addListener from %s.%s to %s.%s", this.getName(), listener.topicMethod, listener.callbackName, listener.callbackMethod));
nes.add(listener);
}
} else {
ArrayList<MRLListener> notifyList = new ArrayList<MRLListener>();
notifyList.add(listener);
log.debug(String.format("adding addListener from %s.%s to %s.%s", this.getName(), listener.topicMethod, listener.callbackName, listener.callbackMethod));
outbox.notifyList.put(listener.topicMethod.toString(), notifyList);
}
}
// -------------------------------- new createPeer end
// -----------------------------------
public void addTask(int interval, String method, Object... params) {
addTask(method, interval, method, params);
}
/**
* a stronger bigger better task handler !
*
* @param name
*/
public void addTask(String name, int interval, String method, Object... params) {
Timer timer = new Timer(String.format("%s.timer", String.format("%s.%s", getName(), name)));
Task task = new Task(name, interval, getName(), method, params);
timer.schedule(task, 0);
tasks.put(name, timer);
}
public HashMap<String, Timer> getTasks() {
return tasks;
}
public void purgeTask(String taskName) {
if (tasks.containsKey(taskName)) {
Timer timer = tasks.get(taskName);
if (timer != null) {
try {
timer.cancel();
timer.purge();
timer = null;
} catch (Exception e) {
log.info(e.getMessage());
}
}
} else {
log.warn("purgeTask - task {} does not exist", taskName);
}
}
public void purgeTasks() {
for (String taskName : tasks.keySet()) {
Timer timer = tasks.get(taskName);
if (timer != null) {
try {
timer.cancel();
timer.purge();
timer = null;
} catch (Exception e) {
log.info(e.getMessage());
}
}
}
tasks.clear();
}
public boolean allowDisplay() {
return allowDisplay;
}
public void allowDisplay(Boolean b) {
allowDisplay = b;
}
// new state functions begin --------------------------
public void broadcastState() {
invoke("publishState");
}
public String clearLastError() {
String le = lastError.toString();
lastError = null;
return le;
}
public void close(Writer w) {
if (w == null) {
return;
}
try {
w.flush();
} catch (Exception e) {
Logging.logError(e);
} finally {
try {
w.close();
} catch (Exception e) {
// don't really care
}
}
}
public synchronized ServiceInterface createPeer(String reservedKey) {
String fullkey = Peers.getPeerKey(getName(), reservedKey);
ServiceReservation sr = dna.get(fullkey);
if (sr == null) {
error("can not create peer from reservedkey %s - no type definition !", fullkey);
return null;
}
// WOW THIS WAS A NASTY BUG !!!
// return Runtime.create(fullkey, sr.fullTypeName);
return Runtime.create(sr.actualName, sr.fullTypeName);
}
// -------------------------------- new createPeer begin
// -----------------------------------
public synchronized ServiceInterface createPeer(String reservedKey, String defaultType) {
return Runtime.create(Peers.getPeerKey(getName(), reservedKey), defaultType);
}
/**
* framework interface for Services which can display themselves most will not
* implement this method. keeps the framework display type agnostic
*/
public void display() {
}
/**
* ` called typically from a remote system When 2 MRL instances are connected
* they contain serialized non running Service in a registry, which is
* maintained by the Runtime. The data can be stale.
*
* Messages are sometimes sent (often in the gui) which prompt the remote
* service to "broadcastState" a new serialized snapshot is broadcast to all
* subscribed methods, but there is no guarantee that the registry is updated
*
* This method will update the registry, additionally it will block until the
* refresh response comes back
*
* @return
*/
public Heartbeat echoHeartbeat(Heartbeat pulse) {
return pulse;
}
/**
*
* @return
*/
public CommunicationInterface getComm() {
return cm;
}
@Override
public String[] getDeclaredMethodNames() {
Method[] methods = getDeclaredMethods();
String[] ret = new String[methods.length];
log.info(String.format("getDeclaredMethodNames loading %d non-sub-routable methods", methods.length));
for (int i = 0; i < methods.length; ++i) {
ret[i] = methods[i].getName();
}
return ret;
}
@Override
public Method[] getDeclaredMethods() {
return this.getClass().getDeclaredMethods();
}
/**
*
* @return
*/
public Inbox getInbox() {
return inbox;
}
@Override
public URI getInstanceId() {
return instanceId;
}
/**
*
* @return
*/
public String getIntanceName() {
return name;
}
public Status getLastError() {
return lastError;
}
// FIXME - use the method cache
public HashSet<String> getMessageSet() {
HashSet<String> ret = new HashSet<String>();
Method[] methods = getMethods();
log.info(String.format("getMessageSet loading %d non-sub-routable methods", methods.length));
for (int i = 0; i < methods.length; ++i) {
ret.add(methods[i].getName());
}
return ret;
}
// FIXME - should be a "Set" not an array !
@Override
public String[] getMethodNames() {
Method[] methods = getMethods();
/*
* Set<String> m = new TreeSet<String>(); m.addAll(methods);
*/
String[] ret = new String[methods.length];
log.info(String.format("getMethodNames loading %d non-sub-routable methods", methods.length));
for (int i = 0; i < methods.length; ++i) {
ret[i] = methods[i].getName();
}
Arrays.sort(ret);
return ret;
}
@Override
public Method[] getMethods() {
return this.getClass().getMethods();
}
/**
*
* @return
* @throws InterruptedException
*/
public Message getMsg() throws InterruptedException {
return inbox.getMsg();
}
/**
*
*/
@Override
public ArrayList<MRLListener> getNotifyList(String key) {
if (getOutbox() == null) {
// this is remote system - it has a null outbox, because its
// been serialized with a transient outbox
// and your in a skeleton
// use the runtime to send a message
@SuppressWarnings("unchecked")
// FIXME - parameters !
ArrayList<MRLListener> remote = (ArrayList<MRLListener>) Runtime.getInstance().sendBlocking(getName(), "getNotifyList", new Object[] { key });
return remote;
} else {
return getOutbox().notifyList.get(key);
}
}
/**
*
*/
@Override
public ArrayList<String> getNotifyListKeySet() {
ArrayList<String> ret = new ArrayList<String>();
if (getOutbox() == null) {
// this is remote system - it has a null outbox, because its
// been serialized with a transient outbox
// and your in a skeleton
// use the runtime to send a message
@SuppressWarnings("unchecked")
ArrayList<String> remote = (ArrayList<String>) Runtime.getInstance().sendBlocking(getName(), "getNotifyListKeySet");
return remote;
} else {
ret.addAll(getOutbox().notifyList.keySet());
}
return ret;
}
/**
*
* @return
*/
public Outbox getOutbox() {
return outbox;
}
public String getPeerKey(String key) {
return Peers.getPeerKey(getName(), key);
}
/**
* a default way to attach Services to other Services An example would be
* attaching a Motor to a MotorControl or a Speaking service (TTS) to a
* Listening service (STT) such that when the system is speaking it does not
* try to listen & act on its own speech (feedback loop)
*
* FIXME - the GUIService currently has attachGUI() and detachGUI() - these
* are to bind Services with their swing views/tab panels. It should be
* generalized to this attach method
*
* @param serviceName
* @return if successful
*
*/
public String getServiceResourceFile(String subpath) {
return FileIO.resourceToString(String.format("%s/%s", this.getSimpleName(), subpath));
}
@Override
public String getSimpleName() {
return simpleName;
}
/**
*
* @return
*/
public Thread getThisThread() {
return thisThread;
}
@Override
public String getType() {
return getClass().getCanonicalName();
}
/**
* returns if the Service has a display - this would be any Service who had a
* display system GUIService (Swing) would be an example, most Services would
* return false keeps the framework display type agnostic
*
* @return
*/
@Override
public boolean hasDisplay() {
return false;
}
public boolean hasError() {
return lastError != null;
}
// TODO Clock example - roles
// no - security (internal) Role - default access - ALLOW
// WebGui - public - no security header - default access DISALLOW +
// exception
// WebGui (remote in genera) - user / group ALLOW
/*
* private boolean hasAccess(Message msg) { // turn into single key ??? //
* type.name.method
*
* // check this type <-- not sure i want to support this
*
* // check this name & method // if any access limitations exist which might
* be applicable if (accessRules.containsKey(msg.name) ||
* accessRules.containsKey(String.format("%s.%s", msg.name, msg.method))) { //
* restricted service - check for authorization // Security service only
* provides authorization ? if (security == null) { return false; } else {
* return security.isAuthorized(msg); }
*
* }
*
* // invoke - SecurityException - log error return false; }
*/
@Override
public boolean hasPeers() {
try {
Class<?> theClass = Class.forName(serviceClass);
Method method = theClass.getMethod("getPeers", String.class);
} catch (Exception e) {
log.debug(String.format("%s does not have a getPeers", serviceClass));
return false;
}
return true;
}
public String help() {
return help("url", "declared");
}
public String help(String format, String level) {
StringBuffer sb = new StringBuffer();
Method[] methods = this.getClass().getDeclaredMethods();
TreeMap<String, Method> sorted = new TreeMap<String, Method>();
for (int i = 0; i < methods.length; ++i) {
Method m = methods[i];
sorted.put(m.getName(), m);
}
for (String key : sorted.keySet()) {
Method m = sorted.get(key);
sb.append("/").append(getName()).append("/").append(m.getName());
Class<?>[] types = m.getParameterTypes();
if (types != null) {
for (int j = 0; j < types.length; ++j) {
Class<?> c = types[j];
sb.append("/").append(c.getSimpleName());
}
}
sb.append("\n");
}
sb.append("\n");
return sb.toString();
}
/**
*
* @param msg
*/
@Override
public void in(Message msg) {
inbox.add(msg);
}
// BOXING - BEGIN --------------------------------------
/**
* This is where all messages are routed to and processed
*
* @param msg
* @return
*/
@Override
final public Object invoke(Message msg) {
Object retobj = null;
if (log.isDebugEnabled()) {
log.debug(String.format("--invoking %s.%s(%s) %s --", name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId));
}
// recently added - to support "nameless" messages - concept you may get
// a message at this point
// which does not belong to you - but is for a service in the same
// Process
// this is to support nameless Runtime messages but theoretically it
// could
// happen in other situations...
if (!name.equals(msg.name)) {
// wrong Service - get the correct one
return Runtime.getService(msg.name).invoke(msg);
}
// SECURITY -
// 0. allowing export - whether or not we'll allow services to be
// exported - based on Type or Name
// 1. we have firewall like rules where we can add inclusion and
// exclusion rules - based on Type or Name - Service Level - Method
// Level
// 2. authentication & authorization
// 3. transport mechanism (needs implementation on each type of remote
// Communicator e.g. XMPP RemoteAdapter WebGui etc...)
// check for access
// if access FAILS ! - check for authenticated access
// not needed "centrally" - instead will impement in Communicators
// which hand foriegn connections
// if (security == null || security.isAuthorized(msg)) {
// "local" invoke - you have a "real" reference
retobj = invokeOn(this, msg.method, msg.data);
// }
// retobject will be returned as another
// message
return retobj;
}
@Override
final public Object invoke(String method) {
return invokeOn(this, method, (Object[]) null);
}
@Override
final public Object invoke(String method, Object... params) {
return invokeOn(this, method, params);
}
/**
* the core working invoke method
*
* @param object
* @param method
* @param params
* @return return object
*/
@Override
final public Object invokeOn(Object obj, String method, Object... params) {
if (obj == null) {
log.error("invokeOn object is null");
return null;
}
Object retobj = null;
Class<?> c = null;
Class<?>[] paramTypes = null;
try {
c = obj.getClass();
if (params != null) {
paramTypes = new Class[params.length];
for (int i = 0; i < params.length; ++i) {
if (params[i] != null) {
paramTypes[i] = params[i].getClass();
} else {
paramTypes[i] = null;
}
}
}
Method meth = null;
// TODO - method cache map
// can not auto-box or downcast with this method - getMethod will
// return a "specific & exact" match based
// on parameter types - the thing is we may have a typed signature
// which will allow execution - but
// if so we need to search
// SECURITY - ??? can't be implemented here - need a full message
meth = c.getMethod(method, paramTypes); // getDeclaredMethod zod !!!
retobj = meth.invoke(obj, params);
// put return object onEvent
out(method, retobj);
} catch (NoSuchMethodException e) {
// cache key compute
// TODO: validate what "params.toString()" returns.
StringBuilder keyBuilder = new StringBuilder();
if (paramTypes != null) {
for (Object o : paramTypes) {
keyBuilder.append(o);
}
}
String methodCacheKey = c.toString() + "_" + keyBuilder.toString();
Method mC = LRUMethodCache.getInstance().getCacheEntry(methodCacheKey);
if (mC != null) {
// We found a cached hit! lets invoke on that.
try {
retobj = mC.invoke(obj, params);
// put return object onEvent
out(method, retobj);
// return
return retobj;
} catch (Exception e1) {
log.error(String.format("boom goes method %s", mC.getName()));
Logging.logError(e1);
}
}
// TODO - build method cache map from errors
log.warn(String.format("no such method %s.%s - attempting upcasting", c.getSimpleName(), MethodEntry.getPrettySignature(method, paramTypes, null)));
// TODO - optimize with a paramter TypeConverter & Map
// c.getMethod - returns on EXACT match - not "Working" match
Method[] allMethods = c.getMethods(); // ouch
log.warn(String.format("ouch! need to search through %d methods", allMethods.length));
for (Method m : allMethods) {
String mname = m.getName();
if (!mname.equals(method)) {
continue;
}
Type[] pType = m.getGenericParameterTypes();
// checking parameter lengths
if (params == null && pType.length != 0 || pType.length != params.length) {
continue;
}
try {
log.debug("found appropriate method");
retobj = m.invoke(obj, params);
// put return object onEvent
out(method, retobj);
// we've found a match. put that in the cache.
LRUMethodCache.getInstance().addCacheEntry(methodCacheKey, m);
return retobj;
} catch (Exception e1) {
log.error(String.format("boom goes method %s", m.getName()));
Logging.logError(e1);
}
}
log.error(String.format("did not find method - %s(%s)", method, CodecUtils.getParameterSignature(params)));
} catch (InvocationTargetException e) {
Throwable target = e.getTargetException();
error(String.format("%s %s", target.getClass().getSimpleName(), target.getMessage()));
Logging.logError(e);
} catch (Exception uknown) {
error(String.format("%s %s", uknown.getClass().getSimpleName(), uknown.getMessage()));
Logging.logError(uknown);
}
return retobj;
}
@Override
public boolean isLocal() {
return instanceId == null;
}
@Override
public boolean isRuntime() {
return Runtime.class == this.getClass();
}
/**
*
* @return
*/
public boolean isReady() {
return true;
}
/**
*
* @return
*/
public boolean isRunning() {
return isRunning;
}
/**
* method of de-serializing default will to load simple xml from name file
*/
@Override
public boolean load() {
return load(null, null);
}
/**
*
* @param o
* @param inCfgFileName
* @return
*/
public boolean load(Object o, String inCfgFileName) {
String filename = null;
if (inCfgFileName == null) {
filename = String.format("%s%s%s.json", cfgDir, File.separator, this.getName());
} else {
filename = inCfgFileName;
}
if (o == null) {
o = this;
}
try {
File cfg = new File(filename);
if (cfg.exists()) {
// serializer.read(o, cfg);
String json = FileIO.toString(filename);
Object saved = CodecUtils.fromJson(json, o.getClass());
copyShallowFrom(o, saved);
return true;
}
log.info(String.format("cfg file %s does not exist", filename));
} catch (Exception e) {
Logging.logError(e);
}
return false;
}
/**
*
* @param msg
*/
public void out(Message msg) {
outbox.add(msg);
}
/**
* Creating a message function call - without specifying the recipients -
* static routes will be applied this is good for Motor drivers - you can swap
* motor drivers by creating a different static route The motor is not "Aware"
* of the driver - only that it wants to method="write" data to the driver
*
* @param method
* @param o
*/
public void out(String method, Object o) {
Message m = createMessage(null, method, o); // create a un-named message
// as output
if (m.sender.length() == 0) {
m.sender = this.getName();
}
if (m.sendingMethod.length() == 0) {
m.sendingMethod = method;
}
outbox.add(m);
}
// override for extended functionality
public boolean preProcessHook(Message m) {
return true;
}
// override for extended functionality
public boolean preRoutingHook(Message m) {
return true;
}
/**
* framework diagnostic publishing method for examining load, capacity, and
* throughput of Inbox & Outbox queues
*
* @param stats
* @return
*/
public QueueStats publishQueueStats(QueueStats stats) {
return stats;
}
/**
* publishing point for the whole service the entire Service is published
*
* @return
*/
public Service publishState() {
return this;
}
@Override
public void releasePeers() {
log.info(String.format("dna - %s", dna.toString()));
String myKey = getName();
log.info(String.format("releasePeers (%s, %s)", myKey, serviceClass));
try {
// TODO: what the heck does this thing do?
Class<?> theClass = Class.forName(serviceClass);
Method method = theClass.getMethod("getMetaData");
ServiceType serviceType = (ServiceType) method.invoke(null);
TreeMap<String, ServiceReservation> peers = serviceType.getPeers();
/*
* DEPRECATED ? If a service shutdown - should it shut down its children ?
* maybe its children are shared with others - that might be bad ..
*
* IndexNode<ServiceReservation> myNode = peers.getDNA().getNode(myKey);
* // LOAD CLASS BY NAME - and do a getReservations on it !
* HashMap<String, IndexNode<ServiceReservation>> peerRequests =
* myNode.getBranches(); for (Entry<String, IndexNode<ServiceReservation>>
* o : peerRequests.entrySet()) { String peerKey = o.getKey();
* IndexNode<ServiceReservation> p = o.getValue();
*
* String fullKey = Peers.getPeerKey(myKey, peerKey); ServiceReservation
* peersr = p.getValue(); ServiceReservation globalSr = dna.get(fullKey);
*
* // TODO - if (globalSr != null) {
*
* log.info(String.format("*releasing** key %s -> %s %s %s", fullKey,
* globalSr.actualName, globalSr.fullTypeName, globalSr.comment));
* ServiceInterface si = Runtime.getService(fullKey); if (si == null) {
* log.info(String.format("%s is not registered - skipping", fullKey)); }
* else { si.releasePeers(); si.releaseService(); }
*
* } }
*/
} catch (Exception e) {
log.debug(String.format("%s does not have a getPeers", serviceClass));
}
}
/**
* Releases resources, and unregisters service from the runtime
*/
@Override
public void releaseService() {
// note - if stopService is overwritten with extra
// threads - releaseService will need to be overwritten too
stopService();
// recently added
releasePeers();
purgeTasks();
Runtime.release(getName());
}
/**
*
*/
public void removeAllListeners() {
outbox.notifyList.clear();
}
/**
*
* @param outMethod
* @param serviceName
* @param inMethod
* @param paramTypes
*/
@Override
public void removeListener(String outMethod, String serviceName, String inMethod) {
if (outbox.notifyList.containsKey(outMethod)) {
ArrayList<MRLListener> nel = outbox.notifyList.get(outMethod);
for (int i = 0; i < nel.size(); ++i) {
MRLListener target = nel.get(i);
if (target.callbackName.compareTo(serviceName) == 0) {
nel.remove(i);
log.info(String.format("removeListener requested %s.%s to be removed", serviceName, outMethod));
}
}
} else {
log.error(String.format("removeListener requested %s.%s to be removed - but does not exist", serviceName, outMethod));
}
}
// ---------------- logging end ---------------------------
@Override
public boolean requiresSecurity() {
return security != null;
}
/**
* Reserves a name for a Peer Service. This is important for services which
* control other services. Internally composite services will use a key so the
* name of the peer service can change, effectively binding a new peer to the
* composite
*
* @param key
* internal key name of peer service
* @param simpleTypeName
* type of service
* @param comment
* comment detailing the use of the peer service within the composite
*/
public void reserve(String key, String simpleTypeName, String comment) {
// creating
String peerKey = getPeerKey(key);
reserveRoot(peerKey, simpleTypeName, comment);
}
public void reserve(String key, String actualName, String simpleTypeName, String comment) {
// creating
String peerKey = getPeerKey(key);
reserveRoot(peerKey, actualName, simpleTypeName, comment);
}
@Override
final public void run() {
isRunning = true;
try {
while (isRunning) {
// TODO should this declaration be outside the while loop? if
// so, make sure to release prior to continue
Message m = getMsg();
if (!preRoutingHook(m)) {
continue;
}
// nameless Runtime messages
if (m.name == null) {
// don't know if this is "correct"
// but we are substituting the Runtime name as soon as we
// see that its a null
// name message
m.name = Runtime.getInstance().getName();
}
// route if necessary
if (!m.getName().equals(this.getName())) // && RELAY
{
outbox.add(m); // RELAYING
continue; // sweet - that was a long time coming fix !
}
if (!preProcessHook(m)) {
// if preProcessHook returns false
// the message does not need to continue
// processing
continue;
}
// TODO should this declaration be outside the while loop?
Object ret = invoke(m);
if (Message.BLOCKING.equals(m.status)) {
// TODO should this declaration be outside the while loop?
// create new message reverse sender and name set to same
// msg id
Message msg = createMessage(m.sender, m.method, ret);
msg.sender = this.getName();
msg.msgId = m.msgId;
// msg.status = Message.BLOCKING;
msg.status = Message.RETURN;
outbox.add(msg);
}
}
} catch (InterruptedException edown) {
info("shutting down");
} catch (Exception e) {
error(e);
}
}
/**
* method of serializing default will be simple xml to name file
*/
@Override
public boolean save() {
try {
File cfg = new File(String.format("%s%s%s.json", cfgDir, File.separator, getName()));
// serializer.write(this, cfg);
info("saving %s", cfg.getName());
if (this instanceof Runtime) {
info("we cant serialize runtime yet");
return false;
}
String s = CodecUtils.toJson(this);
FileOutputStream out = new FileOutputStream(cfg);
out.write(s.getBytes());
out.close();
} catch (Exception e) {
Logging.logError(e);
return false;
}
return true;
}
/**
*
* @param o
* @param cfgFileName
* @return
*/
public boolean save(Object o, String cfgFileName) {
try {
File cfg = new File(String.format("%s%s%s", cfgDir, File.separator, cfgFileName));
String s = CodecUtils.toJson(o);
FileOutputStream out = new FileOutputStream(cfg);
out.write(s.getBytes());
out.close();
} catch (Exception e) {
Logging.logError(e);
return false;
}
return true;
}
/**
*
* @param cfgFileName
* @param data
* @return
*/
public boolean save(String cfgFileName, String data) {
// saves user data in the .myrobotlab directory
// with the file naming convention of name.<cfgFileName>
try {
FileIO.toFile(String.format("%s%s%s.%s", cfgDir, File.separator, this.getName(), cfgFileName), data);
} catch (Exception e) {
Logging.logError(e);
return false;
}
return true;
}
/**
* 0?
*
* @param name
* @param method
*/
public void send(String name, String method) {
send(name, method, (Object[]) null);
}
/**
* boxing - the right way - thank you Java 5
*
* @param name
* @param method
* @param data
*/
public void send(String name, String method, Object... data) {
Message msg = createMessage(name, method, data);
msg.sender = this.getName();
// All methods which are invoked will
// get the correct sendingMethod
// here its hardcoded
msg.sendingMethod = "send";
if (recorder != null) {
try {
recorder.write(msg);
} catch (IOException e) {
logException(e);
}
}
outbox.add(msg);
}
/**
* this send forces remote connect - for registering services
*
* @param url
* @param method
* @param param1
*/
public void send(URI url, String method, Object param1) {
Object[] params = new Object[1];
params[0] = param1;
Message msg = createMessage(name, method, params);
outbox.getCommunicationManager().send(url, msg);
}
/**
*
* @param name
* @param method
* @param data
* @return
*/
public Object sendBlocking(String name, Integer timeout, String method, Object... data) {
Message msg = createMessage(name, method, data);
msg.sender = this.getName();
msg.status = Message.BLOCKING;
msg.msgId = Runtime.getUniqueID();
Object[] returnContainer = new Object[1];
/*
* if (inbox.blockingList.contains(msg.msgID)) { log.error("DUPLICATE"); }
*/
inbox.blockingList.put(msg.msgId, returnContainer);
try {
// block until message comes back
synchronized (returnContainer) {
outbox.add(msg);
returnContainer.wait(timeout); // NEW !!! TIMEOUT !!!!
}
} catch (InterruptedException e) {
logException(e);
}
return returnContainer[0];
}
// BOXING - End --------------------------------------
public Object sendBlocking(String name, String method) {
return sendBlocking(name, method, (Object[]) null);
}
public Object sendBlocking(String name, String method, Object... data) {
return sendBlocking(name, 1000, method, data); // default 1 sec timeout
// - TODO - make
// configurable
}
@Override
public void setInstanceId(URI uri) {
instanceId = uri;
}
/*
* static public setErrorEmail(String to, String host, String user, String
* password){
*
* }
*/
public String setLogLevel(String level) {
Logging logging = LoggingFactory.getInstance();
logging.setLevel(this.getClass().getCanonicalName(), level);
return level;
}
/**
* rarely should this be used. Gateways use it to provide x-route natting
* services by re-writing names with prefixes
*
* @param name
*/
@Override
public void setName(String name) {
// this.name = String.format("%s%s", prefix, name);
this.name = name;
}
@Override
public String getName() {
return name;
}
/**
*
* @param s
* @return
*/
public Service setState(Service s) {
return (Service) copyShallowFrom(this, s);
}
/**
*
* @param thisThread
*/
public void setThisThread(Thread thisThread) {
this.thisThread = thisThread;
}
public void startHeartbeat() {
// getComm().
}
public ServiceInterface startPeer(String reservedKey) {
ServiceInterface si = null;
try {
si = createPeer(reservedKey);
if (si == null) {
error("could not create service from key %s", reservedKey);
return null;
}
si.startService();
} catch (Exception e) {
error(e.getMessage());
Logging.logError(e);
}
return si;
}
public ServiceInterface startPeer(String reservedKey, String defaultType) throws Exception {
ServiceInterface si = createPeer(reservedKey, defaultType);
if (si == null) {
error("could not create service from key %s", reservedKey);
}
si.startService();
return si;
}
public void startRecording() {
invoke("startRecording", new Object[] { null });
}
@Override
public void startService() {
ServiceInterface si = Runtime.getService(name);
if (si == null) {
Runtime.create(name, getSimpleName());
}
if (!isRunning()) {
outbox.start();
if (thisThread == null) {
thisThread = new Thread(this, name);
}
thisThread.start();
isRunning = true;
} else {
log.debug("startService request: service {} is already running", name);
}
}
public void stopHeartbeat() {
}
public void stopMsgRecording() {
log.info("stopped recording");
if (recorder != null) {
try {
recorder.stop();
} catch (Exception e) {
Logging.logError(e);
}
}
}
/**
* Stops the service. Stops threads.
*/
@Override
public void stopService() {
isRunning = false;
outbox.stop();
if (thisThread != null) {
thisThread.interrupt();
}
thisThread = null;
save();
}
// -------------- Messaging Begins -----------------------
public void subscribe(NameProvider topicName, String topicMethod) {
String callbackMethod = CodecUtils.getCallBackName(topicMethod);
subscribe(topicName.getName(), topicMethod, getName(), callbackMethod);
}
public void subscribe(String topicName, String topicMethod) {
String callbackMethod = CodecUtils.getCallBackName(topicMethod);
subscribe(topicName, topicMethod, getName(), callbackMethod);
}
public void subscribe(String topicName, String topicMethod, String callbackName, String callbackMethod) {
log.info(String.format("subscribe [%s/%s ---> %s/%s]", topicName, topicMethod, callbackName, callbackMethod));
MRLListener listener = new MRLListener(topicMethod, callbackName, callbackMethod);
cm.send(createMessage(topicName, "addListener", listener));
}
public void unsubscribe(NameProvider topicName, String topicMethod) {
String callbackMethod = CodecUtils.getCallBackName(topicMethod);
subscribe(topicName.getName(), topicMethod, getName(), callbackMethod);
}
public void unsubscribe(String topicName, String topicMethod) {
String callbackMethod = CodecUtils.getCallBackName(topicMethod);
unsubscribe(topicName, topicMethod, getName(), callbackMethod);
}
public void unsubscribe(String topicName, String topicMethod, String callbackName, String callbackMethod) {
log.info(String.format("subscribe [%s/%s ---> %s/%s]", topicName, topicMethod, callbackName, callbackMethod));
cm.send(createMessage(topicName, "removeListener", new Object[] { topicMethod, callbackName, callbackMethod }));
}
// TODO - remove or reconcile - RemoteAdapter and Service are the only ones
// using this
/**
*
* @param name
* @param method
* @param data
* @return
*/
public Message createMessage(String name, String method, Object data) {
if (data == null) {
return createMessage(name, method, null);
}
Object[] d = new Object[1];
d[0] = data;
return createMessage(name, method, d);
}
// FIXME All parameter constructor
// TODO - Probably simplyfy to take array of object
/**
*
* @param name
* @param method
* @param data
* @return
*/
public Message createMessage(String name, String method, Object[] data) {
Message msg = new Message();
msg.name = name; // destination instance name
msg.sender = this.getName();
msg.data = data;
msg.method = method;
return msg;
}
// -------------- Messaging Ends -----------------------
// ---------------- Status processing begin ------------------
public Status error(Exception e) {
Status ret = Status.error(e);
ret.name = getName();
invoke("publishStatus", ret);
return ret;
}
@Override
public Status error(String format, Object... args) {
Status ret = Status.error(String.format(format, args));
ret.name = getName();
invoke("publishStatus", ret);
return ret;
}
public Status error(String msg) {
Status ret = Status.error(msg);
ret.name = getName();
invoke("publishStatus", ret);
return ret;
}
public Status warn(String msg) {
Status ret = Status.warn(msg);
invoke("publishStatus", ret);
return ret;
}
@Override
public Status warn(String format, Object... args) {
return Status.warn(format, args);
}
/**
* set status broadcasts an info string to any subscribers
*
* @param msg
*/
public String info(String msg) {
Status status = Status.info(msg);
invoke("publishStatus", status);
return msg;
}
/**
* set status broadcasts an formatted info string to any subscribers
*
* @param msg
*/
@Override
public Status info(String format, Object... args) {
return Status.info(format, args);
}
/**
* error only channel publishing point versus publishStatus which handles
* info, warn & error
*
* @param msg
* @return
*/
public Status publishError(Status status) {
return status;
}
public Status publishStatus(Status status) {
status.name = getName();
if (status.level.equals(StatusLevel.ERROR)) {
lastError = status;
log.error(status.toString());
invoke("publishError", status);
} else {
log.info(status.toString());
}
return status;
}
// ---------------- Status processing end ------------------
@Override
public String toString() {
return getName();
}
public Map<String, MethodEntry> getMethodMap() {
return Runtime.getMethodMap(getName());
}
@Override
public void updateStats(QueueStats stats) {
invoke("publishStats", stats);
}
@Override
public QueueStats publishStats(QueueStats stats) {
// log.error(String.format("===stats - dequeued total %d - %d bytes in
// %d ms %d Kbps",
// stats.total, stats.interval, stats.ts - stats.lastTS, 8 *
// stats.interval/ (stats.delta)));
return stats;
}
/*
* static public ArrayList<ServiceReservation> getPeerMetaData(String
* serviceType) { ArrayList<ServiceReservation> peerList = new
* ArrayList<ServiceReservation>(); try {
*
* Class<?> theClass = Class.forName(serviceType); Method method =
* theClass.getMethod("getPeers", String.class); Peers peers = (Peers)
* method.invoke(null, new Object[] { "" }); if (peers != null) { log.info(
* "has peers"); peerList = peers.getDNA().flatten();
*
* // add peers to serviceData serviceType }
*
* } catch (Exception e) { // dont care }
*
* return peerList; }
*/
/**
* Calls the static method getMetaData on the appropriate class. The class
* static data is passed back as a template to be merged in with the global
* static dna
*
* @param serviceClass
* @return
* @throws ClassNotFoundException
*/
static public ServiceType getMetaData(String serviceClass) {
String serviceType;
if (!serviceClass.contains(".")) {
serviceType = String.format("org.myrobotlab.service.%s", serviceClass);
} else {
serviceType = serviceClass;
}
try {
Class<?> theClass = Class.forName(serviceType);
// execute static method to get meta data
Method method = theClass.getMethod("getMetaData");
ServiceType meta = (ServiceType) method.invoke(null);
return meta;
} catch (Exception e) {
// dont care
}
return null;
}
// FIXME - meta data needs to be re-infused into instance
public String getDescription() {
return "FIXME - meta data needs to be re-infused into instance";
}
}