Integrating Concurrent Editing
Oxygen XML Web Author provides the ability for teams to edit and review content
concurrently. The concurrent editing feature can be enabled either with the built-in
Share Editing Session action or by integrating the concurrent editing
support in a CMS using the available API. The Share Editing Session
action works by allowing the user to share their editing session by using the
Share
Editing Session toolbar button and then sending the retrieved URL to other
collaborators and then all of them can edit and review the same document simultaneously.
Enabling the Share Editing Session Action
To enable the Share Editing
Session action, the following conditions must be met:
- The Concurrent Editing Plugin must be enabled in the Administration Page (it is bundled with the Oxygen XML Web Author installers and is enabled by default).
- The
sharedSessionCompatible
loading option must be set totrue
by the CMS connector plugin. Note that the built-in connectors (for e.g. the Git connector) have this option by default configured.
For the feature to work correctly, when a concurrent session is active, the CMS connector plugin should ensure that:
- Any save/commit action is disabled for session guests.
- Any check-in/check-out actions are disabled for session guests.
- The document is editable for guests even though they do not have a lock for it.
var mgr = editor.getEditingSupport().getConcurrentEditingManager();
if (mgr && !mgr.isCreator()) {
...
}
- Concurrent editing does not work if Oxygen XML Web Author is deployed on multiple servers behind a load balancer.
-
Oxygen XML Web Author uses Web Sockets to propagate changes in real time between collaborators. If you are using a reverse proxy, some additional configuration may be required to enable Web Socket connections to the
./ws
endpoint of the application.For example, NGINX requires the following configuration for the /oxygen-xml-web-author/ws path:location /oxygen-xml-web-author/ws { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_pass http://web-author:8080; }
As another example, when using Apache HTTP server as a reverse proxy, a system administrator needs to enable the following modules:
-
rewrite
-
proxy_http
-
proxy_wstunnel
Also, a configuration should be added similar to this:ProxyPass /oxygen-xml-web-author http://<internal-host>:8080/oxygen-xml-web-author ProxyPassReverse /oxygen-xml-web-author http://<internal-host>:8080/oxygen-xml-web-author RewriteEngine on RewriteCond %{HTTP:Upgrade} websocket [NC] RewriteCond %{HTTP:Connection} upgrade [NC] RewriteRule ^/oxygen-xml-web-author/?(.*) "ws://<internal-host>:8080/oxygen-xml-web-author/$1" [P,L]
When using the IIS web server as a reverse proxy, you need to configure it according to this tutorial.
-
- Some customizations can cause problems if used in situations with
high activity or a large number of users simultaneously. These possible limitations
include:
- The
onChange
property for the combo box form control is not supported in concurrent editing sessions. - Inline CSS actions (for example, when used in an
oxy_button
form control). - Editable combo boxes on the floating toolbar.
- Quick fixes that come from XML Schema or DTD.
- The
Disabling the Share Editing Session Action
To remove the Share Editing
Session action from the toolbar, simply disable the Concurrent
Editing Plugin in the Administration Page.
CMS-owned Concurrent Editing Sessions
Aside from using the Share Editing
Session action, a more integrated concurrent editing approach is to have a
concurrent editing room owned by the CMS.
A concurrent editing room contains multiple editors, each working on the same document,
with all changes synchronized in real time. Each editor corresponds to a browser tab and
is represented by an AuthorDocumentModel
in the API.
The CMS needs to ensure that there is at most one concurrent editing room for one document.
- Ensuring that aside from the Web Author users, no other users are modifying the file. The CMS can use a "service" account to hold the "lock" on the edited file, or to do a check-out.
- Creating the concurrent editing room.
- Adding new editors in the room.
- Specifying the Save Strategy.
- Closing the room.
Creating a Concurrent Editing Room
When an user opens a document in an editor, the CMS needs to create a concurrent editing room for that document if one is not already created.
The recommended way for a CMS to create a concurrent editing room is by using the Java API:
import ro.sync.ecss.extensions.api.webapp.ce.RoomsManager;
String roomId = RoomsManager.INSTANCE.createRoomFromDocument(authorDocumentModel)
This can be called in the
ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionStarted()
listener.
Note that the roomId
is automatically stored in the editing context and
it can be obtained when needed by calling: String roomId =
authorDocumentModel.getAuthorAccess().getEditorAccess().getEditingContext(),getAttribute(Room.ROOM_ID_ATTRIBUTE)
.
Adding New Editors in a Room
When a user tries to open a document in an editor that the CMS already created a room
for, it can link the editor to the existing room by using the
ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionAboutToBeStarted()
listener. In the implementation of this listener, the ID of the existing room has to be
added to the sessionAttributes
map using the
ro.sync.ecss.extensions.api.webapp.ce.Room.ROOM_ID_ATTRIBUTE
key.
Saving a Concurrent Editing Session
When saving a concurrently edited document, the application uses a save
strategy that can be specified when creating the room by calling
ro.sync.ecss.extensions.api.webapp.ce.RoomFactory.createRoom(AuthorDocumentModel,
SaveStrategy)
.
The save strategy specifies how the changes are grouped when writing the changed document to the CMS. For example, if multiple users made changes to the document opened in the room since the last save, the save strategy can choose to save the final version to the CMS or to save intermediate versions, with every version containing changes from a single editor.
ro.sync.ecss.extensions.api.webapp.ce.GroupChangesForSinglePeerStrategy
- It facilitates tracking precise authorship of changes, as each written revision contains changes by only one author.Note:TheURLConnection openConnection(URL documentUrl, PeerContext author)
method'sauthor
parameter can be used to determine the user that authored the version of the file that is going to be written in the returnedURLConnection
.ro.sync.ecss.extensions.api.webapp.ce.GroupChangesForMultiplePeersStrategy
- It facilitates a minimum number of writes to the file server per save.Note:TheURLConnection openConnection(URL documentUrl, PeerContext committer, List<PeerContext> authors)
method has two parameters:committer
- It can be used to identify the user that triggered the save.authors
- It can be used to identify the users that authored the version of the file that is going to be written in the returnedURLConnection
.
GroupChangesForMultiplePeersStrategy.openConnection
and
GroupChangesForSinglePeerStrategy.openConnection
(rather than relying
on user credentials by calling url.openConnection
) to prevent request
failures due to expired or invalidated credentials.Closing the Room
A programmatically created room is not closed automatically when the
last user leaves or closes the browser tab. The room should be closed explicitly using
Room.close()
when all the users have left and all the changes are
saved. To identify when a room becomes empty, you can use
WebappEditingSessionLifecycleListener.editingSessionStarted
and
WebappEditingSessionLifecycleListener.editingSessionClosed
.
The WebappEditingSessionLifecycleListener.editingSessionStarted
is
called each time a tab with a Web Author editor is opened.
The WebappEditingSessionLifecycleListener.editingSessionClosed
is called
when the editing session closes. This happens in the following cases:
- When the user closes the tab or browser cleanly.
- If the tab or browser is not closed cleanly:
- When the user's session expires (by default, this happens after 12 hours).
- When using the always.send.keepalive option and the timeout is reached.
To access the room object, you can use
RoomsManager.INSTANCE.getRoom(roomId)
.
WebappEditingSessionLifecycleListener.editingSessionClosed
(rather
than relying on user credentials) to prevent request failures due to expired or
invalidated credentials.