{"id":970,"date":"2020-08-06T07:13:39","date_gmt":"2020-08-06T07:13:39","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=970"},"modified":"2025-04-09T06:15:58","modified_gmt":"2025-04-09T06:15:58","slug":"lesson-10","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-10\/","title":{"rendered":"Lesson 10: Asynchronous Event Handling"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Up until now we used <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">DataObject.onEvent()<\/a> for processing data object\nevents. This method is executed synchronously, i.e. on the same execution\nthread that reads the object data from the communication channel. In many cases\nevent processing is a relatively lengthy operation and doing it synchronously\nmight not only defer execution of data objects of the same type but also slow\ndown dramatically the entire application performance.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Spiderwiz offers an elegant solution to the problem with <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onAsyncEvent()\">DataObject.onAsyncEvent()<\/a>, a message that can be\noverridden instead of <code>onEvent()<\/code> and operates\nasynchronously.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For our exercise, we use the chat system that we have built\nso far and introduce an intentional suspension in the processing of chat\nmessages. We will see how it affects both synchronous and asynchronous\nprocessing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In order to increase the visibility of these effects we add\na <code>time<\/code> field to both <code>ChatMessage<\/code> library class (that handles\nchat room messages) and <code>Chat<\/code> (that\nhandles private messages). Here is the first:<\/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;\nimport org.spiderwiz.zutils.ZDate;\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    @WizField private ZDate time;               \/\/ Contains message time\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    public ZDate getTime() {\n        return time;\n    }\n\n    public void setTime(ZDate time) {\n        this.time = time;\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\">And the second:<\/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;\nimport org.spiderwiz.zutils.ZDate;\n\n\/**\n * Chat class for Spiderwiz tutorial's lesson 2.\n *\/\npublic class Chat extends DataObject {\n    \/**\n     * Mandatory public static field for all data objects.\n     *\/\n    public final static String ObjectCode = &quot;CHAT&quot;;\n    \n    @WizField private String name;              \/\/ Contains the name of the chatter\n    @WizField private String message;           \/\/ Contains a chat message\n    @WizField private ZDate time;               \/\/ Contains message time\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getMessage() {\n        return message;\n    }\n\n    public void setMessage(String message) {\n        this.message = message;\n    }\n\n    public ZDate getTime() {\n        return time;\n    }\n\n    public void setTime(ZDate time) {\n        this.time = time;\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 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\">The introduction of this field requires a slight change to <code>ChatMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson10.chat;\n\nimport java.io.PrintStream;\nimport java.text.ParseException;\nimport java.util.Collection;\nimport java.util.List;\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;Z8.01&quot;;  \/\/ Version Z8.01: Lesson 8 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;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        );\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(ChatterConsume.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    \/**\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.setTime(ZDate.now());\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.setTime(ZDate.now());\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                        myName = query.getName();\n                        birthday = query.getBirthday();\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        System.out.printf(&quot;%s is logged out.n&quot;, myName);\n        myName = null;\n        birthday = null;\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                    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        \/\/ User must be logged in\n        if (myName == null) {\n            System.out.printf(&quot;You must log in before joining a room.n&quot;);\n            return;\n        }\n        \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        \/\/ Check if a young chatters is trying to join an adult room\n        if (!chatRoom.canJoin(birthday)) {\n            System.out.printf(&quot;You cannot join room %s because it is only for adultsn&quot;, room);\n            return;\n        }\n        \n        \/\/ Leave current room if any\n        leaveRoom(null);\n        \n        \/\/ Create 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 changes are in lines 201 and 209 that set the current\ntime in the <code>time<\/code> fields of the\ncreated messages.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The other changes are in the classes that process messages.\nHere is <code>ChatMessageImp<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson10.chat;\n\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\nimport org.spiderwiz.zutils.ZDate;\n\n\/**\n * Consumer implementation of the ChatMessage class\n *\/\npublic class ChatMessageImp extends ChatMessage {\n    \/**\n     * Called synchronously when a chat message is received. If the message was not sent in the room we are in then ignore it.\n     * Otherwise if the message starts with '$' then print it and suspend execution for 20 seconds.\n     * If the message does not start with '$' return false to indicate that the message was not handled and shall be handled\n     * by onAsyncEvent().\n     * @return true to indicate that the event has been handled, false if it was not.\n     *\/\n    @Override\n    protected boolean onEvent() {\n        if (ChatMain.getInstance().isSameRoom(getParent().getParent().getObjectID())) {\n            if (!getMessage().startsWith(&quot;$&quot;))\n                return false;\n            printMessage();\n        }\n        return true;\n    }\n\n    \/**\n     * Print out the message and suspend execution for 20 seconds.\n     * @return true\n     *\/\n    @Override\n    protected boolean onAsyncEvent() {\n        printMessage();\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, or the message is private.\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\n    \/**\n     * Allocate 5 threads to asynchronous processing of this object type.\n     * @return 5\n     *\/\n    @Override\n    protected int getThreadAllocation() {\n        return 5;\n    }\n    \n    \/**\n     * Suspend execution for a random interval between 0 and 20 seconds, then Print out the message and its details.\n     *\/\n    private void printMessage() {\n        try {\n            Thread.sleep(Math.round(Math.random() * ZDate.SECOND * 20));\n            System.out.printf(&quot;%4$s: From %1$s(%3$s): %2$sn&quot;,\n                ((Chatter)getParent()).getName(), getMessage(), getTime().format(ZDate.FULL_DATE), ZDate.now(ZDate.FULL_DATE));\n        } catch (InterruptedException ex) {\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We changed the code of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a> (line 21) so that if the handled\nmessage starts with a dollar sign (\u2018$\u2019) then it is handled within the same\nmethod synchronously. In this case the method returns <code>true<\/code> to indicate that the message has been processed. In other\ncases the message returns <code>false<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Spiderwiz engine always tries <code>onEvent()<\/code> first, and if it returns <code>false<\/code> it calls <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onAsyncEvent()\">onAsyncEvent()<\/a> (line 35) asynchronously. Both of\nthese methods call the private <code>printMessage()<\/code>\nmethod (line 72), which suspends the executing thread for a random time\ninterval between 0 and 20 seconds, and then prints the message details\nincluding message creation time and the current time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The other interesting override is of <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getThreadAllocation()\">getThreadAllocation()<\/a> (line 65). This tells the\nSpiderwiz engine how many execution threads to allocate for asynchronous processing\nof data objects of this type. We set it to 5. The default value, if the method\nis not overridden, is -1, which means that the framework determines the number\nof threads by the number of CPUs available on this machine.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is also a change in <code>ChatConsume<\/code> that prints private messages:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson10.chat;\n\nimport org.spiderwiz.tutorial.objectLib.Chat;\nimport org.spiderwiz.zutils.ZDate;\n\n\/**\n * Consumer implementation of the Chat class\n *\/\npublic class ChatConsume extends Chat{\n    \/**\n     * Called asynchronously when a private message is received. Print the sender name, time of the message and the message.\n     * @return true to indicate that the event has been handled.\n     *\/\n    @Override\n    protected boolean onAsyncEvent() {\n        System.out.printf(&quot;%4$s: From %1$s(sent private-%3$s): %2$sn&quot;,\n            getName(), getMessage(), getTime().format(ZDate.FULL_DATE), ZDate.now(ZDate.FULL_DATE));\n        return true;\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Here we replace <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onEvent()\">onEvent()<\/a> by <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onAsyncEvent()\">onAsyncEvent()<\/a> and print message details\nincluding message creation time and current time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no need for any other change, including not in the\nconfiguration files. Just run everything (we tested it with <em>User Manager<\/em> and <em>Room Producer<\/em> of the previous lessons) and see what happens. With\nthe code as above, you should see that not more than 5 chat room messages are\nprocessed simultaneously and that they are not printed in order (because they\nhave different suspension times). If a message starts with a dollar sign, which\ncauses synchronous processing, then you should see that everything hangs until\nthe message is processed and printed out, including the process of private\nmessages. If you change <code>getThreadAllocation()<\/code>\nto return 1 then chat rooms messages are printed in their order with random\ndelays between them, and private messages are printed promptly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An important point to note is that usually only the <code>onEvent()<\/code> method is processed synchronously. Other events, such as <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onRemoval()\">onRemoval()<\/a>, <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onRename(java.lang.String)\">onRename()<\/a>, <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/QueryObject.html#onInquire()\">onInquire()<\/a>, <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/QueryObject.html#onReply()\">onReply()<\/a> etc. are always handled asynchronously (<a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#onNew()\">onNew()<\/a> is always synchronous because the notification of a new object must be handled before object events are processed). If you want to handle them synchronously override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#getThreadAllocation()\">getThreadAllocation()<\/a> to return zero.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A word about\nperformance<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The goal of Spiderwiz is not only to make the life of programmers easier, but also to let them develop robust and efficient applications. One of the most resource consuming operations in Java is the spawning of new execution threads. For this reason, Spiderwiz never spawns threads on the fly. All of them are created during application initialization and a sophisticated queuing mechanism is applied in order to execute all tasks quickly, efficiently and with the least resource consumption as possible.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You are apparently convinced that Spiderwiz is the state-of-the-art in service-mesh development and Java development in general. You may realize, however, that for the framework to work efficiently the entire system has to be developed under it, and wonder if there is an easy way to bridge between Spiderwiz applications and legacy frameworks. Well, definitely there is, and you will learn about it in the <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-11\/\">next lesson<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Experience Spiderwiz robustness by exercising with synchronous vs. asynchronous event handling.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":10,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-970","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/970","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=970"}],"version-history":[{"count":10,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/970\/revisions"}],"predecessor-version":[{"id":1423,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/970\/revisions\/1423"}],"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=970"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}