Difference between revisions of "Ic-client"

From Gcube Wiki
Jump to: navigation, search
(First Examples)
(First Examples)
Line 34: Line 34:
 
= First Examples =
 
= First Examples =
  
We introduce the API of the <code>ic-client</code>with an example that shows how to submit a query for endpoints of generic services:
+
We introduce the API of the <code>ic-client</code> showing how to submit a query for service endpoints:
  
 
<source lang="java5">
 
<source lang="java5">
Line 50: Line 50:
 
</source>
 
</source>
  
Here, <code>queryFor()</code> and <code>clientFor()</code> are static factory methods of the <code>ICFactory</code> class, which we import for convenience (cf <code>importa static ...*</code>). The first method gives us a pre-prepared <code>Query</code> for service endpoints, which we denote with the corresponding class of the object model in <code>common-gcore-resources</code>. The second method gives us a <code>DiscoveryClient</code> which can execute the query and parse result as instances of the <code>ServiceEndpoint</code> class. Finally, we ask the client to execute the query and collect the parsed results.
+
Here, <code>queryFor()</code> and <code>clientFor()</code> are static factory methods of the <code>ICFactory</code> class, which we import for improved legibility of the code (cf <code>import static ...*</code>). The first method gives us a predefined <code>Query</code> for service endpoints, which we lookup with the corresponding class in the object model of <code>common-gcore-resources</code>. Had we wanted to query the <code>Information Collector</code> for, say, hosting nodes we would have used the <code>HostingNode</code> class of the  object model. The second method gives us a <code>DiscoveryClient</code> which can execute the query and parse its results as instances of the <code>ServiceEndpoint</code> class. Finally, we ask the client to execute the query and collect the parsed results.
  
Note that we use use the <code>ServiceEndpoint</code> class twice, once to indicate the what we want to query and then again to indicate we want the results parsed. This may appear redundant until we realise that the <code>ic-client</code> allows to separate ''what'' we query from ''how'' we process the results. In this case, <code>ServiceEndpoint</code> identifies both query and parser, but this does not need to be always the case.
+
Thus interaction with the <code>ic-client</code> is two-phased. In the first phase we build a query, here one predefined for a given resource types. In the second case we execute the query with a <code>DiscoveryClient</code> that returns the results in the form we want.
  
In the following example, we illustrate this separation by avoiding result parsing altogether.
+
Note that we use use the <code>ServiceEndpoint</code> class twice, a first time to lookup predefined queries and a second time to indicate how we want the results parsed. This may appear redundant until we realise that the <code>ic-client</code> allows us to separate ''what'' we want to query from ''how'' we want the results to be returned. In this case, <code>ServiceEndpoint</code> identifies both query and parsing strategy, but this does not need to be always the case.
We also show how queries can be refined to filter out some service endpoints and return only their addresses:
+
 
 +
In the following example, we illustrate this separation by customising the predefined query so that it returns only the addresses of database service endpoints. Since the results are plain strings our choice of parser is not to have one at all:
  
 
<source lang="java5">
 
<source lang="java5">
Line 63: Line 64:
 
...
 
...
  
SimpleXQuery query = queryFor(ServiceEndpoint.class);
+
SimpleQuery query = queryFor(ServiceEndpoint.class);
  
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
Line 74: Line 75:
 
</source>
 
</source>
  
Once again we obtain a pre-defined query for service endpoints, but this type we type it under a more specific interface, <code>SimpleXQuery</code>, which enables customisations. We first add an XQuery condition -- the query language of the <code>Information Collector</code> -- which filters out endpoints that do not give access to databases. We then customise the result expression to get back only their addresses. In both cases, we rely on the resource schema for service endpoints to formulate expressions, and convene to use <code>$resource</code> as a variable to range over target resources. We remain otherwise oblivious to the structure of the XML database in which the <code>Information Collector</code> keeps resource descriptions.
+
We lookup the predefined query as we did earlier, but this time we type it under a more specific interface, <code>SimpleQuery</code>, which enables customisations. We first add an XQuery condition -- the query language of the <code>Information Collector</code> -- which filters out endpoints that do not give access to databases. We then customise the result expression to get back only their addresses. For both tasks, we rely on the resource schema for service endpoints to formulate expressions, and adhere to the (documented) convention to use <code>$resource</code> as a variable ranging over target resources.  
  
In the next example, we focus on larger portions of service endpoints and retrieve not only addresses but all the information available about access. Hence we reverse to parsing, but pass this time no kore and no less than the JAXB class of the resource model that binds to the retrieved portion of resources:
+
In the next example, we focus on larger portions of service endpoint descriptions, and retrieve not only addresses but all the available access information. We thus return to parsing results, but this time pass the class of the resource model that describes the resource properties that we want retrieved, rather than the top-level class:
  
 
<source lang="java5">
 
<source lang="java5">
Line 84: Line 85:
 
...
 
...
  
SimpleXQuery query = queryFor(ServiceEndpoint.class);
+
SimpleQuery query = queryFor(ServiceEndpoint.class);
  
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
Line 106: Line 107:
 
...
 
...
  
SimpleXQuery query = queryFor(ServiceEndpoint.class);
+
SimpleQuery query = queryFor(ServiceEndpoint.class);
  
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");

Revision as of 23:18, 23 November 2012

The ic-client is a client library for the Information Collector service. It helps clients formulating queries for gCube resource descriptions, submitting them to the service, and processing their results.

Similar facilities for resource discovery are traditionally provided by the gCube Application Framework (gCF) and the IS Client library. The ic-client improves over the latter in a number of ways, most noticeably:

  • it is completely independent from the gCore stack.
the ic-client can be easily embedded in a variety of client runtimes without out-of-band installation or configuration requirements. Clients may be external to gCube, or they may be 2nd-generation gCube services developed and running on stacks other than gCore stack. In this sense, the ic-client is a key part of the Featherweight Stack for gCube clients.
  • it helps formulating a wider range of queries based only on knowledge of resource schemas.
simple queries can be configured with custom namespaces and custom result expressions. As a result, clients can retrieve only parts of resource descriptions or arbitrary combinations of parts. Fine-grained results are more easily processed and improve the performance of both clients and service.
  • it offers increased flexibility in how query results are processed.
clients can configure how results ought to be parsed, or else take direct responsibility for parsing them. For example, clients may configure their own JAXB object bindings, while preprepared bindings for whole resource descriptions or specific properties thereof are readily available.

The ic-client is available in our Maven repositories with the following coordinates:

<artifactId>ic-client</artifactId>
<groupId>org.gcube.resources.discovery</groupId>
<version>...</version>

The library depends on a small set of components of the Featherweight Stack. Among these, the following are visible to library clients:

  • common-gcore-resources: the object-based implementation of the gCube resource model.
clients may and normally will use the classes in common-gcore-resources to parse and process query results.
  • discovery-client: a layer of interfaces and abstract implementations for queries and query submission API.
ic- client customises this layer for queries to the Information Collector.

Note: in what follows, we blur the distinction between the ic- client and the discovery-client. The distinction reflects modular choices for the design of the library but is otherwise of little consequence for its clients. The visibility of the discovery-client is limited only to the package of certain components that we discuss below, which starts with org.gcube.resource.discovery.client. The components of the ic-client are instead in packages that start with org.gcube.discovery.icclient.

First Examples

We introduce the API of the ic-client showing how to submit a query for service endpoints:

import static org.gcube.resources.discovery.icclient.ICFactory.*;
 
...
 
Query query = queryFor(ServiceEndpoint.class);
 
DiscoveryClient<ServiceEndpoint> client = clientFor(ServiceEndpoint.class);
 
List<ServiceEndpoint> resources = client.execute(query);

Here, queryFor() and clientFor() are static factory methods of the ICFactory class, which we import for improved legibility of the code (cf import static ...*). The first method gives us a predefined Query for service endpoints, which we lookup with the corresponding class in the object model of common-gcore-resources. Had we wanted to query the Information Collector for, say, hosting nodes we would have used the HostingNode class of the object model. The second method gives us a DiscoveryClient which can execute the query and parse its results as instances of the ServiceEndpoint class. Finally, we ask the client to execute the query and collect the parsed results.

Thus interaction with the ic-client is two-phased. In the first phase we build a query, here one predefined for a given resource types. In the second case we execute the query with a DiscoveryClient that returns the results in the form we want.

Note that we use use the ServiceEndpoint class twice, a first time to lookup predefined queries and a second time to indicate how we want the results parsed. This may appear redundant until we realise that the ic-client allows us to separate what we want to query from how we want the results to be returned. In this case, ServiceEndpoint identifies both query and parsing strategy, but this does not need to be always the case.

In the following example, we illustrate this separation by customising the predefined query so that it returns only the addresses of database service endpoints. Since the results are plain strings our choice of parser is not to have one at all:

import static org.gcube.resources.discovery.icclient.ICFactory.*;
 
...
 
SimpleQuery query = queryFor(ServiceEndpoint.class);
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
query.setResult("$resource/Profile/AccessPoint/Interface/Endpoint/text()");
 
DiscoveryClient<String> client = client();
 
List<String> addresses = client.execute(query);

We lookup the predefined query as we did earlier, but this time we type it under a more specific interface, SimpleQuery, which enables customisations. We first add an XQuery condition -- the query language of the Information Collector -- which filters out endpoints that do not give access to databases. We then customise the result expression to get back only their addresses. For both tasks, we rely on the resource schema for service endpoints to formulate expressions, and adhere to the (documented) convention to use $resource as a variable ranging over target resources.

In the next example, we focus on larger portions of service endpoint descriptions, and retrieve not only addresses but all the available access information. We thus return to parsing results, but this time pass the class of the resource model that describes the resource properties that we want retrieved, rather than the top-level class:

import static org.gcube.resources.discovery.icclient.ICFactory.*;
 
...
 
SimpleQuery query = queryFor(ServiceEndpoint.class);
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
query.setResult("$resource/Profile/AccessPoint");
 
DiscoveryClient<AccessPoint> client = client(AccessPoint.class);
 
List<AccessPoint> accesspoints = client.execute(query);
 
for (AccessPoint point : accesspoints) {
    ...point.name()....point.address()....
}

Finally, we show how different parts of service endpoint descriptions, such as access points and identifiers, can be composed together and conveniently parsed:

import static org.gcube.resources.discovery.icclient.ICFactory.*;
 
...
 
SimpleQuery query = queryFor(ServiceEndpoint.class);
 
query.addCondition("$resource/Profile/Category/text() eq 'Database'");
query.setResult("<perfect>" +
		            "<id>{$resource/ID/text()}</id>" +
			    "{$resource/Profile/AccessPoint}" +
			  "</perfect>");
 
DiscoveryClient<PerfectResult> client = clientFor(PerfectResult.class);
 
List<PerfectResult> results = client.execute(query);
 
for (PerfectResult result : results) {
	...result.id...result.ap);
}

where PerfectResult is the simple bean defined as:

@XmlRootElement(name="perfect")
class PerfectResult {
 
   @XmlElement(name="id")
   String id;
 
    @XmlElementRef
    AccessPoint ap;
}

Here the results have custom shape hence we take responsibility for producing what is to us the perfect result binding, i.e. the binding that makes it easiest for us to work with results. We declare the binding as a JAXB-annotated bean which reuses similar beans defined in common-gcore-resources as part of the resource model. We then pass the bean the discovery client after having customised the result expression to return the XML serialisations of the bean.

Queries

Query Submission