Interface StandaloneDataSourceUsage


public interface StandaloneDataSourceUsage

Standalone DataSource Usage

The DataSource layer can be used in a standalone application separate from a servlet engine, for use cases like offline batch data processing, sending emails based on database state, or integration of Smart GWT Server logic into a standalone Java Swing application.

In addition, the section below on "Transactions in standalone applications" also applies to any transaction that is initiated separately from an RPCManager and/or the HttpServletRequest lifecycle. This includes use cases like non-servlet web apps (using NIO servers like Grizzly), web apps that set timers to perform delayed actions (using frameworks such as Quartz), as well JMS or other Java frameworks that may introduce non-HttpServletRequest threads within a servlet container.

Note that we do still require servlet-api.jar to be on the classpath of standalone applications. This is only to satisfy class-loading requirements; there are cases where running inside a servlet engine allows us to provide extra functionality, such as declarative authentication and authorization features, so some classes do have member variables or method parameters of types in the Servlet API. They all operate correctly when these variables or parameters are null, but the JVM will still try to load the classes and will crash if they are missing.

Using the DataSource layer to run DataSource operations in your standalone applications is extremely straightforward. This example fetches and prints every record from the "customers" DataSource:

    public static void main(String[] args) {
      DataSource ds = DataSourceManager.get("customers");
      List records = ds.fetch();
      for (Iterator i = records.iterator; i.hasNext(); ) {
        System.out.println(i.next());
      }
    }
  
To make this example fetch just customers in the United States:
      Map criteria = new HashMap();
      criteria.put("countryCode", "US");
      List records = ds.fetch(criteria);
  
This example shows how to run a specific fetch operation, specifying both selection criteria and a sort order, using a DSRequest rather than a DataSource convenience method:
    public static void main(String[] args) {
      DSRequest dsReq = new DSRequest("customers", "fetch");
      dsReq.setOperationId("specialFetch");
      Map criteria = new HashMap();
      criteria.put("countryCode", "US");
      criteria.put("region", "CA");
      dsReq.setCriteria(criteria);
      dsReq.setSortBy("customerName");
      List records = dsReq.execute().getDataList();
    }
  
This example shows how to do a simple update:
    public static void main(String[] args) {
      DataSource ds = DataSourceManager.get("customers");
      Map criteria = new HashMap();
      criteria.put("customerNo", 12345);
      Map values = new HashMap();
      values.put("creditLimit", 10000);
      values.put("currencyCode", "USD");
      ds.update(criteria, values);
    }
  
Finally, this example shows how to perform a specific update operation via a DSRequest:
    public static void main(String[] args) {
      DSRequest dsReq = new DSRequest("customers", "update");
      dsReq.setOperationId("specialUpdate");
      Map criteria = new HashMap();
      criteria.put("customerNo", 12345);
      Map values = new HashMap();
      values.put("creditLimit", 10000);
      values.put("currencyCode", "USD");
      dsReq.setCriteria(criteria);
      dsReq.setValues(values);
      dsReq.execute();
    }
  
NOTES

Because we are not running inside a servlet container, Smart GWT's built-in logic to work out where its application root is does not work. Therefore, you need to manually set a "webRoot" in your server.properties file. The webRoot should point to the root folder of your application (note for SmartGWT applications, this is typically the "war" subfolder of your project). Example entries:

webRoot: /home/testUser/myProject

or:

webRoot: C:\Projects\MyProject\war

Again in server.properties, you may need to set isomorphicPathRootRelative to match the standalone project's layout if you make the standalone mode separate:

isomorphicPathRootRelative: myProject/sc

Note that the directory structure that normally appears in "war/myProject/sc" in your GWT project is required to be present under the configured webRoot+isomorphicPathRootRelative directory when running standalone. This structure is normally created by the GWT compiler, by processing <inherits> tags in your .gwt.xml that cause resources to be copied from smartgwtee.jar. If your build/deployment scripts do not invoke the GWT compiler these files will not be present, so you will need to either add a step to invoke the GWT compiler or manually copy the files. You should place the server.properties file somewhere on your classpath. Typically, the root of your bin or classes folder structure is the most suitable place for it.

Both the built-in DataSources and custom DataSources can be used in standalone applications, but only if you do not introduce dependencies on the servlet container in your DataSource code. For example, if you have a security check in a DMI or custom DataSource that depends on checking the current logged-in user, code in a means of bypassing this, or create a parallel operationBinding that is accessible only to the superuser.

Declarative Security

When you use the DataSource layer in a standalone application, Declarative Security has to be explicitly controlled.

To enable a request for security checks you simply need to call dsRequest.setUserId() or dsRequest.setUserRoles(). If the request is apart of a transaction then security can also be defaulted using dsTransaction.setClientRequest(true/false), however any value set on an individual request will still take priority. For instance if you call dsTransaction.setClientRequest(false) but then also call dsRequest.setUserId(id), then security checks will still take place for that request as it has had security enabled which takes priority over the value on DSTransaction.

Note: If you have Declarative Security checks in your DataSources and/or enabled via your Java code, and you want to completely disable such checks system-wide, you can set security.disabled: true in Server_properties. This causes API calls like dsRequest.setClientRequest() to be completely ignored.

Transactions

In standalone mode, transactions cannot be automatically initiated with the HTTP request lifecyle, however you can still manually initiate and commit transactions (note, only available with a Power or better license). To do so, create a DSTransaction object and associated each DSRequest with it via dsRequest.setDSTransaction(). At the end of processing, call DSTransaction.complete() to ensure commits and rollbacks are executed and that resources are freed up.

Usage Example:

      DSTransaction dst = new DSTransaction();
      DSRequest req1 = new DSRequest("myDataSource", "update");
      req1.setDsTransaction(dst);
 
      DSRequest req2 = new DSRequest("myDataSource", "add");
      req2.setDsTransaction(dst);
 
      DSRequest req3 = new DSRequest("myDataSource", "update");
      req3.setDsTransaction(dst);
 
      try {
          // This will process the queue of requests which have been registered with the transaction.
          dst.processQueue();
      } finally {
          // We put this in a "finally" block to ensure it always runs even on exceptions.
          dst.complete();
      }
  

You can also handle the requests manually instead of using dsTransaction.processQueue(). This can be handy if you wish to perform another operation in between each request or if one request depends on data from the other. At the end of processing, call DSTransaction.complete() to ensure commits and rollbacks are executed and that resources are freed up.

Usage Example:

      DSTransaction dst = new DSTransaction();
 
      try {
          DSRequest req1 = new DSRequest("myDataSource", "fetch");
          req1.setDsTransaction(dst);
 
          DSRequest req2 = new DSRequest("myDataSource", "update");
          req2.setDsTransaction(dst);
 
          req1.execute();
          // Use the response from req1 to modify req2 here
          req2.execute();
      } catch (Exception e) {
          throw new RuntimeException(e);
      } finally {
          // We put this in a "finally" block to ensure it always runs even on exceptions.
          dst.complete();
      }
  

Please note that Transaction chaining is supported while using transactions in a standalone use case - expressions such as $responseData will refer to results from previous DSRequests that are part of the same DSTransaction.

Note the following about standalone transactions:

  • DSRequests that have not had setDSTransaction() called will be outside of any transactional processing - they will be auto-committed at the end of request processing.
  • You may partition your updates into multiple transactions, simply by creating multiple DSTransaction objects and assigning them to DSRequests as required. Note, this will tie up a database connection per DSTransaction until the transactions are committed or rolled back.
  • When using the DSTransaction's built in processQueue() method, error handling will be taken care of automatically for each DSRequest.execute() call and a proper DSResponse will always be returned.
  • Your code is responsible for calling the complete() method, which will commit the transaction if every DSRequest was successful, or roll it back if there were any failures, and then release the database connection. If you do not call complete(), you will leak database connections, so consider placing the call inside a finally block
  • If you do not want Smart GWT Server's default behavior of automatically rolling back if any DSRequest failed, you can manually take over the transaction management by calling commit() or rollback() instead of complete(). Note, if you do this you also take on responsibility for releasing the database connection by calling freeQueueResources() or freeAllResources(). If you fail to do this, you will leak database connections
  • A DSTransaction object can be re-used after complete() has been called. When you do this, a new database connection is borrowed or established, and a new transaction is started

Spring Framework

In a typical web application, Spring configuration is picked up from an "applicationContext" file by a servlet or listener, and then made available to the rest of the app via the servletContext. When running standalone, this is not possible, so instead we read the applicationContext file manually when we need to, eg, create a DataSource object that is configured as a Spring bean.

By default, the framework will create an instance of Spring's FileSystemXmlApplicationContext from a configuration at WEB-INF/applicationContext.xml. If you use another name or location for this file, and you want to run the application outside of a servlet engine, you can tell the framework where to find the configuration(s) by specifying a comma-separated list of paths via the standalone.spring.applicationContext property in your server.properties. You may provide your locations with the classpath: or file: prefixes as shown here or without, in which case Smart GWT will prepend the file: prefix for you at runtime. The default setting for this property looks like this:

     standalone.spring.applicationContext: $webRoot/WEB-INF/applicationContext.xml
  
See Also: