{"id":667,"date":"2020-07-04T20:11:00","date_gmt":"2020-07-04T20:11:00","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=667"},"modified":"2025-04-09T06:15:57","modified_gmt":"2025-04-09T06:15:57","slug":"lesson-5","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-5\/","title":{"rendered":"Lesson 5: Persistent Data Objects and the Data Object Tree"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The Spiderwiz framework is based on the concept of shared <em>Data Objects<\/em>. We have seen until now two examples &#8211; <code>HelloWorld<\/code> in <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-3\/\">Lesson 2<\/a> and <code>Chat<\/code> in the previous two lessons. Both of them were <em>top-level objects<\/em>, the second was <em>disposable<\/em> while the first was not. In this lesson we will demonstrate the use of <em>non-disposable<\/em> objects that live in a <em>Data Object Tree<\/em>. The concept of <em>committing<\/em> and <em>event handling<\/em> of data object modifications will be extended to the entire object tree.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We continue with the chat service of <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-4\/\">Lesson 4<\/a> and turn the static chat rooms that we had into rooms that can be dynamically created, deleted, joined and left. This is achieved with the following data object architecture: <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The top level objects are of type <code>ChatRoom<\/code>. These will be  created  dynamically and will persist until deleted.<\/li><li>Another object type, <code>Chatter<\/code>, represents a user in a room. <code>Chatter<\/code> objects are children of <code>ChatRoom<\/code> objects. They are created when a user joins a chat room and removed when the user leaves the room.<\/li><li>Each <code>Chatter<\/code> has <code>ChatMessage<\/code> children that deliver chat messages. Unlike <code>ChatRoom<\/code> and <code>Chatter<\/code>, <code>ChatMessage<\/code> objects are disposable, for obvious reasons.<\/li><li>We will also keep the <code>Chat<\/code> object type of Lesson 4 for private messaging (that is done out of the chat room). This type will stay a <em>disposable<\/em> <em>root object<\/em>.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As usual, we start with implementing base classes that are placed in the <code>objectLib<\/code> package. Here they go:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.objectLib;\n\nimport org.spiderwiz.annotation.WizField;\nimport org.spiderwiz.core.DataObject;\n\n\/**\n * ChatRoom class for Spiderwiz tutorial\n *\/\npublic class ChatRoom extends DataObject{\n    \/**\n     * Mandatory public static field for all data objects.\n     *\/\n    public final static String ObjectCode = &quot;CHTRM&quot;;\n\n    @WizField boolean adult = false;        \/\/ Indicates whether this is an adult only chat room. False by default.\n\n    public synchronized boolean isAdult() {\n        return adult;\n    }\n\n    public synchronized void setAdult(boolean adult) {\n        this.adult = adult;\n    }\n\n    \/**\n     * @return null as this is a root object.\n     *\/\n    @Override\n    protected String getParentCode() {\n        return null;\n    }\n\n    \/**\n     * @return false because objects of this class are persistent\n     *\/\n    @Override\n    protected boolean isDisposable() {\n        return false;\n    }\n\n    \/**\n     * @return false because chat room names are case insensitive.\n     *\/\n    @Override\n    protected boolean isCaseSensitive() {\n        return false;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Like every <em>data object<\/em>, the class has a static <code>ObjectCode<\/code> field with a preset unique value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We define only one <code>@WizField<\/code> annotated property \u2013 its type is <code>boolean<\/code> and its name is <code>adult<\/code>. The field is used to mark the room as adult only. We will see its use later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We do not have a field for the room name because it is the key value used when creating a child in the object\u2019s parent. We will see this in a moment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ChatRoom<\/code> objects are top-level objects, therefore <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getParentCode()\">getParentCode()<\/a> returns <code>null<\/code>. The objects are persistent, therefore <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\">isDisposable()<\/a> returns <code>false<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We also want the room names to be case insensitive so we override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isCaseSensitive()\">isCaseSensitive()<\/a> to return <code>false<\/code> (the default is <code>true<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Next is <code>Chatter<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.objectLib;\n\nimport org.spiderwiz.annotation.WizField;\nimport org.spiderwiz.core.DataObject;\n\n\/**\n * A data object that represents a chat user in a specific chat room\n *\/\npublic class Chatter extends DataObject {\n    \/**\n     * Mandatory public static field for all data objects.\n     *\/\n    public final static String ObjectCode = &quot;CHTTR&quot;;\n    \n    @WizField private String name;              \/\/ Contains the name of the chatter\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    \/**\n     * @return ChatRoom object code\n     *\/\n    @Override\n    protected String getParentCode() {\n        return ChatRoom.ObjectCode;\n    }\n\n    \/**\n     * @return false because objects of this class are persistent\n     *\/\n    @Override\n    protected boolean isDisposable() {\n        return false;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here we have one <code>@WizField<\/code> annotated property of type <code>String<\/code> named <code>name<\/code> that contains the name of the chatting user. The reason that we do not use the name as the object\u2019s key is because we use the application UUID for that purpose. This will be explained later.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As we have mentioned, <code>Chatter<\/code> objects represent a chatter in a specific chat room, so in this case <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getParentCode()\">getParentCode()<\/a> returns <code>ChatRoom.ObjectCode<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Objects of this class are also persistent so <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\">isDisposable()<\/a> returns <code>false<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We have arrived to <code>ChatMessage<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.objectLib;\n\nimport org.spiderwiz.annotation.WizField;\nimport org.spiderwiz.core.DataObject;\n\n\/**\n * A data object representing a message sent in chat room\n *\/\npublic class ChatMessage extends DataObject {\n    \/**\n     * Mandatory public static field for all data objects.\n     *\/\n    public final static String ObjectCode = &quot;CHTMSG&quot;;\n    \n    @WizField private String message;                   \/\/ Contains a chat message\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    \/**\n     * @return Chatter object code\n     *\/\n    @Override\n    protected String getParentCode() {\n        return Chatter.ObjectCode;\n    }\n\n    \/**\n     * @return true as this object is disposable.\n     *\/\n    @Override\n    protected boolean isDisposable() {\n        return true;\n    }\n\n    @Override\n    protected boolean isUrgent() {\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This is also straightforward. There is one <code>@WizField<\/code> annotated property of type <code>String<\/code> named <code>message<\/code> that contains the message,<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getParentCode()\"> getParentCode()<\/a> returns <code>Chatter.ObjectCode<\/code> and<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\"> isDisposable()<\/a> in this case returns <code>true<\/code>. We also override<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isUrgent()\"> isUrgent()<\/a> to return <code>true<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We use the <code>Chat<\/code> class of Lesson 4 as is.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our library contains now all the base classes that we need and we can continue with the implementation. We keep using the service mesh structure that we had in the previous lessons \u2013 a hub (<code>MyHub<\/code>) connected to by several client chatting applications. We connect another application to the hub \u2013 <em>Room Manager<\/em>, which handles the creation, deletion and modification of chat rooms. Let\u2019s start with it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>Room Manager<\/em> is also a command line application that processes console input lines. Here is <code>RoomManagerMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.roomManager;\n\nimport java.util.List;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\nimport org.spiderwiz.tutorial.objectLib.ConsoleMain;\n\n\/**\n * Provides the entry point of the application. Initializes and executes the Spiderwiz framework.\n *\/\npublic class RoomManagerMain extends ConsoleMain {\n    private static final String ROOT_DIRECTORY = &quot;&quot;;\n    private static final String CONF_FILENAME = &quot;room-manager.conf&quot;;\n    private static final String APP_NAME = &quot;Room Manager&quot;;\n    private static final String APP_VERSION = &quot;Z1.01&quot;;  \/\/ Version Z1.01: Initial version\n\n    private static final String CREATE = &quot;create&quot;;  \/\/ create chat room command\n    private static final String DELETE = &quot;delete&quot;;  \/\/ delete chat room command\n    private static final String MODIFY = &quot;modify&quot;;  \/\/ modify chat room command\n    private static final String RENAME = &quot;rename&quot;;  \/\/ rename chat room command\n    \n    \/**\n     * Class constructor with constant parameters.\n     *\/\n    public RoomManagerMain() {\n        super(ROOT_DIRECTORY, CONF_FILENAME, APP_NAME, APP_VERSION);\n    }\n    \n    \/**\n     * Application entry point. Instantiate the class and initialize the instance.\n     * \n     * @param args the command line arguments. Not used in this application.\n     *\/\n    public static void main(String[] args) {\n        new RoomManagerMain().init();\n    }\n\n    \/**\n     * Print out usage instructions at start up\n     * @return true\n     *\/\n    @Override\n    protected boolean preStart() {\n        System.out.printf(\n            &quot;To create a new room: type 'create &lt;room name&gt;'.n&quot;\n                + &quot;Append a plus sign (+) to the name if you want to create an adult only room.n&quot;\n                + &quot;To delete a room type 'delete &lt;room name&gt;'.n&quot;\n                + &quot;To modify the adult categorization of a room:n&quot;\n                + &quot;Type 'modify &lt;room name&gt;+' if you want to make it an adult room.n&quot;\n                + &quot;Type 'modify &lt;room name&gt;' if you want to remove the adult categorization.n&quot;\n                + &quot;To rename a room type 'rename &lt;room name&gt;=&lt;new name&gt;'.n&quot;\n                + &quot;To exit type 'exit'.n&quot;\n        );\n        return true;\n    }\n\n    \/**\n     * @return the list of produced objects, in this case ChatRoom is the only one.\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{ChatRoom.ObjectCode};\n    }\n\n    \/**\n     * @return the list of consumed objects, in this case we do not consume any so we return an empty list.\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{};\n    }\n\n    \/**\n     * Add ChatRoom 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(ChatRoom.class);\n    }\n\n    \/**\n     * Process an input line\n     * @param line      An input line that contains a room management command\n     * @return true if processed successfully\n     *\/\n    @Override\n    protected boolean processConsoleLine(String line) {\n        try {\n            \/\/ Trim line and split to command \/ parameter.\n            \/\/ Then check if there is a parameter and whether it contains the adult symbol.\n            \/\/ If there is not parameter, ignore the command.\n            String command[] = line.trim().split(&quot;s+&quot;, 2);\n            if (command.length &lt; 2)\n                return true;\n            String name[] = command[1].split(&quot;+&quot;, -1);\n            boolean adult = name.length &gt; 1;\n            \/\/ now split for 'rename' command\n            name = name[0].split(&quot;=&quot;);\n            String roomName = name[0];\n            \n            \/\/ If exists, get the ChatRoom object\n            ChatRoom room = getRootObject().getChild(ChatRoom.class, roomName);\n            \n            \/\/ Switch by command and process it\n            switch (command[0].toLowerCase()) {\n            case CREATE:\n                \/\/ Create a new room if does not exist (ignore if it does)\n                if (room == null) {\n                    room = createTopLevelObject(ChatRoom.class, roomName);\n                    room.setAdult(adult);\n                    room.commit();\n                    System.out.printf(&quot;Room %1$s(%2$s) createdn&quot;, roomName, adult ? &quot;adult&quot; : &quot;unrestricted&quot;);\n                }\n                break;\n            case DELETE:\n                \/\/ Delete the room if exists\n                if (room != null) {\n                    room.remove();\n                    room.commit();\n                    System.out.printf(&quot;Room %1$s deletedn&quot;, roomName);\n                }\n                break;\n            case MODIFY:\n                \/\/ Modify the 'adult' field if the room exits\n                if (room != null) {\n                    room.setAdult(adult);\n                    room.commit();\n                    System.out.printf(&quot;Room %1$s set to %2$sn&quot;, roomName, adult ? &quot;adult&quot; : &quot;unrestricted&quot;);\n                }\n                break;\n            case RENAME:\n                \/\/ Rename the room if exists. The rename command is 'rename &lt;old name&gt;=&lt;new name&gt;'\n                if (room != null &amp;&amp; name.length &gt; 1) {\n                    String newName = name[1];\n                    if (!newName.equalsIgnoreCase(roomName)) {\n                        DataObject renamed = room.rename(newName);\n                        if (renamed == null)\n                            System.out.printf(&quot;Cannot rename to already existing room name %sn&quot;, newName);\n                        else {\n                            renamed.commit();\n                            System.out.printf(&quot;Room %1$s was renamed %2$sn&quot;, roomName, newName);\n                        }\n                    }\n                }\n            }\n            return true;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Theoretically arriving here if any data 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 a data object class&quot;, null, false);\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In lines 17-20 we define labels for the various chat room commands \u2013 <code>create<\/code>, <code>delete<\/code>, <code>modify<\/code> and <code>rename<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#preStart()\">preStart()<\/a> (line 43) to print out user instructions when the application starts.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application produces only one object \u2013 <code>ChatRoom<\/code> (line 62) and consumes none (line 70). <code>ChatRoom<\/code> is also added to the Object Factory in line 79.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application tasks are carried out in <code>processConsoleLine()<\/code> (line 89). Each input line is parsed and processed accordingly. We switch (line 107) between the following options:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>CREATE<\/code><\/strong> (line 108): If a room with the given name does not exist yet, use <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#createTopLevelObject(java.lang.Class,java.lang.String)\">createTopLevelObject()<\/a> to create a <code>ChatRoom<\/code> object, set the value of the <code>adult<\/code> field and commit the object. Also print out a message that a room with the given name has been created.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>DELETE<\/code><\/strong> (line 117): If a room with the given name exists, use <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#remove()\">remove()<\/a> to delete the object and commit the deleted object. Also print out a message that a room with the given name has been deleted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>MODIFY<\/code><\/strong> (line 125): If a room with the given name exists, set its <code>adult<\/code> field as in the parsed command and commit the object. Also print out a message that the <code>adult<\/code> field has been set to the required value.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><code>RENAME<\/code><\/strong> (line 133): If a room with the given name exists and the new name differs from the old name, try to rename the object by calling its <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#rename(java.lang.String)\">rename()<\/a> method. The method returns <code>null<\/code> if an object with the new name already exists, so in this case print out an appropriate message. If renaming was done successfully, the method returns a non-<code>null<\/code> <em>data object<\/em> that we commit in order to propagate the renaming action to the consumers of the object. Also print out a message if renaming is successful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The code is ready to run but we need a configuration file. We use the occasion to demonstrate a hybrid network topology. You probably remember that in <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-3\/\">Lesson 3<\/a> we configured <code>MyHub<\/code> to act as a WebSocket server. We will now make it a dual-protocol server by configuring an additional TCP\/IP server. Here is the modified configuration:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]Logs\n[consumer server-1]websocket;ping-rate=30\n[producer server-1]websocket;ping-rate=30\n[consumer server-2]port=10001\n[hub mode]yes<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We connect <em>Room Manager<\/em> to the newly configured server:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]\/tests\/RoomManager\/Logs\n[producer-1]ip=localhost;port=10001<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Pay attention that although the TCP\/IP server is configured in <code>hub.conf<\/code> as <code>consumer server-2<\/code>, we still configure the client as <code>producer-1<\/code>. There is no connection between the serial number of the server configuration to that of the client. The numbers are arbitrary and their sole purpose is to differentiate multiple entries in the same configuration file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Everything is ready. You can run the application and play with it, but of course it doesn\u2019t have much practical value as long as there are no chatters in the rooms. So let\u2019s see the other application of this lesson \u2013 <em>Dynamic Chat Room Client<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The new chat client has the following features:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Users type <code>!join room-name<\/code> to join a chat room.<\/li><li>Users type <code>!leave<\/code> to leave their chat room.<\/li><li>Users type a message that does not start with \u2018!\u2019 or \u2018>\u2019 to send it to all other members of the room.<\/li><li>Users type \u2018>\u2019 followed by a message to send the message privately to the sender of the last message they have received even if the sender is not in the same room.<\/li><li>When a user joins a room all other room members get a message \u201c<em>user-name<\/em> entered the room\u201d.<\/li><li>When a user leaves a room all other room members get a message \u201c<em>user-name<\/em> left the room\u201d.<\/li><li>Users that are not configured as \u201cadult\u201d cannot join an adult chat room.<\/li><li>When <em>Room Manager<\/em> deletes a room all users that are in that room leave the room automatically and get an appropriate message.<\/li><li>When <em>Room Manager<\/em> modifies a room from non-adult to adult, all users that are in that room and are not configured as adults leave the room automatically and get an appropriate message.<\/li><li>When a user that is configured as \u201cadult\u201d is in an adult room, and the user configuration changes (e.g. by <em>SpiderAdmin<\/em>) to non-adult, the change takes the user out of the room.<\/li><li>When <em>Room Manager<\/em> renames a room all users that are in the room are notified of the change but stay in the room.<\/li><li>When <em>Room Manager<\/em> terminates, gracefully or not, all rooms are deleted and users in the rooms are notified.<\/li><li>When a chatter application terminates while in a room, gracefully or not, all other members of the room get notified that the user left the room.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If this looks like a long list, you will soon see how easy it is to implement all these with Spiderwiz. So we start with <code>ChatMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.chat;\n\nimport java.util.List;\nimport java.util.UUID;\nimport org.spiderwiz.admin.xml.OpResultsEx;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.tutorial.objectLib.Chat;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\nimport org.spiderwiz.tutorial.objectLib.ConsoleMain;\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;Dynamic Chat Room Client&quot;;\n    private static final String APP_VERSION = &quot;Z1.01&quot;;  \/\/ Version Z1.01: Initial version\n\n    private static final String JOIN = &quot;!join&quot;;         \/\/ join a chat room command\n    private static final String LEAVE = &quot;!leave&quot;;       \/\/ leave current chat room command\n    \n    private String myName;                      \/\/ Get this from the configuration file\n    private Chatter chatter = null;             \/\/ A Chatter object representing the user chatting in a specific room\n    private ChatMessage message = null;         \/\/ A ChatMessage object for committing chat messages\n    private Chat privateMessage = null;         \/\/ A Chat object for committing private 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 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,\n     * then print user instructions to the console.\n     * @return true if the name is 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        System.out.printf(\n            &quot;Hello %s. Welcome to the dynamic chat system.n&quot;\n                + &quot;To join a chat room or change your current chat room, type &quot;!join &lt;room name&gt;&quot;.n&quot;\n                + &quot;To leave your chat room, type &quot;!leave&quot;.n&quot;\n                + &quot;To send a message in the current chat room just type the message &quot;\n                + &quot;(a message cannot start with either '&gt;' or '!').n&quot;\n                + &quot;To send a private message to the sender of the last message you have received &quot;\n                + &quot;type '&gt;' followed by the message.n&quot;\n                + &quot;To exit type 'exit'.n&quot;,\n            myName\n        );\n        return true;\n    }\n    \n    \/**\n     * @return the list of produced objects - Chatter, Chat and ChatMessage\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{\n            Chatter.ObjectCode,\n            Chat.ObjectCode,\n            ChatMessage.ObjectCode\n        };\n    }\n\n    \/**\n     * @return the list of consumed objects - ChatRoom, Chatter, Chat and ChatMessage\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{\n            ChatRoom.ObjectCode,\n            Chatter.ObjectCode,\n            Chat.ObjectCode,\n            ChatMessage.ObjectCode\n        };\n    }\n\n    \/**\n     * Add implementation classes 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(ChatRoomConsume.class);\n        factoryList.add(ChatterConsume.class);\n        factoryList.add(ChatConsume.class);\n        factoryList.add(ChatMessageImp.class);\n    }\n\n    \/**\n     * @return true if application is configured as an adult chatter\n     *\/\n    public boolean isAdult() {\n        return getConfig().isPropertySet(&quot;adult&quot;);\n    }\n\n    \/**\n     * Join a room after checking that the room exists, its adult setting matches user configuration,\n     * and the user is not already there. If the user is in another room leave that room first.\n     * @param room  the new room name\n     * @throws NoSuchFieldException     If any of the created data objects does not define ObjectCode static field.\n     * @throws IllegalAccessException   If ObjectCode is not public.\n     *\/\n    private void joinRoom(String room) throws NoSuchFieldException, IllegalAccessException {\n        ChatRoom chatRoom = getRootObject().getChild(ChatRoom.class, room);\n        if (chatRoom == null) {\n            System.out.printf(&quot;Room %s does not existn&quot;, room);\n            return;\n        }\n\n        if (chatter != null &amp;&amp; room.equalsIgnoreCase(chatter.getParent().getObjectID()))\n            return;\n        if (chatRoom.isAdult() &amp;&amp; !isAdult()) {\n            System.out.printf(&quot;You cannot join room %s because it is only for adultsn&quot;, room);\n            return;\n        }\n        leaveRoom(null);\n        chatter = chatRoom.createChild(Chatter.class, getAppUUID().toString());\n        chatter.setName(myName);\n        chatter.commit();       \/\/ let everybody know I joined the room\n        \/\/ Prepare a ChatMessage object for sending messages from now on\n        message = chatter.createChild(ChatMessage.class, null);\n        System.out.printf(&quot;Joined room %sn&quot;, room);\n    }\n    \n    \/**\n     * If the user is in the given room, leave it.\n     * @param room  the name of the room to be checked if the user is there. If null, leave the current room without checking\n     *\/\n    public synchronized void leaveRoom(String room) {\n        if (isSameRoom(room)) {\n            chatter.remove();       \/\/ leave the room\n            chatter.commit();       \/\/ let everybody know I did\n            System.out.printf(&quot;Left room %sn&quot;, chatter.getParent().getObjectID());\n            chatter = null;\n            message = null;\n        }\n    }\n    \n    \/**\n     * Check if the user is in the same room as the parameter\n     * @param room  the name of the room to be checked if the user is there. If null, return true\n     * @return true if the user in 'room' or 'room' is null\n     *\/\n    public synchronized boolean isSameRoom(String room) {\n        return chatter != null &amp;&amp;\n            (room == null || room.equalsIgnoreCase(chatter.getParent().getObjectID()));\n    }\n    \n    \/**\n     * Check whether the user running the application whose UUID is given is on the same room as us. This is done by getting\n     * our current ChatRoom object and see if it has a child with that ID.\n     * @param appUUID   Application UUID to check\n     * @return  true if the application is in the same room as us.\n     *\/\n    public synchronized boolean isMemberOfMyRoom(UUID appUUID) {\n        try {\n            if (chatter == null)\n                return false;\n            ChatRoom chatRoom = (ChatRoom)chatter.getParent();\n            return chatRoom != null &amp;&amp; chatRoom.getChild(Chatter.class, appUUID.toString()) != null;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Theoretically arriving here if a data 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 a data object class&quot;, null, false);\n            return false;\n        }\n    }\n\n    \/**\n     * Called when application configuration has been changed. If the user were changed to &quot;not adult&quot; and she is on\n     * a room defined as adult only then take the user out of the room.\n     * @return the value returned by the super method.\n     *\/\n    @Override\n    public OpResultsEx reloadConfig() {\n        OpResultsEx opResult = super.reloadConfig();\n        if (chatter != null)\n            ((ChatRoomConsume)chatter.getParent()).reloadConfig();\n        return opResult;\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 the line starts with '!' this is a command line\n            if (line.startsWith(&quot;!&quot;)) {\n                String command[] = line.trim().split(&quot;s+&quot;, 2);\n                switch (command[0].toLowerCase()) {\n                case JOIN:\n                    \/\/ if room name is provided join the room\n                    if (command.length &gt; 1)\n                        joinRoom(command[1]);\n                    break;\n                case LEAVE:\n                    \/\/ If the user is in a room, leave it\n                    leaveRoom(null);\n                    break;\n                }\n                return true;\n            }\n            \n            \/\/ If the line starts with '&gt;' send a private message to the sender of the last received message\n            if (line.startsWith(&quot;&gt;&quot;)) {\n                if (getLastSender() == null)\n                    return true;\n                \/\/ If didn't do yet, create an orphan Chatter object for sending private messages,\n                \/\/ then prepare a ChatMessage object for sending private messages\n                if (privateMessage == null) {\n                    privateMessage = createTopLevelObject(Chat.class, null);\n                    privateMessage.setName(myName);\n                }\n                \/\/ Set message and commit to destination\n                privateMessage.setMessage(line.substring(1));\n                privateMessage.commit(getLastSender().toString());\n                return true;\n            }\n            \n            \/\/ If this is a normal message, make sure the user is in a room and broadcast the message\n            if (message != null) {\n                message.setMessage(line);\n                message.commit();\n            }\n            return true;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Theoretically arriving here if a data 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 a data object class&quot;, null, false);\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here too <code>ChatMain<\/code> extends <code>ConsoleMain<\/code>. The user command labels are defined in lines 20-21. We then define <code>myName<\/code> to hold the user name, a <code>Chatter<\/code> object that represents the chatter, a <code>ChatMessage<\/code> object that is used to send messages in the room, a <code>Chat<\/code> object that is used to send private messages and a <code>lastSender<\/code> property to hold the UUID of the last sender for private messaging (in the next lesson we will demonstrate more flexible private messaging).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#preStart()\">preStart()<\/a> (line 75) we load the chatter name from the configuration file and print out usage instructions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The produced data objects are <code>Chatter<\/code>, <code>Chat<\/code> and <code>ChatMessage<\/code> (line 99). These are also consumed, in addition to <code>ChatRoom<\/code> (line 111).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The implementation objects that we will see soon are <code>ChatRoomConsume<\/code>, <code>ChatterConsume<\/code>, <code>ChatConsume<\/code> and <code>ChatMessageImp<\/code>. They are registered in line 125.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The function <code>isAdult()<\/code> (line 136) returns the <code>adult<\/code> property as defined in the configuration file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The interesting stuff starts on line 147 with few utility methods. The first is <code>joinRoom()<\/code> that is called when a user joins a chat room. It gets a room name as a parameter and performs the following:<br><\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getChild(java.lang.Class,java.lang.String)\">getRootObject().getChild(ChatRoom.class, room)<\/a> to get the desired <code>ChatRoom<\/code> object. A <code>null<\/code> response means that the room does not exist, in which case we print a message and quit.<\/li><li>If class field <code>chatter<\/code>, which is an object of type <code>Chatter<\/code>, is not <code>null<\/code>, it means that the user is already in a room. In this case call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getParent()\">chatter.getParent()<\/a> to get the object\u2019s parent, which is a <code>ChatRoom<\/code> object that refers to the room the user is in. Then call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getObjectID()\">getObjectID()<\/a> on the object to get the room name and compare it to the name of the room the user wants to join. If they are equal, do nothing.<\/li><li>Get <code>isAdult()<\/code> property of the <code>ChatRoom<\/code> object. If it is <code>true<\/code>, check the <code>adult<\/code> property in the application\u2019s configuration. If the room is for adults and the user is not then print an appropriate message and quit.<\/li><li>Call the utility method <code>leaveRoom()<\/code> to leave the room the user is now in, if there is any.<\/li><li>Call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#createChild(java.lang.Class,java.lang.String)\">createChild()<\/a> to create a <code>Chatter<\/code> object as a child of the desired <code>ChatRoom<\/code> object. The method returns the created <code>Chatter<\/code> object.<\/li><li>Set user name on the created object.<\/li><li>Commit the object to propagate to other chatters that this user is now in the room.<\/li><li>Call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#createChild(java.lang.Class,java.lang.String)\">createChild()<\/a> on the <code>chatter<\/code> object to create a <code>ChatMessage<\/code> object that will be used for chatting in the room.<\/li><li>Print out an appropriate message.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">As we have just mentioned, we join a user to a room by creating a <code>Chatter<\/code> object as a child of the appropriate <code>ChatRoom<\/code> object. Note that the key used in the creation is the UUID of the application that the user runs (line 161). This facilitates easy routing of messages to appropriate rooms as we will shortly see.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The next utility method to discuss is <code>leaveRoom()<\/code> (line 173). It gets a parameter <code>room<\/code> that is <code>null<\/code> if users shall leave any room that they are now in, or a room name if they shall leave only if they are currently in that room. The code is straightforward \u2013 call <code>isSameRoom()<\/code> to determine whether there is a need to leave the room, and if so call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#remove()\">remove()<\/a> on the <code>Chatter<\/code> object that is currently a child of a <code>ChatRoom<\/code> object, commit the removed object to propagate the removal to other users, print out a message and set <code>chatter<\/code> and <code>message<\/code> fields to <code>null<\/code>, as they are no longer applicable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The function <code>isSameRoom()<\/code> (line 188) returns <code>true<\/code> if either its argument is <code>null<\/code> or it equals the name of the room the user is now in.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last utility method here is <code>isMemberOfMyRoom()<\/code> (line 199). Its parameter is the UUID of an application that needs to be checked whether its user is in the same room as the user of this application. This is done as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If the <code>chatter<\/code> field is <code>null<\/code> then the user is not in any room so return <code>false<\/code>.<\/li><li>Call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getParent()\">getParent()<\/a> on the <code>chatter<\/code> object to get the <code>ChatRoom<\/code> object pertaining to the room the user is now in.<\/li><li>Call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getChild(java.lang.Class,java.lang.String)\">getChild()<\/a> to get the object\u2019s child with a key value equal to the stringified UUID specified as the method parameter. If the returned value is not <code>null<\/code>, the user identified by that UUID is in the room.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">We will use the occasion to demonstrate the use of the overriding <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#reloadConfig()\">reloadConfig()<\/a> method&nbsp; (line 220). It is called when application configuration is changed, usually by <em>SpiderAdmin<\/em>. After the mandatory call to the <code>super<\/code> method, we use the <code>chatter<\/code> field to check whether the user is in a room, and if so we call <code>reloadConfig()<\/code> on its <code>ChatRoom<\/code> parent. You will see below what happens next.&nbsp;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The class concludes with <code>processConsoleLine()<\/code> (line 233). It is straightforward but we will describe it nevertheless:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Determine whether the line contains a command (starts with \u2018!\u2019). If it is then parse it.<\/li><li>If the command is <code>!join<\/code> call <code>joinRoom()<\/code>.<\/li><li>If the command is <code>!leave<\/code> call <code>leaveRoom()<\/code>.<\/li><li>If the line contains a private message (starts with \u2018&gt;\u2019) then proceed as in Lesson 4.<\/li><li>If the line contains a normal message then determine whether the user is in a room by checking that <code>message<\/code> is not <code>null<\/code>.<\/li><li>If <code>message<\/code> is not <code>null<\/code> then it contains a <code>ChatMessage<\/code> object that is a child of the current <code>Chatter<\/code> object. In this case set its <code>message<\/code> property to the message and commit the object.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s see the data object implementation classes. First <code>ChatRoomConsume<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.chat;\n\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\n\n\/**\n * Consumer implementation of the ChatRoom class\n *\/\npublic class ChatRoomConsume extends ChatRoom {\n\n    \/**\n     * If the chat room has been removed and the user is there, leave the room\n     * @return true to confirm the removal.\n     *\/\n    @Override\n    protected boolean onRemoval() {\n        ChatMain.getInstance().leaveRoom(getObjectID());\n        return true;\n    }\n\n    \/**\n     * If the room was set to adult and user is not configured as such, leave the room if user is there\n     * @return true\n     *\/\n    @Override\n    protected boolean onEvent() {\n        if (isAdult() &amp;&amp; !ChatMain.getInstance().isAdult())\n            ChatMain.getInstance().leaveRoom(getObjectID());\n        return true;\n    }\n\n    \/**\n     * Notify on room name change if the user is in the renamed room\n     * @param oldID\n     *\/\n    @Override\n    protected void onRename(String oldID) {\n        if (ChatMain.getInstance().isSameRoom(getObjectID()))\n            System.out.printf(&quot;Your room %1$s was renamed %2$sn&quot;, oldID, getObjectID());\n    }\n    \n    \/**\n     * Called when application configuration has been changed. If the user were changed to &quot;not adult&quot; and the room is an adult room\n     * then onEvent() would take the user out of the room.\n     *\/\n    public void reloadConfig() {\n        onEvent();\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Now that you know that all the hard work is done by Spiderwiz, you are apparently not surprised to see how simple the code is. This is what it does:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onRemoval()\">onRemoval()<\/a> (line 15) to get users out of a deleted room.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a> (line 25) to get non-adult users out of a room that became adult.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onRename(java.lang.String)\">onRename()<\/a> (line 36) to notify users that are in a renamed room about the name change.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last method, <code>reloadConfig()<\/code> (line 45), is called from a <code>ChatMain<\/code> method of the same name when the application configuration is changed. It calls <code>onEvent()<\/code> on the same class. The effect is that users whose configuration was changed from adult to non-adult are thrown out of adult rooms.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>ChatterConsume<\/code> is even simpler:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.chat;\n\nimport org.spiderwiz.tutorial.objectLib.Chatter;\n\n\/**\n * Consumer implementation of Chatter data object. Notify when a chatter enters or leaves my room.\n *\/\npublic class ChatterConsume extends Chatter {\n\n    \/**\n     * Notify when a chatter enters my room.\n     *\/\n    @Override\n    protected void onNew() {\n        \/\/ The room is the parent of this object\n        if (ChatMain.getInstance().isSameRoom((getParent().getObjectID())))\n            System.out.printf(&quot;%s entered the roomn&quot;, getName());\n    }\n\n    \/**\n     * Notify when a chatter leaves my room\n     * @return true to confirm the removal.\n     *\/\n    @Override\n    protected boolean onRemoval() {\n        \/\/ The room is the parent of this object\n        if (ChatMain.getInstance().isSameRoom(getParent().getObjectID()))\n            System.out.printf(&quot;%s left the roomn&quot;, getName());\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This class overrides <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onNew()\">onNew()<\/a> (line 14) to print a message when a new user joins the room the user is currently in, and overrides <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onRemoval()\">onRemoval()<\/a> (line 25) to print a message when a user leaves that room.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Not much more work in <code>ChatMessageImp<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.chat;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\n\n\/**\n * Consumer implementation of the ChatMessage class\n *\/\npublic class ChatMessageImp extends ChatMessage {\n    \/**\n     * Called when a chat message is received. Check if the message has been sent in the room we are in,\n     * Then print the sender name and 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        if (ChatMain.getInstance().isSameRoom(getParent().getParent().getObjectID())) {\n            System.out.printf(&quot;%1$s: %2$sn&quot;, ((Chatter)getParent()).getName(), getMessage());\n            ChatMain.getInstance().setLastSender(getOriginUUID());\n        }\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 user\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            ChatMain.getInstance().isMemberOfMyRoom(appUUID);\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a> (line 19) we check whether the user is in the same room as the sender of the message (although routing is done at the sender side, a wrong message may slip in due to synchronization issues). If she is, we print the message and save the sender\u2019s UUID for private messaging.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In <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> (line 39), which is called when the user is the sender, we check whether this is not a self message and the user in the destination UUID is in the same room as the sender.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last class we have to look at is <code>ChatConsume<\/code>, used to process an incoming private message. This is the simplest of all:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson5.chat;\n\nimport org.spiderwiz.tutorial.objectLib.Chat;\n\n\/**\n * Consumer implementation of the Chat class\n *\/\npublic class ChatConsume extends Chat{\n\n    \/**\n     * Called when a private message is received. Print the sender name followed by a colon, then the message.\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$sn&quot;, getName(), getMessage());\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here we need only to override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a> and print the message. No filtering is needed because a private message is sent out of the room with a direct destination in the <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#commit(java.lang.String)\">commit()<\/a> method.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We are done. As you can see, the code we had to type, excluding the comments, is not much longer than the feature list we outlined above. In fact some features, such as that when an application terminates then all the objects it created are removed from the object tree, are built into the Spiderwiz engine with no need for extra coding.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To run and test the code we need configuration files. Here are the files for three clients \u2013 <code>Chat1<\/code>, <code>Chat2<\/code> and <code>Chat3<\/code>:<\/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<\/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>[log folder]\/tests\/Chat3\/Logs\n[producer-1]websocket=localhost:90\/MyHub\n[my name]DON ALFONSO\n[adult]yes<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The buddies from <em>Cosi Fan Tutte<\/em> star here too. The only adult one is DON ALFONSO. If you know the opera, you would not wonder why.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Note that although <em>Room Manager<\/em> connects to <em>My Hub<\/em> on TCP\/IP, the chat applications connect as WebSocket clients as before. This is a demonstration of a <em>hybrid network topology<\/em> that we mentioned before.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Go ahead, play with the applications and try to test all their features. See how programming is a joy when it is easy as a Lego toy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the next lesson we will continue with the <em>Data Object Tree<\/em> and learn how to easily traverse it to collect information and perform actions.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn about Spiderwiz data object tree and how it is shared between applications. We will use it to introduce dynamic chat rooms into the chat service of Lesson 4.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":5,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-667","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/667","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=667"}],"version-history":[{"count":124,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/667\/revisions"}],"predecessor-version":[{"id":1354,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/667\/revisions\/1354"}],"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=667"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}