Working with Sessions

 

Author: Eike Stepper

This chapter covers all aspects of session management in CDO client applications, including configuration, lifecycle, facilities, and events.

Sessions are the foundation of any CDO client application. They represent the connection to a CDO repository and provide access to views, transactions, and various session facilities. Understanding how to create, configure, and manage sessions is essential for building robust and scalable CDO applications.

Sessions are created (opened) when needed, kept open for the duration of their use, and closed when no longer required. A client application may have multiple sessions open simultaneously, each connected to different repositories or using different configurations.

Session API Structure

The session API in CDO is organized into two main components: the basic CDO session and the specific Net4j session.

This separation allows applications to use the generic session API for repository operations, while leveraging Net4j-specific capabilities when needed for networked environments.

Other transport-specific session types are possible in general, allowing for future extensibility of the session API to support additional transport protocols. However, as of now, only the Net4j implementation is available and supported in CDO. This means that while the API is designed to be flexible, all practical usage currently relies on the Net4j session type for networked repository access.

Thread Safety

Sessions in CDO are inherently thread-safe. This means that if multiple threads access the same session instance concurrently, the session ensures that its internal state remains consistent and that operations are executed in a thread-safe manner. This is particularly important in multi-threaded applications where different threads may need to perform operations on the same session simultaneously.

Table of Contents

Creating and Configuring Sessions
1.1 Session Configurations
1.2 Net4j Connectors
1.3 Authentication and Security
1.4 Recovering from Disconnects
1.5 Passive Updates and Refreshing
1.6 Waiting For Updates
View Management
2.1 Purpose of Views and Transactions
2.2 Branch Points and the View Target
2.3 Opening Views and Transactions
2.4 Accessing Open Views
2.5 Finding Views by ID
2.6 Listening for View-Related Events
2.7 Closing Views
Authorizable Operations
Client and Server Entities
Session Facilities
5.1 Repository Info
5.2 Package Registry
5.3 Revision Manager
5.3.1 Working with Revisions
5.3.2 Pre-fetching Revisions
5.4 Branch Manager
5.4.1 Working with Branches
5.4.2 Working with Tags
5.5 Fetch Rule Manager
5.6 Commit Info Manager
5.7 User Info Manager
5.8 Remote Session Manager
5.8.1 Exchanging Messages With Other Sessions
5.8.2 Collaborating on Shared Topics
5.8.3 Integrating Topics With Your User Interface
Session Events
Session Options
7.1 Generated Package Emulation
7.2 Passive Update Enabled
7.3 Passive Update Mode
7.4 Lock Notification Enabled
7.5 Lock Notification Mode
7.6 Collection Loading Policy
7.7 Large Object Cache
7.8 Permission Updater
7.9 Delegable View Lock Enabled
7.10 Prefetch Send Max Revision Keys
Session Properties
Session Registry

1  Creating and Configuring Sessions

Learn how to instantiate and configure CDOSession objects, including specifying repository details, authentication, and connection parameters. Proper session configuration ensures secure and efficient communication with the CDO server.

1.1  Session Configurations

In order to create a session, you first need to create a session configuration. This configuration specifies the connector to use and the repository name to connect to, as well as various other options.

Here is an example of how to create and open a session using a given connector and repository name:

CreateSession.java      
CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setConnector(connector);
configuration.setRepositoryName(repositoryName);

CDOSession session = configuration.openNet4jSession();
System.out.println("Session opened for repository: " + session.getRepositoryInfo().getName());

// Use the session...

// Finally, close the session when done.
session.close();

Note that the connector must be created and configured separately. For more information about connectors, refer to the Net4j Connectors section or the Net4j documentation.

Also note that the repository must already exist on the server. For more information about repositories, refer to the Server Programming section.

Once a session is opened, you can still change various options on it, such as enabling or disabling passive updates. For more information about session options, refer to the Session Options section.

1.2  Net4j Connectors

Net4j connectors are the transport layer for CDO communication. Learn how to configure TCP, JVM, SSL, WS or WSS connectors to establish reliable and secure connections between client and server.

Typically a Net4j connector is created and configured using a Net4j container. Within an Eclipse environment, the container is usually managed by the Eclipse extension registry. Such a container can be accessed using IPluginContainer.INSTANCE. In a non-Eclipse environment, you can create and manage the container programmatically. Refer to IManagedContainer for more details.

The following example demonstrates how to set up a JVM connector within an Eclipse environment:

CreateConnectorInEclipse.java      
// Obtain the connector from the plugin container by its product group, factory type, and factory-specific
// description.
IConnector connector = IPluginContainer.INSTANCE.getElementOrNull("org.eclipse.net4j.connectors""jvm""acceptor1");

// Use the connector to create and open a session...

// Finally, close the connector when done.
connector.close();

The following example demonstrates how to set up a TCP connector within a non-Eclipse environment:

CreateConnectorInStandalone.java      
IManagedContainer container = ContainerUtil.createContainer();
ContainerUtil.prepareContainer(container); // Register basic Net4j factories.
TCPUtil.prepareContainer(container); // Register TCP connector factory.
container.activate();

// Obtain the connector from the container by its product group, factory type, and factory-specific description.
IConnector connector = container.getElementOrNull("org.eclipse.net4j.connectors""tcp""localhost:2036");

// Use the connector to create and open a session...

// Finally, close the connector when done.
connector.close();

// Deactivate the container when done.
container.deactivate();

More details on the architecture and usage of Net4j connectors can be found in the Net4j documentation.

1.3  Authentication and Security

The repository may require clients to authenticate before allowing access. In this this case, the repository administrator configures one or more authentication providers on the server side. When a client opens a session, an open-session request is sent to the server. If the server requires authentication, it initiates an authentication challenge-response sequence using one of its configured authentication providers. The client must respond with appropriate credentials. If the credentials are valid, the server allows the session to be opened; otherwise, the session opening fails.

Here is an example of how to configure a session with fixed credentials:

ConfigureCredentialsProvider.java      
IPasswordCredentialsProvider credentialsProvider = new PasswordCredentialsProvider("userID""password");

CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setCredentialsProvider(credentialsProvider);

Here is an example of how to configure a session with an interactive credentials provider that opens a CredentialsDialog whenever challenged from the server:

InteractiveCredentialsProvider.java      
IPasswordCredentialsProvider credentialsProvider = new InteractiveCredentialsProvider();

CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setCredentialsProvider(credentialsProvider);

The actual password is transmitted securely using a cryptographically strong protocol called Diffie-Hellman Key Exchange. It is not sent over the network in clear text, but is restored to clear text on the server side for verification.

1.4  Recovering from Disconnects

Discover strategies for handling session disconnects and automatic reconnection. Learn how to maintain session continuity and recover gracefully from network interruptions.

Normally, when a session is disconnected, it is automatically closed and cannot be used anymore. However, you can create a reconnecting session that automatically tries to reconnect when it gets disconnected. This is done by using CDONet4jUtil.createReconnectingSessionConfiguration() instead of CDONet4jUtil.createNet4jSessionConfiguration().

Here is an example of how to create and open a reconnecting session:

ReconnectingSession.java      
IManagedContainer container = IPluginContainer.INSTANCE;

ReconnectingCDOSessionConfiguration configuration = //
    CDONet4jUtil.createReconnectingSessionConfiguration(hostAndPort, repositoryName, container);
configuration.setHeartBeatEnabled(true); // Enable heart beats to detect network problems early.
configuration.setHeartBeatPeriod(5000); // Send a heart beat every 5 seconds.
configuration.setHeartBeatTimeout(2000); // Consider the connection dead if no heart beat response is received
                                         // within 2 seconds.
configuration.setConnectorTimeout(5000); // Timeout if unable to connect within 5 seconds.
configuration.setReconnectInterval(5000); // Try to reconnect every 5 seconds when the connection is lost.
configuration.setMaxReconnectAttempts(-1); // Try to reconnect forever.

CDOSession session = configuration.openNet4jSession();
System.out.println("Session opened for repository: " + session.getRepositoryInfo().getName());
// Use the session...

// Finally, close the session when done.
session.close();

A session can also be disconnected in case of server problems, as opposed to network problems. One way to protect against such situations is to use a fail-over session.

Here is an example of how to create and open a reconnecting session:

FailoverSession.java      
IManagedContainer container = IPluginContainer.INSTANCE;

FailoverCDOSessionConfiguration configuration = //
    CDONet4jUtil.createFailoverSessionConfiguration(monitorConnectorDescription, repositoryGroup, container);
configuration.setHeartBeatEnabled(true); // Enable heart beats to detect network problems early.
configuration.setHeartBeatPeriod(5000); // Send a heart beat every 5 seconds.
configuration.setHeartBeatTimeout(2000); // Consider the connection dead if no heart beat response is received
                                         // within 2 seconds.
configuration.setConnectorTimeout(5000); // Timeout if unable to connect within 5 seconds.

CDOSession session = configuration.openNet4jSession();
System.out.println("Session opened for repository: " + session.getRepositoryInfo().getName());
// Use the session...

// Finally, close the session when done.
session.close();

Fail-over sessions use a special network service, called fail-over monitor, to ask for the address of a live server. The configuration and operation of the fail-over monitor is beyond the scope of this documentation. For more information, refer to FailoverMonitor.

1.5  Passive Updates and Refreshing

Passive updates allow sessions to receive notifications about changes in the repository. Understand how to enable passive updates and refresh views to keep your client data synchronized.

By default, sessions receive passive updates from the server. This means that when other clients make changes to the repository, the server notifies all connected sessions about these changes. The sessions then invalidate the affected objects in their views, marking them as stale. When your application accesses a stale object, it is automatically refreshed from the server, ensuring that your application always works with the latest data.

Passive updates can be disabled if your application requires more control over when objects are refreshed. In this case, you can call the refresh() method on a view to manually refresh all stale objects.

Here is an example of how to enable/disable passive updates and manually refresh views:

PassiveUpdatesAndRefreshing.java      
CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setConnector(connector);
configuration.setRepositoryName(repositoryName);
configuration.setPassiveUpdateEnabled(true); // This is the default.

CDOSession session = configuration.openNet4jSession();
// Use the session with passive updates enabled...

session.options().setPassiveUpdateEnabled(false); // Disable passive updates.
// Use the session with passive updates disabled...

session.refresh(); // Manually refresh all stale objects in all views.
// Use the session with passive updates disabled...

session.options().setPassiveUpdateEnabled(true); // Re-enable passive updates.
// Use the session with passive updates enabled...

// Finally, close the session when done.
session.close();

Note that passive updates are a session-wide setting that affects all views within the session. When passive updates are re-enabled, a refresh is automatically performed to synchronize all views with the server. For more information about views, refer to the Waiting For Updates section.

When passive updates are enabled, the amount of information sent from the server to the client can be controlled using the passive update mode setting. There are three modes available:

Here is an example of how to set the passive update mode:

PassiveUpdateModes.java      
session.options().setPassiveUpdateEnabled(true);
session.options().setPassiveUpdateMode(PassiveUpdateMode.INVALIDATIONS);
// All views in the session now receive invalidations only...

session.options().setPassiveUpdateMode(PassiveUpdateMode.CHANGES);
// All views in the session now receive changes...

session.options().setPassiveUpdateMode(PassiveUpdateMode.ADDITIONS);
// All views in the session now receive changes and additions...

Note that the passive update mode can also be changed at any time on a session via the session options. For more information about passive update modes, refer to PassiveUpdateMode.

1.6  Waiting For Updates

In some scenarios, your application may need to wait for a specific update to be processed before proceeding. As updates are generally triggered by the server asynchronously, for example when another client commits changes, there is no guarantee that an update will be received immediately after it occurs.

To handle such situations, you can use the waitForUpdate(long updateTime) method on a session. This method blocks the calling thread until all updates that occurred before the specified update time have been processed. If you want to limit the time spent waiting, you can also specify a timeout value in milliseconds. If the timeout expires before the updates are processed, the method returns false; otherwise, it returns true.

Here is an example of how to wait for updates:

WaitingForUpdates.java      
long timeout = 10000// Wait for up to 10 seconds.

if (session.waitForUpdate(updateTime, timeout))
{
  // All updates that occurred before the call to waitForUpdate() have been processed.
}
else
{
  // The timeout expired before all updates were processed.
}

Note that more commonly, applications use listeners to be notified of updates asynchronously. For more information about session events and listeners, refer to the Session Events section.

Note also that applications usually wait for updates on views rather than sessions. For more information about views, refer to the View Management section.

2  View Management

Views are the primary mechanism for accessing and working with models that are stored in a CDO repository. They provide a consistent and isolated perspective on the repository state, allowing clients to read and, in the case of transactions, modify data. Understanding how to create, manage, and interact with views is essential for building robust CDO client applications.

Views are created from sessions and are associated with a specific branch point in the repository. This branch point determines the exact state of the repository that the view exposes. Views can be read-only or read-write (transactions), and they can also be configured to access historical states of the repository through audit views.

This section covers the key aspects of view management, including the purpose of views and transactions, branch points and view targets, types of views, finding views by ID, listening for view-related events, and closing views. For more detailed information on working with views, refer to the Working with Views section.

Note that a CDOSession inherits most of the view management functionality through the following super interfaces:

2.1  Purpose of Views and Transactions

A CDO repository can store many different versions of a model. Each time a change is committed to the repository, a new version of the model is created. These versions are organized into branches, allowing for parallel lines of development. Each version of the model is identified by a unique combination of a branch and a timestamp, known as a branch point. This multiplicity of versions and branches is the reason why a CDOSession alone is not sufficient to access a consistent model state of the repository. Instead, clients need views and transactions to specify which version of the model they want to work with.

A view represents a read-only window into the repository. Read-only views allow clients to navigate and query models without the risk of modifying data. For example, a client application might use a single read-only view to display a model in a different places of its user interface, allowing users to browse and inspect the data.

Transactions, on the other hand, are special types of views that enable clients to make changes to the repository. Changes made in a transaction are isolated from other views until the transaction is committed, ensuring data consistency and supporting concurrent development.

For more detailed information on working with views, refer to the Working with Views section.

2.2  Branch Points and the View Target

Every view or transaction in CDO is associated with a specific branch point, which determines the exact state of the repository that the view exposes. The branch point consists of a branch and a timestamp, together called the view target. This mechanism allows clients to access not only the latest state of the main branch, but also historical states (by specifying an earlier timestamp), or parallel lines of development (by specifying a different branch).

Normally, when you open a view or transaction, you specify the branch and/or timestamp that you want to target. Read-only views can target any branch point, including historical states, while transactions always target the head of a branch, allowing clients to make changes to the latest state of that branch.

When you open a view or transaction without specifying a branch point, it defaults to the head of the main branch, showing the most recent state of the repository. To access a different branch or a historical state, you can explicitly specify a branch and/or timestamp when opening the view. This is useful for auditing, time-travel queries, or working with feature branches.

For both views and transactions, the branch point is initially set when the view is created, but it can be changed later using the setBranchPoint() method. Changing the branch point causes the view to refresh its contents to reflect the state of the repository at the new target branch point. All model objects in the view are updated to match the new branch point, ensuring that the view always presents a consistent snapshot of the repository. This process is automatic and absolutely transparent to the client application. It enables powerful scenarios such as switching between different branches or historical states on-the-fly, without needing to close and reopen views. The restriction is that transactions can only be switched to other branch heads, not to historical states.

The head of a branch is represented by a special timestamp value called UNSPECIFIED_DATE. It ultimately represents a timestamp that is always greater than any other timestamp in the repository, effectively pointing to the latest commit on that branch. When a transaction is committed, it creates a new version of the model at the head of the branch, and all views targeting that branch head are automatically updated to reflect the new state. The head of a branch is always moving forward in time as new commits are made, while historical states remain fixed at their respective timestamps. As transactions always target the branch head, they are always working with the most recent state of the branch.

For more information about branches and branch points, see the CDOBranchPoint interface and the CDOBranchManager facility.

2.3  Opening Views and Transactions

Views and transactions are created from a session using the various openView() and openTransaction() methods. These methods allow you to specify the initial branch and/or timestamp that you want to target, as well as a ResourceSet to use for loading model objects.

You can also omit the branch, timestamp, and/or ResourceSet, in which case the view or transaction starts with reasonable defaults as follows:

Here is an example of how to open views and transactions with different configurations:

OpeningViewsAndTransactions.java      
// Open a read-only view targeting the head of the main branch with a new ResourceSet.
CDOView view1 = session.openView();
ResourceSet resourceSet1 = view1.getResourceSet();
Assert.isNotNull(resourceSet1);

// Open a read-only view targeting a specific branch and timestamp with an existing ResourceSet.
CDOBranch branch = session.getBranchManager().getMainBranch().getBranch("feature");
long timeStamp = new Date(2021 199061).getTime();
CDOView view2 = session.openView(branch, timeStamp, resourceSet2);
System.out.println("Resources in resourceSet2: " + resourceSet2.getResources());

// Open a read-only view targeting the head of the main branch with an existing ResourceSet.
ResourceSet resourceSet3 = new ResourceSetImpl();
CDOView view3 = session.openView(resourceSet3);

// Open a transaction targeting the head of a specific branch with a new ResourceSet.
CDOTransaction transaction1 = session.openTransaction(branch);

// Open a transaction targeting the head of the main branch with an existing ResourceSet.
CDOTransaction transaction2 = session.openTransaction(resourceSet3);

// Use the views and transactions...

// Finally, close the views and transactions when done.
CollectionUtil.close(view1, view2, view3, transaction1, transaction2);

Note that views and transactions are lightweight objects that can be created and disposed of as needed. They should be closed when no longer needed to free up resources.

Here is a summary of the various ways to open views and transactions:

2.4  Accessing Open Views

A session maintains a collection of all currently open views and transactions. You can access this collection using the getViews() method, which returns an unmodifiable list of all open views. This allows you to iterate over the views, inspect their properties, or perform operations on them as needed. Example:

AccessOpenViews.java      
CDOView[] views = session.getViews();
for (CDOView view : views)
{
  System.out.println("Open view: " + view.getViewID() //
      + ", Branch: " + view.getBranch().getName() //
      + ", Timestamp: " + view.getTimeStamp());

  if (view instanceof CDOTransaction)
  {
    CDOTransaction transaction = (CDOTransaction)view;
    System.out.println("  is a transaction with " //
        + transaction.getDirtyObjects().size() + " dirty objects.");
  }
}

2.5  Finding Views by ID

Each view in a session is assigned a unique integer ID. You can retrieve a view by its ID using the getView(int viewID) method. This is useful for managing multiple views or for integrating with external systems that track view identifiers. Example:

FindViewByID.java      
CDOView view = session.getView(viewID);
if (view != null)
{
  System.out.println("Found view with ID: " + viewID);
}
else
{
  System.out.println("No view found with ID: " + viewID);
}

2.6  Listening for View-Related Events

A session is an IContainer<CDOView> and, hence, emits events when views are opened or closed. You can listen for these events by registering an IContainerEvent listener with the session. This allows your application to react to changes in the set of open views, such as updating UI components or managing resources. Example:

AddViewsChangedListener.java      
session.addListener(new ContainerEventAdapter<CDOView>()
{
  @Override
  protected void onAdded(IContainer<CDOView> container, CDOView view)
  {
    System.out.println("View opened: " + view);
  }

  @Override
  protected void onRemoved(IContainer<CDOView> container, CDOView view)
  {
    System.out.println("View closed: " + view);
  }
});

2.7  Closing Views

It is important to close views when they are no longer needed to free up resources and avoid memory leaks. You can close a view by calling its close() method. Example:

CloseView.java      
view.close();
System.out.println("View closed.");

3  Authorizable Operations

CDO's main focus is on modeling and model management. From this perspective, models are very much like normal data, and CDO provides a rich set of features for working with model data, including access control and permissions. However, it also includes a basic authorization framework that allows you to perform certain operations only if you have the necessary permissions. An authorizable operation can be associated with various actions within CDO, such as opening a session, creating a transaction, or performing specific repository operations.

The AuthorizableOperation interface represents an operation that can be authorized. Each operation has a unique name and may include additional parameters that provide context for the authorization decision. An instance of AuthorizableOperation is typically created by the client application and then passed to the local session for authorization. The session checks whether the current user has the necessary permissions to perform the operation and returns a veto string if the operation is not authorized. If the operation is authorized, the veto string is null.

Here is an example of how to create and authorize an operation:

AuthorizeOperation.java      
AuthorizableOperation firstMove = AuthorizableOperation //
    .builder("my.chess.game.MoveChessman"//
    .parameter("from""e2"//
    .parameter("to""e4"//
    .build();

String[] veto = session.authorizeOperations(firstMove);
if (veto[0] == null)
{
  // Perform the operation...
}
else
{
  System.out.println("Operation not authorized: " + veto[0]);
}

Note that the actual enforcement of authorization is first performed on the client side for efficiency. For this purpose, the session consults a chain of local authorizers that can be registered with the session. Each authorizer can approve or veto the operation based on its own criteria. It can also modify the operation by adding or changing parameters. If none of the local authorizers vetoes the operation, it is sent to the server for final authorization. The server performs its own authorization checks, which may involve consulting server-side authorizers or access control lists. If the server vetoes the operation, the client is notified of the veto.

For more information about authorizable operations, refer to the AuthorizableOperation interface.

In an RCP application, you often want to hide or disable UI elements that trigger operations the user is not authorized to perform. And often, these operations are associated with a selected EObject in a viewer. In this case, you can use the ContextOperationAuthorization class to create and authorize multiple operations based on the current selection context. This class simplifies the process of checking authorization for operations related to specific model elements. Here is an example of how to use ContextOperationAuthorization:

AuthorizeContextOperation.java      
EObject context = (EObject)viewer.getStructuredSelection().getFirstElement();

ContextOperationAuthorization authorization = new ContextOperationAuthorization(context //
    , "my.chess.game.MoveChessman" //
    , "my.chess.game.GiveUp");

if (authorization.isGranted("my.chess.game.GiveUp"))
{
  System.out.println("User is authorized to give up.");
}
else
{
  String veto = authorization.getVeto("my.chess.game.GiveUp");
  System.out.println("User is not authorized to give up: " + veto);
}

4  Client and Server Entities

A CDO repository can make arbitrary information available to client sessions in the form of entities. Entities have a name and are contained in a namespace. The namespace and name of an entity together form its unique identifier. The associated information is represented by key-value pairs, where both keys and values are strings. The information is usually static and does not change over the lifetime of a session.

Entities are read-only and cannot be modified by client sessions. They are typically used to convey configuration settings, metadata, or other information that clients may need to interact with the repository effectively. For example, a repository might provide entities that describe supported features, data formats, or integration points with other systems. A repository can store and manage any number of entities.

Client entities are a special subset of those entities. They are loaded when a session is opened and remain available for the lifetime of the session. The repository operator can specify which entities are client entities and what information they contain.

Client sessions can access client entities using the clientEntities() method. Calling this method returns a map of all client entities available in the connected repository. You can look up a specific entity by its unique identifier using the map's get() method. For example:

AccessClientEntities.java      
Entity entity = session.clientEntities().get("namespace" + Entity.NAME_SEPARATOR + "name");
if (entity != null)
{
  String value = entity.property("key");
  System.out.println("Property value: " + value);
}
else
{
  // Handle the case where the entity is not found...
}

Non-client entities are not automatically loaded when a session is opened. Instead, they can be accessed using the requestEntities(String namespace, String... names) method. This method allows you to specify a namespace and one or more entity names to retrieve. The method returns a map of the requested entities that exist in the repository. Note that the map keys are the entity names, not their unique identifiers. If an entity with the specified name does not exist in the given namespace, it is simply not included in the returned map. Here is an example of how to request and access specific server entities:

AccessServerEntities.java      
Map<String, Entity> entities = session.requestEntities("namespace""name1""name2");

Entity entity1 = entities.get("name1");
if (entity1 != null)
{
  System.out.println("Entity 1: " + entity1.property("my-key"));
}

Entity entity2 = entities.get("name2");
if (entity2 != null)
{
  System.out.println("Entity 2: " + entity2.property("my-key"));
}

For more information about entities, refer to the Entity interface.

5  Session Facilities

Sessions provide access to several facilities that manage branches, packages, revisions, and more. Each facility offers specialized APIs for advanced repository operations.

5.1  Repository Info

Access metadata and status information about the connected repository, including version, capabilities, and health. The repository info is available as soon as the session is opened.

The CDORepositoryInfo interface provides access to repository metadata and status. It extends CDOCommonRepository, so it inherits all of its methods. Key methods include:

MethodReturn TypeDescription
getSession()CDOSessionReturns the session.
getName()StringReturns the repository name.
getUUID()StringReturns the repository UUID.
getType()TypeReturns the repository type (e.g., MASTER, BACKUP, CLONE).
getState()StateReturns the current repository state (e.g., ONLINE, OFFLINE, SYNCING).
getCreationTime()longReturns the creation time.
getStoreType()StringReturns the store type (e.g., "db").
getObjectIDTypes()Set<CDOID.ObjectType>Returns supported object ID types.
getIDGenerationLocation()IDGenerationLocationReturns where object IDs are generated.
getLobDigestAlgorithm()StringReturns the LOB digest algorithm.
getCommitInfoStorage()CommitInfoStorageReturns the commit info storage type.
getRootResourceID()CDOIDReturns the root resource ID.
isAuthenticating()booleanReturns whether authentication is required.
isSupportingLoginPeeks()booleanReturns whether login peeking is supported.
isSupportingAudits()booleanReturns whether audit views are supported.
isSupportingBranches()booleanReturns whether branching is supported.
isSupportingUnits()booleanReturns whether units are supported.
isSerializingCommits()booleanReturns whether commits are serialized.
isEnsuringReferentialIntegrity()booleanReturns whether referential integrity is enforced.
isAuthorizingOperations()booleanReturns whether operations are authorized.
waitWhileInitial(IProgressMonitor)booleanWaits while the repository is initializing.
getTimeStamp()longReturns the current repository time.
getTimeStamp(boolean)longReturns the current repository time, optionally refreshing it from the server.
IAdaptable.getAdapter(Class<T>)<T> TReturns an associated adapter object.
These methods allow you to query all relevant metadata and capabilities of the repository. In addition a repository can make arbitrary information available to client sessions in the form of entities. For more information about entities, refer to Client and Server Entities.

The CDORepositoryInfo (via CDOCommonRepository) can fire two important events:

To receive these events, register an event listener with the session of the repository info object. This allows your application to react to changes in the repository's type or state, such as updating the UI or triggering fail-over logic:

AddRepositoryInfoListener.java      
session.addListener(event -> {
  if (event instanceof TypeChangedEvent)
  {
    // Handle repository type change.
    handleTypeChanged(((TypeChangedEvent)event).getNewType());
  }
  else if (event instanceof StateChangedEvent)
  {
    // Handle repository state change.
    handleStateChanged(((StateChangedEvent)event).getNewState());
  }
});

5.2  Package Registry

The package registry manages EMF packages known to the session. CDO's package registry is a sub-type of EMF's package registry. It adds support for dynamic package loading from the repository and ensures that packages are available when needed. To achieve this, the package registry can fetch packages from the repository on demand. This is particularly useful when working with models that reference packages not yet registered in the client.

The CDO package registry is automatically available in every session and can be accessed using the getPackageRegistry() method. You can use the package registry to register new packages, look up existing packages, and query cached sub-type information about all registered EClasses.

Please note that EMF packages may contain nested packages and that it is not possible to transfer a package between clients and servers without also transferring all its nested packages. Therefore, CDO introduces the concept of package units. A package unit represents a tree of related packages that can be transferred as a single unit. For each package in the tree, the package unit contains a CDOPackageInfo object that provides metadata about the package, such as its name and namespace URI. The package info is also able to resolve the actual package when needed. Once a package is resolved, it is registered in the package registry and the package info is added to the eAdapters() of the package for easy lookup.

For more information about package units, refer to CDOPackageRegistry, CDOPackageUnit, and CDOPackageInfo.

5.3  Revision Manager

The revision manager loads, caches, and provides object revisions. It is automatically available in every session and can be accessed using the getRevisionManager() method. The revision manager supports efficient retrieval of revisions by their ID and either their branch point or version. It also supports bulk loading of revisions and pre-fetching of related revisions to optimize performance.

Note that the explicit use of revision management is usually not necessary when working with views and transactions. Views and transactions automatically manage revisions for the objects they contain. But the revision manager is inevitable when you need to access revisions directly, for example when implementing version control tools, custom synchronization, or replication logic.

The combination of ID, branch, and version uniquely identifies a revision within the repository. Hence the getRevisionByVersion() method can be used to retrieve a specific revision:

GetRevisionByVersion.java      
CDOID rootResourceID = session.getRepositoryInfo().getRootResourceID();
CDOBranch mainBranch = session.getBranchManager().getMainBranch();
CDOBranchVersion firstVersion = mainBranch.getVersion(CDOBranchVersion.FIRST_VERSION);
boolean loadOnDemand = true// Set to false to only consider locally cached revisions.

CDORevisionManager revisionManager = session.getRevisionManager();
CDORevision firstRevision = revisionManager.getRevisionByVersion(rootResourceID, firstVersion, CDORevision.UNCHUNKED, loadOnDemand);
System.out.println("First revision found: " + firstRevision);

In addition, the revision manager provides methods to retrieve revisions by their branch point. That is, given an ID and a branch point (branch and time), you can retrieve the revision that was current in the specified branch at the specified time. This is useful for accessing historical states of model objects without needing to know their exact version numbers and without opening an audit view. Here is an example:

GetRevision.java      
CDOID rootResourceID = session.getRepositoryInfo().getRootResourceID();
CDOBranch mainBranch = session.getBranchManager().getMainBranch();
CDOBranchPoint tenSecondsAgo = mainBranch.getPoint(System.currentTimeMillis() - 10000); // 10 seconds ago
boolean loadOnDemand = true// Set to false to only consider locally cached revisions.

CDORevisionManager revisionManager = session.getRevisionManager();
CDORevision oldRevision = revisionManager.getRevision(rootResourceID, tenSecondsAgo, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, loadOnDemand);
System.out.println("Old revision found: " + oldRevision);

The revision manager also supports bulk loading of revisions using the getRevisions() method. Here is an example:

GetMultipleRevisions.java      
CDOList list = rootResource.data().getListOrNull(EresourcePackage.Literals.CDO_RESOURCE__CONTENTS);
if (list != null)
{
  List<CDOID> ids = list.stream() //
      .filter(CDOID.class::isInstance) //
      .map(CDOID.class::cast) //
      .collect(Collectors.toList()); // Same as CDOIDUtil.getCDOIDs(list);

  CDORevisionManager revisionManager = session.getRevisionManager();
  CDOBranchPoint head = session.getBranchManager().getMainBranch().getHead();

  // Collect available revisions from the local cache and load missing ones from the repository in one batch.
  List<CDORevision> revisions = revisionManager.getRevisions(ids, head, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, true);
  System.out.println("Revisions found: " + revisions);
}

The above methods assume that you know the IDs of the revisions you want to retrieve. But sometimes you may want to find revisions based on their type, branch, and time. The revision manager provides the handleRevisions() method for this purpose. Here is an example:

HandleRevisions.java      
// Find all revisions of type CDOResourceFolder. Use null to find revisions of any type.
EClass eClass = EresourcePackage.Literals.CDO_RESOURCE_FOLDER;

CDOBranch mainBranch = session.getBranchManager().getMainBranch();
boolean exactBranch = true// Use false to also consider sub-branches.

long timeStamp = CDOBranchPoint.UNSPECIFIED_DATE; // Find latest revisions.
boolean exactTime = false// Use true to only consider revisions with the exact timestamp.

session.getRevisionManager().handleRevisions(eClass, mainBranch, exactBranch, timeStamp, exactTime, //
    revision -> {
      System.out.println("Resource Folder found: " + revision);
      return exactBranch; // Return true to continue processing more revisions.
    });

Sometimes you may want to notified when revisions are loaded into the local cache. The following example shows how to register a listener for this purpose:

ListenToRevisionLoading.java      
// Keep strong references to the loaded revisions to prevent them from being garbage collected.
Set<CDORevision> strongReferences = new HashSet<>();

session.getRevisionManager().addListener(event -> {
  if (event instanceof CDORevisionsLoadedEvent)
  {
    CDORevisionsLoadedEvent e = (CDORevisionsLoadedEvent)event;

    // Add all revisions that were requested to the strong reference set.
    strongReferences.addAll(e.getPrimaryLoadedRevisions());

    // Add additional revisions that were loaded because of prefetching.
    strongReferences.addAll(e.getAdditionalLoadedRevisions());
  }
});

For more information about revision management, refer to the CDORevisionManager interface.

5.3.1  Working with Revisions

A revision is an immutable object that represents a specific state of a model object at a given period in time. A new revision is always created and stored when a new model object is created or an existing model object is modified. Revisions are never modified after they are created. Instead, new revisions are created to represent new states of model objects. This immutability ensures that historical states of model objects are preserved and can be accessed at any time (if the repository mode is at least AUDITING).

Revisions are identified by:

  1. their ID, which uniquely refers to the model object they represent,
  2. their branch, which specifies the branch in which the revision was created, and
  3. their version, which indicates the sequence of the revision within its branch.

The CDORevision interface provides methods to access technical details about a revision, such as its EClass, ID, branch, version, and timestamps. The actual model data of a revision, including attribute values and references, is accessed through the CDORevisionData interface, which can be obtained by calling the CDORevision.data() method. Here is an example of how to access revision data:

AccessRevisionData.java      
EClass eClass = revision.getEClass();
CDORevisionData data = revision.data();

for (EStructuralFeature feature : eClass.getEAllStructuralFeatures())
{
  Object value = data.getValue(feature);
  System.out.println("Feature: " + feature.getName() + " = " + value);
}

There are other methods available on the CDORevisionData interface, especially for working with multi-valued features, such as size(), isEmpty, contains(), indexOf(), and so on.

In addition to accessing revision data, you can also compare two revisions to determine the differences between them. The compare() method computes the delta between two revisions and returns a CDORevisionDelta object that describes the changes. Here is an example of how to compare two revisions:

CompareRevisions.java      
// Compute the delta between two revisions, relative to revision1.
CDORevisionDelta changes = revision2.compare(revision1);

// Print the changes between the two revisions.
changes.getFeatureDeltas().forEach(featureDelta -> {
  EStructuralFeature feature = featureDelta.getFeature();
  System.out.println("Feature changed: " + feature.getName());

  if (featureDelta instanceof CDOSetFeatureDelta)
  {
    CDOSetFeatureDelta setFeatureDelta = (CDOSetFeatureDelta)featureDelta;
    System.out.println("  Old value: " + setFeatureDelta.getOldValue());
    System.out.println("  New value: " + setFeatureDelta.getValue());
  }
  else if (featureDelta instanceof CDOListFeatureDelta)
  {
    CDOListFeatureDelta listFeatureDelta = (CDOListFeatureDelta)featureDelta;

    listFeatureDelta.getListChanges().forEach(listChange -> {
      if (listChange instanceof CDOAddFeatureDelta)
      {
        CDOAddFeatureDelta addChange = (CDOAddFeatureDelta)listChange;
        System.out.println("  Added value: " + addChange.getValue() + " at index " + addChange.getIndex());
      }
      else if (listChange instanceof CDORemoveFeatureDelta)
      {
        CDORemoveFeatureDelta removeChange = (CDORemoveFeatureDelta)listChange;
        System.out.println("  Removed value: " + removeChange.getValue() + " from index " + removeChange.getIndex());
      }
      else if (listChange instanceof CDOSetFeatureDelta)
      {
        CDOSetFeatureDelta setChange = (CDOSetFeatureDelta)listChange;
        System.out.println("  Changed value at index " + setChange.getIndex() + " from " + setChange.getOldValue() + " to " + setChange.getValue());
      }
      else if (listChange instanceof CDOMoveFeatureDelta)
      {
        CDOMoveFeatureDelta moveChange = (CDOMoveFeatureDelta)listChange;
        System.out.println(
            "  Moved value: " + moveChange.getValue() + " from index " + moveChange.getOldPosition() + " to index " + moveChange.getNewPosition());
      }
    });
  }
  else if (featureDelta instanceof CDOUnsetFeatureDelta)
  {
    System.out.println("  Feature was unset.");
  }
  else if (featureDelta instanceof CDOClearFeatureDelta)
  {
    System.out.println("  Feature was cleared.");
  }
  else if (featureDelta instanceof CDOContainerFeatureDelta)
  {
    System.out.println("  Container changed:");
  }
});

// Create a copy of revision1 to demonstrate applying the changes.
CDORevision revision3 = revision1.copy();

// Apply the changes to revision3 (for demonstration purposes only).
// Note that revisions are immutable in most cases. This is just to show how to apply changes
// to a revision, for example, when implementing a custom merge or patching algorithm.
// In a real application, you would typically not do this.
changes.applyTo(revision3);

// Verify that applying the changes results in a revision that is structurally equal to revision2.
CDORevisionDelta empty = revision3.compare(revision2);
Assert.isTrue(empty.isEmpty(), "Revisions should be structurally equal after applying changes.");

For more information about revisions, refer to the CDORevision and CDORevisionData interfaces.

5.3.2  Pre-fetching Revisions

In addition to loading revisions on demand, the revision manager supports pre-fetching of related revisions. When a revision is loaded, the revision manager can automatically load additional revisions that are related to the requested revision. This is particularly useful when you know that you will need related revisions soon after loading a specific revision. The pre-fetched revisions are loaded from the repository in the same batch as the requested revision, which can significantly reduce the number of round-trips to the server and improve performance. They remain in the local cache and can be accessed later without additional network calls.

There are different mechanisms available for pre-fetching related revisions:

5.4  Branch Manager

Manage branches and tags within the repository. Branches enable parallel development and versioning. Tags provide meaningful names for specific points in a branch's history.

The branch manager is automatically available in every session and can be accessed using the getBranchManager() method. The branches and tags managed by the branch manager are specific to the connected repository and are not shared between different repositories or even between different sessions within the same repository. Not all branches and tags may be immediately available in the branch manager. Branches and tags are loaded on demand when they are accessed for the first time.

Note that only the main branch is guaranteed to be available for all repository modes. Sub-branches are only supported in branching. Tags are only supported in auditing.

For more information about branch management, refer to the CDOBranchManager interface.

5.4.1  Working with Branches

Branches are organized in a tree structure, starting from the main branch. In addition to their immutable technical IDs, branches have user-friendly names, which can change over time. Due to the tree structure of branches, a branch name is only unique within its parent branch. To uniquely identify a branch, you need to specify its full path, which includes the names of all its ancestor branches.

Here is an example of how to access branches using the branch manager:

GetBranch.java      
CDOBranchManager branchManager = session.getBranchManager();

// Get the main branch using its constant ID.
CDOBranch mainBranch = branchManager.getBranch(CDOBranch.MAIN_BRANCH_ID);
Assert.isTrue(mainBranch == branchManager.getMainBranch());

// Get a branch from the manager with the full path.
CDOBranch featureBranch = branchManager.getBranch("MAIN/features/awesome-feature");
System.out.println("Feature branch: " + featureBranch);

// Alternatively, navigate the branch tree starting from the main branch.
featureBranch = mainBranch.getBranch("features").getBranch("awesome-feature");

// List all direct sub-branches of a branch.
for (CDOBranch subBranch : mainBranch.getBranch("features").getBranches())
{
  System.out.println("Sub-branch of 'features': " + subBranch);
}

Here is an example of how to retrieve all sub-branches of a given branch:

GetSubBranches.java      
// Get all direct and indirect sub-branches of the branch with ID 815.
LinkedHashSet<CDOBranch> subBranches = branchManager.getBranches(815);
subBranches.forEach(System.out::println);

Here is an example of how to create new branches:

CreateBranch.java      
CDOBranch mainBranch = branchManager.getMainBranch();
CDOBranch features = mainBranch.createBranch("features");
CDOBranch awesomeFeature = features.createBranch("awesome-feature");

// Outputs "MAIN/features/awesome-feature".
System.out.println(awesomeFeature.getPathName());

Here is an example of how to listen for branch changes:

ListenToBranchChanges.java      
branchManager.addListener(new CDOBranchManager.EventAdapter()
{
  @Override
  protected void onBranchCreated(CDOBranch branch)
  {
    // Handle branch creation.
  }

  @Override
  protected void onBranchRenamed(CDOBranch branch)
  {
    // Handle branch renaming.
  }

  @Override
  protected void onBranchesDeleted(CDOBranch rootBranch, int[] branchIDs)
  {
    // Handle branch deletion.
  }
});

For more information about branches, refer to the CDOBranch interface.

5.4.2  Working with Tags

Tags are used to mark specific points in a branch's history with meaningful names. Unlike branches, tags do not have a hierarchical structure. A tag name is unique within the entire repository. Tags refer to a specific branch point, which consists of a branch and a timestamp. Tags are often used to mark releases, milestones, or other significant events in the development process. Both the name and the target branch point of a tag can change over time, also referred to as "renaming tags" and "moving tags".

Here is an example of how to access tags using the branch manager:

GetTag.java      
CDOBranchManager branchManager = session.getBranchManager();
CDOBranchTag relase_1_0 = branchManager.getTag("v1.0");

CDOView auditView = session.openView(relase_1_0);
// Use the audit view which reflects the state of the repository at the time of release v1.0.

auditView.close();

Here is an example of how to retrieve all tags associated with a specific branch:

GetTagsOfBranch.java      
CDOTagList tagList = branchManager.getTagList();
CDOBranchTag[] mainBranchTags = tagList.getTags(branchManager.getMainBranch());

for (CDOBranchTag tag : mainBranchTags)
{
  System.out.println("Tag on main branch: " + tag.getName() + " -> " + tag.getTimeStamp());
}

Here is an example of how to create new tags:

CreateTag.java      
CDOBranch mainBranch = branchManager.getMainBranch();
CDOBranchTag tag = branchManager.createTag("v2.0", mainBranch.getHead());
System.out.println("Created tag: " + tag);

Whether a tag created, renamed, moved, or deleted in your session or in another session, the tag list of the branch manager can notify you about these changes. Here is an example of how to listen for tag changes:

ListenToTagChanges.java      
branchManager.getTagList().addListener(new ContainerEventAdapter<CDOBranchTag>()
{
  @Override
  protected void onAdded(IContainer<CDOBranchTag> tagList, CDOBranchTag tag)
  {
    // Handle tag creation.
  }

  @Override
  protected void onRemoved(IContainer<CDOBranchTag> tagList, CDOBranchTag tag)
  {
    // Handle tag deletion.
  }

  @Override
  protected void notifyOtherEvent(IEvent event)
  {
    if (event instanceof TagRenamedEvent)
    {
      // Handle tag renaming.
    }
    else if (event instanceof TagMovedEvent)
    {
      // Handle tag moving.
    }
  }
});

For more information about tags, refer to the CDOBranchTag and CDOTagList interfaces.

5.5  Fetch Rule Manager

Control how data is fetched from the repository. Learn to define fetch rules for efficient data loading and minimize network overhead.

You can use a session-wide CDOFetchRuleManager to define multiple fetch rules for pre-fetching target revisions of one or more references of the requested revisions. The fetch rules are sent to the server whenever revisions are loaded and evaluated there. The target revisions of the references that match a fetch rule are then pre-fetched and sent back to the client along with the requested revisions.

A fetch rule consists of:

Here is an example of how to create a custom fetch rule manager that pre-fetches the contents of a specific resource whenever that resource is loaded:

UseCustomFetchRuleManager.java      
CDOFetchRuleManager fetchRuleManager = new CDOFetchRuleManager()
{
  private final CDOID importantResource = CDOIDUtil.createLong(42);

  @Override
  public CDOID getContext()
  {
    return importantResource;
  }

  @Override
  public List<CDOFetchRule> getFetchRules(Collection<CDOID> ids)
  {
    if (ids.contains(importantResource))
    {
      CDOFetchRule fetchRule = new CDOFetchRule(EresourcePackage.Literals.CDO_RESOURCE);
      fetchRule.addFeature(EresourcePackage.Literals.CDO_RESOURCE__CONTENTS);
      return Collections.singletonList(fetchRule);
    }

    return null;
  }
};

CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setConnector(connector);
configuration.setRepositoryName(repositoryName);
configuration.setFetchRuleManager(fetchRuleManager);

CDOSession session = configuration.openNet4jSession();
// Use the session with the custom fetch rule manager...

session.close();

As fetch rules specify what features to pre-fetch, it often makes sense to monitor what features are actually used in your application. This can be achieved by using a CDOFeatureAnalyzer that is attached to the view options of your views and transactions.

CDO provides two standard implementations of a combined fetch rule manager and feature analyzer:

Here is an example of how to use the UI feature analyzer in a transaction:

UseUIFeatureAnalyzer.java      
CDONet4jSessionConfiguration configuration = CDONet4jUtil.createNet4jSessionConfiguration();
configuration.setConnector(connector);
configuration.setRepositoryName(repositoryName);
configuration.setFetchRuleManager(CDOUtil.createThreadLocalFetchRuleManager());

CDOSession session = configuration.openNet4jSession();
CDOTransaction transaction = session.openTransaction();

// Attach a UI feature analyzer to the transaction, which automatically creates and updates fetch rules
// based on the features that are accessed through the transaction.
// The fetch rules are sent to the server whenever revisions are loaded in the context of the transaction.
// The parameter (400L) specifies the maximum time between two operations on the same context
// for them to be considered related and thus influence the same fetch rule.
transaction.options().setFeatureAnalyzer(CDOUtil.createUIFeatureAnalyzer(400L));

// Use the transaction with the UI feature analyzer...

session.close(); // Close the session and the transaction.

See also Pre-fetching Revisions and Units. For more information about fetch rules, refer to the CDOFetchRule interface.

5.6  Commit Info Manager

Access commit history and metadata. This facility allows you to query, filter, and analyze commit information for auditing and tracking changes.

A CDOCommitInfo object represents metadata about a single commit operation in the repository. It includes information such as the commit's unique identifier (the timestamp of the commit), the user who made the commit, the branch in which the commit was made, an optional commit message, and an optional CDOCommitData object, which provides detailed information about the changes made in the commit.

Whether a repository stores commit information depends on its mode. Commit information is only available in auditing mode or higher. In addition, the repository must be configured to store commit information, i.e., its IRepository.Props.COMMIT_INFO_STORAGE property must not be set to CommitInfoStorage.YES or CommitInfoStorage.WITH_MERGE_SOURCE.

Whether a repository provides commit information with complete change set data to a client depends on the passive update mode of the client's session, see Passive Updates and Refreshing.

The commit info manager is automatically available in every session and can be accessed using the getCommitInfoManager() method. The commit info manager supports efficient retrieval of commit information by timestamp, branch, and other criteria.

Here is an example of how to retrieve commit information using the commit info manager:

GetCommitInfo.java      
// Create and commit a new resource in session1 to generate some commit info.
CDOTransaction transaction = session1.openTransaction();
transaction.createResource("/my/resource");
CDOCommitInfo commit = transaction.commit();

// The commit ID is the timestamp of the commit.
long commitID = commit.getTimeStamp();
System.out.println("Commit ID: " + commitID);

// Retrieve the commit info in session2 using the commit info manager.
CDOCommitInfo commitInfo = session2.getCommitInfoManager().getCommitInfo(commitID);

// Print commit info details.
System.out.println("Timestamp: " + commitInfo.getTimeStamp());
System.out.println("Previous TimeStamp: " + commitInfo.getPreviousTimeStamp());
System.out.println("Branch: " + commitInfo.getBranch());
System.out.println("User ID: " + commitInfo.getUserID());
System.out.println("Comment: " + commitInfo.getComment());
System.out.println("Affected IDs: " + commitInfo.getAffectedIDs());
System.out.println("New Package Units: " + commitInfo.getNewPackageUnits());
System.out.println("New Objects: " + commitInfo.getNewObjects());
System.out.println("Changed Objects: " + commitInfo.getChangedObjects());
System.out.println("Detached Objects: " + commitInfo.getDetachedObjects());
System.out.println("Merge Source: " + commitInfo.getMergeSource());

Here is an example of how to retrieve the last commit info of a specific branch:

GetLastCommitInfo.java      
CDOBranch mainBranch = session.getBranchManager().getMainBranch();

// Retrieve the last commit info of the main branch.
CDOCommitInfo commitInfo = session.getCommitInfoManager().getLastOfBranch(mainBranch);
System.out.println("Commit Info: " + commitInfo);

There are many more methods available on the commit info manager for querying commit history, such as retrieving commits within a specific time range, filtering commits by user or message, and accessing the detailed change data of commits.

For more information about commit info management, refer to the CDOCommitInfoManager interface.

5.7  User Info Manager

If a repository is configured to authenticate users, the authenticated session provides access to just the user ID of the authenticated user. The user info manager allows you to retrieve additional information about users, such as their full name, email address, and other custom attributes. This information can be useful for displaying user-friendly names in the UI, sending notifications, and performing user-specific operations. Technically, the user infos are made available as Entitys that the repository creates and manages according to its user management configuration. See also Client and Server Entities.

User infos that are loaded from the repository are cached in the user info manager to optimize performance and reduce network overhead.

The user info manager is automatically available in every session and can be accessed using the getUserInfoManager() method.

Here is an example of how to retrieve user information using the user info manager:

GetMultipleUserInfos.java      
Map<String, Entity> userInfos = //
    session.getUserInfoManager().getUserInfos("john.doe""jane.smith");

userInfos.forEach((userID, userInfo) //
-> System.out.println(userID + ", Groups: " + userInfo.property("groups")));

For more information about user info management, refer to the CDOUserInfoManager interface.

5.8  Remote Session Manager

Monitor and interact with other sessions connected to the repository. Useful for collaboration and administrative tasks.

In collaborative environments, it is often necessary to monitor other sessions connected to the same repository, exchange messages, and coordinate activities. The Remote Session Manager provides APIs to observe, interact with, and manage remote sessions, enabling features such as notifications, messaging, and presence.

The CDORemoteSession interface represents a session connected to the repository, other than the local session. It provides methods to query session properties, send messages, and subscribe to events. Events such as session connection, disconnection, and message reception are emitted to allow applications to react to changes in remote session state.

The CDORemoteSessionManager manages all remote sessions for a repository. It allows you to enumerate active sessions, subscribe to session events, and send messages to one or more sessions. Events include session addition, removal, and message delivery. To access the remote session manager, use the getRemoteSessionManager() method.

Here is an example of how to use the remote session manager to enumerate remote sessions:

EnumeratingRemoteSessions.java      
for (CDORemoteSession remoteSession : session.getRemoteSessionManager().getRemoteSessions())
{
  System.out.println("Remote session ID: " + remoteSession.getSessionID());
  System.out.println("Remote session user: " + remoteSession.getUserID());
  System.out.println("Remote session subscription: " + remoteSession.isSubscribed());
}

To opt-in for remote session management, the client's remote session manager must be enabled by subscribing to remote session information. This subscription can be done automatically when you add a listener via addListener() to the remote session manager. Automatic subscription occurs when the first listener is added and is canceled when the last listener is removed. Subscription can also be forced, by calling setForceSubscription(true). Without subscription, the remote session manager will not receive updates about remote sessions.

Here is an example of how to subscribe to remote session events using the remote session manager:

Subscribing.java      
IListener listener = new CDORemoteSessionManager.EventAdapter()
{
  @Override
  protected void onLocalSubscriptionChanged(boolean subscribed)
  {
    System.out.println("Local session subscribed: " true);
  }

  @Override
  protected void onOpened(CDORemoteSession remoteSession)
  {
    System.out.println("Remote session opened: " + remoteSession.getUserID());
  }

  @Override
  protected void onClosed(CDORemoteSession remoteSession)
  {
    System.out.println("Remote session closed: " + remoteSession.getUserID());
  }

  @Override
  protected void onSubscribed(CDORemoteSession remoteSession)
  {
    System.out.println("Remote session able to receive messages: " + remoteSession);
  }

  @Override
  protected void onUnsubscribed(CDORemoteSession remoteSession)
  {
    System.out.println("Remote session unable to receive messages: " + remoteSession);
  }

  @Override
  protected void onMessageReceived(CDORemoteSession remoteSession, CDORemoteSessionMessage message)
  {
    System.out.println("Message from " + remoteSession.getUserID() + ": " + message);
  }
};

session.getRemoteSessionManager().addListener(listener);
// The listener addition causes the remote session manager to subscribe to remote session information.
// Now it will start receiving updates about remote sessions.

// To stop receiving updates, remove the listener.
// The listener removal causes the remote session manager to unsubscribe from remote session information:
session.getRemoteSessionManager().removeListener(listener);

// Alternatively, you can force subscription regardless of listener presence:
session.getRemoteSessionManager().setForceSubscription(true);

5.8.1  Exchanging Messages With Other Sessions

Exchanging messages between sessions enables real-time collaboration, notifications, and coordination among users. This is useful for chat, alerts, or workflow triggers.

The CDORemoteSessionMessage interface represents a message sent between sessions. It contains a type indicating the message's purpose, a priority, and payload data with the actual content in form of a byte[]. To interpret the payload data, you need to agree on a serialization format with the message sender and receiver. The type of the message can be used to differentiate between different kinds of messages and to determine how to process the payload data. CDO does not impose any restrictions on the message content. You can use any serialization format you like, such as JSON, XML, Protocol Buffers, or custom binary formats.

You can send messages to a specific remote session by using CDORemoteSession.sendMessage(CDORemoteSessionMessage). A message sent to a remote session is delivered only if the target session is subscribed to receive messages. You can also broadcast messages to all subscribed remote sessions using CDORemoteSessionManager.sendMessage(CDORemoteSessionMessage, CDORemoteSession...), or CDORemoteTopic.sendMessage(CDORemoteSessionMessage) (see Collaborating on Shared Topics). These methods deliver the message to the target session(s) or topic subscribers.

Here is an example of how to send messages to other sessions using the remote session manager:

SendRemoteMessage.java      
CDORemoteSessionManager remoteSessionManager = session.getRemoteSessionManager();

// Create a message with type "chat", priority HIGH, and payload "Hello, World!".
String messageType = "com.foo.chat";
Priority messagePriority = Priority.HIGH;
byte[] messageData = "Hello, World!".getBytes(StandardCharsets.UTF_8);
CDORemoteSessionMessage message = new CDORemoteSessionMessage(messageType, messagePriority, messageData);

// Determine the target remote sessions (e.g., all sessions of user "john.doe").
CDORemoteSession[] johnsSessions = remoteSessionManager.getRemoteSessions("john.doe");

// Send the message to each of John's sessions.
for (CDORemoteSession remoteSession : johnsSessions)
{
  remoteSession.sendMessage(message);
}

// Alternatively, broadcast the message in a single network request to each of John's sessions.
remoteSessionManager.sendMessage(message, johnsSessions);

// Alternatively, broadcast the message to all subscribed remote sessions.
remoteSessionManager.sendMessage(message);

To receive messages, register a listener for CDORemoteSessionEvent.MessageReceived events. The listener will be notified whenever a message is received from another session or topic.

Here is an example of how to receive messages from other sessions using the remote session manager:

ReceiveRemoteMessage.java      
session.getRemoteSessionManager().addListener(new CDORemoteSessionManager.EventAdapter()
{
  @Override
  protected void onMessageReceived(CDORemoteSession remoteSession, CDORemoteSessionMessage message)
  {
    if ("com.foo.chat".equals(message.getType())) // Check the message type.
    {
      String text = new String(message.getData(), StandardCharsets.UTF_8); // Decode the payload data.
      System.out.println("Chat message from " + remoteSession.getUserID() + ": " + text);
    }
  }
});

5.8.2  Collaborating on Shared Topics

Shared topics allow users to collaborate by grouping sessions around common subjects, such as projects, documents, or activities. Topics provide a way to broadcast messages, track presence, and coordinate work.

The CDORemoteTopic interface represents a collaboration topic. It emits events for topic subscription, message delivery, and presence changes. You can subscribe to a topic using CDORemoteSessionManager.subscribeTopic(String). This adds your session to the topic and enables you to receive messages and presence updates. If the topic does not exist, it is created automatically.

The presence of remote sessions in a topic can be queried using CDORemoteTopic.getRemoteSessions(). As the CDORemoteTopic is an IContainer<CDORemoteSession>, you can also listen for presence changes. Changes in presence are notified via IContainer events.

To collaborate, send messages to a topic using CDORemoteTopic.sendMessage(CDORemoteSessionMessage). These messages are broadcast to all sessions that are subscribed to the topic. Subscribers receive messages via CDORemoteTopicEvent.MessageReceived events.

To leave a topic, call CDORemoteTopic.unsubscribe(). This removes the session from the topic and stops further event notifications. Other sessions in the topic are notified of your departure.

Here is an example of how to collaborate on a shared topic using the remote session manager:

CollaborateOnTopic.java      
CDORemoteSessionManager remoteSessionManager = session.getRemoteSessionManager();

// Subscribe to the "com.foo.projectX" topic; create it if it does not exist yet.
CDORemoteTopic projectX = remoteSessionManager.subscribeTopic("com.foo.projectX");

// List current topic members.
for (CDORemoteSession topicMember : projectX.getRemoteSessions())
{
  System.out.println("Topic member: " + topicMember.getUserID());
}

// Send a message to all topic members.
projectX.sendMessage(new CDORemoteSessionMessage( //
    "com.foo.chat", Priority.NORMAL, "Hello, Project X team!".getBytes(StandardCharsets.UTF_8)));

// Listen for topic events.
projectX.addListener(new ContainerEventAdapter<CDORemoteSession>()
{
  @Override
  protected void onAdded(IContainer<CDORemoteSession> topic, CDORemoteSession remoteSession)
  {
    System.out.println("Topic member joined: " + remoteSession.getUserID());
  }

  @Override
  protected void onRemoved(IContainer<CDORemoteSession> topic, CDORemoteSession remoteSession)
  {
    System.out.println("Topic member left: " + remoteSession.getUserID());
  }

  @Override
  protected void notifyOtherEvent(IEvent event)
  {
    if (event instanceof CDORemoteTopicEvent.MessageReceived)
    {
      CDORemoteTopicEvent.MessageReceived messageEvent = (CDORemoteTopicEvent.MessageReceived)event;

      CDORemoteSessionMessage message = messageEvent.getMessage();
      if ("com.foo.chat".equals(message.getType())) // Check the message type.
      {
        String text = new String(message.getData(), StandardCharsets.UTF_8); // Decode the payload data.
        System.out.println("Chat message from " + messageEvent.getRemoteSession().getUserID() + ": " + text);
      }
    }
  }
});

// Unsubscribe from the topic when done.
projectX.unsubscribe();

5.8.3  Integrating Topics With Your User Interface

Integrating topics into your UI allows users to see available topics, track presence, and view messages in real time. This enhances collaboration and awareness.

You can create a custom UI component to display topics, their members, and messages using the APIs provided by the CDORemoteSessionManager and CDORemoteTopic interfaces. This component can list all subscribed topics, show the presence of members in each topic, and display messages as they are received. Such a custom component can be tailored to fit the specific needs and design of your application.

Alternatively, you can use the built-in CDORemoteTopicsView provided by CDO. The CDORemoteTopicsView provides a user interface for displaying topics, session presence, and messages. It displays all topics that the local session is subscribed to, along with the members of each topic. It can be extended to show additional topic information, such as images, labels, descriptions, or custom metadata. This extension can be achieved by implementing a custom CDOTopicProvider, which defines how topics are represented in the view. The CDOTopicProvider interface allows you to specify the topic ID, label, description, and image for each topic, as well as how to create and manage topic listeners. The view uses the topic provider to display topics and their members.

To integrate an editor with CDORemoteTopicsView, implement a CDOTopicProvider and its Topic and Listener interfaces. For example, the built-in CDOEditor treats CDOResources as topics, subscribing to the relevant topic on activation and unsubscribing on deactivation. This ensures the editor's presence and activity are reflected in the topics view on all clients.

Here is an example of how to integrate topics with your user interface using a custom topic provider:

IntegrateTopicsWithUI.java      
// The CDORemoteTopicsView listens to the active workbench part and queries its topic provider (if any).
// When the active part changes, the view updates to show the topics provided by the new part.
// Here we define a custom editor that provides topics to the CDORemoteTopicsView by implementing the
// IEditorPart.getAdapter() method to return a CDOTopicProvider. You could also implement the
// CDOTopicProvider interface directly in your editor class.
class MyEditor extends EditorPart
{
  // Use a default topic provider that can hold multiple topics.
  // It also cares about topic listener registration and notification.
  private final DefaultTopicProvider topicProvider = new DefaultTopicProvider();

  @Override
  public void init(IEditorSite site, IEditorInput input) throws PartInitException
  {
    // Assume that the editor input provides the necessary topic information.
    if (input instanceof MyEditorInput)
    {
      MyEditorInput myInput = (MyEditorInput)input;

      // Use the session and the topic information from the editor input.
      CDOSession session = myInput.getSession();
      String id = myInput.getTopicID();
      Image image = myInput.getTopicImage();
      String text = myInput.getTopicText();
      String description = myInput.getTopicDescription();

      // Create a topic and add it to the topic provider.
      // Note that this topic is a "UI topic" that exists only in the context of this editor.
      // The actual CDORemoteTopic is created and managed by the CDORemoteTopicsView!
      Topic topic = new Topic(session, id, image, text, description);
      topicProvider.addTopic(topic);
    }
  }

  @Override
  public <T> T getAdapter(Class<T> type)
  {
    // The CDORemoteTopicsView queries this method to get the topic provider.
    // The view will then display the topics provided by this editor and listen for topic events.
    if (type == CDOTopicProvider.class)
    {
      return type.cast(topicProvider);
    }

    return null;
  }
}

6  Session Events

Sessions emit events for lifecycle changes, errors, and repository updates. Learn how to listen for and handle these events to build responsive applications.

The following events are fired from CDOSession:

Applications can listen for these events by registering listeners with the session. This enables responsive handling of repository changes, errors, and lifecycle events.

Example of listening for session events:

AddSessionListener.java      
session.addListener(event -> {
  if (event instanceof CDOSessionInvalidationEvent)
  {
    CDOSessionInvalidationEvent invalidation = (CDOSessionInvalidationEvent)event;
    System.out.println("Commit ID: " + invalidation.getTimeStamp());
    System.out.println("Committed on: " + invalidation.getBranch());
    System.out.println("Committed by: " + invalidation.getUserID());
    System.out.println("Changes: " + invalidation.getChangedObjects());
  }
});

7  Session Options

Configure session options to customize behavior such as passive updates, caching, and performance settings.

The options of a session are accessible via the options() method.

An option has getter and setter methods to retrieve and modify its value. When options are changed, the IOptionsContainer emits events to notify listeners of the changes. This allows applications to react dynamically to configuration changes.

Each option is documented in its own sub-chapter below.

7.1  Generated Package Emulation

Controls whether the session creates dynamic EMF packages for missing generated packages. This is useful when working with models that have not been generated yet. Can be enabled or disabled. By default, it is disabled.

API: CDOSession.Options.isGeneratedPackageEmulationEnabled(), CDOSession.Options.setGeneratedPackageEmulationEnabled(boolean)

7.2  Passive Update Enabled

Controls whether the session receives passive updates from the server. Can be enabled or disabled. By default, it is enabled.

API: CDOSession.Options.isPassiveUpdateEnabled(), CDOSession.Options.setPassiveUpdateEnabled(boolean)

7.3  Passive Update Mode

Specifies the mode for passive updates (INVALIDATIONS, CHANGES, ADDITIONS). The mode determines the type of updates the session receives from the server. By default, it is set to INVALIDATIONS. In this mode, the session receives invalidation notifications for changes made by other sessions. This means that when another session commits changes to the repository, the session is notified that certain objects have changed, but it does not receive the actual changes. Other modes include CHANGES, where the session receives detailed change information, and ADDITIONS, where the session receives notifications about changes and new objects being added. The choice of mode depends on the application's requirements for data freshness and performance.

API: CDOSession.Options.getPassiveUpdateMode(), CDOSession.Options.setPassiveUpdateMode(PassiveUpdateMode)

7.4  Lock Notification Enabled

Controls whether the session receives lock notifications. Can be enabled or disabled. By default, it is disabled.

API: CDOSession.Options.isLockNotificationEnabled(), CDOSession.Options.setLockNotificationEnabled(boolean)

7.5  Lock Notification Mode

Controls the mode for lock notifications OFF, IF_REQUIRED_BY_VIEWS, or ALWAYS. The mode determines whether and when the session receives lock notifications from the server. By default, it is set to IF_REQUIRED_BY_VIEWS, which means that the session receives lock notifications only if it has views that require lock information. Other modes include OFF, where the session does not receive any lock notifications, and ALWAYS, where the session receives all lock notifications regardless of its views. The choice of mode depends on the application's requirements for lock awareness and performance.

API: CDOSession.Options.getLockNotificationMode(), CDOSession.Options.setLockNotificationMode(LockNotificationMode)

7.6  Collection Loading Policy

Defines how large collections are loaded (e.g., all at once or in chunks). Collection loading can impact performance and memory usage. A collection loading policy helps balance performance and memory consumption when dealing with large collections.

You can create custom policies by calling createCollectionLoadingPolicy(int, int) with your desired initial chunk size and resolve chunk size. The initial chunk size defines how many elements are loaded when the collection is first accessed. The resolve chunk size defines how many additional elements are loaded when more elements in the collection are accessed. A smaller chunk size reduces memory consumption but may require more network round-trips. A larger chunk size improves performance but increases memory usage. The optimal chunk sizes depend on your application's access patterns and resource constraints.

API: CDOSession.Options.getCollectionLoadingPolicy(), CDOSession.Options.setCollectionLoadingPolicy(CDOCollectionLoadingPolicy)

7.7  Large Object Cache

Manages caching of large objects to optimize memory usage and performance. You can either implement your own cache by implementing the CDOLobStore interface and setting it via Options.setLobCache(CDOLobStore), or you can use the built-in CDOLobStoreImpl, which provides a simple file-based cache. The built-in cache can be configured to store large objects in a specific directory on the file system. By default, the large object cache is set to CDOLobStoreImpl.INSTANCE, which stores large objects in a temporary directory.

API: CDOSession.Options.getLobCache(), CDOSession.Options.setLobCache(CDOLobStore)

7.8  Permission Updater

Controls how to update permissions of revisions when passive updates are received or the InternalCDOSession.updatePermissions() method is called. By default, the permission updater is set to CDOPermissionUpdater3.SERVER, which updates permissions from the server.

API: CDOSession.Options.getPermissionUpdater(), CDOSession.Options.setPermissionUpdater(CDOPermissionUpdater3)

7.9  Delegable View Lock Enabled

Specifies whether the session supports delegable view locks. All view methods are protected from concurrent access by the view's view lock. By default, the view lock is the constant object returned by CDOView.getViewLock(). This lock is reentrant, but it is not delegable. This means that if a thread holds the view lock and calls a method that tries to acquire the same view lock again, it will succeed. However, if the thread that holds the view lock schedules a task to be executed asynchronously (e.g., using Display.syncExec(Runnable)) and waits for its completion, the task will not be able to acquire the view lock because it is executed in a different thread. This can lead to deadlocks.

To avoid such deadlocks, you can enable delegable view locks. They are based on fact that the thread that holds the view lock does block and no longer needs the lock while waiting for the asynchronous task to complete. Hence, it can delegate the lock to another thread.

When delegable view locks are enabled, the view locks become instances of DelegableReentrantLock, which uses DelegateDetectors that are contributed to IPluginContainer.INSTANCE in order to decide whether a thread is allowed to delegate its lock to another thread. The org.eclipse.net4j.util.ui plug-in contributes a org.eclipse.net4j.util.internal.ui.DisplayDelegateDetector that allows delegation to SWT's UI thread. This is particularly useful when a background thread that holds the view lock (such as a model change listener or EMF Adapter) needs to update the UI synchronously and the update code (which is executed in the UI thread) needs to access the view.

An alternative to globally enabling delegable view locks for all views in a session is to use CDOUtil.setNextViewLock(Lock) to set a specific delegable view lock for the next view that is created in the current thread.

API: CDOSession.Options.isDelegableViewLockEnabled(), CDOSession.Options.setDelegableViewLockEnabled(boolean)

7.10  Prefetch Send Max Revision Keys

Revision prefetching can improve performance by reducing the number of network round-trips required to load multiple revisions. On the other hand, prefetching can lead to some revisions being loaded unnecessarily, because the session already has them in its cache. To address this, the session tells the server which revisions it already has in its cache by sending their revision keys in the prefetch request. However, sending too many revision keys can increase network overhead and impact performance negatively. To balance these concerns, the prefetch send max revision keys option allows you to limit the number of revision keys that are sent in a single prefetch request. The default value is 100. You can also change this default value by setting the system property PREFETCH_SEND_MAX_REVISION_KEYS.

API: CDOSession.Options.getPrefetchSendMaxRevisionKeys(), CDOSession.Options.setPrefetchSendMaxRevisionKeys(int)

8  Session Properties

Access and modify session properties to store custom metadata and configuration values.

The CDOSession.properties() map allows you to store and retrieve custom properties associated with the session. This can be useful for storing session-specific metadata, configuration values, or other information that you want to associate with the session. The properties map is a simple key-value store where the keys are strings and the values are arbitrary objects. The map is thread-safe, so you can safely access and modify it from multiple threads. It also supports change listeners, so you can be notified when properties are added, removed, or changed.

The properties are not persisted and are lost when the session is closed.

9  Session Registry

The CDOSessionRegistry keeps track of all active sessions in the application. It allows you to enumerate, find, and manage sessions. You can also register listeners to be notified of session lifecycle events, such as when sessions are opened or closed.

The session registry is a singleton and can be accessed via CDOSessionRegistry.INSTANCE. It provides methods to get all active sessions and find sessions by their global ID.

Global IDs are assigned to each session when it is opened. They are unique within the application and can be used to identify and reference sessions. The session registry maintains a mapping between global IDs and sessions, allowing you to look up sessions by their global ID. The global ID of a session, which is assigned by the session registry, must not be confused with the session's repository-specific ID, which is assigned by the repository and may not be unique across different repositories.

Here is an example of how to use the session registry to close all active sessions:

CloseAllSessions.java      
for (CDOSession session : CDOSessionRegistry.INSTANCE.getSessions())
{
  session.close();
}