26 Performing Grid Operations with REST

This chapter provides instructions for performing grid operations using the Coherence REST API. The Coherence REST API pre-defines many operations that can be used to interact with a cache. In addition, custom operations such aggregators and entry processors can be created as required.

This chapter includes the following sections:

26.1 Specifying Key and Value Types

The Coherence REST services require metadata about the cache that they expose. The metadata includes the cache entry's key and value types as well as key converters and value marshallers. The key and value types are required in order for Coherence to be able to use built-in converters and marshallers (both XML and JSON are supported).

To define the key and value types for a cache entry, edit the coherence-rest-config.xml file and include the <key-class> and the <value-class> elements within the <resource> element whose values are set to key and value types, respectively. See "resource" for a detailed reference of the <resource> element.

Note:

The <key-class> and <value-class> element can either be defined within the <resource> element or within the <cache-mapping> element in the cache configuration file.

The following example defines a String key class and a value class for a Person user type:

<resources>
   <resource>
      <cache-name>person</cache-name>
      <key-class>java.lang.String</key-class>
      <value-class>example.Person</value-class>
   </resource>
</resources>

26.2 Performing Single-Object REST Operations

The REST API includes support for performing GET, PUT, and DELETE operations on a single object in a cache.

GET Operation

GET http://host:port/cacheName/key

Returns a single object from the cache based on a key. A 404 (Not Found) status code returns if the object with the specified key does not exist. The get operation supports partial results (see "Performing Partial-Object REST Operations" for details). Conditional gets are supported if an object implements the com.tangosol.util.Versionsable interface. The version is added to the response and used to determine if a client has the latest version of an object. If a client already has the latest version of an object, a 304 (Not Modified) status code returns.

The following sample output demonstrates the response of a GET operation:

* Client out-bound request
> GET http://127.0.0.1:8080/dist-http-example/1
> Accept: application/xml
 
* Client in-bound response
< 200
< Content-Length: 212
< Content-Type: application/xml
<
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Person><id>1</id><name>
Mark</name><address><street>500 Oracle Parkway</street><city>Redwood Shores</city>
<country>United States</country></address></Person>

* Client out-bound request
> GET http://127.0.0.1:8080/dist-http-example/1
> Accept: application/json

* Client in-bound response
< 200
< Content-Type: application/json
<
{"@type":"rest.Person","address":{"@type":"rest.Address","city":"Redwood Shores",
"country":"United States","street":"500 Oracle Parkway"},"id":1,"name":"Mark"}

PUT Operations

PUT http://host:port/cacheName/key

Creates or updates a single object in the cache. A 200 (OK) status code returns if the object was updated. If optimistic concurrency check fails, a 409 (Conflict) status code returns with the current object as an entity. See "Understanding Concurrency Control" for details.

The following sample output demonstrates the response of a PUT operation:

* Client out-bound request
> PUT http://127.0.0.1:8080/dist-test-sepx/1
> Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Person><id>1</id><name>
Mark</name><address><street>500 Oracle Parkway</street><city>Redwood Shores</city>
<country>United States</country></address></Person>
 
* Client in-bound response
< 200
< Content-Length: 0
<
 
* Client out-bound request
> PUT http://127.0.0.1:8080/dist-test-sepj/1
> Content-Type: application/json
{"@type":"rest.Person","id":1,"name":"Mark","address":{"@type":"rest.Address","str
eet":"500 Oracle Parkway","city":"Redwood Shores","country":"United States"}}
 
* Client in-bound response
< 200
< Content-Length: 0
<

Delete Operation

DELETE http://host:port/cacheName/key

Deletes a single object from the cache based on a key. A 200 (OK) status code returns if the object is successfully deleted, or a 404 (Not Found) status code returns if the object with the specified key does not exist.

26.3 Performing Multi-Object REST Operations

Multi-object operations allow users to retrieve or delete multiple objects in a single network request and can significantly reduce the network usage and improve network performance.

Note:

PUT operations are not supported as it may produce tainted data. Specifically, it would require that individual objects (in serialized form) within the entity body to be in the same order as the corresponding keys in the URL. In addition, since updates result in a replacement, an entire object serialized form must be provided which can lead to overhead.

GET Operations

GET http://host:port/cacheName/(key1, key2, ...)

Returns a set of objects from the cache based on the specified keys. The ordering of returned objects is undefined and does not need to match the key order in the URL. Missing objects are silently omitted from the results. A 200 (OK) status code always returns. An empty result set is returned if there are no objects in the result set. The get operation supports partial results (see "Performing Partial-Object REST Operations" for details).

DELETE Operations

DELETE http://host:port/cacheName/(key1, key2, ...)

Deletes multiple objects from the cache based on the specified keys. A 200 (OK) status code always returns even if no objects for the specified keys were present in the cache.

26.4 Performing Partial-Object REST Operations

An application may not want (or need) to retrieve a whole object. For example, in order to populate a drop down with a list of options, the application may only need two properties of a potentially large object with many other properties. In order to support this use case, each read operation should accept a list of object properties that the user is interested in as a matrix parameter p.

The following example performs a get operation that retrieves just the id and name attributes for a person:

GET http://localhost:8080/people/123;p=id,name

To include a country attribute of the address as well, the request URL is as follows:

GET http://localhost:8080/people/123;p=id,name,address:(country)

This approach allows an application to selectively retrieve only the properties that are required using a simple, URL-friendly notation.

The following sample output demonstrates the response of a GET operation:

* Client out-bound request
> GET http://127.0.0.1:8080/dist-test-sepj/1;p=name
> Accept: application/json
 
* Client in-bound response
< 200
< Transfer-Encoding: chunked
< Content-Type: application/json
<
{"name":"Mark"}

26.5 Performing Queries with REST

Coherence REST allows users to query a cache. CohQL is the default query syntax; however, additional query syntaxes can be created and used as required.

The section includes the following sections:

26.5.1 Using Direct Queries

Direct queries are query expression that are submitted as the value of the parameter q in a REST URL. By default, the query expression must be specified as a URL-encoded CohQL expression (the predicate part of CohQL). See Developing Applications with Oracle Coherence for details on the CohQL syntax. The syntax of a direct query is as follows:

GET http://host:port/cacheName?q=query

For example, to query the person cache for person objects where age is less than 18:

GET http://host:port/person?q=age%3C18

Direct queries are disabled by default. To enabled direct queries, edit the coherence-rest-config.xml file and add a <direct-query> element for each resource to be queried and set the enabled attribute to true. For example:

<resource>
   <cache-name>persons</cache-name>
   <key-class>java.lang.Integer</key-class>
   <value-class>example.Person</value-class>
   <direct-query enabled="true"/>
</resource>

A 403 (Forbidden) response code is returned if a query is performed on a resource that does not have direct queries enabled.

26.5.2 Using Named Queries

Named queries are query expression that are configured for a resource in the coherence-rest-config.xml file. By default, the query expression must be specified as a CohQL expression (the predicate part of CohQL). Since this expression is configured in an XML file, any special characters (such as < and >) must be escaped using the corresponding entity. See Developing Applications with Oracle Coherence for details on the CohQL syntax. In addition, named queries can include context values as required. The syntax of a named query is as follows:

GET http://host:port/cacheName/namedQuery?param1=value1,param2=value2...

To specify named queries, add any number of <query> elements, within a <resource> element, that each contain a query expression and name binding. For more information on the <query> element, see "query". For example:

<resource>
   <cache-name>persons</cache-name>
   <key-class>java.lang.Integer</key-class>
   <value-class>example.Person</value-class>
   <query>
      <name>minors</name>
      <expression>age &lt; 18</expression>
   </query>
   <query>
      <name>first-name</name>
      <expression>name is :name</expression>
   </query>
</resource>

To use a named query, enter the name of the query within the REST URL. The following example uses the minors named query that is defined in the above example.

GET http://host:port/persons/minors

Parameters provide flexibility by allowing context values to be replaced in the query expression. The following example uses the :name parameter that is defined in the first-name query expression above to only query entries whose name property is Mark.

http://host:port/persons/first-name?name=Mark

Parameter names must be prefixed by a colon character (:paramName). Parameter bindings do not have access to type information, so it's possible to get a false where a true is expected on the comparison operators. To avoid such behavior, specify type hints as part of a query parameter (:paramName;int). Table 26-1 lists the supported type hints.

Table 26-1 Parameter Type Hints

Hint Type

i, int

java.lang.Integer

s, short

java.lang.Short

l, long

java.lang.Long

f, float

java.lang.Float

d, double

java.lang.Double

I

java.math.BigInteger

D

java.math.BigDecimal

date

java.util.Date

uuid

com.tagosol.util.UUID

uid

com.tangosol.util.UID

package.MyClass

package.MyClass

Named queries can also be used in conjunction with aggregation and entry processing. For more information on aggregation and entry processing, see "Performing Aggregations with REST" and "Performing Entry Processing with REST", respectively. For example:

http://host:port/persons/first-name?name=Mark/long-max(age)

http://host:port/persons/first-name?name=Mark/increment(age,1)

26.5.3 Specifying a Query Sort Order

The sort matrix parameter is an optional parameter used within a REST URL that provides the ability to order the returned results of a query. The sort parameter is available for both direct queries and named queries. The value of the sort parameters is a comma-separated list of properties to sort on, each of which can have an optional :asc (default) or :desc qualifier that determines the order of the sort. For example, to sort a list of people by last name with family members sorted from the oldest to the youngest, the sort parameter is defined as follows:

GET http://host:port/persons/minors;sort=lastName,age:desc

The following example uses the sort parameter as part of a direct query.

GET http://host:port/persons;sort=lastName,age:desc?q=age%3C18

26.5.4 Limiting Query Result Size

Queries against large caches can potentially return large result sets that may cause out-of-memory errors. You should always use keys when querying large caches even though the use of keys in queries is optional. If keys are omitted, then the query may return all cache entries.

There are two ways to limit the number of results that are returned to a client: the start and count matrix parameters and the max-results attribute. Both ways are supported for direct and named queries.

The start and count parameters are optional integer arguments that determine the subset of the results to return. The following example uses the parameters as part of a named query and returns the first 10 entries sorted by name.

http://host:port/persons/minors;start=0;count=10;order=name:asc

The following example uses the parameters as part of a direct query.

GET http://host:port/persons;start=0;count=10?q=age%3C18

The max-results attribute is used within the coherence-rest-config.xml file and explicitly limits how many results are returned to the client. Note that this attribute does not limit the number of entries that are returned from a cache. The following example sets the max-results attribute:

<resource max-results="50">
   <cache-name>persons</cache-name>
   <key-class>java.lang.Integer</key-class>
   <value-class>example.Person</value-class>
   <direct-query enabled="true" max-results="25">
   <query max-results="25">
      <name>minors</name>
      <expression>age &lt; 18</expression>
   </query>
</resource>

The max-results value for a direct or named query overrides the resource's max-results value if both are specified. If a query includes a count parameter and a max-results element is also specified, the lesser value is used.

26.5.5 Retrieving Only Keys

It is possible to retrieve just keys of entries stored in cache. Key operations do not support paging and sorting, therefore those query parameters, if submitted, are ignored. The following key retrieval operations are supported:

GET http://host:port/cacheName/keys

Returns the keys of all entries in the cache.

GET http://host:port/cacheName/keys?q=query

Returns the keys of all entries satisfying the direct query criteria.

GET http://host:port/cacheName/namedQuery/keys

Returns the keys of all entries that satisfy the named query criteria.

26.5.6 Using Custom Query Engines

A query engine executes queries for both direct and named queries. The default query engine executes queries that are expressed using a CohQL syntax (the predicate part of CohQL). Implementing a custom query engine allows the use of different query expression syntaxes or the ability to execute queries against data sources other than Coherence (for example, to query a database for entries that are not present in a cache).

This section includes the following topics:

26.5.6.1 Implementing Custom Query Engines

Custom query engines must implement the com.tangosol.coherence.rest.query.QueryEngine and com.tangosol.coherence.rest.query.Query interfaces. Custom implementations can also extend the com.tangosol.coherence.rest.query.AbstractQueryEngine base class which provides convenience methods for parsing query expression and handling parameter bindings. The base class also supports parameter replacement at execution time and type hints that are submitted as part of the query parameter value. Both parameter names and type hints follow the CohQL specification and can be used for other query engine implementations. For details on specifying parameters and type hints, see "Using Named Queries".

For details on the API, see Java API Reference for Oracle Coherence for the AbstractQueryEngine.ParsedQuery class and the AbstractQueryEngine.parseQueryString(String) and AbstractQueryEngine.createBindings(Map, Map).

The following example is a simple query engine implementation that executes SQL queries directly against a database and forces cache read-through. In reality, a query engine implementation would probably support runtime parameter binding, which is not shown in the example.

public class SqlQueryEngine
   extends AbstractQueryEngine
   {
   protected Connection m_con;
   private static final String DB_DRIVER = "oracle.jdbc.OracleDriver";
   private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:orcl";
   private static final String DB_USERNAME = "username";
   private static final String DB_PASSWORD = "password";
 
   public SqlQueryEngine()
      {
      configureConnection();
      }
 
   @Override
   public Query prepareQuery(String sQuery, Map<String, Object> mapParams)
      {
      ParsedQuery parsedQuery = parseQueryString(sQuery);
      String  sSQL            = createSelectPKQuery(parsedQuery.getQuery());
      return new SqlQuery(sSQL);
      }
 
   protected void configureConnection()
      {
      try
         {
         Class.forName(DB_DRIVER);
         m_con = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
         m_con.setAutoCommit(true);
         }
      catch (Exception e)
         {
         throw new RuntimeException(e);
         }
      }
 
   protected String createSelectPKQuery(String sSQL)
      {
      return "SELECT id,name,age FROM " +
         sSQL.substring(sSQL.toUpperCase().indexOf("FROM") + 4);
      }
 
   private class SqlQuery
      implements Query
      {
      protected String m_sql;

      public SqlQuery(String sql)
         {
         m_sql = sql;
         }
 
      @Override
      public Collection values(NamedCache cache, String sOrder, int nStart, 
         int cResults)
         {
         // force read through
         Set setKeys = keySet(cache);
         return cache.getAll(setKeys).values();
         }
 
      @Override
      public Set keySet(NamedCache cache)
         {
         Set setKeys = new HashSet();
         try
            {
            PreparedStatement stmt   = m_con.prepareStatement(m_sql);
            ResultSet         result = stmt.executeQuery();
            while (result.next())
               {
               Object oKey = result.getLong(1);
               setKeys.add(oKey);
               Person person = new Person(result.getString("name"),
                  result.getInt("age"));
               cache.put(oKey, person);
               }
               stmt.close();
            }
         catch (SQLException e)
            {
            throw new RuntimeException(e);
            }
            return setKeys;
         }
   }
}

26.5.6.2 Enabling Custom Query Engines

Custom query engines are enabled in the coherence-rest-config.xml file. To enable a custom query engine, first register the implementation by adding an <engine> element, within the <query-engines> element, that includes a name for the query engine and the fully qualified name of the implementation class. For more information on the <engine> element, see "engine". For example:

<query-engines>
   <engine>
      <name>SQL-ENGINE</name>
      <class-name>package.SqlQueryEngine</class-name>
   </engine>
</query-engines>

To explicitly specify a custom query engine for a named query or a direct query, add the engine attribute, within a <direct-query> element or a <query> element, that refers to the custom query engine's registered name. For example:

<resource>
   <cache-name>persons</cache-name>
   <key-class>java.lang.Integer</key-class>
   <value-class>example.Person</value-class>
   <query engine="SQL-ENGINE">
      <name>less-than-1000</name>
      <expression>select * from PERSONS where id &lt; 1000</expression>
   </query>
   <direct-query enabled="true" engine="SQL-ENGINE"/>
 </resource>

To make a custom query engine the default query engine, use DEFAULT (uppercase mandatory) as the registered name. The following definition overrides the default CohQL-based query engine and is automatically used whenever an engine attribute is not specified.

<query-engines>
   <engine>
      <name>DEFAULT</name>
      <class-name>package.SqlQueryEngine</class-name>
   </engine>
</query-engines>

26.6 Performing Aggregations with REST

Aggregations can be performed on data in a cache. Coherence REST includes a set of pre-defined aggergators and custom aggregators can be created as required.

The following topics are included in this section:

26.6.1 Aggregation Syntax for REST

The following examples demonstrate how to perform aggregations using REST. If the aggregation succeeds, a 200 (OK) status code returns with the aggregation result as an entity.

  • Aggregates all entries in the cache.

    GET http://host:port/cacheName/aggregator(args, ...)
    
  • Aggregates query results. The query must be specified as a URL-encoded CohQL expression (the predicate part of CohQL).

    GET http://host:port/cacheName/aggregator(args, ...)?q=query
    
    GET http://host:port/cacheName/namedQuery/aggregator(args, ...)?param1=value1
    
  • Aggregates specified entries.

    GET http://host:port/cacheName/(key1, key2, ...)/aggregator(args, ...)
    

Coherence REST provides a simple strategy for aggregator creation (out of aggregator related URL segments). Out-of-box, Coherence REST can resolve any registered (either built-in or user registered) aggregator with a constructor that accepts a single parameter of type com.tangosol.util.ValueExtractor (such as LongMax, DoubleMax, and so on). If an aggregator call within a URL doesn't contain any parameters, the aggregator is created using com.tangosol.util.extractor.IdentityExtractor.

If an aggregator segment within the URL doesn't contain any parameters nor a constructor accepting a single ValueExtractor exists, Coherence REST tries to instantiate the aggregator using a default constructor which is the desired behavior for some built-in aggregators (such as count).

The following example retrieves the oldest person in a cache:

GET http://host:port/people/long-max(age)

The following example calculates the max number in a cache containing only numbers:

GET http://host:port/numbers/comparable-max()

The following example calculates the size of the people cache:

GET http://host:port/people/count()

26.6.2 Listing of Pre-Defined Aggregators

The following pre-defined aggregators are supported:

Aggregator Name Aggregator

big-decimal-average

BigDecimalAverage.class

big-decimal-max

BigDecimalMax.class

big-decimal-min

BigDecimalMin.class

big-decimal-sum

BigDecimalSum.class

double-average

DoubleAverage.class

double-max

DoubleMax.class

double-min

DoubleMin.class

double-sum

DoubleSum.class

long-max

LongMax.class

long-min

LongMin.class

long-sum

LongSum.class

comparable-max

ComparableMax.class

comparable-min

ComparableMin.class

distinct-values

DistinctValues.class

count

Count.class

26.6.3 Creating Custom Aggregators

Custom aggregator types can be defined by specifying a name to be used in the REST URL and a class implementing either the com.tangosol.util.InvocableMap.EntryAggregator interface or the com.tangosol.coherence.rest.util.aggregator.AggregatorFactory interface.

An EntryAggregator implementation is used for simple scenarios when aggregation is either performed on single property or on cache value itself (as most of the pre-defined aggregators do).

The AggregatorFactory interface is used when a more complex creation strategy is required. The implementation must be able to resolve the URL segment containing aggregator parameters and use the parameters to create the appropriate aggregator.

Custom aggregators are configured in the coherence-rest-config.xml file within the <aggregators> elements. See "aggregator" for a detailed reference. The following example configures both a custom EntryAggregator implementation and a custom AggregatorFactory implementation:

<aggregators>
   <aggregator>
      <name>my-simple-aggr</name>
      <class-name>com.foo.MySimpleAggregator</class-name>
   </aggregator>
   <aggregator>
      <name>my-complex-aggr</name>
      <class-name>com.foo.MyAggreagatorFactory</class-name>
   </aggregator>
</aggregators>

26.7 Performing Entry Processing with REST

Entry Processors can be invoked on one or more objects in a cache. Coherence REST includes a set of pre-defined entry processors and custom entry processors can be created as required.

The following topics are included in this section:

26.7.1 Entry Processor Syntax for REST

The following examples demonstrate how to perform entry processing using REST. If the processing succeeds, a 200 (OK) status code returns with the processing result as an entity.

  • Process all entries in the cache.

    POST http://host:port/cacheName/processor(args, ...)
    
  • Process query results.

    POST http://host:port/cacheName/processor(args, ...)?q=query
    
    POST http://host:port/cacheName/namedQuery?param1=value1/processor(args, ...)
    
  • Process specified entries.

    POST http://host:port/cacheName/(key1, key2, ...)/processor (args, ...)
    

Unlike aggregators, processors (even the pre-defined processors) have more diverse creation patterns, so Coherence REST does not assume anything about processor creation. Instead, for each entry processor implementation, there needs to be an implementation of the com.tangosol.coherence.rest.util.processor.ProcessorFactory interface that can handle an input string from a URL section and instantiate the processor instance. Out-of-box, Coherence REST provides two such factories for NumberIncrementor and NumberMultiplier.

The following example increments each person's age in a cache by 5:

POST http://localhost:8080/people/increment(age, 5)

The following example multiplies each number in a cache containing only numbers by the factor 10:

POST http://localhost:8080/numbers/multiply(10)

26.7.2 Listing of Pre-defined Entry Processors

The following pre-defined processors are supported:

Processor Name Processor

increment

A NumberIncrementor instance that always returns the new (incremented) value

post-increment

A NumberIncrementor instance that always returns the old (not incremented) value

multiply

A NumberMultiplier instance that always returns the new (multiplied) value

post-multiply

A NumberMultiplier instance that always returns the old (not multiplied) value

26.7.3 Creating Custom Entry Processors

Custom entry processors can be defined by specifying a name to be used in a REST URL and a class that implements the com.tangosol.coherence.rest.util.processor.ProcessorFactory interface.

Custom entry processors are configured in the coherence-rest-config.xml file within the <processors> elements. See "processors" for a detailed reference. The following example configures a custom ProcesorFactory implementation:

<processors>
   <processor>
      <name>my-processor</name>
      <class-name>com.foo.MyProcessorFactory</class-name>
   </processor>
</processors>

26.8 Understanding Concurrency Control

Coherence REST supports optimistic concurrency only as it maps cleanly to the HTTP protocol. When an application submits a GET request for an object that implements the com.tangosol.util.Versionable interface, the current version identifier is returned in an HTTP ETag (as well as in the representation of the object, assuming the version identifier is included in the JSON/XML serialized form). If the application then submits the same GET request for the resource, but this time with an If-None-Match header with the same ETag value, Coherence REST returns a status of 304, indicating that the application has the latest version of the resource.

Likewise, when an application submits a PUT request to update an object that implements the com.tangosol.util.Versionable interface, Coherence REST performs an update only if the existing and new object versions match; otherwise a 409 Conflict status is returned along with the current object so that the client can reapply the changes and retry.

The following example illustrates these concepts:

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import javax.ws.rs.core.MediaType;
import org.codehaus.jettison.json.JSONObject;

public class ConcurrencyTests
   {
   public static void main(String[] asArg)
      throws Exception
      {
      Client      client      = Client.create();
      String      url         = "http://localhost:" + getPort() + "/dist-test1/2";
      WebResource webResource = client.resource(url);
 
      // perform a GET of a server-side resource that implements Versionable
      ClientResponse response = webResource
         .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class);
      assert 200 == response.getStatus(); /* OK */
 
      // verify that the current version of the resource is 1
      JSONObject json    = new JSONObject(response.getEntity(String.class));
      String     version = json.getString("versionIndicator");
      assert "1".equals(version);
      assert new EntityTag("1").equals(response.getEntityTag());
 
      // perform a conditional GET of the same resource and verify that we
      // get a response status of 304: Not Modified
      response = webResource
         .accept(MediaType.APPLICATION_JSON)
         .header ("If-None-Match", '"' + version + '"').get(ClientResponse.class);
      assert 304 == response.getStatus(); /* Not Modified */
 
      // simulate a version change on the server-side by rolling back the
      // version indicator on our representation of the resource
      json.put("versionIndicator", String.valueOf(0));
 
      // perform a conditional PUT of the same resource and verify that we
      // get a response status of 409: Conflict
      response = webResource
         .accept(MediaType.APPLICATION_JSON)
         .put(ClientResponse.class, json);
      assert 409 == response.getStatus(); /* Conflict */
 
      // retry again with the returned value and verify that we now get a
      // response status of 200: OK
      json = new JSONObject(response.getEntity(String.class));
      response = webResource
         .accept(MediaType.APPLICATION_JSON)
         .put(ClientResponse.class, json);
      assert 200 == response.getStatus(); /* OK */
      }
   }

26.9 Specifying Cache Aliases

Cache aliases are used to specify simplified cache names that are used when a cache name is not ideal for the REST URL path segment. The simplified names are mapped to the real cache names.

To define a cache alias, edit the coherence-rest-config.xml file and include the <name> attribute within the <resource> element whose value is set to a simplified cache name.

The following example creates a cache alias named people for a cache with the name dist-extend-not-ideal-name-for-a-cache*:

<resources>
   <resource name="people">
      <cache-name>dist-extend-not-ideal-name-for-a-cache*</cache-name>
      ...
   </resource>
</resources>

26.10 Using Server-Sent Events

Server-sent events allow Coherence REST applications to automatically receive cache events from the Coherence cluster. For example, events can be received when cache entries are inserted or deleted. For a complete example of using server-sent events, see the Coherence REST examples in Installing Oracle Coherence.

Server-sent events require the use of either the Grizzly HTTP server or the Jetty HTTP server. For details on configuring the Grizzly HTTP server with Coherence REST, see "Using Grizzly HTTP Server." For details about configuring the Jetty HTTP server with Coherence REST, see "Using Jetty HTTP Server." In addition, server-sent events must be supported by your web browser. Refer to your browser documentation for support details.

26.10.1 Receiving Server-Sent Events

Web pages use the EventSource object to receive server-sent events. The EventSource object connects to a specified URI where events are generated and custom EventListeners are added to listen and process the incoming server-sent events. The following code from the Coherence REST example uses JavaScript to create a new EventSource object that listens to the /cache/contacts URI and adds event listeners for insert, update, delete, and error events.

$scope.startListeningContacts = function() {
   $scope.contacts.listening = true;
   $scope.contacts.started   = true;

   if ($scope.contacts.filter == 'all') {
      query = '';
   }
   else if ($scope.contacts.filter == '>=45') {
      query = '?q=age%20>=%2045';
      $scope.contacts.filter = 'age >= 45';
   }
   else {
      query = '?q=age%20<%2045';
      $scope.contacts.filter = 'age < 45';
   }

   $scope.contacts.status  = 'Listening: ' + $scope.contacts.filter;
   var eventSourceContacts = new EventSource('/cache/contacts' + query);

   eventSourceContacts.addEventListener('insert', function(event) {
      $scope.contacts.insertCount++;
      $scope.contacts.allCount++;
      $scope.updateContactEvent(JSON.parse(event.data), 'insert');
      $scope.$apply();
   });

   eventSourceContacts.addEventListener('update', function(event) {
      $scope.contacts.updateCount++;
      $scope.contacts.allCount++;
      $scope.updateContactEvent(JSON.parse(event.data), 'update');
      $scope.$apply();
   });

   eventSourceContacts.addEventListener('delete', function(event) {
      $scope.contacts.deleteCount++;
      $scope.contacts.allCount++;
      $scope.updateContactEvent(JSON.parse(event.data), 'delete');
      $scope.$apply();
   });

   eventSourceContacts.addEventListener('error', function(event) {
      var eventData = JSON.parse(event.data);
      alert('error');
   });
};

When an event is received, an application can choose take some meaningful action based on the event. For example:

$scope.updateContactEvent = function(eventData, eventType) {
   $scope.contacts.eventType = eventType;
   $scope.contacts.eventKey  = eventData.key.firstName + ' ' + 
      eventData.key.lastName;

   $scope.contacts.eventNewValue = 'N/A';
   $scope.contacts.eventOldValue = 'N/A';

   if (eventType == 'insert' || eventType == 'update') {
      $scope.contacts.eventNewValue = $scope.getContactString(eventData.newValue);
   }
   if (eventType == 'delete' || eventType == 'update') {
      $scope.contacts.eventOldValue = $scope.getContactString(eventData.oldValue);
   }
};