Package com.isomorphic.servlet

# Class RESTHandler

java.lang.Object
jakarta.servlet.GenericServlet
jakarta.servlet.http.HttpServlet
com.isomorphic.servlet.BaseServlet
com.isomorphic.servlet.RESTHandler

All Implemented Interfaces:
  `jakarta.servlet.Servlet`, `jakarta.servlet.ServletConfig`, `Serializable`

public class RESTHandler extends BaseServlet
This servlet handles built-in SmartClient datasource requests sent from REST clients. The `RestDataSource` provided with SmartClient is such a client, but this handler will work with any REST client that encodes its data as JSON or XML, sends its requests as POST messages and conforms to the REST transfer protocol described in the client-side documentation for `RestDataSource`. Note that you must read these client-side documents in order to understand how to properly format a REST request for processing by this servlet using the RestDataSource's postMessage protocol.

By default JSON responses will be wrapped into special markers so that code is not directly executable outside of your application. This is a preventive measure against javascript hijacking.

The servlet accepts parameter "wrapJSONResponses": `true` - wrap JSON responses; `false` - send plain JSON reponses. The parameter can be set in your web.xml file either as a context parameter (for all servlets) or as a servlet initialization parameter.

If JSON wrapping is on, you can also set the prefix and suffix strings that we use to wrap responses. Again, you do this by setting the parameters "jsonPrefix" and "jsonSuffix" in your web.xml file, either as a context parameter (for all servlets) or as a servlet initialization parameter. If these parameters are not set in web.xml, they default as follows:
jsonPrefix: "<SCRIPT>//'\"]]>>isc_JSONResponseStart>>"
jsonSuffix: "//isc_JSONResponseEnd"

Note that these parameters can be overridden at the DataSource level, by providing a `.ds.xml` file for the DataSource, and specifying `jsonPrefix` and `jsonSuffix` in there.

NOTE: The default settings shown above are also the defaults used by the client-side RestDataSource. If you choose to change the default prefix and/or suffix strings returned by the server, you must obviously also change the strings that the client expects to see. If you are using RestDataSource, you do this by overriding the `jsonPrefix` and `jsonSuffix` properties. See the client-side documentation for details.

The servlet also accepts parameter "defaultDataFormat". This governs whether we expect requests to be encoded as XML or JSON, if no explicit dataFormat is provided with the request. Note that the dataFormat is explicitly sent with every request if you are using the Isomorphic `RestDataSource` (see below), so this parameter has no effect in that case; it is only used when no dataFormat is provided with the request, as would be the case if you are integrating with a third-party REST client.

If you do not specify a defaultDataFormat, "xml" is assumed.

The servlet also accepts parameter "dynamicDataFormatParamName". This governs the name of the dataFormat parameter we look for in incoming requests (as described above in the paragraph about "defaultDataFormat"). If you wish to send the dataFormat to use with each client request, you send an HTTP parameter with this name in the request, with a value of "xml" or "json". By default, the "dynamicDataFormatParamName" is the value used by the SmartClient RestDataSource: "isc_dataFormat".

NOTE: This servlet is configured to automatically set character encoding on requests and responses to UTF-8. If you wish to force a different encoding, you can do so by specifying the `init-param` "encoding" in your web.xml file, as shown in the example below. If you wish to switch off explicit encoding altogether, use the `init-param` to set a value of "none".

Please see the client-side documentation on Internationalization for a discussion of why this procedure is necessary.

This snippet shows how you might change your `web.xml` file to configure the `RESTHandler` servlet:

       <servlet>
           <servlet-name>RESTHandler</servlet-name>
           <servlet-class>com.isomorphic.servlet.RESTHandler</servlet-class>
           <init-param>
               <param-name>defaultDataFormat</param-name>
               <param-value>json</param-value>
           </init-param>
           <init-param>
               <param-name>dynamicDataFormatParamName</param-name>
               <param-value>theDataFormat</param-value>
           </init-param>
           <init-param>
               <param-name>wrapJSONResponses</param-name>
               <param-value>false</param-value>
           </init-param>
           <init-param>
               <param-name>encoding</param-name>
               <param-value>some-other-encoding</param-value>
           </init-param>
       </servlet>
       <servlet-mapping>
           <servlet-name>RESTHandler</servlet-name>
           <url-pattern>/restapi/</url-pattern>
       </servlet-mapping>

With no additional configuration, the SmartClient server will also generate an OpenAPI specification describing your REST api, available at ${urlPattern}/openapi.yaml. If, for example, you've configured your servlet to respond to requests at `/restapi`, then a GET request to `/restapi/openapi.yaml` will yield the generated documentation. Refer to the client-side OpenAPI documentation topic for further discussion. Even if you do not plan to expose the generated documentation, there may be value in referring to it for fuller understanding of the remaining discussion. As it turns out, the RestDataSource postMessage protocol, or "Advanced REST", is not the only option.

Simplified REST

RESTHandler also offers a "Simplified REST" mode in which the DataSource name and (optionally) primary key values appear in the request URI, with simplified request and response formats. This simplified mode may be appropriate for automated systems that need to query or update DataSource data in a simplified manner. Using SimplifiedREST for a SmartClient or SmartGWT browser-based UI is always, always wrong. It will create additional work while crippling your UI's capabilities and also creating performance problems.

Two styles are supported. The first behaves much like the AdvancedREST protocol, in that it responds to POST requests only, makes use of request body content in all cases, and is able to support AdvancedCriteria and some batching capabilities. The second makes use of HTTP verbs and status codes more traditionally, and does none of those things. The remainder of this documentation will refer to both styles as Simplified REST, but refer to the first, more robust style as SimplifiedPOST wherever a distinction needs to be made for clarity.

All SimplifiedREST HTTP requests consist of the URI that you provide (including any params), the HTTP verb, and any params (key/value pairs) specified as posted data. The HTTP response format - either XML or JSON - is determined as discussed above by the servlet parameter `defaultDataFormat` or the request parameter named by the servlet parameter `dynamicDataFormatParamName`. The default for SimplifiedPOST is XML, as it is for the AdvancedREST postMessage protocol. The default for SimplifiedREST is JSON, however, amd HTTP response status codes will more precisely reflect the success (2XX) or failure (4XX/5XX) of the request. Failures will contain one or more error messages. For more details on status codes, see HTTP methods.

URI Interpretation

The basic SimplifiedREST URI syntax is:

         isomorphic/RESTHandler/<DataSource name>[/<primary key value>]

where the DataSource name must always be provided, but a primary key value is optional and in fact is not valid when using SimplifiedPOST. SimplifiedPOST requests always begin with the `RESTDataSource` prefix, but otherwise take a similar form:

         isomorphic/RESTHandler/RESTDataSource/<DataSource name>

Additional field/value pairs may be provided as query parameters or posted data, including the primary key whenever the longer syntax is not used.

Again, SimplifiedPOST responds to the single POST verb only, and returns a uniform response format, regardless of request body content. Requests and responses are handled by other SimplifiedREST requests as follows:

- An HTTP GET performs a fetch, with any query params beyond the primary key interpreted as simple criteria.
- An HTTP DELETE performs a remove, with any query params or posted data beyond the primary key interpreted as simple criteria.
- An HTTP POST performs an add, with both query params and posted data providing the record field content.
- An HTTP PUT performs a single-row update, with both query params and posted data providing the record field content, if said field content contains a primary key value (or the operationBinding `providesMissingKeys`). A PUT with no primary key value performs an add, exactly as if it were a POST request.
- An HTTP PATCH also performs a single-row update, with both query params and posted data providing the record field content, if said field content contains a primary key value. A PUT with no primary key value values(s) performs a multi-row update, subject to the currently configured `allowMultiUpdate` settings for updates on the target DataSource / OperationBinding. Note that direct invocation of multi-row updates is normally discouraged - see the allowMultiUpdate topic included with client documentation for further discussion, including best-practice recommendations.

For example, to fetch the record with primary key value `5` from `countryDS` DataSource, you would send an HTTP GET with URI `isomorphic/RESTHandler/countryDS/5`, and to add a new country record to `countryDS` DataSource, which has an autogenerated sequence primary key, you would send an HTTP POST with URI `isomorphic/RESTHandler/countryDS` and all the record field content as the POSTED data. (Various POSTED data formats, such as application/x-www-form-urlencoded "form data" are supported by the servlet.)

Singular vs. Array Response Format

For both XML and JSON reply formats, whether you receive a singular or array reply is determined by the HTTP request type:

- Singular responses are sent for adds, fetches that supply the primary key value as a path segment (not as a query param or POSTED data), and updates or removes that specify the primary key either as param(s) or as a path segment.
- Array responses are always generated for any fetch which doesn't specify the primary key as a path segment, or any update or remove request that doesn't specify a primary key.

So for example singular responses look like:

    { fieldName : fieldValue, fieldName2: fieldValue2}

for JSON, and

     <record>
         <fieldName>fieldValue</fieldName>
         <fieldName2>fieldValue2</fieldName2>
     </record>

for XML, whereas array responses look like:

    [ { fieldName : fieldValue, fieldName2: fieldValue2}, ... ]

for JSON, and

     <data>
         <record>
             <fieldName>fieldValue</fieldName>
             <fieldName2>fieldValue2</fieldName2>
         </record>
         <record>
             <fieldName>fieldValue</fieldName>
             <fieldName2>fieldValue2</fieldName2>
         </record>
     </data>

for XML. Note that, regardless of the above, if a request returns no results, it will be show up as an empty HTTP response with status 204 - "no content".

Error Response Format

When an error HTTP status (4XX or 5XX) is returned from the server, there will be an error object included. It will contain an error code and an array of error messages (even if there's just one message). For JSON, it might look like:

    { code: -1, messages: ["fatal error: cannot find DataSource 'fred'"]}

and for XML is might look like:

     <error>
         <code>-1</code>
         <messages>
             <message>"fatal error: cannot find DataSource 'fred'"]}</message>
         </messages>
     </error>

Expanded URI Syntax

In addition to the URI syntax discussed above, SimplifiedREST also accepts HTTP requests with the syntax:

         isomorphic/RESTHandler/RESTDataSource/<DataSource name>/<operation>[/<operation ID>]

or

         isomorphic/RESTHandler/<DataSource name>/<operation>[/<operation ID>]

where the operation is one of "add", "remove", "update", "fetch", or "custom". SimplifiedPOST requests also recognize a "batch" pseudo-operation on the path, where it accepts a batch of transactions, each having its operationType & operationId in the request body. Operation IDs on the path are optional, except of course when trying to reach a particular operationId, and for "custom" operations where it is required. For example, the URI `isomorphic/RESTHandler/countryDS/fetch/foobar` specifies that a "fetch" is to be performed with the `operationId` of "foobar". Eliminating the 'foobar' path segment would execute the default fetch operation for the countryDS DataSource. Note that support for this syntax means that you must not use these operation names as primary key values for your DataSource.

Hybrid SimplifiedREST

Hybrid mode allows you to send the HTTP requests as you otherwise would as described above, but receive the responses as normal RestDataSource DSResponses. To use hybrid mode, specify `hybridMode` as a servlet initialization param or query param in the HTTP request. Note that in hybrid mode, the following properties are supported as query params and/or posted data:

- `criteria` - must be valid JSON for simple or AdvancedCriteria, in the request body only
- `startRow` and `endRow` - as numbers, in either query params or request body
- `textMatchStyle` - as a string, in either query params or request body
- `sortBy` - as either a string or an array of simple `sortBy` string specifiers, in query params or request body. In case of the former, something like the following is sufficient

  sortBy=COUNTRY&sortBy=-CUSTOMER_NAME

  where a valid JSON array is required in case of the latter.

See Also:
  - Serialized Form

- ## Method Summary

  Modifier and Type
  Method
  Description
  `DSResponse`
  `handleDSRequest(DSRequest dsRequest, RPCManager rpc, RequestContext context)`
  This method is called by `processRestTransaction(RPCManager, RequestContext)` to handle a DSRequest sent from the client.
  `void`
  `processRequest(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)`
  Servlet entry point to process the request.
  `void`
  `processRestTransaction(RPCManager rpcManager, RequestContext context)`
  Process a REST transaction.

  ### Methods inherited from class com.isomorphic.servlet.BaseServlet

  `handleError, handleError`

- ## Method Details

  - ### processRequest

    public void processRequest(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws jakarta.servlet.ServletException, IOException
    Servlet entry point to process the request. The default implementation instantiates an RPCManager and then invokes `processRestTransaction(RPCManager, RequestContext)`
    Note, if you wish to provide customized handling, processRestTransaction() is a better override point than this method for most use cases.
    Parameters:
      `request` - The HttpServletRequest
      `response` - The HttpServletResponse
    Throws:
      `jakarta.servlet.ServletException` - As per HttpServlet.service()
      `IOException` - As per HttpServlet.service()

  - ### processRestTransaction

    public void processRestTransaction(RPCManager rpcManager, RequestContext context) throws Exception
    Process a REST transaction. This method is called by processRequest() and iterates through each DSRequest in the transaction, executing them by calling `handleDSRequest(DSRequest, RPCManager, RequestContext)`. If you wish to provide customized REST transaction handling, this is the appropriate method to override
    Parameters:
      `rpcManager` - The RPCManager for this REST request
      `context` - RequestContext class provides accessors to Servlet basics like HttpServletRequest, HttpServletResponse
    Throws:
      `Exception` - For backwards compatibility this method is still declared as throwing an exception, but the default implementation traps and handles all Exceptions.

  - ### handleDSRequest

    public DSResponse handleDSRequest(DSRequest dsRequest, RPCManager rpc, RequestContext context) throws Exception
    This method is called by `processRestTransaction(RPCManager, RequestContext)` to handle a DSRequest sent from the client. This method may be called multiple times while processing a single HTTP request (if the client sent multiple requests in a batch via RPCManager.startQueue()).

    The default implementation of this method simply calls `DSRequest.execute()`, catching and handling any Exception by calling `generateFailureResponse(DSRequest, Exception, int)`

    Parameters:
      `dsRequest` - The DSRequest to process
      `rpc` - The RPCManager that was used to demultiplex the DSRequest
      `context` - RequestContext class provides accessors to Servlet basics like HttpServletRequest, HttpServletResponse
    Throws:
      `Exception` - For backwards compatibility this method is still declared as throwing an exception, but the default implementation logic (as shown above) traps and handles all Exceptions.