| = 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. |