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:
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.
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>
The REST API includes support for performing GET, PUT, and DELETE operations on a single object in a cache.
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 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 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.
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 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 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.
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"}
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:
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.
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 < 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 25-1 lists the supported type hints.
Table 25-1 Parameter Type Hints
Hint | Type |
---|---|
i, int |
|
s, short |
|
l, long |
|
f, float |
|
d, double |
|
I |
|
D |
|
date |
|
uuid |
|
uid |
|
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)
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
Queries that are unrestricted or executed against large caches can potentially return large result sets that may consume a client's available memory. There are two ways to limit the size of a query: using the start
and count
matrix parameters and using the max-results
attribute. Both methods 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 a query or a resource to a maximum number of results regardless of how many are requested by the client. For example:
<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 < 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.
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.
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:
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; } } }
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 < 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>
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:
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()
The following pre-defined aggregators are supported:
Aggregator Name | Aggregator |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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>
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:
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:
GET http://localhost:8080/people/increment(age, 5)
The following example multiplies each number in a cache containing only numbers by the factor 10:
GET http://localhost:8080/numbers/multiply(10)
The following pre-defined processors are supported:
Processor Name | Processor |
---|---|
|
A |
|
A |
|
A |
|
A |
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>
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 */ } }
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>