{"id":1012,"date":"2020-08-13T20:28:48","date_gmt":"2020-08-13T20:28:48","guid":{"rendered":"http:\/\/spiderwiz.org\/project\/?page_id=1012"},"modified":"2025-04-09T06:15:59","modified_gmt":"2025-04-09T06:15:59","slug":"lesson-17","status":"publish","type":"page","link":"https:\/\/spiderwiz.org\/project\/tutorial\/lesson-17\/","title":{"rendered":"Lesson 17: Writing an Import Handler Plugin"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The Import\/Export concept that can be used as a mechanism to\nbridge between Spiderwiz-based and legacy applications was introduced in <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-11\/\">Lesson 11<\/a>.\nThe exercise we did there assumed that the interface with the foreign system\nwas based on serialized data, which the default <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html\">ImportHandler<\/a> was capable of processing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are situations in which this assumption cannot be\nheld. The solution in these cases is to extend <code>ImportHandler<\/code>. In this lesson we will show how to do that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The test case that will be demonstrated is the example built\nin <a href=\"https:\/\/www.baeldung.com\/java-websockets\">Baeldung\u2019s\nGuide to the Java API for WebSocket<\/a>. It is a simple chat application\nin which clients use a web browser to communicate with a server over\nWebSockets. They send chat messages and the server distributes them to all\nclients. We are going to bridge it with the chat system that has been escorting\nus throughout this tutorial, so that every message typed in Baeldung\u2019s\napplication (\u201cthe imported system\u201d from now on) shows also on our system and\nvice versa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will achieve our goal by hooking into Baeldung\u2019s code\n(that you can get <a href=\"https:\/\/github.com\/eugenp\/tutorials\/tree\/master\/java-websocket\">here<\/a>) and turning it into a Spiderwiz application\nthat traps chat messages and imports them into the Spiderwiz network, as well\nas exports chat messages received over our network to the imported system.\nSince our system requires that chat messages are exchanged inside Chat Rooms\nand the imported system does not have this notion, we will configure a room\nname to it and users of our system will need to join that room in order to\nexchange messages with the imported system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Baeldung\u2019s project is called <code>java-websocket<\/code>. We will stay with this project and update it. The\nfirst step is to add the <code>spiderwiz-core<\/code>\ndependency to its POM:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>&lt;dependency&gt;\n    &lt;groupId&gt;org.spiderwiz&lt;\/groupId&gt;\n    &lt;artifactId&gt;spiderwiz-core&lt;\/artifactId&gt;\n    &lt;version&gt;4.0&lt;\/version&gt;\n&lt;\/dependency&gt;<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Note that <code>spiderwiz-websocket-server<\/code>\nshall not be included because it would conflict with the mechanism already\nprovided by Baeldung.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We will place all our stuff in a new package &#8211; <code>org.spiderwiz.tutorial.lesson17<\/code>. We\nstart as usual with <code>WebsocketBridgeMain<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson17;\n\nimport com.baeldung.model.Message;\nimport java.util.List;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.core.ImportHandler;\nimport org.spiderwiz.core.Main;\nimport org.spiderwiz.tutorial.objectLib.ChatApp;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\n\n\/**\n * Main class for the Websocket Bridge.\n *\/\npublic class WebsocketBridgeMain extends Main{\n    private static final String ROOT_DIRECTORY = &quot;\/tests\/WebsocketBridge&quot;;\n    private static final String CONFIG_FILE_NAME = &quot;ws-bridge.conf&quot;;\n    private static final String APP_NAME = &quot;Websocket Chat Bridge&quot;;\n    private static final String APP_VERSION = &quot;Z17.01&quot;;  \/\/ Version Z17.01: First working version\n    \n    private WebsocketImportHandler importHandler = null;\n\n    public WebsocketBridgeMain() {\n        super(ROOT_DIRECTORY, CONFIG_FILE_NAME, APP_NAME, APP_VERSION);\n    }\n\n    \/**\n     * @return the class instance as WebsocketBridgeMain type.\n     *\/\n    public static WebsocketBridgeMain getInstance() {\n        return (WebsocketBridgeMain)Main.getInstance();\n    }\n\n    \/**\n     * @return the produced objects\n     *\/\n    @Override\n    protected String[] getProducedObjects() {\n        return new String[]{\n            ChatRoom.ObjectCode,\n            Chatter.ObjectCode,\n            ChatMessage.ObjectCode\n        };\n    }\n\n    \/**\n     * @return the consumed objects\n     *\/\n    @Override\n    protected String[] getConsumedObjects() {\n        return new String[]{\n            Chatter.ObjectCode,\n            ChatMessage.ObjectCode\n        };\n    }\n\n    \/**\n     * Add implementation classes to the object factory list of this application.\n     * @param factoryList\n     *\/\n    @Override\n    protected void populateObjectFactory(List&lt;Class&lt;? extends DataObject&gt;&gt; factoryList) {\n        super.populateObjectFactory(factoryList);\n        factoryList.add(ChatRoom.class);\n        factoryList.add(ChatApp.class);\n        factoryList.add(ChatterBridge.class);\n        factoryList.add(ChatMessageBridge.class);\n    }\n\n    \/**\n     * @return the custom import handler instead of the default one.\n     *\/\n    @Override\n    protected ImportHandler createImportHandler() {\n        return new WebsocketImportHandler();\n    }\n\n    public synchronized WebsocketImportHandler getImportHandler() {\n        return importHandler;\n    }\n\n    public synchronized void setImportHandler(WebsocketImportHandler importHandler) {\n        this.importHandler = importHandler;\n    }\n \n    \/**\n     * Pass an imported message to the current import handler, if exists.\n     * @param message\n     *\/\n    public void processImportedMessage(Message message) {\n        WebsocketImportHandler handler = getImportHandler();\n        if (handler != null)\n            handler.processMessage(message);\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The produced object types (line 39) are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>ChatRoom<\/code>: imports the configured room\nname into our system.<\/li><li><code>Chatter<\/code>: tells when a user of the\nimported system connects or disconnects.<\/li><li><code>ChatMessage<\/code>: imports chat messages.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The consumed object types (line 51) are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>Chatter<\/code>: informs the imported system\nwhen a chatter on our system joins or leaves the configured room.<\/li><li><code>ChatMessage<\/code>: exports received&nbsp; messages to the imported system.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">We register all implementation classes in <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#populateObjectFactory(java.util.List)\">populateObjectFactory()<\/a> (line 63).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">As mentioned, we need to extend <code>ImportHandler<\/code>. We will do it with <code>WebsocketImportHandler<\/code> that we need to register in <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#createImportHandler()\">&nbsp;<\/a><a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/Main.html#createImportHandler()\">createImportHandler()<\/a> (line 75) instead of the\ndefault handler.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rest are methods that we use internally in our\nimplementation \u2013 <code>getImportHandler()<\/code>\n(line 79) and <code>setImportHandler()<\/code>\n(line 83) for getting and setting the <code>importHandler<\/code>\nproperty and <code>processImportedMessage()<\/code>\n(line 91) for processing an imported message. We will see their use in a\nmoment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now to the more interesting stuff. Here is <code>WebsocketImportHandler<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson17;\n\nimport com.baeldung.model.Message;\nimport com.baeldung.websocket.ChatEndpoint;\nimport java.util.Map;\nimport org.spiderwiz.core.DataObject;\nimport org.spiderwiz.core.ImportHandler;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\n\n\/**\n * Implements a custom Import Handler for bridging with Baeldung's Websocket chat service.\n *\/\npublic class WebsocketImportHandler extends ImportHandler {\n    private ChatRoom chatRoom = null;\n    \n    \/**\n     * Check if the configuration is for a bridge with Baeldung's Websocket chat service (&quot;websocket&quot; key exists) and if the\n     * bridge has not been set yet, then configure it. The configuration shall include a &quot;room&quot; parameter that specifies the\n     * chat room used by all users of the bridged service.\n     * @param configParams      a map of key=value configuration parameters.\n     * @param n                 the &lt;em&gt;n&lt;\/em&gt; value of the &lt;em&gt;import-n&lt;\/em&gt; property.\n     * @return true on success.\n     *\/\n    @Override\n    protected boolean configure(Map&lt;String, String&gt; configParams, int n) {\n        if (!configParams.containsKey(&quot;websocket&quot;))\n            return false;\n        if (WebsocketBridgeMain.getInstance().getImportHandler() != null) {\n            WebsocketBridgeMain.getLogger().logEvent(\n                &quot;An import handler for Baeldung's Websocket chat service bridge has already been defined.&quot;);\n            return false;\n        }\n        String room = configParams.get(&quot;room&quot;);\n        if (room == null) {\n            WebsocketBridgeMain.getLogger().logEvent(&quot;No room name has been specified for Baeldung's Websocket chat service bridge&quot;);\n            return false;\n        }\n        WebsocketBridgeMain.getInstance().setImportHandler(this);\n        \n        try {\n            \/\/ Create and commit a room object\n            chatRoom = WebsocketBridgeMain.createTopLevelObject(ChatRoom.class, room);\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            WebsocketBridgeMain.getLogger().logEvent(&quot;Room %1$s could not be created because of %2$s&quot;, room, ex.toString());\n            return false;\n        }\n        chatRoom.commit();\n        return true;\n    }\n\n    \/**\n     * Export an object by broadcasting it to all users of the Websocket chat service.\n     * @param object    the exported object, known to be of type com.baeldung.model.Message.\n     * @return true\n     *\/\n    @Override\n    protected boolean exportObject(Object object) {\n        ChatEndpoint.broadcastInternal((Message)object);\n        return true;\n    }\n\n    \/**\n     * Identifies the handler by the name &quot;websocket&quot;.\n     * @return &quot;websocket&quot;\n     *\/\n    @Override\n    public String getName() {\n        return &quot;websocket&quot;;\n    }\n\n    \/**\n     * At cleanup remove the associated ChatRoom object.\n     *\/\n    @Override\n    protected void cleanup() {\n        if (chatRoom != null) {\n            DataObject removed = chatRoom.remove();\n            if (removed != null)\n                removed.commit();\n        }\n        chatRoom = null;\n    }\n\n    \/**\n     * Process an imported message by calling the framework's processObject() method.\n     * @param message\n     *\/\n    public void processMessage(Message message) {\n        try {\n            processObject(message, null, message.getContent().length());\n        } catch (Exception ex) {\n            WebsocketBridgeMain.getInstance().sendExceptionMail(ex, &quot;While trying to import a message&quot;, null, false);\n        }\n    }\n\n    \/**\n     * @return the associated chat room name.\n     *\/\n    public String getRoomName() {\n        return chatRoom == null ? null : chatRoom.getObjectID();\n    }\n\n    \/**\n     * @return the associated ChatRoom object\n     *\/\n    public ChatRoom getChatRoom() {\n        return chatRoom;\n    }\n }<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The class extends <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html\">ImportHandler<\/a>. First we override <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html#configure(java.util.Map,int)\">configure()<\/a> (line 25). The arguments to this\nmethod are the parameters specified in the <code>import-<\/code><em>n<\/em>\nproperty defined in the application\u2019s configuration file. Our handler must have\nthe \u201cwebsocket\u201d keyword in its configuration and a \u201croom\u201d parameter that\ndefines the room name associated with the imported system. If any of these is\nmissing, or if a handler has already been defined (by another <code>import-<\/code><em>n<\/em> property), then the method\nreturns <code>false<\/code>. If everything is OK\nthen we set the <code>importHandler<\/code>\nproperty of <code>WebsocketBridgeMain<\/code> to <code>this<\/code>, then create and commit a <code>ChatRoom<\/code> object to let our system know\nabout this room.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html#exportObject(java.lang.Object)\">exportObject()<\/a> method, overridden in line 57,\naccepts an object that is already converted to the exported format, in this\ncase a <code>Message<\/code> object as defined in\nBaeldung\u2019s code. All it needs to do is to pass it over to the imported system\nfor distribution. This is done by calling the <code>broadcastInternal()<\/code> method of the <code>ChatEndpoint<\/code> class that we will see below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The method <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html#getName()\">getName()<\/a> (line 67) returns a unique\nidentification for this handler, while <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html#cleanup()\">cleanup()<\/a> (line 75) removes the <code>ChatRoom<\/code> object that is associated with\nit.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rest are methods that are used internally in our\nimplementation \u2013 <code>processMessage()<\/code>\n(line 88) accepts a <code>Message<\/code> object\nfrom the imported system and pass it over for import processing by calling <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/ImportHandler.html#processObject(java.lang.Object,org.spiderwiz.zutils.ZDate,int)\">processObject()<\/a>,&nbsp;\n<code>getRoomName()<\/code> (line 99)\nreturns the room name associated with this handler and <code>getChatRoom()<\/code> (line 106) returns the <code>ChatRoom<\/code> object associated with it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Similarly to Lesson 11, we extend <code>Chatter<\/code> and <code>ChatMessage<\/code>\nto handle import-export. Here is <code>ChatterBridge<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson17;\n\nimport com.baeldung.model.Message;\nimport org.spiderwiz.core.ImportHandler;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\nimport org.spiderwiz.zutils.ZDate;\n\n\/**\n * Implementation class of Chatter for bridging with Baeldung's Websocket chat service.\n *\/\npublic class ChatterBridge extends Chatter {\n\n    \/**\n     * Import the given data which is supposed to be a Message object.\n     * If the content of the message is either &quot;Connected!&quot; or &quot;Disconnected! then it is relevant to Chatter.\n     * @param data          the imported object.\n     * @param channel       the handler of the channel the data is imported from. Shall be &quot;websocket&quot;.\n     * @param ts            the timestamp attached to the data by the channel handler.\n     * @return the key hierarchy of the imported object - room name then application UUID then chatter name, or null\n     * if the content is not &quot;Connected!&quot;.\n     * @throws Exception\n     *\/\n    @Override\n    protected String[] importObject(Object data, ImportHandler channel, ZDate ts) throws Exception {\n        \/\/ check if the import handler name is &quot;websocket&quot;\n        if (!&quot;websocket&quot;.equalsIgnoreCase(channel.getName()))\n            return null;\n        \n        \/\/ switch by the message content\n        Message message = (Message)data;\n        switch(message.getContent()) {\n        case &quot;Disconnected!&quot;:\n            \/\/ Import the removal then proceed as if connected.\n            if (remove() == null)\n                return null;\n        case &quot;Connected!&quot;:\n            \/\/ Set user name and return the key list of the joining (or leaving) user\n            setName(message.getFrom());\n            return new String[] {\n                ((WebsocketImportHandler)channel).getRoomName(),\n                getMyUUID(),\n                getName()\n            };\n        }\n        \n        \/\/ In all other cases return null\n        return null;\n    }\n\n    \/**\n     * Export a &quot;Connected!&quot; or &quot;Disconnected!&quot; message\n     * @param channel   the handler of the channel the object will be exported to. Shall be &quot;bridge&quot;.\n     * @param newID     null if this object is active, empty string if it has been removed, non-empty string if it has been renamed.\n     * @return          the exported message\n     *\/\n    @Override\n    protected Message exportObject(ImportHandler channel, String newID) {\n        \/\/ Do not export my own object\n        if (WebsocketBridgeMain.getInstance().getAppUUID().equals(getOriginUUID()))\n            return null;\n        \n        \/\/ check if the import handler name is &quot;websocket&quot;\n        if (!&quot;websocket&quot;.equalsIgnoreCase(channel.getName()))\n            return null;\n        \n        \/\/ check if the room name equals the name configured in the import handler.\n        if (!getRoomName().equalsIgnoreCase(((WebsocketImportHandler)channel).getRoomName()))\n            return null;\n        \n        \/\/ Generate a message with &quot;Connected!&quot; or a &quot;Disconnected!&quot; content\n        Message message = new Message();\n        message.setFrom(getName());\n        message.setContent(newID == null ? &quot;Connected!&quot; : &quot;Disconnected!&quot;);\n        return message;\n    }\n\n    \/**\n     * When a new chatter in a room is encountered, export it\n     *\/\n    @Override\n    protected void onNew() {\n        commit(&quot;&quot;);\n    }\n\n    \/**\n     * When a chatter leaves a room, export the command\n     * @return true to confirm the removal.\n     *\/\n    @Override\n    protected boolean onRemoval() {\n        \/\/ Done exactly as oneNew(). exportObject() detects whether this is a join or a leave case.\n        commit(&quot;&quot;);\n        return true;\n    }\n    \n    \/**\n     * @return the current application UUID as a string\n     *\/\n    private String getMyUUID() {\n        return WebsocketBridgeMain.getInstance().getAppUUID().toString();\n    }\n    \n    \/**\n     * Utility function to get the room name, which is the ID of the grand parent of the current object.\n     * @return the room name\n     *\/\n    String getRoomName() {\n        return getParent().getParent().getObjectID();\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">If you followed the explanation in Lesson 11 then you should find this straightforward. We do exactly what we did there, except that instead of parsing serialized data we examine a <code>Message<\/code> object. In <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#importObject(java.lang.Object,org.spiderwiz.core.ImportHandler,org.spiderwiz.zutils.ZDate)\">importObject()<\/a> (line 24) we check the <code>content<\/code> property of the object. If it is &#8220;Disconnected!&#8221; then we call <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#remove()\">remove()<\/a> on the object to mark it for deletion, then proceed as in the &#8220;Connected!&#8221; case. In both cases we import a <code>Chatter<\/code> object by returning its key list. In any other case we return <code>null<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"> Likewise for <a href=\"http:\/\/spiderwiz.org\/apidocs\/org\/spiderwiz\/core\/DataObject.html#exportObject(org.spiderwiz.core.ImportHandler,java.lang.String)\">exportObject()<\/a> (line 57) we check if the room name equals the name configured for the imported system, and if it does then we return a <code>Message<\/code> object with the <code>content<\/code> property set to either &#8220;Connected!&#8221; or &#8220;Disconnected!&#8221; as necessary. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is no change compared to Lesson 11 in the rest of the\nclass.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same concept applies to <code>ChatMessageBridge<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson17;\n\nimport com.baeldung.model.Message;\nimport java.util.Map;\nimport java.util.UUID;\nimport org.spiderwiz.core.ImportHandler;\nimport org.spiderwiz.tutorial.objectLib.ChatApp;\nimport org.spiderwiz.tutorial.objectLib.ChatMessage;\nimport org.spiderwiz.tutorial.objectLib.ChatRoom;\nimport org.spiderwiz.tutorial.objectLib.Chatter;\nimport org.spiderwiz.zutils.ZDate;\n\n\/**\n * Implementation class of ChatMessagee for bridging with Baeldung's Websocket chat service.\n*\/\npublic class ChatMessageBridge extends ChatMessage {\n\n    \/**\n     * If this is not a bounced message that was imported by this application then commit it to itself in order to export it.\n     * @return true;\n     *\/\n    @Override\n    protected boolean onEvent() {\n        commit(&quot;&quot;);\n        return true;\n    }\n\n    \/**\n     * Import the given data which is supposed to be a Message object.\n     * @param data          the imported object.\n     * @param channel       the handler of the channel the data is imported from.\n     * @param ts            the timestamp attached to the data by the channel handler.\n     * @return the key hierarchy of the imported object - room name then application UUID then chatter name\n     * @throws Exception\n     *\/\n    @Override\n    protected String[] importObject(Object data, ImportHandler channel, ZDate ts) throws Exception {\n        \/\/ check if the import handler name is &quot;websocket&quot;\n        if (!&quot;websocket&quot;.equalsIgnoreCase(channel.getName()))\n            return null;\n        \n        \/\/ Process the message if it is neither &quot;Connected!&quot; nor &quot;Disconnected!&quot;\n        Message message = (Message)data;\n        switch(message.getContent()) {\n        case &quot;Connected!&quot;:\n        case &quot;Disconnected!&quot;:\n            return null;\n        }\n        \n        setMessage(message.getContent());\n        setTime(ts);\n        \/\/ Return the key sequence\n        return new String[] {\n            ((WebsocketImportHandler)channel).getRoomName(),\n            WebsocketBridgeMain.getInstance().getAppUUID().toString(),\n            message.getFrom()\n        };\n    }\n\n    \/**\n     * Export this object as a Message object.\n     * @param channel   the handler of the channel the object will be exported to.\n     * @param newID     null if this object is active, empty string if it has been removed, non-empty string if it has been renamed.\n     *                  In this case it is always null because ChatMessage is disposable.\n     * @return          the exported Message object\n     *\/\n    @Override\n    protected Message exportObject(ImportHandler channel, String newID) {\n        \/\/ Don't bounce imported objects\n        if (WebsocketBridgeMain.getInstance().getAppUUID().equals(getOriginUUID()))\n            return null;\n        \n        \/\/ check if the import handler name is &quot;websocket&quot;\n        if (!&quot;websocket&quot;.equalsIgnoreCase(channel.getName()))\n            return null;\n\n        \/\/ export\n        String room = getParent().getParent().getParent().getObjectID();\n        if (!room.equalsIgnoreCase(((WebsocketImportHandler)channel).getRoomName()))\n            return null;\n        Message message = new Message();\n        message.setFrom(((Chatter)getParent()).getName());\n        message.setContent(getMessage());\n        return message;\n    }\n\n    \/**\n     * Make sure the object is delivered to the right destinations, i.e. to chatters in the same room or to other bridge applications.\n     * @param appUUID       Application UUID of the destination\n     * @param appName       Application name of the destination. Not relevant here.\n     * @param userID        User ID used for establishing communication. N\/A.\n     * @param remoteAddress Remote address of the destination application. N\/A.\n     * @param appParams     application parameter map of the destination. If it includes &quot;role&quot; -&gt; &quot;bridge&quot; mapping\n     *                      then deliver all messages with no further filtering.\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,\n        String remoteAddress, Map&lt;String, String&gt; appParams)\n    {\n        try {\n            \/\/ Check if the destination is for the same room\n            ChatRoom chatRoom = (ChatRoom)getParent().getParent().getParent();\n            return chatRoom.getChild(ChatApp.class, appUUID.toString()) != null;\n        } catch (NoSuchFieldException | IllegalAccessException ex) {\n            \/\/ Arriving here if ChatApp does not contain ObjectCode static field or the field is not public.\n            \/\/ In this case, send an exception message.\n            WebsocketBridgeMain.getInstance().sendExceptionMail(ex, &quot;Cannot instantiate ChatApp class&quot;, null, false);\n            return false;\n        }\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Again, we do exactly what we did in Lesson 11, except that\ninstead of dealing with serialized data we deal with a <code>Message<\/code> object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We need to initialize the Spiderwiz engine at startup, so\nour package includes <code>RootServlet<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package org.spiderwiz.tutorial.lesson17;\n\n\n\nimport javax.servlet.ServletException;\nimport javax.servlet.annotation.WebServlet;\nimport javax.servlet.http.HttpServlet;\n\n\/**\n * A root servlet for spawning a Spiderwiz application\n *\/\n@WebServlet(name = &quot;RootServlet&quot;, urlPatterns = {&quot;\/RootServlet&quot;}, loadOnStartup = 1)\npublic class RootServlet extends HttpServlet {\n\n    \/**\n     * Instantiate and initialize the Spiderwiz runtime\n     * @throws ServletException\n     *\/\n    @Override\n    public void init() throws ServletException {\n        super.init();\n        new WebsocketBridgeMain().init();\n    }\n\n    \/**\n     * Do Spiderwiz cleanup on application termination\n     *\/\n    @Override\n    public void destroy() {\n        WebsocketBridgeMain.getInstance().cleanup();\n        super.destroy();\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">This is identical to a class with the same name that we had\nin <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-3\/\">Lesson\n3<\/a> except that it instantiates and initializes <code>WebsocketBridgeMain<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It remains to zip our stuff with Baeldung\u2019s code. We do it\nby editing the latter\u2019s <code>ChatEndpoint<\/code>\nclass:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>package com.baeldung.websocket;\n\nimport com.baeldung.model.Message;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.Set;\nimport java.util.concurrent.CopyOnWriteArraySet;\nimport javax.websocket.EncodeException;\nimport javax.websocket.OnClose;\nimport javax.websocket.OnError;\nimport javax.websocket.OnMessage;\nimport javax.websocket.OnOpen;\nimport javax.websocket.Session;\nimport javax.websocket.server.PathParam;\nimport javax.websocket.server.ServerEndpoint;\nimport org.spiderwiz.tutorial.lesson17.WebsocketBridgeMain;\n\n@ServerEndpoint(value = &quot;\/chat\/{username}&quot;, decoders = MessageDecoder.class, encoders = MessageEncoder.class)\npublic class ChatEndpoint {\n    private Session session;\n    private static final Set&lt;ChatEndpoint&gt; chatEndpoints = new CopyOnWriteArraySet&lt;&gt;();\n    private static final HashMap&lt;String, String&gt; users = new HashMap&lt;&gt;();\n\n    @OnOpen\n    public void onOpen(Session session, @PathParam(&quot;username&quot;) String username) throws IOException, EncodeException {\n\n        this.session = session;\n        chatEndpoints.add(this);\n        users.put(session.getId(), username);\n\n        Message message = new Message();\n        message.setFrom(username);\n        message.setContent(&quot;Connected!&quot;);\n        broadcast(message);\n    }\n\n    @OnMessage\n    public void onMessage(Session session, Message message) throws IOException, EncodeException {\n        message.setFrom(users.get(session.getId()));\n        broadcast(message);\n    }\n\n    @OnClose\n    public void onClose(Session session) throws IOException, EncodeException {\n        chatEndpoints.remove(this);\n        Message message = new Message();\n        message.setFrom(users.get(session.getId()));\n        message.setContent(&quot;Disconnected!&quot;);\n        broadcast(message);\n    }\n\n    @OnError\n    public void onError(Session session, Throwable throwable) {\n        \/\/ Do error handling here\n    }\n\n    \/**\n     * Broadcast a message both internally and over the bridge.\n     * @param message \n     *\/\n    private static void broadcast(Message message) {\n        broadcastInternal(message);\n        WebsocketBridgeMain.getInstance().processImportedMessage(message);\n    }\n    \n    \/**\n     * Broadcast a message internally to all Websocket chat users.\n     * @param message\n     *\/\n    public static void broadcastInternal(Message message) {\n        chatEndpoints.forEach(endpoint -&gt; {\n            synchronized (endpoint) {\n                try {\n                    endpoint.session.getBasicRemote()\n                        .sendObject(message);\n                } catch (IOException | EncodeException e) {\n                    \/\/ Use Spiderwiz framework to handle the exception\n                    WebsocketBridgeMain.getInstance().sendExceptionMail(e, &quot;While sending a Message object&quot;, null, false);\n                }\n            }\n        });\n    }\n}<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">We modified the <code>broadcast()<\/code>\nmethod (line 61) by moving its code to <code>broadcastInternal()<\/code>\n(line 70) and adding a call to <code>WebsocketBridgeMain.processImportedMessage()<\/code>.\nThat means that every message is broadcast to both the internal users of the\nimported system and to our system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The method <code>broadcastInternal()<\/code>\nthat was just mentioned is declared <code>public<\/code>\nso that we can call it from <code>WebsocketImportHandler<\/code>\nwhen we need to export a message from our system to the imported system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s all the coding work. For running and testing it we\nneed <code>ws-bridge.conf<\/code>:<\/p>\n\n\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre>[log folder]Logs\n[producer-1]ip=localhost;port=10001\n[import-1]websocket;room=Baeldung<\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">To test it, deploy the project on your web server, on your web browser go to <a href=\"http:\/\/localhost\/java-websocket\/\">http:\/\/localhost\/java-websocket\/<\/a> (specify a port number if necessary), run one or more instances of our Chat Application, run also <em>User Manager<\/em>, login from our chat applications and join the <code>Baeldung<\/code> room, connect users and type messages on any of the systems and see what happens.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We are close to the end of this tutorial. If you have gone\nwith us all the way here then you deserve a personal benefit \u2013 <a href=\"http:\/\/spiderwiz.org\/project\/tutorial\/lesson-18\/\">Lesson 18<\/a>\nwhere you will learn <strong>how to customize<\/strong>\n<strong><em>SpiderAdmin<\/em><\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to extend Spiderwiz by writing a customized Import\/Export handler. We will use it to bridge with <a href=https:\/\/www.baeldung.com\/java-websockets>Baeldung\u2019s WebSocket Chat Application<\/a>.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":145,"menu_order":17,"comment_status":"closed","ping_status":"closed","template":"tutorial-child.php","meta":{"footnotes":""},"class_list":["post-1012","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/1012","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=1012"}],"version-history":[{"count":12,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/1012\/revisions"}],"predecessor-version":[{"id":1408,"href":"https:\/\/spiderwiz.org\/project\/wp-json\/wp\/v2\/pages\/1012\/revisions\/1408"}],"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=1012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}