Before we will create detailed use-case diagrams lets write down requirements “on paper”. I’ll be editing this list from time to time and put mark for each edit (to see original requirement and it’s editions)
- There should exist the ability to receive different types of messages from remote xmpp (jabber) server. Particularly there are public group-chat messages, private group-chat messages, private messages.
- Public group-chat message is sent by some person to group-chat and thereby is addressed to all participants of group-chat
- Private group-chat message is sent in private by some person from group-chat to another person from the same group chat.
- Private message is sent by some jabber user to another jabber user only in private
- There should exist the ability to track down and process events which occur at the time the user joins/leaves group-chat.
- There should exist the ability to initialize and configure the whole XMPP system using single configuration file (e.g. XML configuration).
- There should exist the ability to connect / disconnect from remote xmpp (jabber) server.
- Once connection to remote xmpp (jabber) server is established it should be monitored for connected status. If the connection becomes broken it should be automatically reestablished.
- There should exist the ability to join / leave group-chat using it’s address (name). System should be able to operate with multiple group-chats simultaneously.
- Once joined every group-chat room should be monitored for joined status. If the group-chat is unexpectedly left (e.g. connection to xmpp/jabber server is broken, internet is temporary unavailable and so on) it should be automatically rejoined.
- Text messages which come from remote xmpp/jabber server may contain text commands to system. All known commands among incoming text messages should be detected , executed and if the text answer to some command is needed it should be generated and sent back to xmpp/jabber server.
- The should be running commands log/journal. Only the administrator of the system should be able to access this log both locally and remotely.
- Each text command that comes to system from remote xmpp/jabber server can have multiple handlers. There might be many handlers in the system. All of them should be running in parallel-mode.
- Commands handlers can use system resources of the platform on which the system is currently running. For example some local disk space (with the quota for the whole system), web-resources through internet channel the system is currently running and so on. All available resources should be carefully should be enlisted and specified more precisely.
- There should be designed plugins mechanism for commands handlers. Third parties should be able to write their own commands handlers.
- There should exist the ability to load/unload plugins for handlers of text commands. Administrator of the system should be able to load/unload plugins on-fly without the need to restart the system. This should be done in secure, fail-safe mode.
- Administrator of the system should be able to view the list of all currently loaded plugins with their descriptions.
Lets start from class model of all messages which exist in our xmpp system.
Base class for all messages is abstract Message class. It stores information about message sender and recipient as well as timestamp and text. Message class also implements some common logic. All classes in the hierarchy follow Value Object Pattern.
PrivateMessage is concrete subclass of Message. It is intended to represent a text message that is used by sender and recipient (exactly two people) for private communication and therefore sender and recipient fields of those messages mustn’t be null. ChatMessage is abstract base class for all multi-user chat messages. PrivateChatMessage represents text message that is send by one occupant of multi-user chat to another in private; sender and recipient fields of those messages mustn’t be null. PublicChatMessage represents text message that is sent by one occupant of multi-user chat to all other occupants; sender field mustn’t be null but recipient field is always null. AppearanceMessage is special multi-user chat message that holds special marker indicating that user has just joined or left multi-user chat; sender field mustn’t be null but recipient field is always null.
Now let’s see how those messages are created and processed:
Connection class is responsible for establishing connection to remote xmppp/jabber server. It is also responsible for creation of Room instances. Room class represents multi-user chat room (e.g. group chat). When Connection creates next Room it puts it into internal map (room name -> room object) in order to provide getRoom() by name method. Connection can keep and maintain multiple rooms simultaneously.
During the runtime of system remote xmpp server generates packets. Those packets are caught by various listeners. On the class diagram you can see 4 different listeners. Let’s start from PrivateMessageListener. When one jabber user sends text message to another one directly (e.g. using it’s IM client for private conversation) this message is converted into special network packet that comes to jabber server and then it is redirected by server to message recipient. Recipient in our case is xmpp subsystem we describe in this article. These “private message” packets are received by PrivateMessageListener.
When we talk in multi-user chat room (e.g. group chat) our IM clients also create special network packets which come to remote jabber server and then are redirected by server to all other participants (occupants) of group-chat. There are several types of packets:
- Public group chat packet. Those packets are addressed from one occupant of chat to all other occupants. Those packets are received by GroupChatMessageListener
- Presence packet. Those packets include occupant name and it’s jabber identifier. Those packets are received by ChatPresenceListener
- Event packet. Those packets include information about group-chat events such as occupant joined/left chat, granted/revoked some permissions, invoiced or granted voice and so on. Those packets are received by ChatEventsListener
Now when we’ve learnt how packets are received, lets examine how they are processed. There are multiple scenarios each one depends on packet type. First of all here is class diagram for processing packets perspective:
As you can see 3 of 4 packet listeners are associated with MessageProcessor class which implements IProcessor interface. When some listener receives it’s packet from jabber server this packet is transformed into Message concrete subclass of the messages hierarchy we’ve talked above. Just as reminder we do have PrivateMessage class, PublicChatMessage, PrivateChatMessage and AppearanceMessage. After transforming packets all listeners except ChatPresenceListener redirect their messages to MessageProcessor for further processing.
ChatPresenceListener doesn’t transform packets but operates with special presence packets and manages (populates/updates) PresenceCache. This cache is nothing but mapping between occupant full name ( in format email@example.com/occupant_name) which occupant uses while chatting in one or another group-chat and it’s jabber identifier. PresenceCache is shared among all of the listeners and allows them to perform transformation of packets correctly (one component of the transformation of packets is jabber identifier resolving by occupant name).
Let’s illustrate packets processing using sequence diagrams.
When remote server sends packet that contains private text message from one jabber user to another it is received by PrivateMessageListener. Listener checks that this packet is really private chat packet, creates PrivateMessage instance and sends this message to MessageProcessor.
When remote server sends packet that contains text message from one occupant of the group-chat to all other occupants of the same chat then such a packet is treated as public chat packet is is received by GroupChatMessageListener. Listeners check that this packet is really public chat packet, creates PublicChatMessage instance and sends this message to MessageProcessor
When remote server sends packet that contains text message from one occupant of the group chat to another occupant of the same chat using “private” conversation then such a packet is treated as private chat message. The tricky thing is that such a packets are received by PrivateMessageListener. But this listener already receives private message packets (not from group chat but from direct one). As you can see from sequence diagram PrivateMessageListener attempts to get Room from rooms map of Connection class using occupant name in packet. If there is such a room with given name, then packet was sent from group-chat. In this case listener creates PrivateChatMessage instance and sends this message to MessageProcessor. If there is no such a room then packet is treated as private message packet.
When remote server sends packet that contains any occupants presence then such a packet is received by ChatPresenceListener. Listener check that packet is really presence one. If so listener extract jabber identifier of occupant from this packet and puts binding between occupant name and it’s jabberID into PresenceCache (this cache was described above while talking about listeners on class diagram).
When remote server sends packet that contains group-chat events (currently occupant joined/left is supported by xmpp subsystem) it is received by ChatEventsListener. Listener creates AppearanceMessage and fill it’s data fields using original packet contents and send this message to MessageProcessir for further processing.
So far we’ve analyzed (1), (2), (6) requirements. We’ve partially covered (4) and (8). Let’s stop here and perform analysis of other requirements later.