{"id":621,"date":"2020-06-30T17:21:40","date_gmt":"2020-06-30T17:21:40","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=621"},"modified":"2025-04-09T06:15:57","modified_gmt":"2025-04-09T06:15:57","slug":"lesson-4","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-4\/","title":{"rendered":"Lesson 4: Fine Grained Object Routing"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In the <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-3\/\">previous lesson<\/a> we used a WebSocket hub to broadcast \u201cchat objects\u201d from a producer to all connected consumers. Here we will implement \u201cchat rooms\u201d, which require finer control over the broadcast to ensure that chat objects are delivered only to consumers that are in the same chat room as the producer of the message. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will also show how to send a message to a specific consumer in order to implement \u201cprivate messaging\u201d. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We start with the code of Lesson 3 as a basis. We need to modify <code>ChatMain<\/code> and replace <code>ChatConsume<\/code>. Let\u2019s see the first:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson4;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.tutorial.objectLib.Chat;\nimport org.spiderwiz.tutorial.objectLib.ConsoleMain;\nimport org.spiderwiz.zutils.ZDictionary;\n\n\/**\n * Provides the entry point of the application. Initializes and executes the Spiderwiz framework.\n *\/\npublic class ChatMain extends ConsoleMain{\n    private static final String ROOT_DIRECTORY = &quot;&quot;;\n    private static final String APP_NAME = &quot;Chat Room Client&quot;;\n    private static final String APP_VERSION = &quot;Z1.01&quot;;  \/\/ Version Z1.01: Initial version\n    \n    private String myName;          \/\/ Get this from the configuration file\n    private String myChatRoom;      \/\/ Get this from the configuration file\n    private Chat chat = null;       \/\/ A Chat object for committing chat messages\n    private UUID lastSender = null; \/\/ Stores the application UUID of the last received message\n\n    \/**\n     * Class constructor with constant parameters.\n     * @param confFileName  configuration file name, provided as a command argument\n     *\/\n    public ChatMain(String confFileName) {\n        super(ROOT_DIRECTORY, confFileName, APP_NAME, APP_VERSION);\n    }\n    \n    \/**\n     * @return the class instance as ChatMain type.\n     *\/\n    public static ChatMain getInstance() {\n        return (ChatMain)ConsoleMain.getInstance();\n    }\n\n    public String getMyChatRoom() {\n        return myChatRoom;\n    }\n\n    public synchronized UUID getLastSender() {\n        return lastSender;\n    }\n\n    public synchronized void setLastSender(UUID lastSender) {\n        this.lastSender = lastSender;\n    }\n\n    \/**\n     * Application entry point. Instantiate the class and initialize the instance.\n     * \n     * @param args the command line arguments. The first argument is used as the configuration file name.\n     *\/\n    \/**\n     * @param args the command line arguments\n     *\/\n    public static void main(String[] args) {\n        \/\/ Don't do anything if there is no configuration file name\n        if (args.length == 0) {\n            System.out.println(&quot;Configuration file has not been defined&quot;);\n            return;\n        }\n        new ChatMain(args[0]).init();\n    }\n\n    \/**\n     * Before starting Spiderwiz engine (but after reading program configuration) get the chatter name and chat room.\n     * @return true if both are provided, false if not.\n     *\/\n    @Override\n    protected boolean preStart() {\n        myName = getConfig().getProperty(&quot;my name&quot;);\n        if (myName == null || myName.isBlank()) {\n            System.out.println(&quot;Chatter name has not been defined&quot;);\n            return false;\n        }\n        myChatRoom = getConfig().getProperty(&quot;my room&quot;);\n        if (myChatRoom == null || myChatRoom.isBlank()) {\n            System.out.println(&quot;Chat room has not been defined&quot;);\n            return false;\n        }\n        System.out.printf(&quot;You are chatting as %1$s in room %2$s. Go ahead and type your messages&quot;, myName, myChatRoom);\n        System.out.println();\n        return true;\n    }\n    \n    \/**\n     * @return the list of produced objects, in this case Chat is the only one.\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{Chat.ObjectCode};\n    }\n\n    \/**\n     * @return the list of consumed objects, in this case Chat is the only one.\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{Chat.ObjectCode};\n    }\n\n    \/**\n     * Add ChatImp implementation class to the object factory list of this application.\n     * @param factoryList\n     *\/\n    @Override\n    protected void populateObjectFactory(List&lt;Class&lt;? extends DataObject&gt;&gt; factoryList) {\n        super.populateObjectFactory(factoryList);\n        factoryList.add(ChatImp.class);\n    }\n\n    \/**\n     * Get application parameters that are used for object routing.\n     * @return  a ZDictionary object (extension of Map&lt;String, String&gt;) that contains a mapping of &quot;room&quot; to the chat room retrieved\n     *          from the configuration file.\n     *\/\n    @Override\n    public Map&lt;String, String&gt; getAppParams() {\n        ZDictionary myParams = new ZDictionary();\n        myParams.put(&quot;room&quot;, myChatRoom);\n        return myParams;\n    }\n\n    \/**\n     * Process an input line\n     * @param line      the line to be broadcast as a chat message.\n     * @return true if processed successfully\n     *\/\n    @Override\n    protected boolean processConsoleLine(String line) {\n        try {\n            \/\/ If not yet done, instantiate a Chat object and set its 'name' field.\n            if (chat == null) {\n                chat = ChatMain.createTopLevelObject(Chat.class, null);\n                chat.setName(myName);\n            }\n            \/\/ If the read line starts with '&gt;' send a private message to the sender of the last received message\n            String destination = null;\n            if (line.startsWith(&quot;&gt;&quot;)) {\n                if (getLastSender() == null)\n                    return true;\n                destination = getLastSender().toString();\n                line = line.substring(1);\n            }\n            \/\/ Set chat message and commit\n            chat.setMessage(line);\n            chat.commit(destination);\n            return true;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Theoretically arriving here if the Chat object does not contain ObjectCode static field or the field is not public.\n            \/\/ In this case, send a command exception message.\n            sendExceptionMail(ex, &quot;Cannot instantiate Chat class&quot;, null, false);\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">There are two new fields \u2013 <code>myChatRoom<\/code> (line 20), for storing the chat room name, and <code>lastSender<\/code> (line 22) for storing the name of the sender of the last received message so that we can reply personally. These fields also have getters and a setter (lines 29 \u2013 49), because we need to access them from another class (or for synchronization).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next interesting thing to look at is <code>getInstance()<\/code> (line 35). It casts the value returned by <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getInstance()\">Main.getInstance()<\/a> to its actual type.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In <code>preStart()<\/code> (line 73) we load another property from the configuration file \u2013 <code>my room<\/code>. This determines the name of the chat room used by the application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As we will see shortly, in this lesson we replace <code>ChatConsume<\/code> by <code>ChatImp<\/code>, because our implementation class implements both consumer and producer aspects. <code>ChatImp<\/code> is registered in <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#populateObjectFactory(java.util.List)\">populateObjectFactory()<\/a> (line 112).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The method <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getAppParams()\">getAppParams()<\/a> (line 21), introduced here for the first time, returns a mapping of <em>names<\/em> to <em>values <\/em>that is specific to the application instance. Our application maps <code>\u201croom\u201d<\/code> to the name of the chat room that it read from the configuration file. We will see its use later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, in <code>processConsoleLine()<\/code> (line 133) we add the \u201cprivate message\u201d feature. We check if the message that the user wants to send starts with \u201c&gt;\u201d, and if it does and a last sender exists, the message is sent exclusively to that one.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note the use of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#commit(java.lang.String)\">commit(destinations)<\/a> in line 150. This is another version of the <code>commit()<\/code> method used until now. The argument <code>destinations<\/code> can be a list of stringified application UUIDs concatenated by \u2018;\u2019 or <code>null<\/code>. In the latter case, the message is broadcast to all the consumers of the object type just like a <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#commit()\"><code>commit()<\/code><\/a> without arguments. Here we use it with <code>null<\/code> if we want to send the message to the chat room, and with one UUID when we want to send a private message.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now to <code>ChatImp<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson4;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.tutorial.objectLib.Chat;\n\n\/**\n * Consumer implementation of the Chat class\n *\/\npublic class ChatImp extends Chat{\n\n    \/**\n     * Called when a chat message is received. Print the sender name followed by a colon, then the message.\n     * Store the sender UUID for private messaging.\n     * @return true to indicate that the event has been handled.\n     *\/\n    @Override\n    protected boolean onEvent() {\n        System.out.printf(&quot;%1$s: %2$s&quot;, getName(), getMessage());\n        System.out.println();\n        ChatMain.getInstance().setLastSender(getOriginUUID());\n        return true;\n    }\n\n    \/**\n     * Restrict message sending to:\n     *      1. applications that are not the current application.\n     *      2. applications that are on the same chat room as the current application.\n     * @param appUUID           destination application UUID\n     * @param appName           destination application name\n     * @param userID            destination user ID\n     * @param remoteAddress     remote network address\n     * @param appParams         application parameters of the destination application\n     * @return true if the object should be sent to this destination\n     *\/\n    @Override\n    protected boolean filterDestination(UUID appUUID, String appName, String userID, String remoteAddress,\n        Map&lt;String, String&gt; appParams\n    ) {\n        return\n            !ChatMain.getInstance().getAppUUID().equals(appUUID) &amp;&amp;\n            appParams != null &amp;&amp;\n            ChatMain.getInstance().getMyChatRoom().equals(appParams.get(&quot;room&quot;));\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Like in Lesson 3, we override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a>. In addition to printing out the received message we also call <code>ChatMain.getInstance().setLastSender()<\/code> to save the application UUID of the sender. This is used for private messaging as we mentioned above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that this time we do not compare the UUID of the sender to the current application\u2019s one, because we do the filtering in <code>filterDestination()<\/code> as we will see in a moment. Practically there is no difference between doing it either way, but aesthetically we prefer to put all the filtering in one place.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The new thing here is the override of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#filterDestination(java.util.UUID,java.lang.String,java.lang.String,java.lang.String,java.util.Map)\">filterDestination()<\/a>. This method filters outgoing messages so they are destined to the proper destinations. The method is called once for each potential destination, and you have the option to return <code>true<\/code> to include it in the sending and <code>false<\/code> to exclude it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The filtering method provides few parameters that can be used to determine the filtering. In our case we use <code>appUUID<\/code> to exclude self-messaging, and <code>appParams<\/code> to check whether the chat room name of the potential destination is the same as the one loaded from the configuration file by the current application.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The coding work is completed. It remains to define the configuration files. We will test with the following:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 1\n[log folder]\/tests\/Chat1\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]FERRANDO\n[my room]COSI FAN TUTTE<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 2\n[log folder]\/tests\/Chat2\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]GUGLIELMO<\/pre><\/div>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[application name]Chat 3\n[log folder]\/tests\/Chat3\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]DON ALFONSO\n[my room]COSI FAN TUTTE<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Run it, and you will see that messages sent from FERRANDO are received by DON ALFONSO and vice versa, because both are in COSI FAN TUTTE chat room, but GUGLIELMO, who is not in the room, is excluded.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then run again and add GUGLIELMO to the room. Try issuing a chat message by FERRANDO, and then let GUGLIELMO type a message that starts with \u2018>\u2019, which means a private message to the last sender. Indeed FERRANDO will get it but DON ALFONSO will not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this lesson we had the chat room name loaded from the configuration file. Obviously this is not really useful because in real life chatters enter and exit chat rooms dynamically. This is what we will do in the next chapter, which introduces <em>Persistent Data Objects and the Object Hierarchy<\/em>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>A word about distributed routing<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In common implementations of a chat service, users connect to a central system, which manages messaging and distributes messages to chat rooms. Most Internet services, from social networks to online marketing, work like that. We have introduced here a different paradigm \u2013 distributed routing. With this paradigm, the destination of each <em>data object<\/em> is set by the origin of the object, and the object finds its way to its destination through any of the available paths. This is actually the same paradigm as the Internet itself. This concept has many implications that are out of scope for a tutorial but we address them in our blog. See for example <a href=\"http:\/\/spiderwiz.org\/project\/vision\/\">The Spiderwiz Vision \u2013 Decentralizing the Internet<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to control the routing of specific data objects between specific applications using various techniques. We will use it to introduce chat rooms and private messages into the chat service of Lesson 3.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":4,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-621","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/621","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/comments?post=621"}],"version-history":[{"count":51,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/621\/revisions"}],"predecessor-version":[{"id":1347,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/621\/revisions\/1347"}],"up":[{"embeddable":true,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/145"}],"wp:attachment":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/media?parent=621"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}