blob: c97c21e5a798d7a1ceba2647dd6b637c79429c0e [file] [log] [blame]
= 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 link:localJDBC.html[JDBC], link:xaJDBC.html[XA JDBC], link:localJPA.html[JPA] and link:xaJPA.html[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...
. 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.
. 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).
. 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.
. Resources must not rely on the Transaction Control service recognising duplicate enlistments.
A resource should be enlisted at most once.