Edit online

Integrating Concurrent Editing

Oxygen XML Web Author offers a feature that can be enabled to provide the ability for teams to edit and review content concurrently. A user can share their editing session by using the Share 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.

Edit online

Enabling the Share Session Action

To enable the Share Session action, the following conditions must be met:

  • The Concurrent Editing Plugin must be installed. It can be found in the Download > Connectors/Plugins webpage. Make sure that you read and understand the terms and conditions of its end-user license agreement.
  • The sharedSessionCompatible loading option is set to true by the CMS connector plugin.

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.
To detect that a user is a guest, you can use the following JavaScript code:
var mgr = editor.getEditingSupport().getConcurrentEditingManager();    
if (mgr && !mgr.isCreator()) {      
Important Notes About Concurrent Editing for Integrators:
  • 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/oxygen-xml-web-author/ws";

    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 similar to the one below should be added:
    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]
  • Concurrent editing does not work if an existing plugin registered a ro.sync.ecss.extensions.api.AuthorDocumentFilter implementation.

Disabling the Share Session Action

To remove the Share Session action from the toolbar, simply disable the Concurrent Editing Plugin in the Administration Page.

Edit online

CMS-owned Concurrent Editing Sessions

Aside from using the Share Session action, a more integrated concurrent editing approach is to have a concurrent editing room owned by the CMS.

The CMS should be responsible for:
  • 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.
  • Enabling users to join the room.
  • Saving the content of the room.
  • Closing the room.

Creating a Concurrent Editing Room

The CMS can use the following JavaScript code to create a concurrent editing room:
editor.getEditingSupport().getConcurrentEditingManager().createRoom().then(roomId =>
window.open(window.location.href + '&roomId=' + roomId))

The ID of the room can then be shared with other users. To join an existing room, the roomId URL parameter can be used.

By using the Java API, a room can be created like this:

String roomId = RoomsManager.INSTANCE.createRoomFromDocument(AuthorDocumentModel) 

This code can be used during the editor loading on the following callbacks:

  • ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionAboutToBeStarted(). In this case, the room ID has to be added to the sessionAttributes map using the ro.sync.ecss.extensions.api.webapp.ce.Room.ROOM_ID_ATTRIBUTE key.
  • ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionStarted(). In this case, the room ID has to be added as an attribute to the editing context of the current document using ro.sync.ecss.extensions.api.webapp.ce.Room.ROOM_ID_ATTRIBUTE as the name. The editing context can be obtained by calling: authorDocumentModel.getAuthorAccess().getEditorAccess().getEditingContext().

Saving the Content of the Room

To Save a document edited in a concurrent editing room, the CMS can use the room Observer, which provides the "source-of-truth" for the document content. The Observer integrates changes made by each user in batches. This helps to keep an informative version history.

For example, to implement auto-save, a CMS should:

  • Add an EditListener on the observer (room.getObserver()) to be notified when the document state is changed.
  • Periodically call room.getObserver().sync(peerId -> { }};. The callback is called after integrating a batch of changes from a single user.
  • After integrating each batch of changes, the CMS can call room.getObserver().createInputStream() to obtain a reader over the document content at that point. The reader should be consumed immediately.

Enabling Users to Join a Room

To enable users to join a room, there are several options:

  • Add roomId=<the-id> as a URL parameter.
  • Add roomId=<the-id> as a loading option in a JavaScript plugin.
  • Set roomId=<the-id> as a session attribute in one of the following Java listeners:
    • ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionAboutToBeStarted()
    • ro.sync.ecss.extensions.api.webapp.access.WebappEditingSessionLifecycleListener.editingSessionStarted()

Closing the Room

A room created this way is not closed when the last user leaves. It should be closed using Room.close() when all the users have left and all the changes are saved.