Package com.isomorphic.datasource

# Class DSTransaction

java.lang.Object
com.isomorphic.datasource.AbstractDSCacheManager
com.isomorphic.datasource.DSTransaction

All Implemented Interfaces:
  `com.isomorphic.datasource.DSCacheManager`

public class DSTransaction extends com.isomorphic.datasource.AbstractDSCacheManager
Manages a transaction that involves multiple `DSRequest` or `RPCRequest` objects.

DSTransaction tracks a queue of requests and provides APIs for commiting, rolling back, managing error status, freeing resources, and enabling features like "Transaction Chaining" (see client-side docs).

For an overview of usage, see the client-side documentation overview "Standalone DataSource Usage".

Each request object represents exactly one logical operation in a transaction. A request can be registered more than once while that operation is processed (e.g.: a DMI re-enters `DSRequest.execute()`), but it continues to occupy one position in the queue and has only one response slot. Re-using the same request object for a second logical operation does not add another queue entry; it overwrites the existing transaction bookkeeping. Transaction failure detection, transaction chaining, and post-processing can therefore observe only one of the responses. Application code must create a separate request object for each distinct operation in a transaction.

A `DSTransaction` is confined to the thread on which it was created. All requests in the transaction must be created, registered, executed, and completed serially on that same thread. In particular, associating the transaction with several requests does not make it valid to execute those requests concurrently. The transaction contains mutable queue state and transaction-scoped JDBC, Hibernate, or JPA resources, and its `DataSourceResourceCaptureScope` uses thread-local state. Concurrent use or transfer to another thread could otherwise share a connection concurrently, race commit against request execution, or return a DataSource while it is still in use.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `addToScriptContext(String name, Object value)`
  If you're using any of the script-based, server-side request or response transformation features - `transformRequestScript`, `transformResponseScript` or `fieldValueScript` - you can make additional Java objects available to the script for every request in the current queue by calling this method.
  `void`
  `addToTemplateContext(Object key, Object value)`
  Same as the `three-argument version`, but the third argument (isSnippet) is always false.
  `void`
  `addToTemplateContext(String name, Object value, boolean isSnippet)`
  Adds an attribute to the template context which makes it available when using Velocity templates.
  `void`
  `applyEarlierResponseValues(DSRequest dsRequest)`
  Applies values from earlier responses in this transaction to the supplied DSRequest, in accordance with the and tags on the DSRequest's operationBinding.
  `void`
  `commit()`
  Commits the current transaction.
  `void`
  `complete()`
  Commits or rolls back the current transaction and frees the DSTransaction's resources.
  `<T extends BaseResponse>
  T`
  `findFirstResponse(String dsName, String opType)`
  Returns the `response` for the first request where the DataSource and operation type match the parameter values (null parameters match any DataSource / operation type).
  `<T extends BaseResponse>
  T`
  `findLastResponse(String dsName, String opType)`
  Returns the `response` for the request most immediately prior to the current request, where the DataSource and operation type match the parameter values.
  `void`
  `freeAllResources()`
  Frees all shared resources (for example, DataSource instances and database connections) used by this DSTransaction, including any DSRequest-level resources that have not already been freed automatically at the end of request processing.
  `void`
  `freeQueueResources()`
  Frees shared resources (for example, DataSource instances and database connections) used by this DSTransaction and associated with the overall queue, as opposed to a particular DSRequest within that queue.
  `Boolean`
  `getAuthenticated()`
  Returns true if we have an authenticated user for this request.
  `DataSource`
  `getDataSource(String dsName)`
  Returns an instance of the DataSource named in the "dsName" parameter.
  `Set<BaseRequest>`
  `getRequests()`
  Returns a list of the requests that are registered with this transaction.
  `<T extends BaseResponse>
  T`
  `getResponse(BaseRequest baseRequest)`
  Retrieves a response for a specific request which has been registered with this transaction.
  `String`
  `getTenantId()`
  Returns the tenant ID associated with the queue of requests for this transaction.
  `int`
  `getTransactionPolicy()`
  Gets the TransactionPolicy currently in place for this transaction.
  `String`
  `getUserId()`
  Returns the user ID associated with the queue of requests being managed by this RPCManager.
  `List<String>`
  `getUserRoles()`
  Returns a list of the roles associated with the user who is authenticated for this request.
  `Boolean`
  `isClientRequest()`
  Returns this transactions default value for clientRequest.
  `boolean`
  `isSkipRemainingQueue()`
  Check if the remaining requests in the queue will be skipped.
  `Map<BaseRequest,BaseResponse>`
  `processQueue()`
  Mainly for standalone usage of DSTransaction, this method will go through each request that has been registered with the DSTransaction and pass it to the request handler.
  `Map<BaseRequest,BaseResponse>`
  `processQueue(BaseRequestHandler requestHandler)`
  Mainly for standalone usage of DSTransaction, this method will go through each request that has been registered with the DSTransaction and pass it to the request handler.
  `boolean`
  `queueHasFailures()`
  Returns true if any request in the current queue failed.
  `void`
  `registerRequest(BaseRequest request)`
  Registers a request with the transaction.
  `void`
  `registerResponse(BaseRequest request, BaseResponse response)`
  Registers a response for a request with the transaction.
  `void`
  `rollback()`
  Rolls back the current transaction.
  `void`
  `setAuthenticated(boolean authenticated)`
  Pass true to this method to indicate that every request in the queue is associated with an authenticated user.
  `void`
  `setClientRequest(Boolean clientRequest)`
  Sets the default clientRequest value for any request included in this transaction.
  `void`
  `setTenantId(String tenantId)`
  Set the tenant ID associated with the queue of requests for this transaction.
  `void`
  `setTransactionPolicy(int transactionPolicy)`
  Sets the `TransactionPolicy` to use for this transaction, by default this will be fetched from server.properties "RPCManager.transactionPolicy" and if not found will be NOT_SET.
  `void`
  `setUserId(String userId)`
  Set the user ID associated with the queue of requests being managed by this RPCManager.
  `void`
  `setUserRoles(String rolesString)`
  Accepts a comma-separated String representing the list of roles associated with the user who is authenticated for this request.
  `void`
  `setUserRoles(String... roles)`
  Accepts a List of roles associated with the user who is authenticated for this request.
  `void`
  `setUserRoles(List<String> rolesList)`
  Accepts a List of roles associated with the user who is authenticated for this request.
  `void`
  `skipRemainingQueue()`
  Calling this tells the `DSTransaction` that all the remaining requests in the queue should be skipped.

- ## Method Details

  - ### queueHasFailures

    public boolean queueHasFailures()
    Returns true if any request in the current queue failed. This method can be used by user code to avoid processing later operations if earlier ones have failed.

  - ### skipRemainingQueue

    public void skipRemainingQueue()
    Calling this tells the `DSTransaction` that all the remaining requests in the queue should be skipped. Those requests will instead return a response with a status of `BaseResponse.STATUS_PROCESSING_SKIPPED`.

    This ensures that the remaining requests don't actually do any logic processing. A typical use-case for this is when you manually handle a transaction queue and after executing a request, use `queueHasFailures()` to check if there is a failure, you can tell the remaining queue to skip processing, giving you consistent responses for those requests that were skipped.

  - ### isSkipRemainingQueue

    public boolean isSkipRemainingQueue()
    Check if the remaining requests in the queue will be skipped.
    Returns:
      true if the remaining requests in the queue will be skipped.

  - ### registerRequest

    public void registerRequest(BaseRequest request)
    Registers a request with the transaction.

    If the request is already registered, it retains its original queue position and its response slot is reset. Repeated registration is supported while processing the same logical operation, but does not create another operation in the transaction. Use a distinct request object for each distinct operation. See the class overview for details.

    Parameters:
      `request` - the request to add to the transaction.

  - ### registerResponse

    public void registerResponse(BaseRequest request, BaseResponse response)
    Registers a response for a request with the transaction.
    Parameters:
      `request` - the request for which the response belongs to.
      `response` - the response.

  - ### getRequests

    public Set<BaseRequest> getRequests()
    Returns a list of the requests that are registered with this transaction.
    Returns:
      list of requests.

  - ### commit

    public void commit() throws Exception
    Commits the current transaction.

    Typically, there is no need to commit manually because it is done automatically when you call `complete()`, if all the DSRequests associated with this DSTransaction completed successfully. Therefore, this method is only necessary if you elect to manually manage the transaction. Note, this method does not free the database connection, so if you do manage the transaction manually, you are responsible for freeing resources - see `freeQueueResources()`.

    In general, use this API only if you are sure you have a good reason for not using `complete()`.

    Throws:
      `Exception`

  - ### rollback

    public void rollback() throws Exception
    Rolls back the current transaction.

    Typically, there is no need to rollback manually because it is done automatically when you call `complete()`, if one or more of the DSRequests associated with this DSTransaction failed. Therefore, this method is only necessary if you elect to manually manage the transaction. Note, this method does not free the database connection, so if you do manage the transaction manually, you are responsible for freeing resources - see `freeQueueResources()`.

    In general, use this API only if you are sure you have a good reason for not using `complete()`.

    Throws:
      `Exception`

  - ### getTransactionPolicy

    public int getTransactionPolicy()
    Gets the TransactionPolicy currently in place for this transaction.
    Returns:
      the transaction policy.

  - ### setTransactionPolicy

    public void setTransactionPolicy(int transactionPolicy) throws QueueAlreadyStartedException
    Sets the `TransactionPolicy` to use for this transaction, by default this will be fetched from server.properties "RPCManager.transactionPolicy" and if not found will be NOT_SET.
    Parameters:
      `transactionPolicy` - the transaction policy to set.
    Throws:
      `QueueAlreadyStartedException`

  - ### complete

    public void complete()
    Commits or rolls back the current transaction and frees the DSTransaction's resources. This API is the one normally used to complete and clean up a DSTransaction when all the associated DSRequests have been executed.

    If all the DSRequests associated with this DSTransaction completed successfully, this method commits the transaction; otherwise, it rolls the transaction back. Either way, resources are freed (see `freeQueueResources()`).

  - ### freeQueueResources

    public void freeQueueResources()
    Frees shared resources (for example, DataSource instances and database connections) used by this DSTransaction and associated with the overall queue, as opposed to a particular DSRequest within that queue. DSRequest-level resources are freed automatically at the end of request processing unless the request is not marked `freeOnExecute`, in which case its resources are considered to be queue-level resources and will be freed by this method. If you have a need to free all resources associated with a DSTransaction, see `freeAllResources()`.

    Note, any DSRequest targeting @link{com.isomorphic.hibernate.HibernateDataSource} or `JPADataSource` is `freeOnExecute:false` by default in order to allow traversing lazy entity associations.

    Typically, there is no need to free resources manually because it is done automatically when you call `complete()`. Therefore, this method is only necessary if you elect to manually manage the transaction by calling `commit()` or `rollback()` instead of `complete()`.

    Note that the resources freed by this API are made immediately available to other threads: do not cache DataSource instances or other resources and attempt to reuse them after calling this API. In general, use this API only if you are sure you have to.

  - ### freeAllResources

    public void freeAllResources()
    Frees all shared resources (for example, DataSource instances and database connections) used by this DSTransaction, including any DSRequest-level resources that have not already been freed automatically at the end of request processing. See also `freeQueueResources()`.

    Typically, there is no need to free resources manually because it is done automatically when you call `complete()`. Therefore, this method is only necessary if you elect to manually manage the transaction by calling `commit()` or `rollback()` instead of `complete()`.

    Note that the resources freed by this API are made immediately available to other threads: do not cache DataSource instances or other resources and attempt to reuse them after calling this API. In general, use this API only if you are sure you have to.

  - ### getResponse

    public <T extends BaseResponse> T getResponse(BaseRequest baseRequest)
    Retrieves a response for a specific request which has been registered with this transaction.
    Parameters:
      `baseRequest` - the request for which to get the response.
    Returns:
      the response, either RPCResponse or DSResponse but can also be referenced using the abstract class BaseResponse.

  - ### processQueue

    public Map<BaseRequest,BaseResponse> processQueue()
    Mainly for standalone usage of DSTransaction, this method will go through each request that has been registered with the DSTransaction and pass it to the request handler.

    The handling of how requests are executed and how errors should be handled can be customised by creating an implementation of `BaseRequestHandler` and setting it on the DSTransaction.

    NOTE: this feature is supported only in Power Edition or above.

    Returns:
      a map of requests and responses so that the calling code can get access to these after processing.

  - ### processQueue

    public Map<BaseRequest,BaseResponse> processQueue(BaseRequestHandler requestHandler)
    Mainly for standalone usage of DSTransaction, this method will go through each request that has been registered with the DSTransaction and pass it to the request handler.

    The handling of how requests are executed and how errors should be handled can be customised by creating an implementation of `BaseRequestHandler` and setting it on the DSTransaction.

    NOTE: this feature is supported only in Power Edition or above.

    Parameters:
      `requestHandler` - a custom request handler which will deal with individual request execution and error handling.
    Returns:
      a map of requests and responses so that the calling code can get access to these after processing.

  - ### applyEarlierResponseValues

    public void applyEarlierResponseValues(DSRequest dsRequest) throws Exception
    Applies values from earlier responses in this transaction to the supplied DSRequest, in accordance with the and tags on the DSRequest's operationBinding. We call this process "Transaction Chaining". See the client-side OperationBinding documentation for details.
    Parameters:
      `dsRequest` - The DSRequest to apply values to
    Throws:
      `Exception`

  - ### findLastResponse

    public <T extends BaseResponse> T findLastResponse(String dsName, String opType)
    Returns the `response` for the request most immediately prior to the current request, where the DataSource and operation type match the parameter values. For example:

           DSResponse resp = dsTransaction.findLastResponse("customer", "update");

    would return the response to the most recent update request on the "customer" DataSource prior to the request currently being processed (ie, the first one in the queue with no response)

    This method is just one way to access the response to a request earlier in the queue - it is a programmatic equivalent of using `$responseData.last()` in a Velocity expression. Scan the client-side documentation for "transaction chaining" for a full discussion of the various options available when using the Transaction Chaining approach.

    Parameters:
      `dsName` - The name of the DataSource to find a response for (null means any DataSource)
      `opType` - The operation type to find a response for (null means any operation type)
    Returns:
      the DSResponse matching the search criteria

  - ### findFirstResponse

    public <T extends BaseResponse> T findFirstResponse(String dsName, String opType)
    Returns the `response` for the first request where the DataSource and operation type match the parameter values (null parameters match any DataSource / operation type). For example:

           DSResponse resp = dsTransaction.findFirstResponse("customer", "update");

         would return the response to the first update request on the "customer" DataSource.

         This method is just one way to access the response to a request earlier in the queue -
         it is a programmatic equivalent of using $responseData.first() in a Velocity
         expression.  Scan the client-side documentation for "transaction chaining" for a full
         discussion of the various options available when using the Transaction Chaining approach.

    Parameters:
      `dsName` - The name of the DataSource to find a response for (null means any DataSource)
      `opType` - The operation type to find a response for (null means any operation type)
    Returns:
      the DSResponse matching the search criteria

  - ### addToTemplateContext

    public void addToTemplateContext(Object key, Object value)
    Same as the `three-argument version`, but the third argument (isSnippet) is always false.
    Parameters:
      `key` - the attribute key.
      `value` - the attribute value, this can be a fully nested Java object.

  - ### addToTemplateContext

    public void addToTemplateContext(String name, Object value, boolean isSnippet)
    Adds an attribute to the template context which makes it available when using Velocity templates.

    The `isSnippet` parameter controls whether the Framework treats the context object as a regular context variable, or a user-defined snippet of text. If isSnippet is true, the context object will be evaluated through Velocity immediately prior to the main Velocity evaluation taking place. This means that you can create context variables that themselves contain variable references, and those references will be correctly resolved in the final output. Read the "User-defined snippets" section of the "customQuerying" article in the client-side docs for more details.

    Note, context variables added via this method will be overridden by context variables of the same name added via the com.isomorphic.datasource.DSRequest#addToTemplateContext(java.lang.Object, java.lang.Object, boolean) DSRequest.addToTemplateContext() method}

    Parameters:
      `name` - the name of the new object as it should appear in the Velocity namespace
      `value` - arbitrary Java object
      `isSnippet` - Is the context object a user-defined snippet?

  - ### addToScriptContext

    public void addToScriptContext(String name, Object value)
    If you're using any of the script-based, server-side request or response transformation features - `transformRequestScript`, `transformResponseScript` or `fieldValueScript` - you can make additional Java objects available to the script for every request in the current queue by calling this method. To make objects available to just a single request, see `DSRequest.addToScriptContext()`

    For example, set up a string in Java code:

             dsRequest.getDsTransaction().addToScriptContext("mySimpleString", "This is a test");

    and then use that string in your transformation script:

         <DataSource ID="myDS"> <transformResponseScript language="groovy"> // Add a property to the response object responseObject["testingTransform"] = mySimpleString; . . . </transformResponseScript> . . . </DataSource>

    Parameters:
      `name` - the name of the new object as it should be made available to the script
      `value` - arbitrary Java object

  - ### getDataSource

    public DataSource getDataSource(String dsName) throws Exception
    Returns an instance of the DataSource named in the "dsName" parameter. This method borrows an independent object from the Framework's DataSource pool and records it for release by `freeDataSources()`. It is the recommended way to obtain an arbitrary DataSource object in your own server-side code. Note that this method is intended for use if you need to obtain some arbitrary DataSource. If what you want is the DataSource associated with the current DSRequest (a common use case), use `DSRequest.getDataSource()` instead. Also, if you are trying to access some arbitrary DataSource purely because you need to run a DSRequest on it (another very common use case), consider just creating the DSRequest instead, using one of the constructors that accepts a DataSource name.

    If you still use this method, you must call `freeDataSources()` when finished. The borrow deliberately bypasses ResourceCaptureScope name caching so that explicitly freeing it cannot return a DataSource instance shared with another holder in the same transaction or request.

    Parameters:
      `dsName` - The name of the DataSource to return
    Throws:
      `Exception`
    See Also:
      - `DSRequest.getDataSource()`
      - `DSRequest(java.util.Map, com.isomorphic.servlet.RequestContext, com.isomorphic.rpc.RPCManager)`

  - ### setUserId

    public void setUserId(String userId)
    Set the user ID associated with the queue of requests being managed by this RPCManager. This value will be used by the framework to populate fields of types "creator" and "modifier". This API is intended for use when you are a using some custom authentication scheme - it is unnecessary if you are using the Servlet API for authentication, because we will default to using the value returned by `HttpServletRequest.getRemoteUser()`.

    Calling this API automatically sets the authentication status of the RPCManager. If you pass a non-null value, the authentication status is set to true. If you pass null, the authentication status is also set to null.

    Parameters:
      `userId` - The user ID to associate with the queue of requests being managed by this RPCManager
    See Also:
      - `RPCManager.getUserId()`
      - `RPCManager.setAuthenticated(boolean)`

  - ### getUserId

    public String getUserId()
    Returns the user ID associated with the queue of requests being managed by this RPCManager. This value can be set by the `RPCManager.setUserId(String)`; if that method has not been called and we are running in the context of the servlet API, we call the servlet API's `getRemoteUser()` method.
    Returns:
      The user ID associated with the queue of requests being managed by this RPCManager, or null if there is no authenticated user
    See Also:
      - `RPCManager.setUserId(String)`

  - ### setTenantId

    public void setTenantId(String tenantId)
    Set the tenant ID associated with the queue of requests for this transaction.
    Parameters:
      `tenantId` - Tenant ID to associate with the queue of requests for this transaction
    See Also:
      - `RPCManager.setTenantId(java.lang.String)`

  - ### getTenantId

    public String getTenantId()
    Returns the tenant ID associated with the queue of requests for this transaction.
    Returns:
      the renant ID associated with the queue of requests for this transaction
    See Also:
      - `RPCManager.getTenantId()`

  - ### getAuthenticated

    public Boolean getAuthenticated()
    Returns true if we have an authenticated user for this request. Please see the client-side documentation for DataSource.requiresAuthentication/ for details of how the authentication system works
    Returns:
      true if we have an authenticated user, otherwise false
    See Also:
      - `RPCManager.setAuthenticated(boolean)`

  - ### setAuthenticated

    public void setAuthenticated(boolean authenticated)
    Pass true to this method to indicate that every request in the queue is associated with an authenticated user. You will need to do this if you wish to implement an authentication that is not based on the scheme built in to the servlet API. Please see the client-side documentation for DataSource.requiresAuthentication for details of how the authentication system works
    Parameters:
      `authenticated` - if true we have an authenticated user
    See Also:
      - `RPCManager.getAuthenticated()`
      - `RPCManager.setUserRoles(String)`
      - `RPCManager.setUserRoles(java.util.List)`

  - ### getUserRoles

    public List<String> getUserRoles()
    Returns a list of the roles associated with the user who is authenticated for this request. Note that this list must be manually populated by calling setUserRoles(); it is an alternative mechanism, in case you wish to implement a role-based security system other than the one built into the servlet API. Please see the client-side documentation for OperationBinding.requiresRole for details of how the authentication/security system works
    Returns:
      The list of roles associated with the currently authenticated user, or null if no such list has been set up
    See Also:
      - `RPCManager.getAuthenticated()`
      - `RPCManager.setUserRoles(java.util.List)`
      - `RPCManager.setUserRoles(String)`

  - ### setUserRoles

    public void setUserRoles(String rolesString)
    Accepts a comma-separated String representing the list of roles associated with the user who is authenticated for this request. This method allows you to implement a role-based security system other than the one built in to the servlet API. Please see the client-side documentation for OperationBinding.requiresRole for details of how the authentication/security system works
    Parameters:
      `rolesString` - A comma-separated String representing the list of roles associated with the authenticated user
    See Also:
      - `RPCManager.setAuthenticated(boolean)`
      - `RPCManager.getUserRoles()`

  - ### setUserRoles

    public void setUserRoles(String... roles)
    Accepts a List of roles associated with the user who is authenticated for this request.

    This method allows you to implement a role-based security system other than the one built in to the servlet API. Please see the client-side documentation for OperationBinding.requiresRole for details of how the authentication/security system works

    Parameters:
      `roles` - The list of roles associated with the authenticated user
    See Also:
      - `RPCManager.setAuthenticated(boolean)`
      - `RPCManager.getUserRoles()`

  - ### setUserRoles

    public void setUserRoles(List<String> rolesList)
    Accepts a List of roles associated with the user who is authenticated for this request.

    This method allows you to implement a role-based security system other than the one built in to the servlet API. Please see the client-side documentation for OperationBinding.requiresRole for details of how the authentication/security system works

    Parameters:
      `rolesList` - The list of roles associated with the authenticated user
    See Also:
      - `RPCManager.setAuthenticated(boolean)`
      - `RPCManager.getUserRoles()`

  - ### isClientRequest

    public Boolean isClientRequest()
    Returns this transactions default value for clientRequest.
    Returns:
      true if default value is set to true, otherwise false.

  - ### setClientRequest

    public void setClientRequest(Boolean clientRequest)
    Sets the default clientRequest value for any request included in this transaction. If set to true, this will enabled declarative security checks for all requests in this transaction.
    Parameters:
      `clientRequest` - true if this is a clientRequest transaction.
    See Also:
      - `BaseRequest.setClientRequest(Boolean)`