eXo Platform 3.5

## Chapter 47. JCR Workspace Data Container (architecture contract)

## Goals

- Cover the requirements on Workspace Data Container implementation

- Describe container life cycle

- Describe relations between container and high-level DataManagers

## Concepts

### Container and connection

Workspace Data Container (container) serves Repository Workspace persistent storage. WorkspacePersistentDataManager (data manager) uses container to perform CRUD operation on the persistent storage. Accessing to the storage in the data manager is implemented via storage connection obtained from the container (WorkspaceDataContainer interface implemenatiton). Each connection represents a transaction on the storage. Storage Connection (connection) should be an implementation of WorkspaceStorageConnection.

- Container acts as a factory of a new storage connections. Usually, this method is designed to be synchronized to avoid possible concurrent issues.

    WorkspaceStorageConnection openConnection() throws RepositoryException;

- Open read-only WorkspaceStorageConnection. Read-only connections can be potentially a bit faster in some cases.

    WorkspaceStorageConnection openConnection(boolean readOnly) throws RepositoryException;

### *EXPERIMENTAL*

Read-only WorkspaceStorageConnection is experimental feature and not currently handled in JCR. Actually, such connections didn't prove their performance, so JCR Core doesn't use them.

- Storage connection might also be reused. This means reuse of physical resource (e.g. JDBC Connection) allocated by one connection in another. This feature is used in a data manager for saving ordinary and system changes on the system Workspace. But the reuse is an optional feature and it can work, otherwise a new connection will open.

    WorkspaceStorageConnection reuseConnection(WorkspaceStorageConnection original) throws RepositoryException;

- When checking Same-Name Siblings (SNS) existence, JCR Core can use new connection or not. This is defined via Workspace Data Container configuration and retrieved by using a special method.

    boolean isCheckSNSNewConnection();

Container initialization is only based on a configuration. After the container has been created, it's not possible to change parameters. Configuration consists of implementation class and set of properties and Value Storages configuration.

### Value storages

Container provides optional special mechanism for Value storing. It's possible to configure external Value Storages via container configuration (available only via configuration). Value Storage works as fully independent pluggable storage. All required parameters storage obtains from its configuration. Some storages are possible for one container. Configuration describes such parameters as ValueStoragePluginimplementation class, set of implementation specific properties and filters. The filters declares criteria for Value matching to the storage. Only matched Property Values will be stored. So, in common case, the storage might contains only the part of the Workspace content. Value Storages are very useful for BLOB storing. E.g. storing on the File System instead of a database.

Container obtains Values Storages from ValueStoragePluginProvider component. Provider acts as a factory of Value channels (ValueIOChannel). Channel provides all CRUD operation for Value Storage respecting the transaction manner of work (how it can be possible due to implementation specifics of the storages).

### Lifecycle

Container is used for read and write operations by data manager. Read operations (getters) uses connection once and close it on the finally. Write operations performs in commit method as a sequence of creating/ updating calls and final commit (or rollback on error). Writes uses one connection (or two - another for system workspace) per commit call. One connection guaranties transaction support for write operations. Commit or rollback should free/clean all resources consumed by the container (connection).

### Value storage lifecycle

Value storage is used from the container inside. Reads are related to a container reads. Writes are commit-related. Container (connection) implementation should use transaction capabilities of the storages in the same way as for other operations.

## Requirements

Connection creation and reuse should be a thread safe operation. Connection provides CRUD operations support on the storage.

### Read operations

- Read ItemData from the storage by item identifier.

    ItemData getItemData(String identifier) throws RepositoryException, IllegalStateException;

- Read ItemData from the storage by using the parent and name of the item, related to the parent location.

    ItemData getItemData(NodeData parentData, QPathEntry name) throws RepositoryException,IllegalStateException;

- Read List of NodeData from the storage by using the parent location of the item.

    List<NodeData> getChildNodesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData from the storage by using the parent location of the item.

    List<PropertyData> getChildPropertiesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData with empty ValueData from the storage by using the parent location of the item.

This methiod specially dedicated for non-content modification operations (e.g. Items delete).

    List<PropertyData> listChildPropertiesData(NodeData parent) throws RepositoryException, IllegalStateException;

- Reads List of PropertyData from the storage by using the parent location of the item.

It's REFERENCE type: Properties referencing Node with given nodeIdentifier. See more in javax.jcr.Node.getReferences()

    List<PropertyData> getReferencesData(String nodeIdentifier) throws RepositoryException,IllegalStateException,UnsupportedOperationException;

### Write operations

- Add single NodeData.

    void add(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Add single PropertyData.

    void add(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Update NodeData.

    void update(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Update PropertyData.

    void update(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Rename NodeData by using Node identifier and new name and indexing from the data.

    void rename(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Delete NodeData.

    void delete(NodeData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Delete PropertyData.

    void delete(PropertyData data) throws RepositoryException,UnsupportedOperationException,InvalidItemStateException,IllegalStateException;

- Persist changes and closes connection. It can be database transaction commit for instance etc.

    void commit() throws IllegalStateException, RepositoryException;

- Refuse persistent changes and closes connection. It can be database transaction rollback for instance etc.

    void rollback() throws IllegalStateException, RepositoryException;

All methods throw IllegalStateException if connection is closed. UnsupportedOperationException if the method is not supported (e.g. JCR Level 1 implementation etc). RepositoryException if some errors occur during preparation, validation or persistence.

### State operations

- Return true if connection can be used.

    boolean isOpened();

### Validation of write operations

Container has to care about storage consistency (JCR constraints) on write operations: (InvalidItemStateException should be thrown according the spec). At least, the following checks should be performed:

- On ADD errors

  - Parent not found. Condition: Parent ID (Item with ID is not exists).

  - Item already exists. Condition: ID (Item with ID already exists).

  - Item already exists. Condition: Parent ID, Name, Index (Item with parent ID, name and index already exists).

- On DELETE errors

  - Item not found. Condition ID.

  - Can not delete parent till children exists.

- On UPDATE errors

  - Item not found. Condition ID.

  - Item already exists with higher Version. Condition: ID, Version (Some Session had updated Item with ID prior this update).

### Consistency of save

The container (connection) should implement consistency of Commit (Rollback) in transaction manner. I.e. If a set of operations was performed before the future Commit and another next operation fails. It should be possible to rollback applied changes using Rollback command.

## Value storages API

### Storages provider:

Container implementation obtains Values Storages option via ValueStoragePluginProvider component. Provider acts as a factory of Value channels (ValueIOChannel) and has two methods for this purpose:

- Return ValueIOChannel matched this property and valueOrderNumer. Null will be returned if no channel matches.

    ValueIOChannel getApplicableChannel(PropertyData property, int valueOrderNumer) throws IOException;

- Return ValueIOChannel associated with given storageId.

    ValueIOChannel getChannel(String storageId) throws IOException, ValueStorageNotFoundException;

There is also method for consistency check, but this method doesn't used anywhere and storage implementations has it empty.

### Value storage plugin

Provider implementation should use ValueStoragePlugin abstract class as a base for all storage implementations. Plugin provides support for provider implementation methods. Plugin's methods should be implemented:

- Initialize this plugin. Used at start time in ValueStoragePluginProvider.

    public abstract void init(Properties props, ValueDataResourceHolder resources) throws RepositoryConfigurationException, IOException;

- Open ValueIOChannel.Used in ValueStoragePluginProvider.getApplicableChannel(PropertyData, int) and getChannel(String)

    public abstract ValueIOChannel openIOChannel() throws IOException;

- Return true if this storage has the same storageId.

    public abstract boolean isSame(String valueDataDescriptor);

### Value I/O channel

Channel should implement ValueIOChannel interface. CRUD operation for Value Storage:

- Read Property value.

    ValueData read(String propertyId, int orderNumber, int maxBufferSize) throws IOException;

- Add or update Property value.

    void write(String propertyId, ValueData data) throws IOException;

- Delete Property all values.

    void delete(String propertyId) throws IOException;

### Transaction support via channel

Modification operations should be applied only when commiting. Rollback is required for data created cleanup.

- Commit channel changes.

    void commit() throws IOException;

- Rollback channel changes.

    void rollback() throws IOException;