Making your own Resource Provider

The TransactionControl service is based around the use of ResourceProvider instances. When combined the client receives a thread-safe resource instance that they can use.

Resource Provider implementations already exist for JDBC, XA JDBC, JPA and XA JPA

Create an interface

If you need to create your own Resource Provider then you should define a sub-interface which declares the type of the resource. This allows clients type-safe access to the resources, and makes it easier to identify the right Resource Provider at runtime.

public interface MyCustomResourceProvider extends 
                    ResourceProvider<MyCustomResource> {}

Create an implementation

The implementation of a ResourceProvider should return a thread-safe delegating proxy. This may be a dynamic proxy or a static one. The proxy delegates to the real resource, and is responsible for ensuring that the thread consistently accesses the same physical resource throughout the scope. Note that even when multiple proxies are created they should share the same physical resource throughout the life of the context.

Lifecycle - first access

Whenever the resource is accessed then it must check to see whether it has already been accessed within the current scope. If not then a resource should be selected and associated with the scope. This physical resource must then be used for the rest of the scope.

Note that if there is no active scope when the resource is accessed then the resource access should fail with a TransactionException.

/**
 *  A resource method
 */
@Override
public boolean doSomething() {
    if(!txControl.activeScope()) {
        throw new TransactionException("There is no scope currently active");
    }

    // Locate, or create and associate
    MyCustomResource delegate = locateDelegate(txControl.getCurrentContext());

    return delegate.doSomething();
}

Lifecycle - enlisting

If a transaction is active then the resource should enlist with it when it is first used. If the resource is usable with XA transactions then it should register an XAResource.

private MyCustomResource locateDelegate(TransactionContext context) {
    MyCustomResource resource = findExisting(context);

    if(resource != null) {
        resource = getNewDelegate(context);

        if(context.activeTransaction()) {
            if(context.supportsXA()) {
                context.registerXAResource(resource.getXAResource());
            } else {
                throw new TransactionException("The transaction does not support XA resources");
            }
        }
        // Other resource preparation
        ...
    }

    return resource;
}

Local resources are similar, but they register a LocalResource, not an XAResource.

private MyCustomResource locateDelegate(TransactionContext context) {
    MyCustomResource resource = findExisting(context);

    if(resource != null) {
        resource = getNewDelegate(context);

        if(context.activeTransaction()) {
            if(context.supportsLocal()) {
                context.registerLocalResource(resource.getLocalesource());
            } else {
                throw new TransactionException("The transaction does not support Local resources");
            }
        }
        // Other resource preparation
        ...
    }

    return resource;
}

Lifecycle - Tidying up

In addition to registering with an active transaction the resource should also register for cleanup at the end of the scope. This may involve closing the physical resource, or returning it to a pool.

// Other resource preparation

context.postCompletion(s -> resource.release());

Other things to look out for...

  1. A client must have a friendly way to access the ResourceProvider. This may be directly via the OSGi Service Registry or via a factory service of some kind.

  2. A resource must remain valid throughout a scope, therefore clients should not be able to close or return the resource by making calls on the thread-safe proxy. Typically calls to close should be a silent no-op (the actual close will occur when the scope ends).

  3. When a transaction is active clients must not be able to manually commit or rollback the resource. Methods like commit, rollback and setRollbackOnly must fail with a TransactionException indicating the incorrect usage. This is different from close behaviour because unlike deferring a close ignoring a rollback results in a different overall result from the client's perspective.

  4. Resources must not rely on the Transaction Control service recognising duplicate enlistments. A resource should be enlisted at most once.