WorkspaceCollaboration Extension
The Workspace Collaboration feature enables users to communicate with other users through chat and discussion messages in FNZ Studio Runtime.
Introduction
This document explains the concept of collaboration in FNZ Studio Runtime, covers the components necessary to implement chats and discussions, and provides examples of how to implement these features.
This chapter outlines the concepts of Chat and Discussion as used in Workspace Collaboration.
Concepts behind Workspace Collaboration
When working on part of a Process, users may need to communicate with each other within the framework of FNZ Studio Runtime e.g to ask a quick question about a certain topic, or see what others have already asked.
With Workspace Collaboration, communication is handled within FNZ Studio and not on a different platform. FNZ Studio Notification, Chat, and Discussion capabilities enable collaboration and cooperation within FNZ Studio Runtime.
Note: This document covers Chats and Discussions. Notifications are covered in a separate document.
Chat: Concept
A Chat lets you build an instant messaging functionality into FNZ Studio Runtime of your FNZ Studio Solution.
The Chat Widget shown above uses the Chat Screen Component to allow users to communicate in real time.
As an example use, you may want all users logged into FNZ Studio to have access to a general chat so they can discuss how various processes are progressing. You may also want to give users working on a specific Process Instance the possibility to chat with each other within FNZ Studio. In a Process-specific chat, users could ask questions and discuss problems related to the Process Instance.
The central panel shows the conversation, while the top bar lists the online participants. Click the "Add User button in the top bar to invite more users.
All chats in FNZ Studio are persistent, such that users who (re)enter a chat at a later point can see previously sent messages.
Discussion: Concept
Discussion lets you implement message feeds in FNZ Studio Runtime, where users can post and reply to questions or issues raised by other users. Discussions can be filtered and organized in different feeds.
The Discussion List shown above displays a list of posted messages and replies to these messages. Users can reply to a posted message by clicking the Reply button below the message.
Messages posted via Discussions do not belong to one specific container, like the messages within a chat. Instead, discussions have a Context and can be filtered and displayed in different feeds based on that context.
Consider Facebook: Everybody has a "wall" on their profile, to which they and other people can post. However, each user also has a "news feed" where messages posted to friends' walls are aggregated. Walls and news feeds are examples of feeds which display differing views of the same universe of messages.
Workspace Collaboration Installation and Compatibility
This section covers requirements for using Workspace collaboration tools, installing the WorkspaceCollaboration extension, and outlines known compatibility issues.
Requirements
- You need a version of the WorkspaceCollaboration extension compatible with your FNZ Studio Platform.
- Your FNZ Studio installation can be a single-node or a cluster installation. Note that the discussion features benefit from having more than one node since the CPU load of filtering messages is partitioned amongst the nodes of the cluster.
- If you expect a large volume of messages (more than 10,000 discussion messages or 500,000 chat messages present in your system at a time, for example), it is recommended that you use Cassandra or a relational database as FNZ Studio’s storage layer. See the Workspace Collaboration Architecture section.
- The Workspace Collaboration feature is integrated with the Workspace Notification system (see the Workspace Notifications documentation). ComponentExtension version 3.5 or greater is required in order to use the notification functionality.
Installation
- Upload and start the WorkspaceCollaboration extension on your FNZ Studio system. No further installation is required.
- Certain business objects deploy automatically upon initial startup, and provide the default configurations. The business objects are collected in a custom list: WorkspaceCollaboration_DefaultObjects. You can change default configurations, and modify or delete the default objects deployed.
- If you apply any changes, restarting the extension does not undo them. To undo your changes and revert to the default business objects, topics and configurations:
- Right-click the WorkspaceCollaboration extension in the Extensions panel.
- Choose Restore defaults.
- Restart the extension.
Configuration
Screen Component Polling Interval
Workspace Collaboration-related Screen Components poll the server to fetch updates. The default polling interval is 5 seconds. The polling interval can be changed through the following properties:
workspacecollaboration.chat.pollTimeoutfor Chat and Chat Popupworkspacecollaboration.discussion.pollTimeoutfor the Discussion List
User Profile Page
If your solution has a User Profile page, you can put its URL in the platform property nm.usermessaging.userprofile.url. This makes user names and avatars in notifications link to the User Profile page.
The URL should contain the placeholder that will be replaced by the actual user ID. If the URL does not contain the placeholder, the query string "?userId=" is automatically appended to the URL.
Examples:
http://example.com/userProfile/{userId}becomeshttp://example.com/userProfile/testUserhttp://example.com/userProfilebecomeshttp://example.com/userProfile?userId=testUser
Browser Compatibility
Using the Workspace Collaboration feature has the same requirements regarding browsers as the FNZ Studio version in use. See the FNZ Studio technical requirements for FNZ Studio Runtime for more information on supported browsers.
Workspace Collaboration Architecture
The Workspace Collaboration architecture can roughly be divided into two parts: front-end and back-end.
-
Front-End The front-end of the Workspace Collaboration feature is created using the Screen Components, which are implemented entirely in the WorkspaceCollaboration extension. The client side of the Screen Components updates its state via RESTful AJAX requests to the server. The requests are triggered at regular polling intervals and on user interaction. The state is kept only on the client. Screen Components send independent requests, but listen to all the related responses in the page. The Screen Components will update if the received content is relevant to their scope. For example, consider the case of a New Post Field and a Discussion List on the same page. As said, they communicate separately with the server. Let's assume that the user posts a new discussion message through the New Post Field. The Screen Component sends a request, and receives a response containing the new message. If the Discussion List includes the New Post Field’s context in its configuration, the Discussion List will also handle the response and show the new message, instead of waiting for its independent request to fetch the update.
The WorkspaceCollaboration extension also provides Script Functions and Integration Link Start Components. The back end of the Workspace Collaboration feature is implemented in the FNZ Studio core. The communication data is distributed amongst the nodes of the cluster.
Furthermore, the core offers APIs that can be used by extensions. -
Back-end The Workspace Collaboration back-end relies on the Hazelcast in-memory datagrid to distribute data amongst cluster nodes. To filter discussions efficiently (that is, collect the right discussions for the different feeds) and to provide full-text search, the Workspace Collaboration feature relies on the Lucene document indexing library. There is a persistent Lucene index for all discussion messages that is replicated on each node in the FNZ Studio cluster. When a new message arrives, it is inserted into this index on each node. Filtering messages using the index is a local operation. Thus, by adding more nodes, you can achieve more read throughput for discussion feeds.
Security
Workspace Collaboration security is based on FNZ Studio authentication and authorization: A Screen in FNZ Studio is only accessible for authorized users; any chat or discussion feed displayed on a Screen also follows this logic. However, Workspace Collaboration data is not only accessed in regular FNZ Studio HTTP requests on processes or Screens. Workspace Collaboration-related Screen Components rely on AJAX requests to periodically fetch new messages or send updates to the server. To ensure that these requests are also secure, Workspace Collaboration has an additional security mechanism.
When a Workspace Collaboration Screen Component is rendered, a token is generated based on the user ID of the requesting user, and the accessed chat or discussion feed. Every AJAX request made by the component has to include the token, and the token validity is checked on the server side. This makes sure that AJAX requests come from authenticated and authorized sources only.
The token is constructed from a deterministic and a random part, and is then hashed using a configurable hashing algorithm. The stronger the hashing algorithm is, the harder it is to guess the token. The three options for the hashing algorithm are:
- crypt (cryptographically secure)
- md5 (non-trivial)
- none (trivial)
Computing the cryptographic hash is, however, computationally expensive. Thus, we recommend the md5 configuration (which is also the default).
The hashing algorithm for the security token can be configured via the property notification.security.level. Default is md5. Other possible values are crypt and none.
Persistence
For persistence, Workspace Collaboration uses the configured storage layer — which by default is the filesystem storage. As an alternative, you can use a relational database as the store (RelationalDbHazelcastStore extension), or Cassandra (CassandraHazelcastStore extension) by installing the corresponding extensions.
For a large volume of messages, using Cassandra or a relational database instead of the filesystem is to be recommended: Using the filesystem will result in a large number of files, and operations like retrieving the total number of messages or retrieving all message IDs (which is mainly used for administration functionality in the Studio) will be slower.
Message Text Format
All messages in Workspace Collaboration (discussion posts, replies and chat messages), as well as notification messages, contain text in String format. See the Notification Text Format section in FNZ Studio Runtime Notifications documentation for more information.
Consider that:
- Discussion messages also support hashtags, i.e. words prefixed with a "#". They are extracted from the text and indexed.
- Chat and discussion messages do not support the mainLink-annotation supported by notifications.
Chat
This section covers the concepts, components and functions related to chats. It discusses context and invitations, primitive types and possibilities for customization.
Context-based Chat
The central concept behind the chat functionality is that of a persistent chatroom. A chatroom (shortened to "chat") is a container for messages sent by users. A chat is identified by an arbitrary unique string ID. A chat is created automatically when accessed the first time. Users can join and leave the chat. All messages are stored indefinitely, unless you explicitly delete them using a Script Function (see Delete Chat Messages).
If users are logged into FNZ Studio Runtime and are permitted to see a chat window, such users are automatically registered as being online in that chat. They are then immediately able to use the chat window to communicate with all other users who have access to the chat. In this way, users working on a common task can communicate in a chat associated with that task.
Invitations
Users may need to add other users (who are not working on that same task or process, for example) to a chat. This can be done via the invitation mechanism. A user can trigger an invitation by searching for and selecting another user via one of the chat Screen Components (see Chat – Screen Components). This calls the so-called ChatProvider in charge of notifying the invited user and allowing them to join the chat.
Figure 3 :
Configuring and Customizing Invitations
The ChatProvider for FNZ Studio users is called ScriptAppwayChatProvider and is implemented in the WorkspaceCollaboration extension. When a user is invited to a chat, the Script Function configured in the extension property workspacecollaboration.chat.provider.script.invitationFunction is executed.
This Script Function has the following signature:
Function WorkspaceCollaboration_HandleUserInvitedToChat(String $chatId, String $userId, String $inviterUserId) : Nothing
The function receives the arguments noted in the list below, then sends out a notification to the invited user containing a link to join the chat.
$chatId– the ID of the chat for which the invitation has been requested$userId– the ID of the user being invited$inviterUserId– the ID of the user triggering the invitation
By default, the WorkspaceCollaboration_HandleUserInvitedToChat function is configured as the function to call.
The default implementation deployed by the WorkspaceCollaboration extension is shown below. It sends a notification to the invited user that contains a link to join the chat:
Function WorkspaceCollaboration_HandleUserInvitedToChat(String $chatId, String $userId, String $inviterUserId) : Nothing Begin
// Make link to the default join chat screen
String $url := CONTEXTPATH() & '/internal/screen/WorkspaceCollaboration_JoinChat?chatId=' & $chatId;
// Make invitation text
String $inviterName := $inviterUserId;
User $inviter := User:Get($inviterUserId);
If $inviter != null Then
$inviterName := $inviter.getFullName(true);
End
String $text := $inviterName & ' invited you to the chat ' & $chatId & '. \\mainLink{' & $url & '}{Join chat}';
// Send addressed notification to make sure the invited user receives the notification
SendAddressedNotification([$userId]:String, null, $inviterUserId, 'Chat', $text);
End
It may be that the invited users do not have access to the context (that is, Screen or Process) they are being invited to. The invited user therefore needs a different entry point into the chat. To allow for this, there has to be a dedicated standalone chat Screen. The WorkspaceCollaboration extension deploys such a Screen by default, which is the WorkspaceCollaboration_JoinChat Screen.
The standalone chat Screen has an assigned variable, chatId. The Screen contains a chat widget with the chatId variable set as the chat ID, and the inviteOnly property enabled. More information about the chat widget can be found in Screen Components and Properties.
As you can see from the implementation of the WorkspaceCollaboration_HandleUserInvitedToChat function, the link to the chat is a link to the standalone chat screen, with the chatId as the GET parameter. When users click the link in the invitation, this standalone screen is loaded with the assigned variable chatId set to the actual chat ID – displaying the correct chat.
Without any further security measures, any user could enter any chat by using this screen. However, the inviteOnly property of the chat Screen Component makes sure that only users who are invited to the chat are granted access.
You can customize the screen used to join chats, and the way a user is notified about an invitation, by adjusting the corresponding Business Objects. Alternatively, you can create your own Objects and adjust the extension configuration accordingly.
Note: For invitations to work, the Screen to join the chat must be publicly available.
Invitation Lifecycle
If a user does not accept an invitation for a certain amount of time, the invitation expires and is deleted. If users try to join the chat after the expiry of the invitation, they are not granted access. The timeout can be configured using the FNZ Studio configuration property nm.usermessaging.chatinvitationtimeout. The default expiry time is 30 seconds.
If an invited user joins the chat, and then leaves the chat (for example by closing the chat window), the invitation is also deleted. The previously-invited user does not remain a participant in the chat, and is removed from the chat completely.
Inviting Users from Lync or other Third-Party Systems
Additional chat providers can be implemented via the SDK. Users can then invite others to participate in FNZ Studio-internal chats via external instant messaging systems like Microsoft Lync, for example.
Message and Event Data
All messages sent in chats are persisted. A system message is generated for each event in a chat and added to the chat’s persistent stream of messages. An event is one of the following:
- User joined
- User left
- User invited
- User invitation failed
- User invitation timed out
When retrieving messages via the Screen Components or the Script Functions, you can choose if you want to retrieve user messages, or all messages (including system messages).
Chats are created lazily and are not deleted unless you explicitly delete all messages using the DeleteChatMessages Script Function.
Unused Chats will eventually be evicted from memory, so they don’t put any load on the system. They consume disk space, however, so deleting chats that are no longer needed is to be recommended: If you have a chat belonging to a process instance, export that chat’s messages.
Chat Screen Components
Chat Screen Components include:
- Chat
- Chat Popup
Chat
The Chat Screen Component allows writing messages and adding online users to the chat. Sent messages are listed in chronological order.
Chat: Edit mode
Chat: Preview mode
Configuration The component has the following properties (Properties tab):
- Chat ID — Unique ID that specifies which chat to display.
- Allow Adding Users — If selected, it allows the user to invite attendees to the chat.
- Invite Only — If selected, the chat is only shown to users with a pending invitation for that chat.
- Show System Messages — If selected, system messages are displayed. They are created when a user logs in or out, or is invited to the chat.
- Timestamp Format Pattern — Allows selecting a Format Pattern Business Object to set the desired date format.
Chat Style Business Object
The Chat Style Business Object (Style tab) allows you to style all the elements of the chat (numbers refer to the styling properties described below).
- Conversation Area Background – (1) Sets the background color for the message area.
- Background – Sets the background color for the whole component.
- Border – Sets the style (thickness, color, and so on) of the border for the whole component.
- Conversation Area Border – (1) Sets the style (thickness, color, and so on) of the border for the message area.
- Inner Spacing – Sets the space between the conversation area and the component border.
- Conversation Area Inner Spacing – Sets the space between the text inside the conversation area and the conversation area border itself.
- Send Button – (2) Refers to a Button Style Business Object to set the style of the Send button on the chat.
- Attendee Search Button – (3) Refers to a Button Style Business Object to set the style of the button at the top right of the chat window, which is used to search for a chat attendee.
- User Message Bubble – (4) Refers to a Bubble (virtual) Style Business Object, which styles the bubble containing the user’s messages.
- Responder Message Bubble – (5) Refers to a a Bubble (virtual) Style Business Object, which styles the bubble containing the responders’ messages.
- Attendee Name Label – Refers to a Text Style Business Object to set the style of the text representing the names of the chat attendees.
- Attendee List Entry – Refers to a List Entry (virtual) Style Business Object that sets the style of the entries of the list of chat attendees resulting from the user search.
- Message Field – (6) refers to a Text Area Style Business Object that sets the style of the chat message area.
- Message Link – (7) Refers to a Link Style Business Object that sets the style of links or user mentions.
- Attendee Search Field – Refers to a Text Input (virtual) Style Business Object to define the style of the text entered in the attendee search field.
- Attendee Search Popup – Refers to a Popup (virtual) Style Business Object to define the style of the popup displaying the list of chat attendees resulting from the user search.
Chat Popup
The Chat Popup Screen Component opens the chat in a popup once you click on the chat icon:
- If there are new messages, the button shows a badge, notifying about the numbers of unread messages.
- If you click on the button again, the chat window is hidden.
Chat Popup: Edit mode
Chat Popup: Preview mode
The component has the following properties (Properties tab):
- Chat ID — Unique ID that specifies which chat to display.
- Allow Adding Users — If selected, it allows the user to invite attendees to the chat.
- Invite Only — If selected, the chat is only shown to users with a pending invitation for that chat.
- Show System Messages — If selected, system messages are displayed. They are created when a user logs in or out, or is invited to the chat.
- Timestamp Format Pattern — Allows selecting a Format Pattern Business Object to set the desired date format.
- Popup Icon — Allows selecting the icon identifying the button opening the Popup.
The Popup Position property is only displayed if Legacy Styles are applied.
Chat Popup Style Business Object
The Chat Popup Style Business Object (Style tab) allows you to style all the elements of the chat (numbers refer to the styling properties described below).
- Unread Message Badge – (1) Sets the color of the badge that indicates the number of unread messages.
- Icon – (2) Refers to the Icon Style Business Object that determines the style of the chat icon.
- Chat – (3) Refers to the Chat Style Business Object that determines the style of the chat area.
- Popup – (4) Refers to the Popup (virtual) Style Business Object to set the style of the whole popup.
- Unread Message Counter – (1) Refers to the Text Style Business Object that determines the style of the number of unread messages.
Functions and Callbacks
Primitive Types
There are three primitive types: ChatMessage, ChatEvent and ChatSystemMessage. Each is explained in turn below.
ChatMessage
- Immutable
- Return Type
The Primitive Type ChatMessage represents a message sent in a chat and contains:
- The user ID of the sender
- The send timestamp
- The message text
The function to load chat messages returns a list of ChatMessage objects. They are read-only (immutable) objects. You never have to instantiate a ChatMessage yourself.
The available methods are as follows:
| Method | Description |
|---|---|
String getID() |
Get the message ID. |
String getUserID() |
Get the ID of the user who sent the message. |
long getTimestamp() |
Get the timestamp of the message. |
String getText() |
Get the message content. |
String getTextTranslated() |
Get the text with labels translated to the current language (in a HTTP request, the current user's language) |
String getTextTranslated(String $language, String $userID) |
Get the text with labels translated. The language is determined as follows: |
- If a language is specified, the string is translated to the given language.
- If no language but a user ID is specified, the string is translated to the language predicted for the given user.
- If neither language nor user are specified, the string is translated to the current language (in a HTTP request, the current user's language).
- `attributionSubst` – Substitution for the attributions, placeholders `$1` and` $2` are replaced with user ID and attribution display value, respectively.
- `mentionSubst` – Substitution for the mentions, placeholders `$1` and `$2` are replaced with user ID and mention display value, respectively.
- `linkSubst` – Substitution for the links, placeholders `$1` and `$2` are replaced with URL and link name, respectively.
The Primitive Type ChatSystemMessage is a subtype of ChatMessage. This Primitive Type represents messages sent by the chat system that were triggered by events happening in the chat.
When you retrieve chat messages with the LoadChatMessages function, and the parameter includeSystemMessages is set to true, the result contains a chat system message for each event that happened in the chat. By default, this parameter is set to false.
The overridden and additional methods with respect to ChatMessage are the following:
| Method | Description |
|---|---|
String getUserID() | Returns "SYSTEM:chat". |
String getText() | Get the text describing the event. The texts for the different events are based on the labels "usermessaging.chat.systemmessage.*". The text is a user-readable representation of the event and can be used, for instance, to display the event to a user or log it into a report document. |
ChatStateEvent getEvent() | Get the event triggering the system message. nIt can only be a ChatStateEvent and not a ChatMessageEvent since ChatMessageEvent does not trigger additional messages. |
ChatEvent
Users joining or leaving a chat are counted as events, as are activities related to invitations. These events are represented by instances of Primitive Types that are subtypes of ChatEvent. ChatEvent itself is abstract; there are no instances of the ChatEvent type itself, only of its subtypes.
ChatEvents are read-only (immutable) objects. ChatEvent instances are returned as part of chat system messages by the function LoadChatMessages (if a flag to include system messages is true) or the Integration Link Start Component's exchange.
The following methods are available on the type ChatEvent directly:
| Method | Description |
|---|---|
String getChatID() | Get the ID of the chat this event occurred in. |
Long getTimestamp() | Get the time (in milliseconds) from epoch when this event occurred. |
ChatEvent has the subtypes ChatMessageEvent and ChatStateEvent. These subtypes are explained in the sections below.
ChatMessageEvent
A ChatMessageEvent represents a message sent in a chat by a user. ChatMessageEvent provides the following additional method:
| Method | Description |
|---|---|
ChatMessage getMessage() | Get the user message that triggered this event. |
ChatStateEvent
A ChatStateEvent represents a change in the chat state, such as a user joining or leaving the chat, or a user being invited. It is an abstract type that provides the following additional methods:
| Method | Description |
|---|---|
String getSystemMessageText() | Get a system message describing the given event. The system messages for the different events are based on the labels "usermessaging.chat.systemmessage.*". The system message is a user-readable representation of the event and can be used, for instance, to display the event to a user or log it into a report document. |
ChatStateEvent has the following subtypes:
- UserJoinedEvent
- UserLeftEvent
- UserInvitedEvent
- UserInvitationFailedEvent
- UserInvitationTimedOutEvent
Each of these subtypes are explained in the following paragraphs.
UserJoinedEvent
UserJoinedEvent is triggered when users join the chat because they opened a screen with the corresponding chat component on it, or because the users were invited and accepted the invitation. It offers the following methods:
| Method | Description |
|---|---|
String getUserID() | Get the user ID of the joining user. |
Boolean isInvited() | Returns true if the joining user has been invited.nReturns false if the joining user joined the chat by entering a screen with a corresponding chat component. |
UserLeftEvent
UserLeftEvent is triggered when users leave the chat in one of two ways:
- By leaving a screen with the corresponding chat component on, and not reentering for the configured
nm.usermessaging.chat.heartbeatinterval, or - By leaving the corresponding (external) chat system (for users participating via the invitation mechanism).
The methods are the following:
| Method | Description |
|---|---|
String getUserID() | Gets the user ID of the user who left the chat. |
Boolean isInvited() | Returns true if the user who left was invited to the chat. |
Boolean isRemoved() | Returns true if the user who left was removed from the chat (currently not supported by the components). |
UserInvitedEvent
UserInvitedEvent is triggered immediately when a user is invited to a chat (before the acceptance or rejection of an invitation). The methods are the following:
| Method | Description |
|---|---|
String getUserID() | Get the user ID of the invited user. |
String getInvitingUserID() | Get the user ID of the user who initiated the invitation. |
UserInvitationFailedEvent
UserInvitationFailedEvent is triggered when the user invitation failed on the side of the chat provider (Microsoft Lync, for example). If the invitation times out on the FNZ Studio side, a UserInvitationTimedOut event is triggered instead.
The methods for UserInvitationFailedEvent are the following:
| Method | Description |
|---|---|
String getUserID() | Get the user ID of the invited user. |
UserInvitationFailureReason getReason() | Get the reason for the failure. The return type is an enum not available as a Primitive Type. To work with it, you can call its toString() method. n The possible values are |
- USER_UNAVAILABLE
- USER_DECLINED
- INVITATION_TIMED_OUT
- TECHNICAL_PROBLEM
UserInvitationTimedOutEvent
UserInvitationTimedOutEvent is triggered when the user invitation times out on the FNZ Studio side (if the invitation times out on the chat provider side, a UserInvitationFailedEvent is triggered).
The time (in seconds) before a chat invitation times out in FNZ Studio can be configured via the nm.usermessaging.chatinvitationtimeout configuration property. The default value is 30 seconds. The additional methods of UserInvitationTimedOutEvent with respect to the ChatStateEvent are the following:
| Method | Description |
|---|---|
String getUserID() | Get the user ID of the invited user. |
Send a Chat Message
The Script Function SendChatMessage sends a chat message and returns the created ChatMessage object.
SendChatMessage($chatID, $userID, $text)
| Parameter | Description |
|---|---|
$chatID nString | ID of the chat to which to submit the message. |
$userIDnString | The ID of the user on behalf of whom to send this message. |
$textnString | The message text to send. |
$attachmentsnIndexed UserMessagingAttachment, optional | The attachments to add to the message. Attachments are references to cluster files that should be associated with the message. The parameter is optional and can just be left out if the message has no attachments.nNote: Attachments are already available in the APIs, but are not yet supported. |
| Return value | Description |
|---|---|
$chatMessagenChatMessage | The created chat message object. |
Load Newest Chat Messages
The Script Function LoadChatMessages loads the newest chat messages sent in the given chat in reverse chronological order.
LoadChatMessages($chatID, $n, $includeSystemMessages)
| Parameter | Description |
|---|---|
$chatIDnString | ID of the chat whose message to load. |
$nnInteger | The maximum number of messages to return.nIf there are less than n messages in the chat, all are returned.nOtherwise, the n newest messages are returned. nIf "n = -1", all messages are returned. |
$includeSystemMessagesnBoolean, optional | If true, the returned messages also contain system messages generated for the following events: |
- User joined
- User left
- User was invited
- User invitation failed (on chat provider side)
- User invitation timed out (on FNZ Studio side)
| Return value | Description |
|---|---|
$chatMessages nIndexed ChatMessage | A list of the n (or all, if there are less than n) newest chat messages sent in the given chat in reverse chronological order (newest first). |
Delete Chat Messages
Delete all chat messages sent in a given chat. We recommend performing this operation for chats whose messages are not needed any more, in order to free resources on the storage system (filesystem/database). In particular, it makes sense to delete chats belonging to a process instance when the process instance ends, after archiving them in a database or audit trail document.
DeleteChatMessages($chatID)
| Parameter | Description |
|---|---|
$chatIDnString | The ID of the chat for which you wish to delete messages. |
Returns nothing.
Get Online User IDs
Gets the user IDs of all the users that are currently online in the chat with the given chat ID.
GetOnlineChatUserIds($chatID)
| Parameter | Description |
|---|---|
$chatIDnString | The ID of the chat. |
Returns an Indexed String.
Integration Link Start Component
The extension provides an Integration Link Start Component that can be used to react to new chat messages or events.

To use it, create a new Integration Link and select Chat Event as the start element. The only configuration property of the element is Messages Only, which determines if the start element should trigger for new messages only, or also for events like users joining or leaving a chat. Now, when the Integration Link is running, it triggers for every new message or event that is sent or happens in any chat.
The corresponding ChatEvent object (a message is a special case of an event, see Chat Event for a description of the ChatEvent type and its subtypes) is the body of the in-message. To access the event data, use the following script:
ChatEvent $chatEvent := CAST(ChatEvent, $in.getBody())
In the chain that comes afterwards, you can define arbitrary logic that will be executed for each new message/event. You can, for example, use the Send Notification End Component to send a notification for the new message or event.
Example: Persistent Chat for a Process Instance
To have a context-specific persistent chatroom for every Process Instance, for example, place the chat component in every Screen of the process (using a template to avoid duplication), and set the chat ID to the process instance ID. Then, people working on the same Process Instance can communicate in this chat.
By default, there won’t be any notifications about new messages when a user is not currently in the chat. However, you can listen for new messages using an Integration Link (see Integration Link Start Component) and send notifications to the users who are participants of the process but not currently active in the chat.
Once a Process is finished, you can export or archive the messages alongside the Process data.
Discussion
This section covers the concepts, components and functions related to Discussions.
Contexts
Each discussion started in the discussion system is associated with a context.
A context is a set of String key-value pairs (Named String). This context is defined by a developer at design time. This also applies to discussions generated by users via a screen component, and discussions generated by you via script functions. The context is defined on the discussion’s posted messages and is inherited by each reply.
The context should capture the business context in which the discussion was started. This might include the process ID, the process instance ID, a client ID, or the ID of an activity. You can then make use of the context for retrieving discussions in a selective way: You can specify a filter for discussions that selects discussions based on the context data and other associated information (like user, timestamp, tags, etc.) for both the discussion list and the LoadDiscussions Script Function.
For example, if you had discussion functionality in an account opening process, the context for discussions generated in a specific account opening could contain the ID of the account opening, and the ID of the client. Then, you could have a feed in the account opening showing only the discussions that were started in the current account opening, and another feed that would show you all discussions related to the specific client, aggregated from all account openings for that client.
Discussion Filtering
Filters for discussions are used to specify which discussions to display/retrieve by the Discussion List (see the Discussion Screen Component section).
Filters can use the following data associated with discussion messages:
- Discussion context
- User who sent the message
- Time the message was sent
- Tags and attributions in the message
- Boolean combinations of the above
A simple filter for discussion messages is represented by the primitive type DiscussionMessageFilter. Composite filters are of type DiscussionMessageFilterExpression and can be created using the DiscussionMessageFilterExpressionBuilder.
Simple Filters
The DiscussionMessageFilter allows you to define the properties discussions should be filtered by. You can filter based on all fields of the DiscussionPost and DiscussionReply messages.
A discussion matches a discussion message filter if it contains at least one message (that is, the post or one of the replies to the post) matching the filter. Note that the filtering happens on the message granular level, not discussions. For example, if you have a post that contains the tags #foo and #bar, and your filter criteria are the set of tags, #foo and #bar, the corresponding discussion matches the filter. But if you have a post containing #foo, and a reply containing #bar, none of the messages match the filter; in this case, the discussion as a whole does not match the filter.
The filter semantics are as follows: After instantiating a filter, no filter properties (criteria) are defined. Therefore all messages match the filter. You can then use the setters to define filter criteria and refine the results. In order for a message to match the filter, all set criteria must match (like an AND-filter).
The available methods are:
| Method | Description |
|---|---|
DiscussionMessageFilter() | Initialize an empty filter. nAn empty filter matches all discussion messages. |
setType(Class<?> type) nClass<?> type getType() | Set and get the type of message to filter. One of DiscussionPost and DiscussionReply. In the script language, you can define them by using java.lang.Class.forName(‘DiscussionPost’) or java.lang.Class.forName(‘DiscussionReply’) respectively. |
setDiscussionID(String discussionPostID)nString getDiscussionPostID() | Set and get the ID of the discussion to filter for. The corresponding post and all its replies match this criterion if set. |
setDiscussionPostTimeRange(DateRange discussionPostTimerange)nTimeRange getDiscussionPostTimeRange() | Set and get the time range within which the timestamp of a discussion has to be for its messages (post and replies) to match the filter |
setContext(Map<String, String> context)nMap<String, String> getContext() | Set and get the key-value pairs that are required in a message's context in order for the message to match the filter |
setTags(List<String> tags) nList<String> getTagsAsList() | Set and get the tags required to occur in a message to match the filter. |
setAttributions(List<String> attributions) nList<String> getAttributionsAsList() | Set and get the attributions required to occur in a message to match the filter. |
The DiscussionMessageFilter type also offers some convenience methods to instantiate a filter with a certain property already set. These are:
DiscussionMessageFilter.contextFilter(Map<String,String>)DiscussionMessageFilter.contextFilter(String key, String value)DiscussionMessageFilter.userFilter(String userID)DiscussionMessageFilter.timestampFilter(DateRange timeRange)DiscussionMessageFilter.discussionPostTimestampFilter(DateRange timeRange)DiscussionMessageFilter.tagFilter(String tag)
Composite Filters
To build a composite (Boolean) filter for discussions, e.g. a filter that requires messages to have not a fixed context but one out of a fixed set of contexts (which is a disjunction of context filters), you can use the static methods of DiscussionMessageFilterExpressionBuilder type. It offers a so-called fluent API that you can use to build your filter step by step.
To start you instantiate a builder in the following way:
DiscussionMessageFilterExpressionBuilder $builder := DiscussionMessageFilterExpressionBuilder.builder()
Now, if you want to require all criteria defined by a set of filters, you can start a conjunction of filters using
$builder := $builder.beginAnd()
Then, you can add your filters one after the other like this:
$builder.filter($myFilter1);
$builder.filter($myFilter2);
Finally, you close the conjunction with:
$builder.endAnd()
You can get the built filter expression with:
DiscussionMessageFilterExpression $finalFilter := $builder.getResult()
Instead of conjunctions, you can also do disjunctions using the beginOr() and endOr() methods. The semantics are that for a message to match your filter, it has to match at least one of the filters in the disjunction (as opposed to all for the conjunction).
Furthermore, you can require messages not to have certain properties by using the beginNot() and endNot() methods and placing your filter in between.
The conjunctions, disjunctions, and negations can also be nested. For example, you would define a filter for messages which are written by user ‘Igor’ or user ‘Maria’ but do not contain the tag ‘#fatca’ like this:
DiscussionMessageFilterExpressionBuilder $builder := DiscussionMessageFilterExpressionBuilder.builder();
$builder.beginAnd();
$builder.beginOr();
$builder.filter(DiscussionMessageFilter.userFilter(Igor));
$builder.filter(DiscussionMessageFilter.userFilter(Maria));
$builder.endOr();
$builder.beginNot();
$builder.filter(DiscussionMessageFilter.tagFilter(fatca));
$builder.endNot();
$builder.endAnd();
DiscussionMessageFilterExpression $filter := $builder.getResult();
Sorting Discussions
The default sort order for discussions is reverse chronological. Discussions are sorted according to the timestamp of the post that initiated the discussion. Alternatively, you can configure the discussion component to sort the discussions according to the timestamp of the newest message so that if somebody replies to a discussion, that discussion is shifted to the topmost position (see Discussion List Component).
Search
Workspace Collaboration indexes all discussions for full text search. The language for indexing and searching is the language configured as the default language on the platform. Users can issue a search from the Discussion List Screen Component (see Discussion List Component) which yields the matching discussions, ranked by the score of the discussion for the search (better matches are ranked higher). Matching text passages and users are highlighted.
For technical reasons, the maximal number of returned search results is currently limited. This limit is 100 by default. It can be configured via the extension configuration property workspacecollaboration.discussion.maxSearchResults.
Discussion Screen Components
There are two Screen Components used for discussions: the Discussion List component, and the Discussion New Post Field component. Text in both Screen Components can be adapted to the user's language preference and / or locale using labels.
Discussion List
The Discussion List Screen Component displays discussions that match either the given filter, or whose context contains all given key-value pairs.

Discussion List: edit mode

Discussion List: preview mode
Legend:
- Post
- Message
Configuration
The component has the following properties (Properties and Options tabs):
Message Filter Expression – Identifies a stream of discussion messages. The expression must return either:
- A DiscussionMessageFilterExpression – Displays all messages matching the given filter. It is possible to create custom filters through Script Language and even combine different discussion contexts.
- A NamedString (to target a single discussion context) – Displays the posts belonging to the discussion context mapped to the specified key-value pairs.
Sort Order – The order in which discussion messages are displayed:
- Discussion Post Timestamp Descending – Displays the latest discussion post first.
- Latest Message Timestamp Descending – Displays the discussion post with the most recent message first.
Timestamp Format:
- Absolute – It shows the exact date and time. In this case, you can set:
- Timestamp Format Pattern – It allows you to select a Format Pattern Business Object to set the desired date format.
- Relative: It shows the approximate elapsed time (e.g. 1 hour ago).
- Hybrid: It shows the approximate elapsed time if lower than 48 hours, otherwise it shows the exact time and date.
- Absolute – It shows the exact date and time. In this case, you can set:
Allow Inline Replies – If selected, users can reply to discussion messages. If deselected, the Reply button is hidden but all existing replies are still displayed.
Allow Search – If selected, users can search messages.
Allow Editing Messages – If selected, users can edit their own messages.
Allow Deleting Messages – If selected, users can delete their own messages.
Discussion List Style Business Object
The Discussion List Style Business Object (Style tab) allows you to style all the elements of the Discussion List (numbers refer to the styling properties described below):

- Color – Sets the background color for the whole component.
- Border – Sets the style (thickness, color, and so on) of the border for the whole component.
- Message Separator – (1) Sets the style of the separator line between messages.
- Inner Spacing – Sets the space between the message area and the component border.
- Search Button – (2) Refers to a Button Style Business Object to set the style of the Search button next to the Search Field.
- Search Field – (3) Refers to a Text Field Style Business Object to set the style of the search field.
- Show More Button – Refers to a Button Style Business Object to set the style of the corresponding button, which is displayed when some text is hidden.
- Message Timestamp – (4) Refers to a Text Style Business Object to set the style of the message timestamp.
- Message Text – (5) Refers to a Text Style Business Object to set the style of the actual text message.
- Message Link/Mention – (6) Refers to a Link Style Business Object to set the style of links or user mentions.
- Message Username – (7) Refers to a Text Style Business Object to set the style of the chat participants’ usernames.
- Message Actions – (8) Refers to a Button Style Business Object to set the style of the action buttons below each message, Reply, Edit, and Delete.
- Text Area – (9) Refers to a Text Area Style Business Object to set the style of the area where the user writes the actual message.
- Send Button – (10) Refers to a Button Style Business Object to set the style of the Send button next to the message text area. Consider that the same style applies to the Done button, when displayed.
- User Popup – Refers to a Popup (virtual) Style Business Object to define the style of the popup that displays the list of names that match the user’ search.
- User List Entry – Refers to a List Entry (virtual) Style Business Object that sets the style of each User Popup entry (see above).
Discussion New Post Field
The Discussion New Post Field consists of a text input field where the user can input and submit a new post. All posts created with this specific instance of the component are linked through the Discussion Context property.

Discussion New Post Field: Edit mode

Discussion New Post Field: Preview Mode
Configuration
The component has the following properties (Properties tab):
- Discussion Context – (Mandatory) Named String. It identifies a thread of posts. All posted messages are associated with this context and can be filtered based on it.
- Display Placeholder – If selected, a placeholder is displayed in the input field when no text has been entered.
New Post Field Style Business Object
The New Post Field Style Business Object (Style tab) allows you to style all the elements of the Discussion New Post Field (numbers refer to the styling properties described below):

- Background – Sets the background color for the whole component.
- Border – Sets the style (thickness, color, and so on) of the border for the whole component.
- Inner Spacing – Sets the space between the message area and the component border.
- Link/Mention – (1) Refers to a Link Style Business Object to set the style of links or user mentions.
- Send Button – Refers to a Button Style Business Object to set the style of the Send button next to the message text area.
- Text Area – (2) Refers to a Text Area Style Business Object to set the style of the area where the user writes the actual message.
- User Popup – (3) Refers to a Popup (virtual) Style Business Object to define the style of the popup that displays the list of names that match the user’ search.
- User List Entry – (4) Refers to a List Entry (virtual) Style Business Object that sets the style of each User Popup entry (see above).
Actions The Discussion New Post Field can trigger a specific event (Actions tab):
- onafterpost – The action set is triggered once the post is confirmed by clicking the Send button.
Functions
Primitive Types
There are four primitive types to represent discussions and messages:
DiscussionPostDiscussionReplyDiscussionMessageDiscussions
The three most important types are described below.
DiscussionPost
The Primitive Type DiscussionPost represents a post in the discussion system.
Conceptually, when a post is created, the associated discussion also starts to exist: It is just a discussion with a post but no replies.
A DiscussionPost object contains:
- The user ID of the sender
- The sent timestamp
- The message text
- Tags – words indicated with a starting "#" that users include in the message text as keywords for the message content
- Links – Hyperlinks in the text
- Attributions – IDs of users who were mentioned with @-attributions in the message text
DiscussionPost message objects are read-only (immutable) objects. You receive DiscussionPost objects as part of the Discussion objects returned by the LoadDiscussions function. You never have to instantiate a DiscussionPost yourself.
The methods of the DiscussionPost class are the following:
| Method | Description |
|---|---|
String getID() | Get the message ID. |
long getUserID() | Get the ID of the user who sent the message. |
long getTimestamp() | Get the timestamp of the post this reply is a reply to. |
String getText() | Get the message content. |
String getTextTranslated() | Get the text with labels translated to the current language (in a HTTP request, the current user's language). |
String getTextTranslated(String $language, String $userID) | Get the text with labels translated. The language is determined as follows: |
- If a language is specified, the string is translated to the given language.
- If no language but a user ID is specified, the string is translated to the language predicted for the given user.
- If neither language nor user are specified, the string is translated to the current language (in an HTTP request, the current user's language).
- `$attributionSubst` – Substitution for the attributions, placeholders `$1` and `$2` are replaced with user ID and attribution display value, respectively.
- `$mentionSubst` – Substitution for the mentions, placeholders `$1` and `$2` are replaced with user ID and mention display value, respectively.
- `$linkSubst` – Substitution for the links, placeholders `$1` and `$2` are replaced with URL and link name, respectively.
The Primitive Type DiscussionReply represents a reply in the discussion system. A DiscussionReply is always associated with a DiscussionPost and contains the corresponding post's ID as the parentPostID field. Furthermore, a DiscussionReply inherits the context of its parent post, which it also contains as a field, and it contains a copy of the parent post's timestamp.
Like the DiscussionPost class, the DiscussionReply contains:
- The user ID of the sender
- The send timestamp
- The message text
- Tags – words indicated with a starting "#" that users include in the message text as keywords for the message content
- Links – Hyperlinks in the text
- Attributions – IDs of users who were mentioned with @-attributions in the message
DiscussionReply message objects are read-only (immutable) objects. You receive DiscussionReply objects as part of the Discussion objects returned by the LoadDiscussions function. You never have to instantiate a DiscussionReply yourself.
The DiscussionReply class has the same methods as the DiscussionPost class, plus the following additional ones:
| Method | Description |
|---|---|
String getParentPostID() | Get the message ID of the post this reply is a reply to. |
long getParentPostTimestamp() | Get the timestamp of the post this reply is a reply to. |
Discussion
The Primitive Type Discussion simply aggregates a DiscussionPost and a list of DiscussionReply objects. Discussions are read-only (immutable) objects.
The LoadDiscussions function returns a list of Discussion objects.
The following methods are available:
| Method | Description |
|---|---|
String getID() | Get the ID of this discussion which is the ID of the associated post. |
long getTimestamp() | Get the timestamp of the discussion which is the timestamp of the discussion’s initial post. |
DiscussionPost getPost() | Get the discussion post. Never null. |
List <DiscussionReply> getReplies() | Get the replies in this discussion in chronological order. Never null, but can be empty. |
Send a Discussion Post
The Script Function SendDiscussionPost sends a discussion post and returns the created post object.
SendDiscussionPost($context, $userID, $text, $attachments)
| Parameter | Description |
|---|---|
$contextnNamed String | The key-value pairs to associate with the sent message. |
$userIDnString | ID of the user who is the sender of this message. |
$textnString | The message text to send. |
$attachmentsnIndexed UserMessageAttachment, optional | The attachments to add to the messagenNote: Attachments are already available in the APIs, but are not yet supported. |
$timestampnLong, Optional | Unix timestamp (in seconds). |
| Return value | Description |
|---|---|
$postnDiscussionPost | The created Discussion Post object. |
Reply to a Discussion
Sends a discussion reply and returns the created reply object.
SendDiscussionReply($parentPostId, $userID, $text, $attachments)
| Parameter | Description |
|---|---|
$parentPostIdnString | ID of the post which to reply to. |
$userIDnString | ID of the user who is the sender of this message. |
$textnString | The message text to send. |
$attachmentsnIndexed UserMessageAttachment, optional | The attachments to add to the messagenNote: Attachments are already available in the APIs, but are not yet supported. |
| Return value | Description |
|---|---|
$replynDiscussionReply | The created Discussion Reply object. |
Update or Remove a Message
The Script Function UpdateDiscussionMessage replaces an existing discussion message with a modified one. This allows you to submit a modified message, or a message marked as removed. Note that marking a message as removed is different from deleting a message. A message marked as removed still exists and can be retrieved for archiving, but is not displayed in the Workspace.
To modify a message or mark it as removed, you first have to retrieve it, then call one of the following functions:
DiscussionMessage $modifiedDiscussionMessage := $discussionMessage.modifyText('New text');
DiscussionMessage $modifiedDiscussionMessage := $discussionMessage.modifyAttachments($newAttachments)
DiscussionMessage $modifiedDiscussionMessage := $discussionMessage.markAsRemoved()
Then, overwrite the original message with the modified message using:
UpdateDiscussionMessage($modifiedDiscussionMessage)
| Parameter | Description |
|---|---|
$modifiedDiscussionMessagenDiscussionMessage | A modified discussion message to store. |
Returns nothing.
Load Discussions
LoadDiscussions takes a discussion message filter and a maximum number of results, and returns the n newest (or all, if there are less than n) matching discussions in reverse chronological order (that is, newest first). Note that a discussion's timestamp (considered for the ordering and for limiting the result to the n newest discussions) is the timestamp of the initial discussion message posted.
See Discussion Filtering for information on how to construct a filter.
LoadDiscussions($filter, $n)
| Parameter | Description |
|---|---|
$filternDiscussionMessageFilterExpression | The filter containing the criteria returned discussions must match. |
$nnInteger | Maximum number of discussions to return. If n = -1, all messages are returned. |
| Return value | Description |
|---|---|
$discussionsnIndexed Discussion | A list of the newest n (or all, if there are less than n) discussions matching the given filter in reverse chronological order (newest first). |
Delete Discussion Messages
Deletes all discussion messages that match a given discussion message filter.
See Discussion Filtering for more information.
This function should be used to delete any discussion messages that are no longer needed. This applies, for example, to discussion messages with a context that has been destroyed, that is, discussion messages that belong to a process instance that ends. It is also safe to use this function if messages matching the given filter are created concurrently or in the future.
However, it is not efficient for batch-deleting large volumes of messages and should therefore not be used for this purpose. Batch-deleting should be done via the console (seeStudio Administration and Monitoring).
DeleteDiscussionMessages($filter)
| Parameter | Description |
|---|---|
$filternDiscussionMessageFilterExpression | The filter containing the criteria for messages to be deleted. |
Integration Link Start Component
The Workspace Collaboration discussion system allows you to react to new discussion messages in the solution using Integration Links.
The extension provides an Integration Link Start Component that triggers for new messages.

To use the component, create a new Integration Link and select "Discussion Message" as the start element. The only configuration property of the element is "Discussion Filter", which determines a filter for which discussion messages should trigger the Integration Link. See Discussion Filtering for information on how to construct a filter. When the Integration Link is running, it is triggered by every new message matching the given filter.
The corresponding DiscussionMessage object is the body of the in-message, so to access the message, use the following script:
DiscussionMessage $message := CAST(DiscussionMessage, $in.getBody())
In the chain that comes afterwards, you can define arbitrary logic that executed for each new message. You can, for example, use the Send Notification End Component to send a notification for the new message.
Example: Implementing Comment Functionality
If you would like to offer users the possibility of commenting on certain items in your solution — tasks in a case management scenario, for example — add a New Post Field and a Discussion List component to the Screen displaying the task. On both the New Post Field and the Discussion List, configure the context indicating to which task the comment belongs so they are identical. This could be something like the script below, where $taskId contains the ID of the task displayed.
{'discussionType'='taskComment', 'taskId'=$taskId}:String
Note: You can define both the keys and the values arbitrarily. In a system that uses the discussion module only for comments of one specific type of item, you would not need the key `discussionType`, for example.nHowever, if the discussion module is used in different places, make sure the context contains all the information needed to display the correct discussions in the correct place.
Studio Administration and Monitoring
Workspace Collaboration is administered in Studio throrugh the Solution Maintenance > Workspace Collaboration submodule.
There are two sections: Chats and Discussion Contexts.

Chats
Chats are listed in a table according to the Chat ID, and can be filtered to display all chats ("*"), only active chats ("On"), or only inactive chats ("Off").
The remaining columns show:
- The users in a chat at that point in time
- The date of last activity in that chat
- The number of chunks in that chat
What is a chunk? Messages are not stored individually, but as "chunks". A chunk consists of 50 messages.
Delete a specific chat
To delete all messages in a specific chat, right-click the chat and select Delete messages.
Delete all chats
To delete all chats in the system (useful while developing to clean the system), select Delete All Chat Messages from the top left menu.
Discussion Contexts
The Discussion Contexts section provides an overview of all discussions in the system. The number of messages in the system is at the bottom of the section; the table in the middle of the section shows all discussions, grouped by context. Updating this table has to be triggered explicitly via clicking the Update Stats button, since the computation requires going through all discussion messages in the system.
For each discussion context, the table shows:
- A textual representation of the context
- The number of posts and replies for that context
- The date of last activity in this context (last new message)
Recreate discussion index
In an FNZ Studio cluster, every node has its own index. When one node crashes, all other nodes continue. If you sense that the node that crashed did not synchronize the message index correctly, you can recreate the discussion index on that node:
- Click on Reindex Discussions Locally in the menu at the top left.
- Select a node.
- Click Reindex Discussions Locally.
Note: Reindexing is performed asynchronously.
Delete a specific context
To delete all messages in a specific discussion context, right-click the context and select Delete messages.
Delete discussions
To delete all discussions, or delete all discussions older than a given timestamp (useful for cleaning the system during development), select Delete All Discussions or Delete Old Discussions from the menu at the top left of the section.
Performance Monitoring
The Node Inspector (System Maintenance > Statistics > Node Inspector) can be used to monitor Workspace Collaboration's memory consumption. The relevant maps are Chat Chunks, Chat Chunk Index Entries, and Discussion Messages (expand the flap on the right side to search them).
CPU statistics are recorded for the Workspace Collaboration AJAX helpers. Statistics with Context Type AjaxHelpers, and Context SecureChatAjaxHelper and SecureDiscussionAjaxHelper are found under System Maintenance > Statistics > CPU Statistics. The statistics include the number of calls, as well as various server-side timing measurements.
As a health check, you can look at the realTime (avg) column. It shows the total time consumed by an AJAX call on the server-side, which should normally be smaller than 20 ms. Furthermore, you can reset the statistics and check the realTime (sum) after e.g. 60 seconds. Dividing the value (in seconds) by (60 * number of cores) yields the utilization of the system by chat or discussion AJAX calls, respectively.
Should chats or discussion consume too much CPU, increase the polling interval.
Finally, I/O statistics of the Lucene index underlying the discussions can be checked in System Maintenance > Statistics > I/O Statistics.
Workspace Collaboration Backend
The Workspace Collaboration sub-system is composed of a front end, built mainly using Ajax, and a Java back end running using Hazelcast. The remainder of this document will focus on the back-end part. Workspace Collaboration feature in FNZ Studio allows users to communicate with each other by means of chats and discussions. Also, it notifies users when certain events occur, for example, when a new incoming message is received.
The UserMessagingService is composed of three modules, ChatModule, NotificationModule and DiscussionModule.
Chat Module
The Chat component allows organizations to build instant messaging solutions inside an FNZ Studio solution. The most important objects of this module are:

The ChatModule is the main component of the chat subsystem. It allows one to query the chat subsystem (for example, get the number of active chats), delete chats, add or remove event listeners etc. Note that the class implementing this interface manages the UserHeartbeatDistributor thread which periodically notifies the distributed chat states about user heartbeats (more details will be discussed later).
As the name suggests, the Chat interface represents a user’s view on a Workspace chat. It provides functionalities to add/remove users to/from the chat, query for messages (with or without filters), send new messages, add/remove event listeners, etc. It also allows one to submit heartbeats to the UserHeartbeatDistributor thread.
The ChatMessage class represents a message within the chat. Its state simply contains the chat identifier and the “encoded” version of the text, i.e., the string containing the message, the user ID of the user sending the message, etc.
The ChatState represents the current state of a chat, including which users are currently online and which ones have been invited to the chat. The most important method provided by this class is onUserHeartbeats(). Its scope is two-fold: (1) updating the list of online users and (2) cleaning the list of invited users. The former is achieved by means of heartbeats lasting 10 seconds by default - see UserMessagingConstants.CHAT_USER_MAX_HEARTBEAT_INTERVAL_S, while the latter is done by forcing invitations to have a limited validity of 30 seconds by default - see UserMessagingConstants.CHAT_INVITATION_TIMEOUT_S.
The UserHeartbeatDistributor updates the ChatStates every 2/3 of the UserMessagingConstants.CHAT_USER_MAX_HEARTBEAT_INTERVAL_S by means of an Hazelcast EntryProcessor. See also the ChatState.onUserHeartbeats() method.
Note that the chat states are stored into a non-persistent distributed map, i.e., every time the FNZ Studio platform is shut down the chat states are lost. Chat histories are persisted in permanent storage instead.
Also, the chat module relies on Hazelcast to remove chats which are not active anymore (a chat is not active when there are no online users): the map has an idle timeout of 5 minutes in order to cope with large garbage collector pauses, while the time-to-live (TTL) timeout on the map’s near-cache is 20 seconds. The reason behind such a short value is that entries are invalidated frequently due to heartbeats, so large values would be useless, and potentially harmful, for example, whenever a chat becomes inactive the state change would be detected after a longer period of time.
Adding And Removing Users
Users may have to add other users to a chat. In order to to this, the Chat interface provides two methods for adding users to a chat, addUser() and addInvitedUser(). As the name suggests, while the former simply adds a new user to an existing chat, the latter works with invitations (events triggered by a user already in the chat), i.e., a functionality enabling real-time collaboration. While the details of the two methods are slightly different, the internals are the same: the user identifier is added to the distributed chat state (see the ChatState class for more details) by means of an entry processor, which in turn adds a heartbeat for the user (heartbeats have to be periodically updated, and users failing to do that will be considered offline), and publishes a new event of type UserJoinedEvent to (possibly remote) interested parties by means of an Hazelcast distributed task. Users can be removed from a chat by means of the removeUser() method. When this function is called, all pending heartbeats related to the selected user are deleted from the UserHeartbeatDistributor thread, the chat state is updated (as before, an entry processor is employed to execute the operation in an atomic manner), and an event of type UserLeftEvent is sent to interested parties.
Sending Messages
Messages within a chat are sent via the submitMessage() method. This function is more complex than those above because messages are stored within Hazelcast in chunks of 50 rather than individually (see UserMessagingConstants.CHAT_CHUNK_SIZE). Hence, this function embeds the logic required to append a new message to an existing chunk, create a new chunk if required, etc. Upon success, an event of type ChatMessageEvent is sent to interested parties (e.g., other users in the chat).
Deleting Messages
The deleteAllMessages() method deletes all messages related to a specific chat. This method is automatically invoked whenever a chat is deleted from the system. For more details, refer to the WorkspaceCollaborationChatModule.deleteChat() method in the WorkspaceCollaboration extension. Since messages are stored in chunks and chunks are indexed, the removal is a rather simple operation: an entry processor retrieves the index entry for the specific chat from the distributed map storing all chat indexes, and all chunks related to the chat are deleted from the map storing the chat chunks.
Heartbeats And Removing Offline Users
Users within a chat are required to periodically send heartbeats in order to be online, for example, see ChatAjaxHelper.getChatUsers() in the WorkspaceCollaboration extension. Heartbeat updates are triggered via the Chat.sendUserHeartbeat() method, which simply passes the user identifier to the UserHeartbeatDistributor thread - see Figure 2. Approximately every 7 seconds the above thread cleans the list of received heartbeats by means of an entry processor which calls the onUserHeartbeats() method on each ChatState instance. This removes offline users and expired invitations.

Notification Module
Notifications are alerts that aim at informing users about specific events, for example, a request to join a chat or being mentioned in a discussion. Notifications are delivered to interested parties by means of one or more “delivery channels”. The default delivery channel is implemented within the FNZ Studio core and is responsible for delivering notifications within the platform, i.e., via the user interface in the Studio or Workspace. Another example of delivery channels is the email channel. The most important objects in this module are described below.

The NotificationModule interface provides functionalities to create/delete topics, submit/delete notifications, subscribe/unsubscribe to topics and notifications, among the others. Its implementation contains a dictionary of delivery channels (i.e., the default channel working within the FNZ Studio platform, and possibly others such as an email delivery channel) and an object managing notification topics and subscriptions.
Every user has an FNZ Studio-internal notification inbox which can be displayed in several ways using Workspace Notification Screen components. Such inbox is represented by an instance of the NotificationInbox class, which contains both read and unread notifications for a given user. Unlike other objects, inbox instances are created only upon delivery of the first notification, see Figure 4. In order not to clog the system, the NotificationCleanupJob task takes care of removing from the Hazelcast map the notifications that were created more than nm.usermessaging.notification.ttl_days before the current time. The scheduler service runs it every hour, however its logic is executed at most once a day. Note: Unlike other objects, users’ inboxes are created only upon delivery of the first notification, see Figure 4.
The default delivery channel, FNZ StudioNotificationDeliveryChannel, is responsible for delivering notifications to users via the UI in the Studio or Workspace. Among others, a method that exposes users’ inbox is provided. However, rather than returning a NotificationInbox object, this method returns a so-called “view” on the inbox (type NotificationInboxView).
Instances of the NotificationMessage class (and its subclasses) represent notifications. Such items can be filtered and matched through a NotificationFilter, allowing users to receive only certain notification messages.
Objects of type NotificationInboxEntry represent a notification in a user’s inbox. Their state includes the time when the notification was created and a flag indicating whether the notification has been read by the user.
Instances of the NoticationIndexEntry class represent a list of references to NotificationInbox objects that contain the same notification. The state of these objects is represented by a notification identifier, the timestamp when the notification was sent and a list of inboxes containing the notification.
Topics are represented by NotificationTopic objects. Apart from name, etc., each NotificationTopic instance has a dictionary that maps each delivery channel to a set of subscribed users.

Deleting Notifications
Notifications deletion is delegated to the delivery channel. The default delivery channel, AppwayNotificationDeliveryChannel, uses Hazelcast distributed tasks. The choice of which task to employ depends on whether filters are used, e.g., deleting all notifications older than a certain time.
Topics Management
The NotificationModule delegates all the operations related to topics to a NotificationTopicManager, which are stored in a distributed map. All methods changing the state of a topic subscription (for example, subscribing a user to a topic) are performed using an entry processor. The appendix describes how topics can be used within the notification system.
Discussion Module
The discussion module enables organizations to implement message feeds where users can post questions and answers within FNZ Studio Runtime. Compared to the chat module described above, discussions can be filtered and organized in different feeds. The most important objects are:

- Discussion objects represent discussions within the system. They consist of a “post” and a number (≥ 0) of answers (“replies”).
- The DiscussionModule is the main component of the discussion subsystem. It manages discussions within the system, for example, by removing all discussions, submitting new discussions, searching for discussions based on specific filters (e.g., users involved in the conversation), etc.
- The utility classes in the package com.nm.usermessaging.discussion.booleanfiltering provide a number of filters (e.g., based on OR or AND operands) to be used when searching for discussions.
- The com.nm.usermessaging.discussion.indexing and search packages provide the functionalities necessary to filter discussions efficiently and to provide full-text search. These component rely on the Lucene library.
Adding And Removing Discussions And Messages
Since there are no chunks and the index is maintained by Lucene, adding or removing discussions and messages is straightforward: the workflow consists of performing the desired operation (for example, deleting a message) on the distributed map and, in case the map was modified, updating the index. See deleteDiscussionMessage() and related methods in the DiscussionModuleImpl class.
Appendix: Types And Scripting Language
Types like AddressedNotificationMessage, ChatMessage, DiscussionPost and DiscussioReply are exposed in the scripting language, however they **should ** not (and cannot) be instantiated directly (autoConstruct is not set in DefaultTypeDefinitions):
AddressedNotificationMessage $n := NEW(AddressedNotificationMessage, …);
CONTEXT().getUserMessagingService().getNotificationModule().submitNotification($n);
Executing the above code snippet gets validated, however its execution results in the following error:
com.nm.sdk.NmEvaluatorException: Exception evaluating method
com.nm.usermessaging.notification.NotificationModuleImpl.submitNotification(1) [token:., line:9, charpos:59,
scope:null,
The AddressedNotificationMessageBuilder can be used to instantiate objects of type AddressedNotificationMessage, however it is not made available in the scripting language. What should be used instead is the function SendAddressedNotification:
Indexed String $receivers := ['pippo']:String;
Indexed String $deliveryChannels := ['appway']:String;
String $userId := 'NM';
String $type := 'System';
String $text := 'notification text';
Indexed UserMessageAttachment $attachments := null;
SendAddressedNotification($receivers, $deliveryChannels, $userId, $type, $text, $attachments);
Similarly, pub-sub notifications can be sent as:
String $topicID := 'new-american-client';
CONTEXT().getUserMessagingService().getNotificationModule().createTopic($topicID, 'topic name', 'description');
CONTEXT().getUserMessagingService().getNotificationModule().subscribeForNotifications($topicID, 'appway', 'pippo');
// equivalent to
CreateNotificationTopic($topicID);
SubscribeForNotifications('pippo', $topicID, 'appway');
Indexed String $topics := [$topicID]:String;
SendPubSubNotification($topics, null, 'NM', 'System', 'SendPubSubNotification text', null, false);
Chat messages can be sent within a script in the following manner:
SendChatMessage('defaultChatId', 'NM', 'chat text');
Finally, the code snippet below shows how to send discussion posts and replies:
Named String $context := {'exampleContextKey1'='value1', 'exampleContextKey2'= 'value2'} :String;
DiscussionPost $post := SendDiscussionPost($context, 'pippo', 'discussion post');
DiscussionReply $reply := SendDiscussionReply($post.getID(), 'NM', 'discussion reply');