Package com.isomorphic.rpc

# Class RPCManager

java.lang.Object
com.isomorphic.datasource.AbstractDSCacheManager
com.isomorphic.rpc.RPCManager

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

public class RPCManager extends com.isomorphic.datasource.AbstractDSCacheManager
Provides the ability to decode `RPCRequests` and `DSRequests` sent by the browser into Java Objects, and send back Java Object as responses.

This class has a client-side counterpart that is also called RPCManager. The client-side RPCManager enables you to send one or more RPCRequests to an arbitrary URL. In the Java logic at those URLs, you'll need to instantiate this RPCManager to process and respond to the requests.

Note that a single RPC transaction can contain more than one RPC request. See the discussion on queueing in the documentation for the client-side RPCManager on why this is useful.

All RPC requests require a response. If your application needs nothing more than an ack of the successful completion of a given request, use the sendSuccess()/sendFailure() convenience methods on this class to ack the request. The RPCManager keeps track of request/response pairs and automatically streams all responses to the client as a batch once you've provided a response for each request.

Note that when you call any of the send() methods on this class, no data is actually sent back over the wire until you have responded to all requests.

## Special notes on queueing

While processing a queue, the RPCManager will not abort the queue if one of the requests fail, it will instead carry on until the last request has been executed and then issue a callback to the datasources involved. The reason for this is that the code in a DMI might already handle the error condition, and that any DataSource operations performed in the context of the failed transaction might likewise be trying to compensate for the error or gather information to send back in an error message. If a request failed, the `RPCManagerCompletionCallback.onFailure(com.isomorphic.datasource.DSTransaction, boolean)` would be called and the datasources would either commit or rollback a transaction. If you instead wanted to abort the execution of the rest of the queue upon failure you will have to manually call `queueHasFailures()` to see if the queue has any failures before executing the next request. Also note that `RPCRequests` issuing DMI calls do not have the concept of commit and rollback and will therefore require you to use this method to abort a queue on failure.

See Also:
  - `RPCRequest`
  - `RPCResponse`
  - `Init`

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `RPCManager(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)`
  RPCManager constructor for use in Servlets or Filters.
  `RPCManager(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Writer out)`
  RPCManager constructor for use in JSPs.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `doCustomResponse()`
  If you're using Direct Method Invocation, you can call doCustomResponse() to suppress the normal RPCManager response so that you can send your own custom response via the servletResponse output stream.
  `DSResponse`
  `findFirstResponse(String dsName, String opType)`
  Returns the `DSResponse` for the first DSRequest where the DataSource and operation type match the parameter values (null parameters match any DataSource / operation type).
  `DSResponse`
  `findLastResponse(String dsName, String opType)`
  Returns the `DSResponse` for the DSRequest most immediately prior to the current DSRequest, where the DataSource and operation type match the parameter values.
  `Object`
  `getAttribute(String key)`
  Returns an Object that has previously been stored in this RPCManager's attribute map.
  `Boolean`
  `getAuthenticated()`
  Returns true if we have an authenticated user for this request.
  `Object`
  `getData()`
  Convenience method for getting the data of a single RPCRequest.
  `DataSource`
  `getDataSource(String dsName)`
  Returns an instance of the DataSource named in the "dsName" parameter.
  `DSRequest`
  `getDSRequest()`
  Convenience method for getting a single DSRequest when you know this request only contains one DSRequest.
  `RPCRequest`
  `getRequest()`
  Convenience method for getting a single RPCRequest when you know this HTTP request only contains one RPCRequest.
  `List<BaseRequest>`
  `getRequests()`
  Returns a list of RPC requests sent in this HTTP transaction.
  `String`
  `getTenantId()`
  Returns the tenant ID associated with the queue of requests being managed by this RPCManager.
  `int`
  `getTransactionPolicy()`
  Returns this RPCManager's transaction policy.
  `String`
  `getUserId()`
  Returns the user ID associated with the queue of requests being managed by this RPCManager.
  `List`
  `getUserRoles()`
  Returns a list of the roles associated with the user who is authenticated for this request.
  `boolean`
  `hasTenantId()`
  Returns whether a tenant ID has been associated with the queue of requests being managed by this RPCManager.
  `static boolean`
  `isRPC(jakarta.servlet.http.HttpServletRequest request)`
  `static boolean`
  `isXmlHttp(jakarta.servlet.http.HttpServletRequest request)`
  `static void`
  `processRequest(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)`
  Instantiates an RPCManager and processes any `RPCRequest` or `DSRequest` from the provided HttpServletRequest.
  `boolean`
  `queueHasFailures()`
  Returns true if any request in the current queue failed.
  `void`
  `registerCallback(RPCManagerCompletionCallback callback)`
  Register an implementation of `RPCManagerCompletionCallback` with this RPCManager.
  `void`
  `removeAttribute(String key)`
  Removes an Object that has previously been stored in this RPCManager's attribute map.
  `int`
  `requestCount()`
  Returns the number of RPC requests contained in the current HTTP request.
  `void`
  `send(DSRequest dsRequest, DSResponse dsResponse)`
  When responding to DataSource requests sent by the client, use this method.
  `void`
  `send(DSRequest dsRequest, Object data)`
  Convenience method.
  `void`
  `send(RPCRequest rpcRequest, RPCResponse rpcResponse)`
  When responding to a set of RPC requests sent as part of one HTTP request (if you used startQueue() sendQueue() on the client) you need to pair the responses to the requests.
  `void`
  `send(RPCRequest rpcRequest, Object data)`
  Convenience method.
  `void`
  `send(RPCResponse rpcResponse)`
  Convenience method for sending some data back in response to a single RPCRequest.
  `void`
  `send(Object data)`
  Convenience method for sending some data back in response to a single RPCRequest.
  `void`
  `sendFailure(Object request, String error)`
  If the request processing failed for some reason, you can encode your own failure response in a standard response, or use this convenience method to send a failure notification on the client.
  Unless your client-side request specified the willHandleError flag, whatever message you send back here will be alert()ed on the client.
  `void`
  `sendFailure(Object request, Throwable t)`
  Takes a `Throwable`, formats the stack trace and calls sendFailure(rpcRequst, error).
  `void`
  `sendSuccess(RPCRequest rpcRequest)`
  Every RPC request requires a response.
  `void`
  `sendXMLString(DSRequest dsRequest, String xml)`
  Convenience method.
  `void`
  `sendXMLString(RPCRequest rpcRequest, String xml)`
  Convenience method.
  `void`
  `setAttribute(String key, Object value)`
  Stores an object in this RPCManager's attribute map, associated with the passed key.
  `void`
  `setAuthenticated(boolean authenticated)`
  Pass true to this method to indicate that every request in the queue is associated with an authenticated user.
  `void`
  `setResponseCharset(String charset)`
  Sets the charset of the response.
  `RPCManager`
  `setTenantId(String tenantId)`
  Set the tenant ID to associate with the queue of requests being managed by this RPCManager.
  `void`
  `setTransactionPolicy(int tp)`
  Set this RPCManager's transaction policy.
  `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 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.
  `Connection`
  `startSQLTransaction(DSRequest dsReq)`
  Starts the SQL transaction that will be used for all SQL DSRequests belonging to this RPCManager.

- ## Constructor Details

  - ### RPCManager

    public RPCManager(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws Exception
    RPCManager constructor for use in Servlets or Filters.
    Parameters:
      `request` - The 'request' variable provided in the context of the JSP
      `response` - The 'response' variable provided in the context of the JSP
    Throws:
      `ClientMustResubmitException` - if the client must resubmit the request.
      `Exception` - if the request is malformed.

  - ### RPCManager

    public RPCManager(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Writer out) throws Exception
    RPCManager constructor for use in JSPs. The RPC requires control of the output stream to send responses to the client. Since JSPs automatically call response.getOutputStream() to provide the 'out' variable in the context of the JSP and since it's further illegal to call getOutputStream() more than once, so you'll need to use this form of the RPCManager constructor when processing RPC requests in a JSP.
    Parameters:
      `request` - The 'request' variable provided in the context of the JSP
      `response` - The 'response' variable provided in the context of the JSP
      `out` - The 'out' variable provided in the context of the JSP
    Throws:
      `ClientMustResubmitException` - if the client must resubmit the request.
      `Exception` - if the request is malformed.

- ## Method Details

  - ### isRPC

    public static boolean isRPC(jakarta.servlet.http.HttpServletRequest request)
    Returns:
      true if the passed HttpServletRequest contains a SmartClient RPC request, false otherwise.

  - ### isXmlHttp

    public static boolean isXmlHttp(jakarta.servlet.http.HttpServletRequest request)
    Returns:
      true if the current RPC request was made using the xmlHttpRequest transport, false otherwise.

  - ### doCustomResponse

    public void doCustomResponse()
    If you're using Direct Method Invocation, you can call doCustomResponse() to suppress the normal RPCManager response so that you can send your own custom response via the servletResponse output stream.

    This is useful for implementing file download functionality in response to an RPC request.

    Note if you are not using DMI, but simply using the RPCManager, you can avoid the RPCManager response by simply not calling RPCManager.send().

  - ### setResponseCharset

    public void setResponseCharset(String charset)
    Sets the charset of the response. Must be called before any send() methods are called.

  - ### getData

    public Object getData()
    Convenience method for getting the data of a single RPCRequest. Calling this method is equivalent to calling getRequest.getData()

  - ### getDSRequest

    public DSRequest getDSRequest()
    Convenience method for getting a single DSRequest when you know this request only contains one DSRequest. If more than one DSRequest was actually sent by the client, this method returns the first one and logs a warning.
    Returns:
      `DSRequest`. Returns `null` if request is not instance of `DSRequest`.

  - ### getRequest

    public RPCRequest getRequest()
    Convenience method for getting a single RPCRequest when you know this HTTP request only contains one RPCRequest. If more than one RPC request was actually sent by the client, this method returns the first one and logs a warning.

  - ### getRequests

    public List<BaseRequest> getRequests()
    Returns a list of RPC requests sent in this HTTP transaction. The request objects in this list will be either of type RPCRequest (if you used RPCManager.send() on the client) or of type DSRequest if the request was generated by a databound component or datasource on the client or a mix of RPCRequest and DSRequest objects.

  - ### requestCount

    public int requestCount()
    Returns the number of RPC requests contained in the current HTTP request.
    Returns:
      number of RPC requests contained in the current HTTP request.

  - ### send

    public void send(Object data) throws Exception
    Convenience method for sending some data back in response to a single RPCRequest. Calling this method is equivalent to calling send(new RPCResponse(data))/
    Parameters:
      `data` - to send back
    Throws:
      `Exception` - if there is an error sending the response

  - ### send

    public void send(RPCResponse rpcResponse) throws Exception
    Convenience method for sending some data back in response to a single RPCRequest. If you're processing multiple RPC request sent as part of one HTTP request, you need to pair the responses to the requests by calling send(rpcRequest, rpcResponse) instead.
    Parameters:
      `rpcResponse` - containing the data to send back
    Throws:
      `Exception` - if there is an error sending the response

  - ### send

    public void send(RPCRequest rpcRequest, RPCResponse rpcResponse) throws Exception
    When responding to a set of RPC requests sent as part of one HTTP request (if you used startQueue() sendQueue() on the client) you need to pair the responses to the requests.
    Parameters:
      `rpcRequest` - the request you're responding to
      `rpcResponse` - your response
    Throws:
      `Exception` - if there is an error sending the response

  - ### send

    public void send(RPCRequest rpcRequest, Object data) throws Exception
    Convenience method. This method does the following:

         RPCResponse rpcResponse = new RPCResponse();
         rpcResponse.setData(data);
         rpcResponse.setStatus(RPCResponse.STATUS_SUCCESS);
         send(rpcRequest, rpcResponse);

    Parameters:
      `rpcRequest` - the request you're responding to
      `data` - to send as the payload of the response
    Throws:
      `Exception` - if there is an error sending the response

  - ### sendXMLString

    public void sendXMLString(RPCRequest rpcRequest, String xml) throws Exception
    Convenience method. Transforms the provided XML in a manner equivalent to the isomorphic:XML JSP tag (custom Isomorphic tag) and sets that as the payload of a new RPCResponse. Calling this method is equivalent to parsing the XML and passing the resulting top-level `org.w3c.dom.Element` object to RPCResponse.setData().
    Throws:
      `Exception` - if there is an error sending the response

  - ### send

    public void send(DSRequest dsRequest, DSResponse dsResponse) throws Exception
    When responding to DataSource requests sent by the client, use this method.
    Parameters:
      `dsRequest` - the request you're responding to
      `dsResponse` - your response
    Throws:
      `Exception` - if there is an error sending the response

  - ### send

    public void send(DSRequest dsRequest, Object data) throws Exception
    Convenience method. This method does the following:

         DSResponse dsResponse = new DSResponse();
         dsResponse.setData(data);
         dsResponse.setStatus(DSResponse.STATUS_SUCCESS);
         send(dsRequest, dsResponse);

    Parameters:
      `dsRequest` - the request you're responding to
      `data` - to send as the payload of the response
    Throws:
      `Exception` - if there is an error sending the response

  - ### sendXMLString

    public void sendXMLString(DSRequest dsRequest, String xml) throws Exception
    Convenience method. Transforms the provided XML in a manner equivalent to the isomorphic:XML JSP tag (custom Isomorphic tag) and sets that as the payload of a new DSResponse. Calling this method is equivalent to parsing the XML and passing the resulting top-level an org.w3c.dom.Element object to DSResponse.setData().
    Throws:
      `Exception` - if there is an error sending the response

  - ### sendSuccess

    public void sendSuccess(RPCRequest rpcRequest) throws Exception
    Every RPC request requires a response. If your application does not require a response to a particular request, use this method to ack the successful completion of the request on the server.
    Parameters:
      `rpcRequest` - the request that completed successfully
    Throws:
      `Exception` - if there is an error sending the response

  - ### sendFailure

    public void sendFailure(Object request, String error) throws Exception
    If the request processing failed for some reason, you can encode your own failure response in a standard response, or use this convenience method to send a failure notification on the client.
    Unless your client-side request specified the willHandleError flag, whatever message you send back here will be alert()ed on the client.
    Parameters:
      `request` - the request that failed
      `error` - the error string to send to the client
    Throws:
      `Exception` - if there is an error sending the response

  - ### sendFailure

    public void sendFailure(Object request, Throwable t) throws Exception
    Takes a `Throwable`, formats the stack trace and calls sendFailure(rpcRequst, error).
    Parameters:
      `request` - the request that failed
      `t` - the exception you wish to report to the client
    Throws:
      `Exception` - if there is an error sending the response

  - ### queueHasFailures

    public boolean queueHasFailures()
    Returns true if any request in the current queue failed. This method can be used by user code in a DMI or custom DataSource execute method 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.

  - ### findLastResponse

    public DSResponse findLastResponse(String dsName, String opType)
    Returns the `DSResponse` for the DSRequest most immediately prior to the current DSRequest, where the DataSource and operation type match the parameter values. For example:

           DSResponse resp = rpcManager.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 DSResponse findFirstResponse(String dsName, String opType)
    Returns the `DSResponse` for the first DSRequest where the DataSource and operation type match the parameter values (null parameters match any DataSource / operation type). For example:

         DSResponse resp = rpcManager.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

  - ### processRequest

    public static void processRequest(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws jakarta.servlet.ServletException, IOException
    Instantiates an RPCManager and processes any `RPCRequest` or `DSRequest` from the provided HttpServletRequest.

    Depending on the `transactionPolicy` specified and the settings for the underlying datasources and the incoming requests, the list of requests can end up as transaction. If this happens and one of the requests fail during execution, the RPCManager will notify underlying datasources with a `RPCManagerCompletionCallback` of either success or failure after all requests have been executed. The underlying `DataSource` will then either commit or rollback. The queue will not stop during mid processing because the previous request failed and will instead carry on to the end before issuing the callback.

    If you want to be able to abort a queue from processing the rest of the requests after a failure you will have to manually implement the queue processing and use `queueHasFailures()` to determine if the next request should be processed.

    Parameters:
      `request` - The HttpServletRequest
      `response` - The HttpServletResponse
    Throws:
      `jakarta.servlet.ServletException` - As per HttpServlet.service()
      `IOException` - As per HttpServlet.service()
    See Also:
      - `RPCManager for special notes on queueing.`

  - ### getDataSource

    public DataSource getDataSource(String dsName) throws Exception
    Returns an instance of the DataSource named in the "dsName" parameter. This method borrows an object from the framework's DataSource pool, and ensures that it is freed at the end of the request cycle. 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.
    Parameters:
      `dsName` - The name of the DataSource to return
    Throws:
      `Exception`
    See Also:
      - `DSRequest.getDataSource()`
      - `DSRequest(String, String, 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:
      - `getUserId()`
      - `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 `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:
      - `setUserId(String)`

  - ### setTenantId

    public RPCManager setTenantId(String tenantId)
    Set the tenant ID to associate with the queue of requests being managed by this RPCManager. Can be set in an override of `IDACall.prepareRPCTransaction()`.

    See the client-side Multi-Tenacy documentation for major concepts and how to implement authorization.

    Returns:
      the RPC manager
    See Also:
      - `DSTransaction.setTenantId(java.lang.String)`

  - ### getTenantId

    public String getTenantId()
    Returns the tenant ID associated with the queue of requests being managed by this RPCManager.
    See Also:
      - `DSTransaction.getTenantId()`

  - ### hasTenantId

    public boolean hasTenantId()
    Returns whether a tenant ID has been associated with the queue of requests being managed by this RPCManager.
    Returns:
      Whether a tenant ID has been associated with the queue of requests being managed by this RPCManager
    See Also:
      - `setTenantId(java.lang.String)`

  - ### 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:
      - `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:
      - `getAuthenticated()`
      - `setUserRoles(java.lang.String)`

  - ### getUserRoles

    public List 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:
      - `getAuthenticated()`
      - `setUserRoles(java.lang.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 user who is authenticated for this reques
    See Also:
      - `setAuthenticated(boolean)`
      - `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:
      - `setAuthenticated(boolean)`
      - `getUserRoles()`

  - ### setUserRoles

    public void setUserRoles(List 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:
      - `setAuthenticated(boolean)`
      - `getUserRoles()`

  - ### getAttribute

    public Object getAttribute(String key)
    Returns an Object that has previously been stored in this RPCManager's attribute map. This method intentionally mirrors the method of the same name available on HttpServletRequest, and is provided as an alternstive way to add objects to an RPCManager without introducing a dependency on the Servlet API.
    Parameters:
      `key` - The key of the object in the DSRequest's attribute map
    See Also:
      - `DSRequest.getAttribute(java.lang.String)`

  - ### setAttribute

    public void setAttribute(String key, Object value)
    Stores an object in this RPCManager's attribute map, associated with the passed key. This method intentionally mirrors the method of the same name available on HttpServletRequest, and is provided as an alternstive way to add objects to an RPCManager without introducing a dependency on the Servlet API.
    Parameters:
      `key` - The key of the object in the DSRequest's attribute map
      `value` - The object to store
    See Also:
      - `DSRequest.setAttribute(java.lang.String, java.lang.Object)`

  - ### removeAttribute

    public void removeAttribute(String key)
    Removes an Object that has previously been stored in this RPCManager's attribute map. This method intentionally mirrors the method of the same name available on HttpServletRequest. It is provided as an alternstive, to avoid introducing a dependency on the Servlet API.
    Parameters:
      `key` - The key of the object in the DSRequest's attribute map
    See Also:
      - `DSRequest.removeAttribute(java.lang.String)`

  - ### registerCallback

    public void registerCallback(RPCManagerCompletionCallback callback)
    Register an implementation of `RPCManagerCompletionCallback` with this RPCManager. The callback object will have its onSuccess() or onFailure() method called when every request in the RPCManager's queue has completed. Note that the RPCManager is only considered successful - and hence onSuccess() will be called - if every request in the queue is successful; if any one fails, onFailure() will be called.
    Parameters:
      `callback` - An instance of `RPCManagerCompletionCallback`

  - ### getTransactionPolicy

    public int getTransactionPolicy()
    Returns this RPCManager's transaction policy. The transaction policy is in force for the entire lifecycle of the queue being managed by the RPCManager; it cannot be changed once queue processing has started. Transaction policy is one of the following `TransactionPolicy` constants:
    - NOT_SET: No special TransactionPolicy is in force; the system will fall back to settings in `server.properties`, as described in the client-side documentation. This is the default for RPCManager instances.
    - ANY_CHANGE: Bundle all requests into a transaction, including leading fetches, if there is any change operation in the queue. Since ANY_CHANGE is the default `.properties` setting, this is effectively the default behavior of an RPCManager where transaction policy has not been set.
    - FROM_FIRST_CHANGE: Start a transaction on the first request in the queue that potentially changes data (in other words, don't include leading fetches in the transaction).
    - ALL: Bundle all requests into a transaction unconditionally.
    - NONE: Switch off transactions for this RPCManager; each request that does not have a DataSource- or OperationBinding-level override to the contrary will be committed independently

    Change operation in listing above is add, update or remove operation. Note that custom operation is not considered a change operation as far as whether a transaction is started.

    The default `.properties` setting is ANY_CHANGE.

    Returns:
      This RPCManager's transaction policy

  - ### setTransactionPolicy

    public void setTransactionPolicy(int tp) throws QueueAlreadyStartedException
    Set this RPCManager's transaction policy. The transaction policy is in force for the entire lifecycle of the queue being managed by the RPCManager; it cannot be changed once queue processing has started. Therefore, this method will fail with an exception if you call it after the first request in its queue has started processing.

    When you change transaction policy, you effectively override the global setting for autoJoinTransactions, for this request queue only. However, autoJoinTransactions settings specified at the DataSource or OperationBinding level will still be honored. If you need to override these finer-grained settings, you can do so with DSRequest method `setJoinTransaction`. For details of configuring autoJoinTransactions, scan the client documentation for that phrase.

    Transaction policy is one of the following `TransactionPolicy` constants:

    - NOT_SET: No special TransactionPolicy is in force; the system will fall back to settings in `server.properties`, as described in the client-side documentation. This is the default for RPCManager instances.
    - ANY_CHANGE: Bundle all requests into a transaction, including leading fetches, if there is any change operation in the queue. Since ANY_CHANGE is the default `.properties` setting, this is effectively the default behavior of an RPCManager where transaction policy has not been set.
    - FROM_FIRST_CHANGE: Start a transaction on the first request in the queue that potentially changes data (in other words, don't include leading fetches in the transaction).
    - ALL: Bundle all requests into a transaction unconditionally.
    - NONE: Switch off transactions for this RPCManager; each request that does not have a DataSource- or OperationBinding-level override to the contrary will be committed independently

    Change operation in listing above is add, update or remove operation. Note that custom operation is not considered a change operation as far as whether a transaction is started.

    Note that you can also use transactions without an RPCManager or HttpServletRequest - see `DSTransaction`.

    Throws:
      `QueueAlreadyStartedException` - if the queue has already started processing

  - ### startSQLTransaction

    public Connection startSQLTransaction(DSRequest dsReq) throws Exception
    Starts the SQL transaction that will be used for all SQL DSRequests belonging to this RPCManager. Call this method to be handed a `Connection` that you can use for your own, non-DSRequest operations and have them participate in the same SQL transaction as the queue of DSRequests.

    Note that you are not required to call this method; the SQL subsystem will automatically start a transaction if necessary, in accordance with the `transaction policy` in force). Only call this method if you want to insert extra SQL operations into the transaction (using a custom DataSource implementation, DMI method or server scriptlet) before the first DSRequest operation.

    Also note that you are not required to close, commit or release the connection. Again, the framework will automatically do this as required. For this reason, do not attempt to cache the connection object and reuse it elsewhere.

    Returns:
      The Connection to be used for the RPCManager's SQL transaction
    Throws:
      `Exception`