Last Updated 6/10/99 - Jack Harich
The Message Subsystem needs a second iteration to support the goals of UHR better. Here we examine the forces and present the new design. Document Status - Under design and feedback.
Improvement Forces
First read the first iteration Message Subsystem for the Mini Process info on all this. As the BA is examined, used by more developers, and faces new domains, shortcomings are apparent. These are:
It needs to be interfaced based for Infinite Extensibility. This is by far the greatest force.
This subsystem is incredibly important to BA systems. In general the first iteration needed the usual rewrite. As Fred Brooks said, "Plan to throw one away. You will anyhow."
It needs to use Java Collections for various reasons, such as advanced system structure analysis, visual tools and use of third party software. However, since Collections will not be part of Java Micro edition, we will use Hashtable and Vector instead. Sigh....
MessageListener and MessageSource both need to return a single "Info" object describing themselves. This will allow greater extensibility.
MessageDefs need to be shared via a resuable mechanism. Presently sources must each create their MessageDefs, a problem if the same def comes from multiple sources.
There is too much non-symmetry between sources and listeners. The presence of symmetry is an potential indicator of stronger design, regardless of domain.
Various minor cleanup needs to be done.
Several classes were for the BA Editor (a non-kernel domain) or others, and so should be elsewhere.
Need to support or allow multi-threaded use.
"Internal Message Synchronization" mechanism - processMessage() is entered. Then the part would put a lock on an internal object, process the Message, then release the lock, and then return from the processMessage() method.
Use Case 1 - If a Message is for notification only, it's only read, and so no
problem.Use Case 2 - If a Message is an "acquire Message" then writes to it occur. Due to the delivery mechanism, the Message is delivered to only one listener at a
time, and so no problem.Use Case 3 - If a Message needs to be processed synchronized by the sender's choice, use Internal Message Synchronization if the Message has the property "IsSynchronized" = true. If we use this a lot, we'll add isSynchronized() to Message.
Use Case 4 - If a Message needs to be processed synchronized by the listener's
choice, use Internal Message Synchronization.
New Design
This will use Java 1.1 Collections. It will be interface based with default implementations. The Bean Spec naming style is used for uniformity and clarity. The extraneous classes used by the BA Editor or others will be removed, so that only core classes remain. Here's the Class Diagram. As you can see, we have a central axis of infrastructure to support:
- Passing Messages from sources to listeners.
- Easy definition and querying of Message structure and links for uses such as validation, inspection and runtime uses, such as trader style routing.
- Sharing of MessageDefs via the MessageDefRegistry, a completely new feature.

Here are the interfaces in condensed format, in approximate order of importance. The interfaces will be in org.jcon.msg. The default implementations and abstract classes will be in org,jcon.msg.std. Related utilities (such as MessageRouterRegistry, maybe a MessageLib) will be in org.jcon.msg.util.
// Sent from a MessageSource to its MessageListeners. public interface Message extends PropMap, Cloneable, Serializable { // Additional methods not in PropMap are: // Note name and isAcquire are set only via constructors public String getName(); // The name is immutable // Future use - public boolean isAcquire(); // If true acquire properties can be set public Object clone(String messageName); } // Allows listening to Messages of interest. public interface MessageListener extends MessageDefRegistryUser { public void processMessage(Message message); public MessageListenerInfo getMessageListenerInfo(); } // Allows subscription to named Messages, sends Messages to subscribers. public interface MessageSource extends MessageDefRegistryUser { public void addMessageListener(String eventName, MessageListener listener); public void removeMessageListener(String eventName, MessageListener listener); public MessageRouter getMessageRouter(); // For tool use only public MessageSourceInfo getMessageSourceInfo(); } // Allows a shared mechanism to be provided public interface MessageDefRegistryUser { public void setMessageRegistry(MessageDefRegistry registry); } // Routes Messages to subscribers. Most MessagesSources have one of these. // Null is not allowed for messageName unless indicated. // Note - Getting top big when added filters, needs subdividing public interface MessageRouter extends BasicPropMap { // Additional methods not in BasicPropMap follow. Some are just for clarity. public void addListener(String messageName, MessageListener listener); public void removeListener(String messageName, MessageListener listener); public boolean hasListener(String messageName, MessageListener listener); public boolean hasListener(String messageName); public Enumeration getMessageNames(); public Enumeration getMessageListeners(String messageName); // null for all public Enumeration getMessageNamesWithListener(MessageListener listener); // Messages can be reused and resent for speed, to avoid creating new // objects. Thus listeners should NOT keep a reference to Messages received. // If they need it, clone it. // The second is a convenience method, for Messages with no properties. public boolean sendMessage(Message message, Object source); public boolean sendMessage(String messageName, Object source); // For filters, a new feature. A messageName of null means all. // *** Change of design - Do not do filter feature now public void addPreFilter(String messageName, MessagePreFilter filter); public void removePreFilter(String messageName, MessagePreFilter filter); public Enumeration getPreFilters(String messageName); public void addPostFilter(String messageName, MessagePostFilter filter); public void removePostFilter(String messageName, MessagePostFilter filter); public Enumeration getPostFilters(String messageName); } // QUESTIONS - Do we need a "MessageCastFilter" that's called before // each cast? Does the router need to be in the filter call arguments? // Filters are designed to allow observing, modifying and/or canceling Message // multicasts, either before or after each multicast. Their use is optional. public interface MessagePreFilter { // Called before a Message is multicast. The returned Message is the one // ultimately multicast. Returning null means the Message should not be sent. public Message preFilterMessage(Message message); } public interface MessagePostFilter { // Called after a Message is multicast. The returned Message is the one // used in the next postFilterMessage() call, if any. Returning null means // further postFilterMessage() calls, if any, should stop. public Message postFilterMessage(Message message); } // Contains MessageDefs the listener is interested in processing. Will evolve. public interface MessageListenerInfo { public void setMessageDef(MessageDef def); public Enumeration getMessageNames(); public MessageDef getMessageDef(String messageName); public Enumeration getMessageDefs(); } // Contains MessageDefs that can be sent. Will evolve. // Note similarity to MessageListenerInfo. They may evolve differently. public interface MessageSourceInfo { public void setMessageDef(MessageDef def); public Enumeration getMessageNames(); public MessageDef getMessageDef(String messageName); public Enumeration getMessageDefs(); } // Defines the structure of a Message, which is its name and properties. public interface MessageDef { public void setMessageName(String messageName); public String getMessageName(); public void setProperty(MessageDefProperty property); public void setProperty(String propertyName, Class type, String shortDescription); public MessageDefProperty removeProperty(String propertyName); public Enumeration getPropertyNames(); public MessageDefProperty getProperty(String propertyName); public Enumeration getProperties(); } // Defines a single Message property. public interface MessageDefProperty { public void setName(String name); public String getName(); public void setType(Class type); public Class getType(); // Future feature - public void setAcquire(boolean isAcquire); // Future feature - public boolean isAcquire(); // If true property can be set by listeners public void setShortDescription(String shortDescription); public String getShortDescription(); public void setLongDescription(String longDescription); public String getLongDescription(); } // A registry containing MessageDefs that clients such as sources and listeners // can refer to instead of having to create their own MessageDefs. They use // getDef() or the create convenience methods. The design is NOT complete public interface MessageDefRegistry implements PropMap { // Additional methods not in PropMap are: (may add more convenience methods) public void setDef(MessageDef def); public void replaceDef(MessageDef def); public Iterator getMessageNames(); public MessageDef getDef(String messageName); public Iterator getDefs(); // These are convenience methods: public MessageListenerInfo createMessageListenerInfo(String messageName); public MessageListenerInfo createMessageListenerInfo(String name1, String name2); public MessageListenerInfo createMessageListenerInfo(String name1, String name2, String name3); public MessageListenerInfo createMessageListenerInfo(String[] messageNames); public MessageListenerInfo createMessageListenerInfo(Iterator messageNames); // And 5 similar methods for createMessageSourceInfo(...) } // The workhorse. In org.jcon.util public interface PropMap extends BasicPropMap, ConvenientStringMap, Serializable, Cloneable { } // Contains a key value collection like Hashtable. All keys are Strings. public interface BasicPropMap { // In org.jcon.util public Object set(String key, Object value); public Object get(String key); public Object removeKey(String key); // Future feature - public int removeValue(Object value); public boolean containsKey(String key); public boolean containsValue(Object value); public Enumeration enumerateKeys(); // Future feature - public Enumeration enumerateValues(); public boolean isEmpty(); public int getSize(); public void replaceAllWith(PropMap propMap); public String toString(); public Object clone(); } // Contains convenience methods to reduce code bloat and improve clarity // when using a PropMap. It can also be used with other types of collections. // For consistency Param will also implement this in its 2nd iteration. // Note - We changed "put" to "set" to be more Bean Spec like. public interface ConvenientStringMap { // In org.jcon.util public void setString(String key, String value); public String getString(String key); public String getStringDefault(String key, String defaultValue); public void setBoolean(String key, boolean value); public void setTrue(String key); public void setFalse(String key); public boolean getBoolean(String key); // For symmetry public boolean isTrue(String key); public boolean isTrueDefault(String key, boolean defaultValue): public boolean isFalse(String key); public boolean isFalseDefault(String key, boolean defaultValue): public void setInt(String key, int value); public int getInt(String key); public int getIntDefault(String key, int defaultValue); public void setLong(String key, long value); public long getLong(String key); public long getLongDefault(String key, long defaultValue); public void setFloat(String key, float value); public float getFloat(String key); public float getFloatDefault(String key, float defaultValue); public void setDouble(String key, double value); public double getDouble(String key); public double getDoubleDefault(String key, double defaultValue); }.