This chapter includes the following sections:
Parent topic: Using Coherence REST
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.
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>
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. 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.
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.
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.
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.
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"}
The section includes the following topics:
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 Filtering Entries in a Result Set in Developing Applications with Oracle Coherence. 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 Filtering Entries in a Result Set in Developing Applications with Oracle Coherence. 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. 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 26-1 lists the supported type hints.
Table 26-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. 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 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 < 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. See Using Named Queries.
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. 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>
This section includes the following topics:
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. 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>
This section includes the following topics:
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)
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. The following example configures a custom ProcesorFactory
implementation:
<processors> <processor> <name>my-processor</name> <class-name>com.foo.MyProcessorFactory</class-name> </processor> </processors>
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 */ } }
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>
Server-sent events require the use of either the Grizzly HTTP server or the Jetty HTTP server. See Using Grizzly HTTP Server and Using Jetty HTTP Server, respectively. In addition, server-sent events must be supported by your web browser. Refer to your browser documentation for support details.
This section includes the following topic:
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); } };