Best Practices and Common Pitfalls

 

Author: Eike Stepper

CDO applications combine EMF object access with session, view, transaction, repository, and network lifecycles. The most serious failures usually happen at those boundaries: an object is used after its view has closed, a dirty transaction is kept open across unrelated work, a callback performs blocking UI work, or a retry repeats a non-repeatable operation. This chapter is a concise set of CDO-specific decisions and failure modes.

The detailed API references remain in Working with Sessions, Working with Views, Working with Transactions, Locking, Large Objects, Branching and Versioning, Notifications and Event Handling, Integrating with EMF and Other Frameworks, and Advanced Topics.

Table of Contents

1 Own Sessions, Views, Transactions, and Resources Explicitly
2 Reuse Sessions, Keep Views Purposeful, and Bound Transactions
3 Use Supported Concurrency Boundaries
4 Choose Optimistic or Pessimistic Coordination Deliberately
5 Handle Conflicts and Retries as Business Decisions
6 Load Only What the Operation Needs
7 Treat Remote Changes and Recovery as Asynchronous Failure Modes
8 Preserve Branch, History, and Integration Context
9 Distinguish Failure Classes and Keep APIs Current
10 Common CDO Pitfalls

1  Own Sessions, Views, Transactions, and Resources Explicitly

Decide which application component owns each session, view, transaction, and CDO-managed resource set. The owner closes it after the last dependent component has stopped using it. A transaction is also a view, but closing the transaction is the transaction owner's responsibility; do not let a helper close a view or session that it did not open.

A resource set associated with a view is not an independent connection. CDO resources and objects obtained from it must not be treated as usable after the owning view has closed. Likewise, a listener registration and an adapter registration are application resources: retain the exact listener or adapter instance so it can be removed from the same notifier. Readers and streams returned for CDO large objects should be closed with try-with-resources; views, sessions, and transactions expose explicit close() lifecycle methods rather than being Java AutoCloseable resources.

Close a transaction before its session, remove listeners before disposing the observed component, and release a LOB stream before closing the view or session that supplies it. The example below shows the owning component opening its own transaction, committing one bounded operation, rolling back failures, and closing the transaction:

InTransaction.java      
CDOTransaction transaction = session.openTransaction();
try
{
  operation.accept(transaction);
  transaction.commit();
}
catch (CommitException ex)
{
  if (!transaction.isClosed() && transaction.isDirty())
  {
    transaction.rollback();
  }

  throw ex;
}
catch (RuntimeException | Error ex)
{
  if (!transaction.isClosed() && transaction.isDirty())
  {
    transaction.rollback();
  }

  throw ex;
}
finally
{
  if (!transaction.isClosed())
  {
    transaction.close();
  }
}

2  Reuse Sessions, Keep Views Purposeful, and Bound Transactions

Reuse a session when its connector, repository, authentication, and passive-update policy are appropriate for the component. Repeatedly opening and closing sessions adds connection and package/revision setup cost. Reuse a view when its branch, time point, notification policy, and consistency requirements match; otherwise open a separate view rather than silently changing the context of unrelated work.

Keep transactions focused on one coherent business operation. A long-lived dirty transaction holds local state, increases the conflict window, and makes recovery after disconnects or user cancellation harder. Commit or roll back deliberately, and check CDOTransaction.isDirty() and CDOTransaction.hasConflict() at boundaries. scopes are useful for composable operations: a scope can commit into its parent or roll back to its opening state, but CDOTransactionScope.commit() is not a repository commit. Only the root transaction commit persists changes.

Separate read-only views from editing transactions when a reader must not observe or mutate work-in-progress. Detailed session and view lifecycle belongs to Working with Sessions and Working with Views.

3  Use Supported Concurrency Boundaries

CDO views and their model objects support concurrent single accesses. That does not make a sequence of reads atomic with respect to invalidation. When several reads must describe one consistent observation, execute them through the view's critical section. Keep the critical section short and do not hold it while waiting for UI work, another thread, network I/O, or a long-running computation.

Sessions and views are not a reason to add application-wide synchronization around every call. Use repository locks when the business rule requires coordination between clients, and use application locks only for application-owned state. Listener callbacks can run on the thread that delivers the event; they should capture the relevant state and hand expensive or UI work to an application executor. See Working with Views, Locking, and Notifications and Event Handling for the respective contracts.

4  Choose Optimistic or Pessimistic Coordination Deliberately

Prefer normal optimistic transaction conflict detection when concurrent edits are uncommon or can be merged at the domain level. Use explicit object locks when a short operation must exclude competing writers, and use durable locks when ownership must survive a view or session lifetime and the repository's durable-locking support is appropriate. A lock is coordination state, not a replacement for a transaction boundary or conflict policy.

Do not acquire broad or long-lived locks merely to avoid learning how conflicts work. Handle lock acquisition timeouts as a decision for the caller, and release locks according to the transaction's configured lock policy. Locking ownership, timeouts, and durable areas are covered in Locking.

5  Handle Conflicts and Retries as Business Decisions

Distinguish ConcurrentAccessException from a general CommitException. A concurrent-access failure means that the operation must decide whether to refresh, merge, ask the user, or retry. A retry is safe only when the operation is repeatable and its inputs are still valid. Bound the number of attempts and avoid an infinite loop that turns contention into unbounded load.

Conflict resolvers can automate a known policy, but they do not make every domain merge correct. Reapply only the repeatable part of a failed operation, inspect transaction conflict state, and preserve user input that cannot be reconstructed. The transaction chapter's bounded retry example and conflict guidance in Working with Transactions are the normative details.

LockTimeoutException and commit conflicts are different failures: one concerns lock acquisition, the other concerns concurrent repository state. Do not handle both by blindly retrying the same mutation.

6  Load Only What the Operation Needs

Treat loading as a cost decision. Prefer server-side queries when the task is to identify a bounded set of objects, and prefer deliberate units when a known disjoint subtree is the working set. For graph traversal, choose collection chunks and revision prefetch depth based on measured access patterns. Avoid accidental full traversal of a large containment tree, infinite prefetch, and forcing full collection materialization for a list that is only sampled.

A cache hit is not the same as current repository truth: cache-aware revision requests describe where a revision may be loaded from, while view invalidation and branch/time semantics determine what the view should observe. Use CDO large-object types and streams for large payloads instead of materializing them into memory. See Large Objects and Advanced Topics for the detailed loading and query APIs.

7  Treat Remote Changes and Recovery as Asynchronous Failure Modes

A passive update and view invalidation signal that the visible state may have changed; it is not automatically a feature-level delta for every adapter. Use subscriptions only for objects that need detailed remote adapter delivery, remove subscriptions and listeners when the observing component ends, and move expensive callback work off the delivery thread. The notification chapter explains the distinction between invalidation, adapters, and CDO events in detail.

Reconnecting sessions can restore a connection, but they do not make an interrupted business operation idempotent. An application must inspect session, view, and transaction state after a disconnect and decide whether to retry, rebuild a transaction, or report failure. Never assume that a commit was not applied merely because the client lost its response. Recovery configuration and recovery events are described in Working with Sessions and Advanced Topics.

8  Preserve Branch, History, and Integration Context

A revision version is meaningful together with its branch; it is not a globally unique object version. Keep the branch and time point with identifiers used for history operations. A historical view is a read context, not a revert operation, and a merge produces transaction changes that still require an explicit repository commit. CDOBranchVersion therefore belongs in a branch-aware API rather than in a bare integer field.

CDO URIs, provider-created views, and CDO-managed resource sets carry ownership and context. Do not treat a CDO URI as an ordinary platform file URI, do not assume a provider caches or closes views for you, and do not dispose a view's resource set while EMF clients still use its resources. See Branching and Versioning and Integrating with EMF and Other Frameworks.

9  Distinguish Failure Classes and Keep APIs Current

Handle commit failures, concurrent access, lock timeouts, transport disconnects, query failures, LOB I/O failures, and closed-lifecycle errors according to their recovery meaning. Preserve causes and repository context in logs, but do not expose credentials or sensitive URI user information. After a failure, query documented state such as CDOView.isClosed(), transaction dirty/conflict state, and session lifecycle state before deciding what to do.

Prefer current public APIs. Avoid internal implementation packages and compatibility APIs marked deprecated, even when an older example still compiles. A public SPI can be used for its documented extension purpose, but it should be treated as an extension contract, not as ordinary application state. The integration and advanced-topics chapters identify the current replacements for provider and loading patterns.

10  Common CDO Pitfalls

Before shipping a client, check the boundaries that are easiest to get wrong:

Each item is a CDO lifecycle or consistency rule, not a generic Java style preference. Use the linked subsystem chapters when the application needs the precise option or recovery API.