Creating Room Modules with Java

Room modules add functionality to a room, and typically implement the logic for a particular application. For example, in a game of trivia a room module might trigger questions and calculate scores.

Each room module is associated with with a specific instance of a room, and is bound to that room's life cycle. When a room is created, its associated room modules are started; when a room is destroyed, its associated modules are destroyed.

To create a room module with Java, first write the module code and then deploy the module. The following steps describe the process in detail. (See also Creating Room Modules with JavaScript.)

Step 1: Create a the Module Source File

On your local hard drive, create a directory named /module_src/net/user1/union/example/roommodule/.

In /module_src/net/user1/union/example/roommodule/, create a text file named ChatBotRoomModule.java.

Step 2: Implement the Module Interface

The following Java class, ChatBotRoomModule, shows the bare minimum code for a valid room module. Add the source code shown below to ChatBotRoomModule.java from Step 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package net.user1.union.example.roommodule;

import net.user1.union.api.Module;
import net.user1.union.core.context.ModuleContext;

/**
 * Joins a chat room (see Your First Union Application at www.unionplatform.com) and welcomes users, says goodbye,
 * and occasionally adds to the conversation.
 */

public class ChatBotRoomModule implements Module {
    public boolean init(ModuleContext ctx) {
        return true;
    }
   
    public void shutdown() {
    }
}

The ChatBotRoomModule class's init() and shutdown() methods are both required. The init() method is invoked by Union Server when the module is instantiated, and should contain the module's initialization code. The init() method is passed a ModuleContext object that provides access to the public objects on the server, such as Room objects and Client objects.

The init() method's return value tells Union Server whether the module should be allowed to start. If init() returns true, Union Server will activate the module. If init() returns false, Union Server logs an error and does not activate the module. For example, if a module fails to load a required local asset, it might choose to abort startup by returning false from init().

Once a room module has started, it remains active until its associated room is removed from the server, at which time Union Server automatically invokes the module's shutdown() method. The shutdown() method should contain cleanup code that frees all resources in use by the module.

Let's expand the preceding ChatBotRoomModule class to create a "chat bot" module that sends messages to a chat room.

Step 3: Register for Events

Modules can register to receive events that occur on the server. To receive an event, specify the event and the method to run when the event occurs.

1
m_ctx.getRoom().addEventListener(RoomEvent.ADD_CLIENT, this, "onAddClient");

The preceding code tells the module's associated room to call the method "onAddClient" in our module whenever the RoomEvent.ADD_CLIENT event occurs.

For this example, we want to receive events whenever a client joins or leaves the chat room so we can send a message to the chat room.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package net.user1.union.example.roommodule;

import net.user1.union.api.Module;
import net.user1.union.core.context.ModuleContext;
import net.user1.union.core.event.RoomEvent;

/**
 * Joins a chat room (see Your First Union Application at www.unionplatform.com) and welcomes users, says goodbye,
 * and occasionally adds to the conversation.
 */

public class ChatBotRoomModule implements Module {
    private ModuleContext m_ctx;

    public boolean init(ModuleContext ctx) {
        // --- save the context.  the room in the context (getRoom()) is the room to which this room module
        // --- is associated
        m_ctx = ctx;
       
        // --- register to receive notification when a client is added or removed to the room
        m_ctx.getRoom().addEventListener(RoomEvent.ADD_CLIENT, this, "onAddClient");
        m_ctx.getRoom().addEventListener(RoomEvent.REMOVE_CLIENT, this, "onRemoveClient");
       
        // --- everything is OK!
        return true;
    }
   
    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is added to the room.
     */

    public void onAddClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "Welcome User" + evt.getClient().getClientID() + ". We are USER1.");
    }
   
    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is removed from the room.
     */
   
    public void onRemoveClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "I'll miss User" + evt.getClient().getClientID() + ".");
    }

    public void shutdown() {
    }
}

Step 4: Add Logic

We want our module to chat with users, so we'll create a thread to send a random message at a random interval. Here's the new code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package net.user1.union.example.roommodule;

import net.user1.union.api.Module;
import net.user1.union.core.context.ModuleContext;
import net.user1.union.core.event.RoomEvent;

/**
 * Joins a chat room (see Your First Union Application at www.unionplatform.com) and welcomes users, says goodbye,
 * and occasionally adds to the conversation.
 */

public class ChatBotRoomModule implements Module, Runnable {
    private ModuleContext m_ctx;
    private Thread m_thrThis;    

    public boolean init(ModuleContext ctx) {
        // --- save the context.  the room in the context (getRoom()) is the room to which this room module
        // --- is associated
        m_ctx = ctx;
       
        // --- register to receive notification when a client is added or removed to the room
        m_ctx.getRoom().addEventListener(RoomEvent.ADD_CLIENT, this, "onAddClient");
        m_ctx.getRoom().addEventListener(RoomEvent.REMOVE_CLIENT, this, "onRemoveClient");
       
        // --- start our thread
        m_thrThis = new Thread(this);
        m_thrThis.start();
       
        // --- everything is OK!
        return true;
    }
   
    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is added to the room.
     */

    public void onAddClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "Welcome User" + evt.getClient().getClientID() + ". We are USER1.");
    }
   
    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is removed from the room.
     */
   
    public void onRemoveClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "I'll miss User" + evt.getClient().getClientID() + ".");
    }

    public void run() {
        while (m_thrThis != null) {
            // --- if there are clients in the room then say something random
            if (m_ctx.getRoom().getNumClients() > 0) {
                m_ctx.getRoom().sendMessage("CHAT_MESSAGE", m_messages[(int)(Math.random()*m_messages.length)]);
            }

            // --- sleep for 1-60 seconds before we say something else
            try {
                Thread.sleep((long)(Math.random()*60000+1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void shutdown() {
    }
}

Step 5: Clean Up Resources

Finally, when the module shuts down, we need to clean up the resources we've created. This includes removing the event listeners we registered in the init() method. The following listing shows the final code. Copy it to your ChatBotRoomModule.java from Step 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package net.user1.union.example.roommodule;

import net.user1.union.api.Module;
import net.user1.union.core.context.ModuleContext;
import net.user1.union.core.event.RoomEvent;

/**
 * Joins a chat room (see Your First Union Application at www.unionplatform.com) and welcomes users, says goodbye,
 * and occasionally adds to the conversation.
 */

public class ChatBotRoomModule implements Module, Runnable {
    private ModuleContext m_ctx;
    private Thread m_thrThis;
   
    // --- the random messages the module will add to the chat
    private String[] m_messages = {
            "We have them just where they want us.",
            "Random chance seems to have operated in our favor.",
            "Mr Spock, sometimes I think if I hear that word frequency once more I'll cry."
    };
   
    public boolean init(ModuleContext ctx) {
        // --- save the context.  the room in the context (getRoom()) is the room to which this room module
        // --- is associated
        m_ctx = ctx;
       
        // --- register to receive notification when a client is added or removed to the room
        m_ctx.getRoom().addEventListener(RoomEvent.ADD_CLIENT, this, "onAddClient");
        m_ctx.getRoom().addEventListener(RoomEvent.REMOVE_CLIENT, this, "onRemoveClient");
       
        // --- start our thread
        m_thrThis = new Thread(this);
        m_thrThis.start();
       
        // --- everything is OK!
        return true;
    }

    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is added to the room.
     */

    public void onAddClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "Welcome User" + evt.getClient().getClientID() + ". We are USER1.");
    }
   
    /**
     * This method is the callback for the event we specified in the init method.  It will be called whenever a client
     * is removed from the room.
     */
   
    public void onRemoveClient(RoomEvent evt) {
        m_ctx.getRoom().sendMessage("CHAT_MESSAGE", "I'll miss User" + evt.getClient().getClientID() + ".");
    }
   
    public void run() {
        while (m_thrThis != null) {
            // --- if there are clients in the room then say something random
            if (m_ctx.getRoom().getNumClients() > 0) {
                m_ctx.getRoom().sendMessage("CHAT_MESSAGE", m_messages[(int)(Math.random()*m_messages.length)]);
            }

            // --- sleep for 1-60 seconds before we say something else
            try {
                Thread.sleep((long)(Math.random()*60000+1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
   
    public void shutdown() {
        // --- stop listening for the events we registered for in the init method
        m_ctx.getRoom().removeEventListener(RoomEvent.ADD_CLIENT, this, "onAddClient");
        m_ctx.getRoom().removeEventListener(RoomEvent.REMOVE_CLIENT, this, "onRemoveClient");
       
        m_thrThis = null;
    }
}

JavaScript Version

For comparison, here's the equivalent room module in JavaScript. For more information on JavaScript room modules, see Creating Room Modules with JavaScript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
importClass(net.user1.union.core.event.RoomEvent);
importClass(net.user1.union.api.Client);

var moduleContext;
var wrapper;
var loopInterval;

// This method must be implemented by a room module script.
function init(ctx, wrap) {
    moduleContext = ctx;
    wrapper = wrap;
   
    messages =  [
        "We have them just where they want us.",  
        "Random chance seems to have operated in our favor.",  
        "Mr Spock, sometimes I think if I hear that word frequency once more I'll cry."
        ];
   
    // --- since this is a script we have to register for
    // --- events through the rooms wrapper
    wrapper.addRoomEventListener(RoomEvent.ADD_CLIENT,
        "onRoomAddClient");
    wrapper.addRoomEventListener(RoomEvent.REMOVE_CLIENT,
        "onRoomRemoveClient");
   
    // --- we will send a random message every 30 seconds
    loopInterval = setInterval(mainLoop, 30000);
}

function mainLoop() {
    // --- if there are clients in the room then say
    // --- something random
    if (moduleContext.getRoom().getNumClients() > 0) {
        var args = [messages[Math.floor(Math.random()*messages.length)]];
        moduleContext.getRoom().sendMessage("CHAT_MESSAGE", args);
    }
}

function onRoomAddClient(evt) {
    var args = ["Welcome User" + evt.getClient().getClientID()
        + ". We are USER1."];
    moduleContext.getRoom().sendMessage("CHAT_MESSAGE", args);
}

function onRoomRemoveClient(evt) {
    var args = ["I'll miss User" + evt.getClient().getClientID()
         + "."];
    moduleContext.getRoom().sendMessage("CHAT_MESSAGE", args);
}

// This method must be implemented by a room module script.
function shutdown() {
    wrapper.removeRoomEventListener(RoomEvent.ADD_CLIENT,
        "onRoomAddClient");
    wrapper.removeRoomEventListener(RoomEvent.REMOVE_CLIENT,
        "onRoomRemoveClient");
   
    clearInterval(loopInterval);
}

// --- timing support
var timers = new Array();
var setInterval = function(fn, time){
var num = timers.length;
var isRunning = true;
var timerObj = {
thread: new java.lang.Thread(new java.lang.Runnable({
    run: function() {
        while (isRunning) {                                      
            java.lang.Thread.currentThread().sleep(time);
            fn();
         }
    }
})),
stop: function() {isRunning = false;}
};
timers[num] = timerObj;
timers[num].thread.start();
       
return num;
};
       
var clearInterval = function(num){
    if (timers[num]) {
        timers[num].stop();
        delete timers[num];
    }
};

Step 6: Give Union Server Access to the Module

First, compile the module class by running the following command from the /module_src/ directory. Replace "UNION_HOME" with your Union Server's installation directory:

Windows

javac -cp UNION_HOME\lib\union.jar net\user1\union\example\roommodule\ChatBotRoomModule.java

Mac/Unix

javac -cp UNION_HOME/lib/union.jar net/user1/union/example/roommodule/ChatBotRoomModule.java

Next, create a .jar containing the compiled class by running the following command from the /module_src/ directory:

Windows

jar -cf chatbot.jar net\user1\union\example\roommodule\ChatBotRoomModule.class

Mac/Unix

jar -cf chatbot.jar net/user1/union/example/roommodule/ChatBotRoomModule.class

Finally, place the chatbot.jar file in UNION_HOME/modules directory.

For a more convenient workflow during testing, you may wish to skip the creation of chatbot.jar and instead place the entire /net/user1/union/example/roommodule/ folder directly into the UNION_HOME/modules directory. Union Server will locate the module's .class file whether it resides in a .jar file or in a regular directory. However, as a best practice for production, module source code should be stored in a version control repository, and not included in the UNION_HOME/modules directory.

Now that the module's jar file is in the /modules/ directory, the module can be deployed.

Step 7: Deploy the Module

Room modules can be deployed at server-startup time via union.xml or at runtime via client-side code . To deploy the ChatBotRoomModule module using union.xml, edit union.xml and add the following code under the root element <union>.

<rooms>
    <room>
        <id>chatBotRoom</id>
        <modules>
            <module>
                <source type="class">
                      net.user1.union.example.roommodule.ChatBotRoomModule
                </source>
            </module>
        </modules>
    </room>
</rooms>

Alternatively, to deploy the ChatBotRoomModule module from the client-side, specify the name and type of the module when creating a chat room. For example, to deploy the ChatBotRoomModule in Adobe Flash, use the following ActionScript code :

var modules:RoomModules = new RoomModules();
modules.addModule("net.user1.union.example.roommodule.ChatBotRoomModule", RoomModuleType.CLASS);
chatRoom = reactor.getRoomManager().createRoom("chatRoom", null, null, modules);

To check whether the module deployed successfully, search log.txt for ChatBotRoomModule. If the module is deployed, log.txt will include the following message:

INFO - Loaded module type [class] with code [net.user1.union.example.roommodule.ChatBotRoomModule]