From f6ec6fb113c97c0f5ec19c41c63a17a70186a77a Mon Sep 17 00:00:00 2001 From: Branden Butler Date: Tue, 23 May 2023 19:12:27 -0500 Subject: [PATCH 1/3] Add Pair and ObjectTypePair --- .../org/myrobotlab/utils/ObjectTypePair.java | 17 ++++++++++++++ src/main/java/org/myrobotlab/utils/Pair.java | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/main/java/org/myrobotlab/utils/ObjectTypePair.java create mode 100644 src/main/java/org/myrobotlab/utils/Pair.java diff --git a/src/main/java/org/myrobotlab/utils/ObjectTypePair.java b/src/main/java/org/myrobotlab/utils/ObjectTypePair.java new file mode 100644 index 0000000000..640940f967 --- /dev/null +++ b/src/main/java/org/myrobotlab/utils/ObjectTypePair.java @@ -0,0 +1,17 @@ +package org.myrobotlab.utils; + +import org.myrobotlab.framework.StaticType; + +/** + * A container class that holds an object with its + * associated type information. This makes + * constraining objects and types while inside of collections + * or other containers easier. + * @param The type of the object contained + * @author AutonomicPerfectionist + */ +public class ObjectTypePair extends Pair> { + public ObjectTypePair(T first, StaticType second) { + super(first, second); + } +} diff --git a/src/main/java/org/myrobotlab/utils/Pair.java b/src/main/java/org/myrobotlab/utils/Pair.java new file mode 100644 index 0000000000..4ebb4f15a0 --- /dev/null +++ b/src/main/java/org/myrobotlab/utils/Pair.java @@ -0,0 +1,23 @@ +package org.myrobotlab.utils; + + +/** + * A simple container for two objects + * of potentially different types. Allows + * generics to be leveraged for type constraining + * or more compiler-friendly collections operations. + * + * @param The type of {@link #first} + * @param The type of {@link #second} + * @author AutonomicPerfectionist + */ +public class Pair { + public A first; + public B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } + +} From e943117ac5ae99512e619c9bf97c03c5cfd67854 Mon Sep 17 00:00:00 2001 From: Branden Butler Date: Tue, 23 May 2023 19:17:04 -0500 Subject: [PATCH 2/3] Enhance invoke(), sendBlocking(), and downstream methods with StaticType --- .../java/org/myrobotlab/codec/CodecUtils.java | 8 ++ .../java/org/myrobotlab/framework/Inbox.java | 25 ++++-- .../org/myrobotlab/framework/Message.java | 35 ++++++-- .../java/org/myrobotlab/framework/Outbox.java | 3 +- .../org/myrobotlab/framework/Service.java | 90 ++++++++++--------- .../org/myrobotlab/framework/StaticType.java | 9 ++ .../framework/interfaces/Invoker.java | 16 +++- .../framework/interfaces/MessageInvoker.java | 13 ++- .../framework/interfaces/MessageSender.java | 37 ++++++-- .../framework/interfaces/ServiceQueue.java | 5 -- .../java/org/myrobotlab/service/Solr.java | 2 +- .../service/interfaces/LogPublisher.java | 17 +--- .../service/interfaces/MrlCommPublisher.java | 6 +- src/main/java/org/myrobotlab/utils/Pair.java | 2 +- 14 files changed, 170 insertions(+), 98 deletions(-) diff --git a/src/main/java/org/myrobotlab/codec/CodecUtils.java b/src/main/java/org/myrobotlab/codec/CodecUtils.java index 0b7a5523f2..6d03fc4985 100644 --- a/src/main/java/org/myrobotlab/codec/CodecUtils.java +++ b/src/main/java/org/myrobotlab/codec/CodecUtils.java @@ -35,6 +35,7 @@ import org.myrobotlab.framework.Message; import org.myrobotlab.framework.MethodCache; import org.myrobotlab.framework.StaticType; +import org.myrobotlab.framework.interfaces.ServiceInterface; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.LoggingFactory; @@ -654,11 +655,18 @@ static public String getServiceType(String inType) { static /*@Nonnull*/ Message decodeMessageParams(/*@Nonnull*/ Message msg) { String serviceName = msg.getFullName(); Class clazz = Runtime.getClass(serviceName); + ServiceInterface si = Runtime.getService(serviceName); //Nullability of clazz is checked with this, if null //falls back to virt class field boolean useVirtClassField = clazz == null; if (!useVirtClassField) { + String blockingKey = String.format("%s.%s", msg.getFullName(), msg.getMethod()); + if (si != null && Message.MSG_TYPE_RETURN.equals(msg.msgType) && si.getInbox().blockingList.containsKey(blockingKey)) { + msg.data[0] = fromJson((String) msg.data[0], si.getInbox().blockingList.get(blockingKey).second); + msg.encoding = null; + return msg; + } try { Object[] params = MethodCache.getInstance().getDecodedJsonParameters(clazz, msg.method, msg.data); if (params == null) diff --git a/src/main/java/org/myrobotlab/framework/Inbox.java b/src/main/java/org/myrobotlab/framework/Inbox.java index 1bf23c387d..40f5bf271f 100644 --- a/src/main/java/org/myrobotlab/framework/Inbox.java +++ b/src/main/java/org/myrobotlab/framework/Inbox.java @@ -30,9 +30,11 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import org.myrobotlab.framework.interfaces.MessageListener; import org.myrobotlab.logging.LoggerFactory; +import org.myrobotlab.utils.ObjectTypePair; import org.slf4j.Logger; public class Inbox implements Serializable { @@ -50,9 +52,16 @@ public class Inbox implements Serializable { // value // support remote blocking... in-process blocking uses invoke - public HashMap blockingList = new HashMap<>(); + /** + * Maps blocking keys ("fullName.method") to an + * ObjectTypePair that is used to store the return + * type and return value. DO NOT SET THE VALUE + * TO A NEW OBJECT, we are synchronizing on the values, + * doing so will result in the threads never waking up. + */ + public Map> blockingList = new HashMap<>(); - List listeners = new ArrayList(); + List listeners = new ArrayList<>(); public Inbox() { this("Inbox"); @@ -62,6 +71,7 @@ public Inbox(String name) { this.name = name; } + @SuppressWarnings("unchecked") public void add(Message msg) { /** *
@@ -74,16 +84,17 @@ public void add(Message msg) {
      */
 
     // --- sendBlocking support begin --------------------
-    // TODO - possible safety check msg.status == Message.RETURN
-    // &&
     String blockingKey = String.format("%s.%s", msg.getFullName(), msg.getMethod());
     if (blockingList.containsKey(blockingKey)) {
-      Object[] returnContainer = blockingList.get(blockingKey);
+      // Generates an unchecked warning because of generic invariance, but we don't care
+      ObjectTypePair returnContainer = (ObjectTypePair) blockingList.get(blockingKey);
       if (msg.data == null) {
-        returnContainer[0] = null;
+        returnContainer.first = null;
       } else {
         // transferring data
-        returnContainer[0] = msg.data[0];
+        // Would not be able to do this if returnContainer had wildcard generic type,
+        // which is why it's Object
+        returnContainer.first = msg.data[0];
       }
 
       synchronized (returnContainer) {
diff --git a/src/main/java/org/myrobotlab/framework/Message.java b/src/main/java/org/myrobotlab/framework/Message.java
index 5a64f5a0e1..450c48dc73 100644
--- a/src/main/java/org/myrobotlab/framework/Message.java
+++ b/src/main/java/org/myrobotlab/framework/Message.java
@@ -47,6 +47,23 @@ public class Message implements Serializable {
 
   private static final long serialVersionUID = 1L;
 
+  /**
+   * A message has this type if it is sent with the expectation that a return
+   * message will be received. Services should always respond
+   * with a return message after invoking the blocking message.
+   *
+   * @see #MSG_TYPE_RETURN
+   */
+  public static final String MSG_TYPE_BLOCKING = "BLOCKING";
+
+  /**
+   * A message has this type when it contains a single data
+   * element meant to represent a service method return value.
+   * The type of the return value is not explicitly carried with the message,
+   * since it can cross language boundaries.
+   */
+  public static final String MSG_TYPE_RETURN = "RETURN";
+
   // FIXME msgId should be a String encoded value of src and an atomic increment
   // ROS comes with a seq Id, a timestamp, and a frame Id
   /**
@@ -74,8 +91,7 @@ public class Message implements Serializable {
    * history of the message, its routing stops and Services it passed through.
    * This is important to prevent endless looping of messages. Turns out
    * ArrayList is quicker than HashSet on small sets
-   * http://www.javacodegeeks.com
-   * /2010/08/java-best-practices-vector-arraylist.html
+   * Java Best Practices
    */
   protected List historyList;
 
@@ -88,11 +104,12 @@ public class Message implements Serializable {
 
   /**
    * status is currently used for BLOCKING message calls the current valid state
-   * it can be in is null | BLOCKING | RETURN FIXME - this should be msgType not
-   * status
+   * it can be in is null | BLOCKING | RETURN
+   *
+   * @see #MSG_TYPE_BLOCKING
+   * @see #MSG_TYPE_RETURN
    */
-
-  public String status;
+  public String msgType;
 
   public String encoding; // null == none |json|cli|xml|stream ...
 
@@ -151,7 +168,7 @@ final public void set(final Message other) {
     historyList = new ArrayList();
     historyList.addAll(other.historyList);
 
-    status = other.status;
+    msgType = other.msgType;
     encoding = other.encoding;
     method = other.method;
     // you know the dangers of reference copy
@@ -299,7 +316,7 @@ public boolean equals(Object o) {
             && Objects.equals(sendingMethod, message.sendingMethod)
             && Objects.equals(historyList, message.historyList)
             && Objects.equals(properties, message.properties)
-            && Objects.equals(status, message.status)
+            && Objects.equals(msgType, message.msgType)
             && Objects.equals(encoding, message.encoding)
             && Objects.equals(method, message.method)
             && Arrays.deepEquals(data, message.data);
@@ -310,7 +327,7 @@ public int hashCode() {
     int result = Objects.hash(
                     msgId, name, sender,
                     sendingMethod, historyList,
-                    properties, status, encoding,
+                    properties, msgType, encoding,
                     method
     );
     result = 31 * result + Arrays.hashCode(data);
diff --git a/src/main/java/org/myrobotlab/framework/Outbox.java b/src/main/java/org/myrobotlab/framework/Outbox.java
index c3ea088003..58ad3b7279 100644
--- a/src/main/java/org/myrobotlab/framework/Outbox.java
+++ b/src/main/java/org/myrobotlab/framework/Outbox.java
@@ -206,8 +206,7 @@ public void run() {
           continue;
         }
 
-        for (int i = 0; i < subList.size(); ++i) {
-          MRLListener listener = subList.get(i);
+        for (MRLListener listener : subList) {
           msg.setName(listener.callbackName);
           msg.method = listener.callbackMethod;
           send(msg);
diff --git a/src/main/java/org/myrobotlab/framework/Service.java b/src/main/java/org/myrobotlab/framework/Service.java
index 8aa17d20f3..8ffdcc14a2 100644
--- a/src/main/java/org/myrobotlab/framework/Service.java
+++ b/src/main/java/org/myrobotlab/framework/Service.java
@@ -70,6 +70,7 @@
 import org.myrobotlab.service.interfaces.AuthorizationProvider;
 import org.myrobotlab.service.interfaces.QueueReporter;
 import org.myrobotlab.service.meta.abstracts.MetaData;
+import org.myrobotlab.utils.ObjectTypePair;
 import org.slf4j.Logger;
 
 /**
@@ -748,8 +749,7 @@ public void addListener(String topicMethod, String callbackName, String callback
       // iterate through all looking for duplicate
       boolean found = false;
       List nes = outbox.notifyList.get(listener.topicMethod);
-      for (int i = 0; i < nes.size(); ++i) {
-        MRLListener entry = nes.get(i);
+      for (MRLListener entry : nes) {
         if (entry.equals(listener)) {
           log.debug("attempting to add duplicate MRLListener {}", listener);
           found = true;
@@ -1046,15 +1046,13 @@ public List getNotifyList(String key) {
       // and your in a skeleton
       // use the runtime to send a message
       // FIXME - parameters !
-      ArrayList remote = null;
       try {
-        remote = (ArrayList) Runtime.getInstance().sendBlocking(getName(), "getNotifyList", new Object[] { key });
+        return Runtime.getInstance().sendBlocking(getName(), "getNotifyList", new StaticType<>(){}, new Object[] { key });
       } catch (Exception e) {
         log.error("remote getNotifyList threw", e);
+        return null;
       }
 
-      return remote;
-
     } else {
       return getOutbox().notifyList.get(key);
     }
@@ -1071,7 +1069,7 @@ public ArrayList getNotifyListKeySet() {
 
       ArrayList remote = null;
       try {
-        remote = (ArrayList) Runtime.getInstance().sendBlocking(getName(), "getNotifyListKeySet");
+        remote = Runtime.getInstance().sendBlocking(getName(), "getNotifyListKeySet", new StaticType<>(){});
       } catch (Exception e) {
         log.error("remote getNotifyList threw", e);
       }
@@ -1177,9 +1175,10 @@ public void in(Message msg) {
   /**
    * This is where all messages are routed to and processed
    */
+  @SuppressWarnings("unchecked")
   @Override
-  final public Object invoke(Message msg) {
-    Object retobj = null;
+  final public  R invoke(Message msg, StaticType returnType) {
+    R retobj;
 
     if (log.isDebugEnabled()) {
       log.debug("--invoking {}.{}({}) {} --", name, msg.method, CodecUtils.getParameterSignature(msg.data), msg.msgId);
@@ -1194,17 +1193,17 @@ final public Object invoke(Message msg) {
     // happen in other situations...
     if (Runtime.getInstance().isLocal(msg) && !name.equals(msg.getName())) {
       // wrong Service - get the correct one
-      return Runtime.getService(msg.getName()).invoke(msg);
+      return Runtime.getService(msg.getName()).invoke(msg, returnType);
     }
 
     String blockingKey = String.format("%s.%s", msg.getFullName(), msg.getMethod());
     if (inbox.blockingList.containsKey(blockingKey)) {
-      Object[] returnContainer = inbox.blockingList.get(blockingKey);
+      ObjectTypePair returnContainer = (ObjectTypePair) inbox.blockingList.get(blockingKey);
       if (msg.getData() == null) {
-        returnContainer[0] = null;
+        returnContainer.first = null;
       } else {
         // transferring data
-        returnContainer[0] = msg.getData()[0];
+        returnContainer.first = (R) msg.getData()[0];
       }
 
       synchronized (returnContainer) {
@@ -1215,19 +1214,19 @@ final public Object invoke(Message msg) {
       return null;
     }
 
-    retobj = invokeOn(false, this, msg.method, msg.data);
+    retobj = invokeOn(false, this, msg.method, returnType, msg.data);
 
     return retobj;
   }
 
   @Override
-  final public Object invoke(String method) {
-    return invokeOn(false, this, method, (Object[]) null);
+  final public  R invoke(String method, StaticType returnType) {
+    return invokeOn(false, this, method, returnType, (Object[]) null);
   }
 
   @Override
-  final public Object invoke(String method, Object... params) {
-    return invokeOn(false, this, method, params);
+  final public  R invoke(String method, StaticType returnType, Object... params) {
+    return invokeOn(false, this, method, returnType, params);
   }
 
   /**
@@ -1264,8 +1263,8 @@ final public Object broadcast(String method, Object... params) {
    * @return the returned value from invoking
    * 
    */
-  final public Object invokeOn(String serviceName, String methodName, Object... params) {
-    return invokeOn(false, Runtime.getService(serviceName), methodName, params);
+  final public  R invokeOn(String serviceName, String methodName, StaticType returnType, Object... params) {
+    return invokeOn(false, Runtime.getService(serviceName), methodName, returnType, params);
   }
 
   /**
@@ -1280,8 +1279,8 @@ final public Object invokeOn(String serviceName, String methodName, Object... pa
    * @return return object
    */
   @Override
-  final public Object invokeOn(boolean blockLocally, Object obj, String methodName, Object... params) {
-    Object retobj = null;
+  final public  R invokeOn(boolean blockLocally, Object obj, String methodName, StaticType returnType, Object... params) {
+    R retobj = null;
     try {
       MethodCache cache = MethodCache.getInstance();
       if (obj == null) {
@@ -1293,7 +1292,7 @@ final public Object invokeOn(boolean blockLocally, Object obj, String methodName
         error("could not find method %s.%s(%s)", obj.getClass().getSimpleName(), methodName, MethodCache.formatParams(params));
         return null; // should this be allowed to throw to a higher level ?
       }
-      retobj = method.invoke(obj, params);
+      retobj = (R) method.invoke(obj, params);
       if (blockLocally) {
         List subList = outbox.notifyList.get(methodName);
         // correct? get local (default?) gateway
@@ -1301,9 +1300,7 @@ final public Object invokeOn(boolean blockLocally, Object obj, String methodName
         if (subList != null) {
           for (MRLListener listener : subList) {
             Message msg = Message.createMessage(getFullName(), listener.callbackName, listener.callbackMethod, retobj);
-            if (msg == null) {
-              log.error("Unable to create message.. null message created");
-            }
+            msg.msgType = Message.MSG_TYPE_RETURN;
             msg.sendingMethod = methodName;
             if (runtime.isLocal(msg)) {
               ServiceInterface si = Runtime.getService(listener.callbackName);
@@ -1704,12 +1701,12 @@ public void sendAsync(String name, String method, Object... data) {
   }
 
   @Override
-  public Object sendBlocking(String name, Integer timeout, String method, Object... data) throws InterruptedException, TimeoutException {
+  public  R sendBlocking(String name, Integer timeout, String method, StaticType returnType, Object... data) throws InterruptedException, TimeoutException {
     Message msg = Message.createMessage(getName(), name, method, data);
     msg.sender = this.getFullName();
     msg.msgId = Runtime.getUniqueID();
 
-    return sendBlocking(msg, timeout);
+    return sendBlocking(msg, timeout, returnType);
   }
 
   /**
@@ -1726,11 +1723,12 @@ public Object sendBlocking(String name, Integer timeout, String method, Object..
    * 
    */
   @Override
-  public Object sendBlocking(Message msg, Integer timeout) throws InterruptedException, TimeoutException {
+  public  R sendBlocking(Message msg, Integer timeout, StaticType returnType) throws InterruptedException, TimeoutException {
+    msg.msgType = Message.MSG_TYPE_BLOCKING;
     if (Runtime.getInstance().isLocal(msg)) {
-      return invoke(msg);
+      return invoke(msg, returnType);
     } else {
-      return waitOn(msg.getFullName(), msg.getMethod(), timeout, msg);
+      return waitOn(msg.getFullName(), msg.getMethod(), timeout, msg, returnType);
     }
   }
 
@@ -1757,8 +1755,8 @@ public Object sendBlocking(Message msg, Integer timeout) throws InterruptedExcep
    * @throws TimeoutException
    *           boom
    */
-  protected Object waitOn(String fullName, String method, Integer timeout, Message sendMsg) throws InterruptedException, TimeoutException {
-
+  @SuppressWarnings("unchecked")
+  protected  R waitOn(String fullName, String method, Integer timeout, Message sendMsg, StaticType returnType) throws InterruptedException, TimeoutException {
     String subscriber = null;
     if (sendMsg != null) {
       // InProcCli proxies - so the subscription needs to be from the sender NOT
@@ -1771,14 +1769,14 @@ protected Object waitOn(String fullName, String method, Integer timeout, Message
     // put in-process lock in map
     String callbackMethod = CodecUtils.getCallbackTopicName(method);
     String blockingKey = String.format("%s.%s", subscriber, callbackMethod);
-    Object[] blockingLockContainer = null;
+    ObjectTypePair blockingLockContainer;
     if (!inbox.blockingList.containsKey(blockingKey)) {
-      blockingLockContainer = new Object[1];
+      blockingLockContainer = new ObjectTypePair<>(null, returnType);
       inbox.blockingList.put(blockingKey, blockingLockContainer);
     } else {
       // if it already exists - other threads are already waiting for the
       // same callback ...
-      blockingLockContainer = inbox.blockingList.get(blockingKey);
+      blockingLockContainer = (ObjectTypePair) inbox.blockingList.get(blockingKey);
     }
 
     // send subscription
@@ -1811,26 +1809,32 @@ public void run() {
     // cleanup
     unsubscribe(fullName, method, subscriber, CodecUtils.getCallbackTopicName(method));
 
-    return blockingLockContainer[0];
+    if (returnType.equals(blockingLockContainer.second)) {
+      // Unchecked cast is fine because the deserializer ensures second is assignable to R
+      return blockingLockContainer.first;
+    } else {
+      error("Return type was changed during blocking call. Was {}, now {}", returnType, blockingLockContainer.second);
+      return null;
+    }
 
   }
 
   // equivalent to sendBlocking without the sending a message
   @Override
-  public Object waitFor(String fullName, String method, Integer timeout) throws InterruptedException, TimeoutException {
-    return waitOn(fullName, method, timeout, null);
+  public  R waitFor(String fullName, String method, Integer timeout, StaticType returnType) throws InterruptedException, TimeoutException {
+    return waitOn(fullName, method, timeout, null, returnType);
   }
 
   // BOXING - End --------------------------------------
   @Override
-  public Object sendBlocking(String name, String method) throws InterruptedException, TimeoutException {
-    return sendBlocking(name, method, (Object[]) null);
+  public  R sendBlocking(String name, String method, StaticType returnType) throws InterruptedException, TimeoutException {
+    return sendBlocking(name, method, returnType, (Object[]) null);
   }
 
   @Override
-  public Object sendBlocking(String name, String method, Object... data) throws InterruptedException, TimeoutException {
+  public  R sendBlocking(String name, String method, StaticType returnType, Object... data) throws InterruptedException, TimeoutException {
     // default 1 second timeout - FIXME CONFIGURABLE
-    return sendBlocking(name, 1000, method, data);
+    return sendBlocking(name, 1000, method, returnType, data);
   }
 
   @Override
diff --git a/src/main/java/org/myrobotlab/framework/StaticType.java b/src/main/java/org/myrobotlab/framework/StaticType.java
index fddc2912f9..b1969db08d 100644
--- a/src/main/java/org/myrobotlab/framework/StaticType.java
+++ b/src/main/java/org/myrobotlab/framework/StaticType.java
@@ -70,6 +70,11 @@ protected StaticType() {
 
     }
 
+    private StaticType(Type storedType) {
+        this.storedType = storedType;
+        validateType(storedType);
+    }
+
     /**
      * Gets the stored {@link Type}
      * instance. This type should contain the type of
@@ -111,4 +116,8 @@ private static void validateType(Type type) {
         }
 
     }
+
+    public static  StaticType fromJavaType(Type type) {
+        return new StaticType<>(type){};
+    }
 }
diff --git a/src/main/java/org/myrobotlab/framework/interfaces/Invoker.java b/src/main/java/org/myrobotlab/framework/interfaces/Invoker.java
index f3524ac6e4..e17d71d7f1 100644
--- a/src/main/java/org/myrobotlab/framework/interfaces/Invoker.java
+++ b/src/main/java/org/myrobotlab/framework/interfaces/Invoker.java
@@ -1,9 +1,21 @@
 package org.myrobotlab.framework.interfaces;
 
+import org.myrobotlab.framework.StaticType;
+
 public interface Invoker {
 
-  public Object invoke(String method);
+  default Object invoke(String method) {
+    return invoke(method, new StaticType<>(){});
+  }
+
+  default  R invoke(String method, StaticType returnType) {
+    return invoke(method, returnType, new Object[0]);
+  }
+
 
-  public Object invoke(String method, Object... params);
+  default Object invoke(String method, Object... params) {
+    return invoke(method, new StaticType<>() {}, params);
+  }
+   R invoke(String method, StaticType returnType, Object... params);
 
 }
diff --git a/src/main/java/org/myrobotlab/framework/interfaces/MessageInvoker.java b/src/main/java/org/myrobotlab/framework/interfaces/MessageInvoker.java
index 9c80862978..f5f80bd392 100644
--- a/src/main/java/org/myrobotlab/framework/interfaces/MessageInvoker.java
+++ b/src/main/java/org/myrobotlab/framework/interfaces/MessageInvoker.java
@@ -1,6 +1,7 @@
 package org.myrobotlab.framework.interfaces;
 
 import org.myrobotlab.framework.Message;
+import org.myrobotlab.framework.StaticType;
 
 public interface MessageInvoker {
   /**
@@ -10,7 +11,11 @@ public interface MessageInvoker {
    * @param msg
    * @return
    */
-  public Object invoke(Message msg);
+  default Object invoke(Message msg) {
+    return invoke(msg, new StaticType<>(){});
+  }
+
+   R invoke(Message msg, StaticType returnType);
 
   /**
    * Invoke a method on a service with params.
@@ -21,6 +26,10 @@ public interface MessageInvoker {
    * @param params - the parameters to pass to the method
    * @return - the return value of the method
    */
-  public Object invokeOn(boolean blockLocally, Object obj, String method, Object... params);
+  default Object invokeOn(boolean blockLocally, Object obj, String method, Object... params) {
+    return invokeOn(blockLocally, obj, method, new StaticType<>(){}, params);
+  }
+
+   R invokeOn(boolean blockLocally, Object obj, String method, StaticType returnType, Object... params);
 
 }
diff --git a/src/main/java/org/myrobotlab/framework/interfaces/MessageSender.java b/src/main/java/org/myrobotlab/framework/interfaces/MessageSender.java
index ba0f5f1f19..4da259fe24 100644
--- a/src/main/java/org/myrobotlab/framework/interfaces/MessageSender.java
+++ b/src/main/java/org/myrobotlab/framework/interfaces/MessageSender.java
@@ -1,6 +1,7 @@
 package org.myrobotlab.framework.interfaces;
 
 import org.myrobotlab.framework.Message;
+import org.myrobotlab.framework.StaticType;
 import org.myrobotlab.framework.TimeoutException;
 
 public interface MessageSender extends NameProvider {
@@ -14,7 +15,7 @@ public interface MessageSender extends NameProvider {
    * @param method
    *          - method of destination service
    */
-  public void send(String name, String method);
+  void send(String name, String method);
 
   /**
    * Send invoking messages to remote location to invoke {name} instance's
@@ -27,7 +28,7 @@ public interface MessageSender extends NameProvider {
    * @param data
    *          - parameter data
    */
-  public void send(String name, String method, Object... data);
+  void send(String name, String method, Object... data);
 
   /**
    * Base method for sending messages.
@@ -35,16 +36,36 @@ public interface MessageSender extends NameProvider {
    * @param msg
    *          - message to be sent
    */
-  public void send(Message msg);
+  void send(Message msg);
 
-  public Object sendBlocking(String name, String method) throws InterruptedException, TimeoutException;
+  default Object sendBlocking(String name, String method) throws InterruptedException, TimeoutException {
+    return sendBlocking(name, method, new StaticType<>() {});
+  }
 
-  public Object sendBlocking(String name, String method, Object... data) throws InterruptedException, TimeoutException;
+   R sendBlocking(String name, String method, StaticType returnType) throws InterruptedException, TimeoutException;
 
-  public Object sendBlocking(String name, Integer timeout, String method, Object... data) throws InterruptedException, TimeoutException;
+  default Object sendBlocking(String name, String method, Object... data) throws InterruptedException, TimeoutException {
+    return sendBlocking(name, method, new StaticType<>(){}, data);
+  }
 
-  public Object sendBlocking(Message msg, Integer timeout) throws InterruptedException, TimeoutException;
+   R sendBlocking(String name, String method, StaticType returnType, Object... data) throws InterruptedException, TimeoutException;
 
-  public Object waitFor(String fullName, String method, Integer timeout) throws InterruptedException, TimeoutException;
+  default Object sendBlocking(String name, Integer timeout, String method, Object... data) throws InterruptedException, TimeoutException {
+    return sendBlocking(name, timeout, method, new StaticType<>(){}, data);
+  }
+
+   R sendBlocking(String name, Integer timeout, String method, StaticType returnType, Object... data) throws InterruptedException, TimeoutException;
+
+  default Object sendBlocking(Message msg, Integer timeout) throws InterruptedException, TimeoutException {
+    return sendBlocking(msg, timeout, new StaticType<>(){});
+  }
+
+   R sendBlocking(Message msg, Integer timeout, StaticType returnType) throws InterruptedException, TimeoutException;
+
+  default Object waitFor(String fullName, String method, Integer timeout) throws InterruptedException, TimeoutException {
+    return waitFor(fullName, method, timeout, new StaticType<>() {});
+  }
+
+   R waitFor(String fullName, String method, Integer timeout, StaticType returnType) throws InterruptedException, TimeoutException;
 
 }
diff --git a/src/main/java/org/myrobotlab/framework/interfaces/ServiceQueue.java b/src/main/java/org/myrobotlab/framework/interfaces/ServiceQueue.java
index b2c85cc97b..e83abc1354 100644
--- a/src/main/java/org/myrobotlab/framework/interfaces/ServiceQueue.java
+++ b/src/main/java/org/myrobotlab/framework/interfaces/ServiceQueue.java
@@ -19,11 +19,6 @@ public interface ServiceQueue {
 
   public void out(Message msg);
 
-  // TODO - put in seperate Invoking interface
-  public Object invoke(String method);
-
-  public Object invoke(String method, Object... params);
-
   // public boolean isLocal();
 
 }
diff --git a/src/main/java/org/myrobotlab/service/Solr.java b/src/main/java/org/myrobotlab/service/Solr.java
index fe3932c5fb..753c6eb0ed 100644
--- a/src/main/java/org/myrobotlab/service/Solr.java
+++ b/src/main/java/org/myrobotlab/service/Solr.java
@@ -960,7 +960,7 @@ public void onMessage(Message message) {
     doc.setField("message_dataEncoding", message.encoding);
     doc.setField("message_name", message.getName());
     doc.setField("sender_method", message.sendingMethod);
-    doc.setField("message_status", message.status);
+    doc.setField("message_status", message.msgType);
     /*
      * This makes no sense.. if (message.getHops() != null) { for (String
      * history : message.getHops()) { doc.addField("history",
diff --git a/src/main/java/org/myrobotlab/service/interfaces/LogPublisher.java b/src/main/java/org/myrobotlab/service/interfaces/LogPublisher.java
index bba0b85e10..45ef8e3c98 100644
--- a/src/main/java/org/myrobotlab/service/interfaces/LogPublisher.java
+++ b/src/main/java/org/myrobotlab/service/interfaces/LogPublisher.java
@@ -1,5 +1,6 @@
 package org.myrobotlab.service.interfaces;
 
+import org.myrobotlab.framework.interfaces.Invoker;
 import org.myrobotlab.framework.interfaces.NameProvider;
 
 /**
@@ -9,7 +10,7 @@
  *         A LogPublisher can publish its own logging messages
  * 
  */
-public interface LogPublisher extends NameProvider {
+public interface LogPublisher extends NameProvider, Invoker {
 
   /**
    * A String is currently used as the log entry - but it could be an object in
@@ -21,17 +22,5 @@ public interface LogPublisher extends NameProvider {
    *          msg to publish
    * @return string
    */
-  public String publishLog(String msg);
-
-  /**
-   * a way to publish the log messages and log entries
-   * 
-   * @param method
-   *          method
-   * @param params
-   *          params
-   * @return returned object
-   * 
-   */
-  public Object invoke(String method, Object... params);
+  String publishLog(String msg);
 }
diff --git a/src/main/java/org/myrobotlab/service/interfaces/MrlCommPublisher.java b/src/main/java/org/myrobotlab/service/interfaces/MrlCommPublisher.java
index 563f1e007a..3769229ed7 100755
--- a/src/main/java/org/myrobotlab/service/interfaces/MrlCommPublisher.java
+++ b/src/main/java/org/myrobotlab/service/interfaces/MrlCommPublisher.java
@@ -1,6 +1,7 @@
 package org.myrobotlab.service.interfaces;
 
 import org.myrobotlab.arduino.BoardInfo;
+import org.myrobotlab.framework.interfaces.Invoker;
 import org.myrobotlab.sensor.EncoderData;
 import org.myrobotlab.service.data.PinData;
 import org.myrobotlab.service.data.SerialRelayData;
@@ -12,7 +13,7 @@
  * @author kwatters
  *
  */
-public interface MrlCommPublisher {
+public interface MrlCommPublisher extends Invoker {
 
   public void onBytes(byte[] data);
 
@@ -48,8 +49,5 @@ public BoardInfo publishBoardInfo(Integer version/* byte */,
 
   public void ackTimeout();
 
-  // Necessary evil so Msg.java can invoke the publish methods on the publisher
-  // service.
-  public Object invoke(String method, Object... params);
 
 }
diff --git a/src/main/java/org/myrobotlab/utils/Pair.java b/src/main/java/org/myrobotlab/utils/Pair.java
index 4ebb4f15a0..9aeca2e80c 100644
--- a/src/main/java/org/myrobotlab/utils/Pair.java
+++ b/src/main/java/org/myrobotlab/utils/Pair.java
@@ -6,7 +6,7 @@
  * of potentially different types. Allows
  * generics to be leveraged for type constraining
  * or more compiler-friendly collections operations.
- * 
+ *
  * @param  The type of {@link #first}
  * @param  The type of {@link #second}
  * @author AutonomicPerfectionist

From b83674082c3b4902e0a8ba17da6f2368445a0a40 Mon Sep 17 00:00:00 2001
From: Branden Butler 
Date: Tue, 23 May 2023 19:18:13 -0500
Subject: [PATCH 3/3] Update tests for invoke(), sendBlocking(), etc StaticType
 enhancements; add tests for Return message deser

---
 .../myrobotlab/arduino/MrlCommDirectTest.java |  3 +-
 .../org/myrobotlab/codec/CodecUtilsTest.java  | 50 +++++++++++++++++++
 .../org/myrobotlab/service/PythonTest.java    |  1 +
 .../service/VirtualArduinoTest.java           |  3 +-
 .../org/myrobotlab/service/WebGuiTest.java    | 11 ++--
 5 files changed, 63 insertions(+), 5 deletions(-)

diff --git a/src/test/java/org/myrobotlab/arduino/MrlCommDirectTest.java b/src/test/java/org/myrobotlab/arduino/MrlCommDirectTest.java
index 7bb84fdc91..6443b08d75 100755
--- a/src/test/java/org/myrobotlab/arduino/MrlCommDirectTest.java
+++ b/src/test/java/org/myrobotlab/arduino/MrlCommDirectTest.java
@@ -7,6 +7,7 @@
 
 import org.junit.Test;
 import org.myrobotlab.framework.QueueStats;
+import org.myrobotlab.framework.StaticType;
 import org.myrobotlab.logging.LoggerFactory;
 import org.myrobotlab.sensor.EncoderData;
 import org.myrobotlab.serial.PortJSSC;
@@ -361,7 +362,7 @@ public void ackTimeout() {
   }
 
   @Override
-  public Object invoke(String method, Object... params) {
+  public  R invoke(String method, StaticType returnType, Object... params) {
     log.warn("Dont invoke in a unit test!!!!!!!!!!!!!!!!!!!!!!");
     return null;
   }
diff --git a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java
index 57152b0d92..9af4c76287 100644
--- a/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java
+++ b/src/test/java/org/myrobotlab/codec/CodecUtilsTest.java
@@ -1,6 +1,7 @@
 package org.myrobotlab.codec;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
 
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -8,8 +9,15 @@
 
 import org.junit.Ignore;
 import org.junit.Test;
+import org.myrobotlab.codec.json.JsonDeserializationException;
+import org.myrobotlab.framework.Message;
+import org.myrobotlab.framework.StaticType;
+import org.myrobotlab.framework.TimeoutException;
+import org.myrobotlab.net.Http;
+import org.myrobotlab.service.Runtime;
 import org.myrobotlab.service.data.Locale;
 import org.myrobotlab.test.AbstractTest;
+import org.myrobotlab.utils.ObjectTypePair;
 
 public class CodecUtilsTest extends AbstractTest {
 
@@ -100,4 +108,46 @@ public void testDefaultSerialization() {
     
   }
 
+  @Test
+  public void returnMessageTestSimpleTypes() {
+    String retVal = "retVal";
+    // Put directly in blocking list because sendBlocking() won't use it for local services
+    Runtime.getInstance().getInbox().blockingList.put(
+            "runtime.onBlocking",
+            new ObjectTypePair<>(null, new StaticType(){})
+    );
+
+    Message returnMsg = Message.createMessage("test", "runtime", "onBlocking", new Object[]{CodecUtils.toJson(retVal)});
+    returnMsg.msgType = Message.MSG_TYPE_RETURN;
+    returnMsg.encoding = "json";
+    Message deserMessage = CodecUtils.decodeMessageParams(new Message(returnMsg));
+
+    assertEquals(retVal, deserMessage.data[0]);
+
+    // This should now throw, notice the Integer type instead of String
+    Runtime.getInstance().getInbox().blockingList.put(
+            "runtime.onBlocking",
+            new ObjectTypePair<>(null, new StaticType(){})
+    );
+    returnMsg = Message.createMessage("test", "runtime", "onBlocking", new Object[]{CodecUtils.toJson(retVal)});
+    returnMsg.msgType = Message.MSG_TYPE_RETURN;
+    returnMsg.encoding = "json";
+    Message finalReturnMsg = returnMsg;
+    assertThrows(JsonDeserializationException.class, () -> CodecUtils.decodeMessageParams(finalReturnMsg));
+
+    Runtime.getInstance().getInbox().blockingList.put(
+            "runtime.onBlocking",
+            new ObjectTypePair<>(null, new StaticType(){})
+    );
+    returnMsg = Message.createMessage("test", "runtime", "onBlocking", new Object[]{CodecUtils.toJson(retVal)});
+    // Setting msgType to null makes CodecUtils ignore blockingList
+    returnMsg.msgType = null;
+    returnMsg.encoding = "json";
+    deserMessage = CodecUtils.decodeMessageParams(new Message(returnMsg));
+
+    assertEquals(retVal, deserMessage.data[0]);
+
+
+  }
+
 }
diff --git a/src/test/java/org/myrobotlab/service/PythonTest.java b/src/test/java/org/myrobotlab/service/PythonTest.java
index 97a3f2fe0c..2b9d3dadf6 100644
--- a/src/test/java/org/myrobotlab/service/PythonTest.java
+++ b/src/test/java/org/myrobotlab/service/PythonTest.java
@@ -5,6 +5,7 @@
 
 import java.util.Map;
 
+import org.junit.Test;
 import org.myrobotlab.framework.Service;
 import org.python.core.PyInteger;
 
diff --git a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java
index bac8388134..aa47a198e0 100755
--- a/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java
+++ b/src/test/java/org/myrobotlab/service/VirtualArduinoTest.java
@@ -11,6 +11,7 @@
 import org.myrobotlab.arduino.virtual.MrlServo;
 import org.myrobotlab.framework.QueueStats;
 import org.myrobotlab.framework.Service;
+import org.myrobotlab.framework.StaticType;
 import org.myrobotlab.sensor.EncoderData;
 import org.myrobotlab.service.data.PinData;
 import org.myrobotlab.service.data.SerialRelayData;
@@ -218,7 +219,7 @@ public void ackTimeout() {
   }
 
   @Override
-  public Object invoke(String method, Object... params) {
+  public  R invoke(String method, StaticType returnType, Object... params) {
     log.warn("Don't invoke in a unit test!!!!!!");
     return null;
   }
diff --git a/src/test/java/org/myrobotlab/service/WebGuiTest.java b/src/test/java/org/myrobotlab/service/WebGuiTest.java
index b82b1bf72a..ecd159f7eb 100644
--- a/src/test/java/org/myrobotlab/service/WebGuiTest.java
+++ b/src/test/java/org/myrobotlab/service/WebGuiTest.java
@@ -17,10 +17,12 @@
 import org.myrobotlab.codec.CodecUtils;
 import org.myrobotlab.framework.Message;
 import org.myrobotlab.framework.Service;
+import org.myrobotlab.framework.StaticType;
 import org.myrobotlab.framework.TimeoutException;
 import org.myrobotlab.logging.LoggerFactory;
 import org.myrobotlab.net.Http;
 import org.myrobotlab.test.AbstractTest;
+import org.myrobotlab.utils.ObjectTypePair;
 import org.slf4j.Logger;
 
 public class WebGuiTest extends AbstractTest {
@@ -147,8 +149,11 @@ public void noQuotesTest() {
   public void sendBlockingTest() throws InterruptedException, TimeoutException {
     String retVal = "retVal";
     // Put directly in blocking list because sendBlocking() won't use it for local services
-    Runtime.getInstance().getInbox().blockingList.put("runtime.onBlocking", new Object[1]);
-    Object[] blockingListRet = Runtime.getInstance().getInbox().blockingList.get("runtime.onBlocking");
+    Runtime.getInstance().getInbox().blockingList.put(
+            "runtime.onBlocking",
+                    new ObjectTypePair<>(null, new StaticType(){})
+    );
+    ObjectTypePair blockingListRet = Runtime.getInstance().getInbox().blockingList.get("runtime.onBlocking");
 
     // Delay in a new thread so we can get our wait() call in first
     new Thread(() -> {
@@ -167,7 +172,7 @@ public void sendBlockingTest() throws InterruptedException, TimeoutException {
       }
     }
 
-    assertEquals(retVal, blockingListRet[0]);
+    assertEquals(retVal, blockingListRet.first);
   }