{"id":926,"date":"2020-07-31T15:52:46","date_gmt":"2020-07-31T15:52:46","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=926"},"modified":"2025-04-09T06:15:58","modified_gmt":"2025-04-09T06:15:58","slug":"lesson-9","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-9\/","title":{"rendered":"Lesson 9: Manual Data Object Reset"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In the previous lessons we led you through the Spiderwiz paradigm of the <em>Shared Data Object Tree<\/em>. You exercised with application code that accessed data rather than communicating with services. One of the major concepts of this paradigm is that as soon as the application starts, it gets all the data that it needs in its space and can access and manipulate it with no extra mechanism.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Under the hood, this concept is implemented with a mechanism called <em>Data Object Reset<\/em>. When an application starts (or when data integrity is compromised, <a href=\"https:\/\/docs.google.com\/document\/d\/1dgVE2v7xa63AHi3WDk838BbiTpCC5wbq\/edit#bookmark=id.dfdvkjbvtazg\">see below<\/a>), it broadcasts a <em>Reset<\/em> request for all the object types it consumes. Producers of these types respond by sending the objects that they have created over the channel through which the Reset request arrived.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Normally the entire process is automatic and programmers need not intervene. However there are cases when the programmer wants to control the process programmatically, for instance for resetting <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\">disposable<\/a> objects that are fetched from an external source and not kept in the shared data object tree. This technique is demonstrated in this lesson.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We stay with the chat service of the previous lessons and add a new service \u2013 <em>Room Producer<\/em> \u2013 that creates chat rooms by reading a list of room names (possibly with the \u2018+\u2019 adult indicator) from a file, one name per line. <em>Room Producer<\/em> does not keep the rooms in memory (it overrides <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\">isDisposable()<\/a> of <code>ChatRoom<\/code> to return <code>true<\/code>), so every time a consumer of this object type asks for reset it needs to open the file, read it, create <code>ChatRoom<\/code> objects from it and send them to the requester. This is done in <code>RoomProducerMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson9.roomProducer;\n\nimport java.io.BufferedReader;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.util.List;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.core.Resetter;\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 RoomProducerMain extends ConsoleMain {\n    private static final String ROOT_DIRECTORY = &quot;&quot;;\n    private static final String CONF_FILENAME = &quot;room-producer.conf&quot;;\n    private static final String APP_NAME = &quot;Room Producer&quot;;\n    private static final String APP_VERSION = &quot;Z1.01&quot;;  \/\/ Version Z1.01: Initial version\n    private static final String ROOM_FILE = &quot;room file&quot;;\n\n    \/**\n     * Class constructor with constant parameters.\n     *\/\n    public RoomProducerMain() {\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 RoomProducerMain().init();\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(ChatRoomProducer.class);\n    }\n\n    \/**\n     * If room file is not defined in the configuration file, log an error message in the log file and in stdout and quit\n     * the application.\n     * @return true if and only if a configuration file is defined.\n     *\/\n    @Override\n    protected boolean preStart() {\n        if (getConfig().getProperty(ROOM_FILE) == null) {\n            getLogger().logEvent(&quot;Room file is not defined.&quot;);\n            return false;\n        }\n        return true;\n    }\n\n    \/**\n     * If the resetter object is for ChatRoom reset all chat rooms from the file defined in the configuration file and return true;\n     * @param resetter\n     * @return true if reset is done here.\n     *\/\n    @Override\n    protected boolean onObjectReset(Resetter resetter) {\n        \/\/ Check resetter type\n        if (!resetter.getObjectCode().equals(ChatRoom.ObjectCode))\n            return false;\n        \n        \/\/ Set reset rate to zero in order to eliminate moderation so that every object is sent as soon as possible\n        resetter.setResetRate(0);\n        \n        \/\/ Get configured file name\n        String filename = getConfig().getProperty(ROOM_FILE);\n        \n        \/\/ Read lines from the file\n        try (BufferedReader in = new BufferedReader(\n                new InputStreamReader(new FileInputStream(filename))))\n        {\n            String line;\n            while ((line = in.readLine()) != null) {\n                \/\/ Check if the resetter has not been aborted by an overriding reset request.\n                if (resetter.isAborted())\n                    return true;\n                \n                \/\/ Parse the line for room name and adult symbol\n                String name[] = line.split(&quot;+&quot;, -1);\n                String roomName = name[0];\n                boolean adult = name.length &gt; 1;\n                \n                \/\/ Create a ChatRoom object, set its properties and reset it.\n                ChatRoom obj = createTopLevelObject(ChatRoom.class, roomName);\n                obj.setAdult(adult);\n                resetter.resetObject(obj);\n            }\n            \n            \/\/ Mark end of data\n            resetter.endOfData();\n        } catch (IllegalAccessException | NoSuchFieldException ex) {\n            sendExceptionMail(ex, &quot;When creating a ChatRoom object&quot;, null, false);\n        } catch (IOException ex) {\n            getLogger().logEvent(&quot;Open file failure: %1$s.&quot;, ex.getMessage());\n            sendNotificationMail(&quot;Open file failure&quot;, ex.getMessage(), null, true);\n        }\n        return true;\n    }\n\n    \/**\n     * Called after delivery of all items. Print item count.\n     * @param resetter  the Resetter object used for delivery.\n     *\/\n    @Override\n    protected void onResetCompleted(Resetter resetter) {\n        System.out.printf(&quot;Reset done. %d items have been delivered.n&quot;, resetter.getResetCount());\n    }\n\n    \/**\n     * @param line\n     * @return true since we do not do any line processing\n     *\/\n    @Override\n    protected boolean processConsoleLine(String line) {\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">As expected, the application produces <code>ChatRoom<\/code> (line 43) and does not consume anything (line 51). It extends <code>ChatRoom<\/code> by <code>ChatRoomProducer<\/code> and registers it (line 62).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The file that contains the room names should be specified in the configuration file. In <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#preStart()\">preStart()<\/a> (line 71) we verify the existence of the <code>room file<\/code> property and terminate the program by returning <code>false<\/code> if it does not.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The interesting method that performs the reset is the overriding <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#onObjectReset(org.spiderwiz.core.Resetter)\">onObjectReset()<\/a> (line 85). The method is called every time a reset for a specific object type that is produced by this application is requested by any peer application. Its parameter is a <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html\">Resetter<\/a> object that specifies the requested object type and also serves as a carrier that delivers objects of this type back to the requester, as we are going to see now.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The first thing <code>onObjectReset()<\/code> does is to check whether the requested type is what we care about. This is done by calling <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html#getObjectCode()\">getObjectCode()<\/a> of the <code>resetter<\/code> parameter and comparing it to <code>ChatRoom.ObjectCode<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We call the object\u2019s <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html#setResetRate(int)\">setResetRate()<\/a> to eliminate its default moderation effect, because in our test we expect to deliver a relatively small amount of objects that should better be delivered as quickly as possible. If the test file contained a very large number of items and the network speed were constrained then we could set an appropriate rate (or leave the default of 30,000 items per minute) to avoid network congestion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We then open the configured input file and loop on reading its lines. Within each iteration we first verify that the resetter is still active by calling <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html#isAborted()\">isAborted()<\/a>. It might be aborted if, during its operation, another reset request for the same object type arrives on the same channel. In this case, in order to avoid unnecessary duplicate data, the framework aborts the operation of the former resetter assuming that the data would be completely delivered by the latter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If everything is OK, we parse the input line, create one object by calling <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#createTopLevelObject(java.lang.Class,java.lang.String)\">createTopLevelObject()<\/a>, set its values and call the resetter\u2019s <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html#resetObject(org.spiderwiz.core.DataObject)\">resetObject()<\/a> method to deliver the object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When we reach the file end we call the reseter\u2019s optional <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Resetter.html#endOfData()\">endOfData()<\/a> method. We will see its use in a moment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will use the occasion to dwell upon the two <code>catch<\/code> clauses that enclose the code. The first catches <code>IllegalAccessException<\/code> and <code>NoSuchFieldException<\/code> exceptions that might happen if <code>ChatRoom<\/code> class does not define an <code>ObjectCode<\/code> static field or the field is not declared <code>public<\/code>. In these cases we call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#sendExceptionMail(java.lang.Throwable,java.lang.String,java.lang.String,boolean)\">sendExceptionMail()<\/a> to report the exception and its stack trace to the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The standard error stream.<\/li><li>The application\u2019s log system.<\/li><li>By mail to the address configured in the application configuration file if relevant. We will see below an example of it.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The second catch clause catches <code>IOException<\/code> exceptions, which might happen if, for any reason, the input file could not be opened or read. The clause logs the event in the application\u2019s log system and also calls <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#sendNotificationMail(java.lang.String,java.lang.String,org.spiderwiz.zutils.ZDate,boolean)\">sendNotificationMail()<\/a> to report the event by mail. Note that addressees of notification mails, which are usually system administrators, may be configured differently than addressees of exception mails that are usually developers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The last method to look at is the overriding <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#onResetCompleted(org.spiderwiz.core.Resetter)\">onResetCompleted()<\/a> (line 133). We mentioned above that, having submitted all reset items, we call <code>resetter.endOfData()<\/code> to mark the end of the operation. Since the submission of the items includes buffering, it may be that some items have not been dispensed yet when the method is called. When they finally are, <code>onResetCompleted()<\/code> is called and we override it to print out some information, in this case the total number of items that were reset.<\/p>\n\n\n\n<a id=\"ChatRoomProducer\"><\/a>\n<p>The application package includes one more class \u2013 <code>ChatRoomProducer<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson9.roomProducer;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\n\n\/**\n * Overrides ChatRoom to return true in isDisposable\n *\/\npublic class ChatRoomProducer extends ChatRoom {\n\n    \/**\n     * Filter the destination so that logged in chatters will receive the object, and only if either this is not an adult room\n     * or the destination is of an adult user. Destinations other than chat applications will receive the object with no filtering.\n     * @param appUUID       application UUID.\n     * @param appName       application name.\n     * @param userID        n\/a\n     * @param remoteAddress remote address of the destination application.\n     * @param appParams     application parameter map as set by Main.getAppParams() of the filtered application. If the destination\n     *                      is a chat application the map is not null and it contains a mapping of the &quot;state&quot; key to any of\n     *                      &quot;logout&quot;, &quot;login&quot; or &quot;adult&quot;. The latter means the user is logged in as an adult.\n     * @return              true to approve distribution to this application, false to deny it.\n     *\/\n    @Override\n    protected boolean filterDestination(UUID appUUID, String appName, String userID, String remoteAddress, Map&lt;String, String&gt; appParams) {\n        if (appParams == null)\n            return true;\n        String state = appParams.get(&quot;state&quot;);\n        if (state == null)\n            return true;\n        switch(state) {\n        case &quot;logout&quot;:\n            return false;\n        case &quot;login&quot;:\n            return !isAdult();\n        case &quot;adult&quot;:\n            return true;\n        default:\n            return false;\n        }\n    }\n    \n    @Override\n    protected boolean isDisposable() {\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This extends <code>ChatRoom<\/code> and overrides two methods \u2013 <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 25) and <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#isDisposable()\">isDisposable()<\/a> (line 44). Please ignore the first for now \u2013 we will come back to it later. The second method is implemented to return <code>true<\/code>. Everything would work fine without it, but since we reread item data from file every time we are requested to reset them,&nbsp; there is no reason to keep all <code>ChatRoom<\/code> objects in memory.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Again, to run it we need a configuration file:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]\/tests\/RoomProducer\/Logs\n[producer-1]ip=localhost;port=10001\n[room file]\/tests\/RoomProducer\/rooms.txt\n[mail system]smtp;server=smtp.gmail.com;user=tester@spiderwiz.org;pwd=dumptrump;port=465;ssl=true\n[from address]Room Producer&lt;alert@spiderwiz.org&gt;\n[to email]Spiderwiz Administrator&lt;spiderwiz.admin@gmail.com&gt;\n[to exception email]Spiderwiz Programmer&lt;spiderwiz.geek@gmail.com&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">You can see the custom <code>room file<\/code> property that we use to get the room file name. You can also see an example of mail system and address configuration for bug reports and alerts as discussed above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The discussion so far pertains to the case when an\napplication that receives a reset request handles it programmatically. There\nare cases when programming intervention is required at the requester side.\nConsider for example the following:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Chat Application that we use in this lesson works by\nreceiving all <code>ChatRoom<\/code> objects as\nsoon as they are available. It also receives a <code>Chatter<\/code> object every time a chatter in any peer application enters\nany room. However, the application does not let a user join a room before\nlogging in or registration. Additionally, some rooms may be defined as <em>adult rooms<\/em>, so the application checks\nthe user&#8217;s age before letting them join such a room.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This works but involves superfluous overhead in terms of\ntraffic volume and memory. Wouldn\u2019t it be nice if we could hold the\ntransmission of <code>ChatRoom<\/code> and <code>Chatter<\/code> objects to a chat application\nuntil its user logs in, and when that happens check the user\u2019s age and refrain\nfrom sending adult room objects to young users?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, we can, and we are going to show now how to do it. For\nthat we will need to slightly modify the Chat application. Here is <code>ChatMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson9.chat;\n\nimport java.io.PrintStream;\nimport java.text.ParseException;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.tutorial.objectLib.ActiveUserQuery;\nimport org.spiderwiz.tutorial.objectLib.Chat;\nimport org.spiderwiz.tutorial.objectLib.ChatHistoryQuery;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\nimport org.spiderwiz.tutorial.objectLib.ConsoleMain;\nimport org.spiderwiz.tutorial.objectLib.LoginQuery;\nimport org.spiderwiz.zutils.ZDate;\nimport org.spiderwiz.zutils.ZUtilities;\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;Z9.01&quot;;  \/\/ Version Z9.01: Lesson 9 modifications\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    private static final String LOGIN = &quot;!login&quot;;       \/\/ start login procedure\n    private static final String LOGOUT = &quot;!logout&quot;;     \/\/ log the user out\n    private static final String REGISTER = &quot;!register&quot;; \/\/ start registration procedure\n    private static final String HISTORY = &quot;!history&quot;;   \/\/ print my chat history\n    \n    private String myName = null;               \/\/ The user login name\n    private ZDate birthday = null;              \/\/ User birth date. Get it when logging in\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\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    \/**\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     * Print user instructions to the console.\n     * @return true\n     *\/\n    @Override\n    protected boolean preStart() {\n        System.out.printf(\n            &quot;Hello %s. Welcome to the dynamic chat system.n&quot;\n                + &quot;To log in type !login.n&quot;\n                + &quot;To log out type !logout.n&quot;\n                + &quot;To register type !register.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 type '&gt;' followed by name of the user you want to send &quot;\n                + &quot;the private message to, followed by ':' for by the message.n&quot;\n                + &quot;To print the history of your own messages, type &quot;!history &lt;hours&gt;h&quot; or &quot;!history &lt;days&gt;d&quot;n&quot;\n                + &quot;(for instance &quot;!history 3h&quot; to see messages since 3 hours ago and &quot;!history 1d&quot; to see messages since 1 day&quot;\n                + &quot; ago).n&quot;\n                + &quot;or type just &quot;!history&quot; to see all your messages.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, ChatMessage and LoginQuery\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{\n            Chatter.ObjectCode,\n            Chat.ObjectCode,\n            ChatMessage.ObjectCode,\n            LoginQuery.ObjectCode,\n            ChatHistoryQuery.ObjectCode\n        };\n    }\n\n    \/**\n     * @return the list of consumed objects - ChatRoom, Chatter, Chat, ChatMessage and ActiveUserQuery\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{\n            ChatRoom.ObjectCode,\n            Chatter.ObjectCode,\n            Chat.ObjectCode,\n            ChatMessage.ObjectCode,\n            ActiveUserQuery.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(ChatterImp.class);\n        factoryList.add(ChatConsume.class);\n        factoryList.add(ChatMessageImp.class);\n        factoryList.add(LoginQuery.class);\n        factoryList.add(ActiveUserQueryReply.class);\n        factoryList.add(ChatHistoryQueryInquire.class);\n    }\n\n    \/**\n     * Maps &quot;state&quot; to either &quot;logout&quot;, &quot;login&quot; or &quot;adult&quot;\n     * @return\n     *\/\n    @Override\n    public Map&lt;String, String&gt; getAppParams() {\n        return new HashMap&lt;String, String&gt;() {\n            {\n                put(&quot;state&quot;, myName == null ? &quot;logout&quot; : ZDate.now().diffMonths(birthday) &gt;= 12 * 18 ? &quot;adult&quot; : &quot;login&quot;);\n            }\n        };\n    }\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                case LOGIN:\n                    login();\n                    break;\n                case LOGOUT:\n                    logout();\n                    break;\n                case REGISTER:\n                    register();\n                    break;\n                case HISTORY:\n                    if (myName == null)\n                        System.out.println(&quot;You must log in before requesting history.&quot;);\n                    else if (!getHistory(command.length &lt; 2 ? null : command[1]))\n                        System.out.println(&quot;Invalid command argument&quot;);\n                    break;\n                }\n                return true;\n            }\n            \n            \/\/ If the line starts with '&gt;' split the line on the colon (':') and send the test\n            \/\/ after the colon as a private message to the user with the name before the colon.\n            if (line.startsWith(&quot;&gt;&quot;)) {\n                String cmd[] = line.substring(1).split(&quot;:&quot;);\n                if (cmd.length &lt; 2)\n                    return true;\n                String destination = findUser(cmd[0]);\n                if (destination == 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(cmd[1]);\n                privateMessage.commit(destination);\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    public String getMyName() {\n        return myName;\n    }\n\n    public ZDate getBirthday() {\n        return birthday;\n    }\n    \n    \/**\n     * Get user name and password and log in.\n     * @return a PrintStream object. Not needed by the caller but we use it as a trick to save coding.\n     * @throws NoSuchFieldException     If LoginQuery does not define ObjectCode static field.\n     * @throws IllegalAccessException   If LoginQuery.ObjectCode is not public.\n     *\/\n    private PrintStream login() throws NoSuchFieldException, IllegalAccessException {\n        String username;\n        String password;\n\n        \/\/ Check if already logged in\n        if (myName != null)\n            return System.out.printf(&quot;You are already logged in as %s. To log in with another name log out first.n&quot;, myName);\n        \n        \/\/ Create a login query\n        LoginQuery query = createQuery(LoginQuery.class);\n        while(true) {\n            \/\/ Get user name\n            System.out.printf(&quot;(type Enter to exit login)n&quot;);\n            username = readConsoleLine(&quot;User name:&quot;);\n            if (username.isBlank())\n                return exitLogin();\n            do {\n                \/\/ Get password\n                password = readConsoleLine(&quot;Password:&quot;);\n                if (password.isBlank())\n                    return exitLogin();\n\n                \/\/ Post the login query\n                query.setNewUser(false);\n                query.setName(username);\n                query.setPassword(password);\n                query.post(5 * ZDate.SECOND);\n                \n                \/\/ If the query did not expire check login results, if it did, repeat.\n                if (query.waitForReply()) {\n                    switch (query.getResult()) {\n                    case OK:\n                        \/\/ Log in and request a general reset\n                        myName = query.getName();\n                        birthday = query.getBirthday();\n                        reset();\n                        return System.out.printf(&quot;Logged in successfully as %s.n&quot;, myName);\n                    case NOT_EXISTS:\n                        System.out.printf(&quot;User %s does not exist. Please try again.n&quot;, username);\n                        break;\n                    case WRONG_PASSWORD:\n                        System.out.printf(&quot;Password did not match, please try again.n&quot;);\n                        break;\n                    }\n                } else\n                    return unavailableUserManager();\n            } while (query.getResult() == LoginQuery.ResultCode.WRONG_PASSWORD);\n        }\n    }\n    \n    \/**\n     * Log out if already logged in\n     *\/\n    private void logout() {\n        if (myName == null) {\n            System.out.printf(&quot;No user is logged in.n&quot;);\n            return;\n        }\n        leaveRoom(null);\n        \n        \/\/ Log out and request a general reset.\n        System.out.printf(&quot;%s is logged out.n&quot;, myName);\n        myName = null;\n        birthday = null;\n        reset();\n    }\n\n    private PrintStream register() throws NoSuchFieldException, IllegalAccessException {\n        String username;\n        String password, password2;\n\n        \/\/ Check if already logged in\n        if (myName != null)\n            return System.out.printf(&quot;You are already logged in as %s. To register a new user log out first.n&quot;, myName);\n        \n        \/\/ Create a registration query\n        LoginQuery query = createQuery(LoginQuery.class);\n        while(true) {\n            \/\/ Get user name\n            System.out.printf(&quot;(type Enter to exit login)n&quot;);\n            username = readConsoleLine(&quot;User name:&quot;);\n            if (username.isBlank())\n                return exitRegistration();\n            do {\n                \/\/ Get password\n                password = readConsoleLine(&quot;Password:&quot;);\n                if (password.isBlank())\n                    return exitRegistration();\n\n                \/\/ Repeat the password\n                password2 = readConsoleLine(&quot;Repeat password:&quot;);\n                if (password2.isBlank())\n                    return exitRegistration();\n                \n                \/\/ Check if passwords are equal\n                if (password.equals(password2))\n                    break;\n                System.out.println(&quot;Passwords do not match.&quot;);\n            } while (true);\n            \n            \/\/ Get and parse birth date\n            do {\n                String s = readConsoleLine(&quot;Date of birth (yyyy\/mm\/dd):&quot;);\n                if (s.isBlank())\n                    return exitRegistration();\n                try {\n                    birthday = ZDate.parseTime(s, &quot;yyyy\/MM\/dd&quot;, null);\n                } catch (ParseException ex) {\n                    birthday = null;\n                    System.out.printf(&quot;Invalid date %s. Please try again.n&quot;, s);\n                }\n            } while (birthday == null);\n\n            \/\/ Post the registration query\n            query.setNewUser(true);\n            query.setName(username);\n            query.setPassword(password);\n            query.setBirthday(birthday);\n            query.post(5 * ZDate.SECOND);\n\n            \/\/ If the query did not expire check login results, if it did, repeat.\n            if (query.waitForReply()) {\n                switch (query.getResult()) {\n                case OK:\n                    myName = username;\n                    reset();\n                    return System.out.printf(&quot;Registered and logged in successfully as %s.n&quot;, myName);\n                case EXISTS:\n                    System.out.printf(&quot;User %s already exista. Please try again with another name.n&quot;, username);\n                    break;\n                }\n            } else\n                return unavailableUserManager();\n        }\n    }\n    \n    \/**\n     * Print a message when login existed by typing a blank Enter\n     * @return a PrintStream object. Not needed by the caller but we use it as a trick to save coding.\n     *\/\n    private PrintStream exitLogin() {\n        return System.out.printf(&quot;Login exitedn&quot;);\n    }\n    \n    \/**\n     * Print a message when registration existed by typing a blank Enter\n     * @return a PrintStream object. Not needed by the caller but we use it as a trick to save coding.\n     *\/\n    private PrintStream exitRegistration() {\n        return System.out.printf(&quot;Registration exitedn&quot;);\n    }\n    \n    \/**\n     * Print a message when user management is not available\n     * @return a PrintStream object. Not needed by the caller but we use it as a trick to save coding.\n     *\/\n    private PrintStream unavailableUserManager() {\n        return System.out.printf(&quot;No user management entity is available to handle this requestn&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 synchronized void joinRoom(String room) throws NoSuchFieldException, IllegalAccessException {\n        \/\/ Check whether room exists and the user is not already in the room\n        ChatRoomConsume chatRoom = getRootObject().getChild(ChatRoomConsume.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        \n        \/\/ Leave current room if any\n        leaveRoom(null);\n        \n        \/\/ Creaet a Chatter object for the user that joins the specified room\n        chatter = chatRoom.createChild(Chatter.class, getAppUUID().toString());\n        chatter.setName(myName);\n        chatter.setBirthday(birthday);\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            DataObject removed = chatter.remove();  \/\/ leave the room\n            if (removed != null)                 \/\/ can be null if the 'chatter' object has already been removed.\n                removed.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     * Get current room name, if any\n     * @return if the user is currently in a room return the room name, otherwise return null.\n     *\/\n    public synchronized String getCurrentRoom() {\n        return chatter == null ? null : chatter.getParent().getObjectID();\n    }\n    \n    \/**\n     * Find the user whose name equals the parameter\n     * @param name  user name to look for\n     * @return      If a Chatter object for this user found then return its object ID, which is the UUID of the application\n     *              that the given user runs, otherwise return null.\n     * @throws NoSuchFieldException     If any of the referred data objects does not define ObjectCode static field.\n     * @throws IllegalAccessException   If ObjectCode is not public.\n     *\/\n    private String findUser(String name) throws NoSuchFieldException, IllegalAccessException {\n        FindUserFilter filter = new FindUserFilter(name);\n        Collection&lt;Chatter&gt; users = getRootObject().getFilteredChildren(filter);\n        \/\/ If the returned collection is not empty return the object ID (which is a UUID) of the first,\n        \/\/ otherwise return null.\n        for (Chatter user : users) {\n            return user.getObjectID();\n        }\n        return null;\n    }\n    \n    \/**\n     * Execute !history command\n     * @param arg   command argument - &lt;n&gt;H or &lt;n&gt;D\n     * @return  true if command has been parsed successfully.\n     * @throws NoSuchFieldException     If ChatHistoryQuery does not define ObjectCode static field.\n     * @throws IllegalAccessException   If ChatHistoryQuery.ObjectCode is not public.\n     *\/\n    private boolean getHistory(String arg) throws NoSuchFieldException, IllegalAccessException {\n        \/\/ Parse the argument and calculate time\n        ZDate since = null;\n        if (arg != null) {\n            arg = arg.trim().toLowerCase();\n            int n = ZUtilities.parseInt(arg.substring(0, arg.length() - 1));\n            if (n &lt;= 0)\n                return false;\n            int unit;\n            switch (arg.substring(arg.length() - 1)) {\n            case &quot;h&quot;:\n                unit = ZDate.HOUR;\n                break;\n            case &quot;d&quot;:\n                unit = ZDate.DAY;\n                break;\n            default:\n                return false;\n            }\n            since = ZDate.now().add(-n * unit);\n        }\n        \n        \/\/ create a query and post it\n        ChatHistoryQuery query = createQuery(ChatHistoryQuery.class);\n        query.setUsername(myName);\n        query.setSince(since);\n        query.post(5 * ZDate.SECOND);\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The most notable change is the override of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#getAppParams()\">getAppParams()<\/a> in line 151. Recall that this method is used for fine-grained routing by peer applications. In our case we want to filter out <code>ChatRoom<\/code> and <code>Chat<\/code> objects depending on whether a user is logged in and the age of the user in the case of adult rooms. So we map the key <code>\u201cstate\u201d<\/code> to either <code>\"logout\"<\/code>, <code>\"login\"<\/code> or <code>\"adult\"<\/code> depending on the values of class variables <code>myName<\/code> (that is <code>null<\/code> if no user has logged in or registered) and <code>birthday<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Before looking at how this mapping is used for object\nfiltering, we need to tackle an intriguing problem that goes back to the issue\nof Data Object Reset that is the focus of this lesson. The way the framework\nworks, the value returned by <code>getAppParams()<\/code>\nis conveyed to peer applications as part of Reset requests posted by the\napplication. Normally Reset requests are transmitted whenever an application\nconnects to the network. But in our case the mapping of the <code>\u201cstate\u201d<\/code> key changes when a user logs in\nor logs out, and that has nothing to do with established network connections.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What we need is to be able to programmatically broadcast a\nnew Reset request every time something is changed that may affect object\nfiltering by peer applications, in other words, Manual Data Object Reset at the\nrequester side. This&nbsp; is exactly what the\n<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#reset()\">reset()<\/a> method does. More precisely, the\nmethod clears the entire <em>Data Object Tree<\/em>\nsaved in the memory of the calling application and broadcasts a new Reset\nrequest for all the object types that it consumes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In our case, we call the <code>reset()<\/code>\nmethod in line 283, after a successful user login, in line 312, after a logout\nand in line 373, after user registration.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To see that the trick does the job, we remove the code that\nverifies that the user is logged in before joining a room (line 416). Since an\napplication should not receive any <code>ChatRoom<\/code>\nobjects if the user is not logged in, an attempt to join a room in this\nsituation should result in a message that the room is not available. This\nshould also happen when an underage user is trying to enter an adult room, so\nwe remove the code that checks that as well (line 426).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It remains to see how object filtering works with the\nprocedure that was introduced here. Let\u2019s start first with the Chat\napplication. We want to filter the transmission of <code>Chatter<\/code> objects, so we do it in <code>ChatterImp<\/code>\nthat we renamed from <code>ChatterConsume<\/code>\n(because it now handles produced objects as well as the consumed ones):<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson9.chat;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\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 ChatterImp 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 (!isStealth() &amp;&amp; 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. If the chatter is myself leave the room gracefully.\n     * @return true to confirm the removal.\n     *\/\n    @Override\n    protected boolean onRemoval() {\n        if (ChatMain.getInstance().getAppUUID().toString().equals(getObjectID()))\n            ChatMain.getInstance().leaveRoom(getParent().getObjectID());\n        else if (!isStealth() &amp;&amp; ChatMain.getInstance().isSameRoom(getParent().getObjectID()))\n            System.out.printf(&quot;%s left the roomn&quot;, getName());\n        return true;\n    }\n\n    \/**\n     * Filter the destination so that if it is a chat application, it will receive the object only if the user is logged in,\n     * and if the room the current chatter is in is an adult room, only if the user of the destination application is adult.\n     * Destinations other than chat applications will receive the object with no filtering.\n     * @param appUUID       application UUID.\n     * @param appName       application name.\n     * @param userID        n\/a\n     * @param remoteAddress remote address of the destination application.\n     * @param appParams     application parameter map as set by Main.getAppParams() of the filtered application. If the destination\n     *                      is a chat application the map is not null and it contains a mapping of the &quot;state&quot; key to any of\n     *                      &quot;logout&quot;, &quot;login&quot; or &quot;adult&quot;. The latter means the user is logged in as an adult.\n     * @return              true to approve distribution to this application, false to deny it.\n     *\/\n    @Override\n    protected boolean filterDestination(UUID appUUID, String appName, String userID, String remoteAddress,\n        Map&lt;String, String&gt; appParams\n    ) {\n        if (appParams == null)\n            return true;\n        String state = appParams.get(&quot;state&quot;);\n        if (state == null)\n            return true;\n        switch(state) {\n        case &quot;logout&quot;:\n            return false;\n        case &quot;login&quot;:\n            return !((ChatRoom)getParent()).isAdult();\n        case &quot;adult&quot;:\n            return true;\n        default:\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The added code 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> (line 50), which filters\nobjects according to the mapping of the <code>\u201cstate\u201d<\/code>\nkey in the given <code>appParams<\/code> parameter\nand the value of the <code>adult<\/code> property\nof the object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can now understand the part that we asked you <a href=\"#ChatRoomProducer\">above<\/a> to ignore\nwhen we discussed the <code>ChatRoomProducer<\/code>\nclass of the <em>Room Producer<\/em>\napplication. It implements <code>filterDestination()<\/code>\nin a similar way for <code>ChatRoom<\/code> objects\nthat the application produces. All done!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a id=\"data_integrity\"><\/a><strong>More about data integrity<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As was mentioned above, Data Object Reset is a procedure that takes place when a consumer of a certain object type detects that some objects are missing. This is the obvious case when an application starts up, and may also happen in situations such as interrupted communication, data loss due to network congestion etc. For an in-depth discussion of this topic, see the blog post <a href=\"http:\/\/spiderwiz.org\/project\/under-the-hood\/\">Lean and Mean \u2013 Under the Spiderwiz hood<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You have seen so far that the Spiderwiz framework is strongly event-driven. In the <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-10\/\">next lesson<\/a> we will talk about synchronous vs. asynchronous event handling.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn about the concept of Data Object Reset, how it is performed automatically and how to control it programmatically.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":9,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-926","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/926","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=926"}],"version-history":[{"count":23,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/926\/revisions"}],"predecessor-version":[{"id":1422,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/926\/revisions\/1422"}],"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=926"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}