Edit online

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.

Edit online

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 to true 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.
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;
    }

    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.

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.

Edit online

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.

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.
  • 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.

There are two base classes to be extended:
  1. 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:
    The URLConnection openConnection(URL documentUrl, PeerContext author) method's author parameter can be used to determine the user that authored the version of the file that is going to be written in the returned URLConnection.
  2. ro.sync.ecss.extensions.api.webapp.ce.GroupChangesForMultiplePeersStrategy - It facilitates a minimum number of writes to the file server per save.
    Note:
    The URLConnection 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 returned URLConnection.
Note:
It is recommended to use an application account to make requests to the CMS within 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:

  1. When the user closes the tab or browser cleanly.
  2. If the tab or browser is not closed cleanly:
    1. When the user's session expires (by default, this happens after 12 hours).
    2. When using the always.send.keepalive option and the timeout is reached.

To access the room object, you can use RoomsManager.INSTANCE.getRoom(roomId).

Note:
It is recommended to enable the always.send.keepalive option to ensure that even if the browser crashes, the editor is disposed faster (within minutes instead of 12 hours when the session expires).
Note:
It is recommended to use an application account to make requests to the CMS within WebappEditingSessionLifecycleListener.editingSessionClosed (rather than relying on user credentials) to prevent request failures due to expired or invalidated credentials.