Difference between revisions of "Ic-client"

From Gcube Wiki
Jump to: navigation, search
(First Examples)
(Queries)
Line 144: Line 144:
  
 
= Queries =
 
= Queries =
 +
 +
In the examples [[#First Examples|above]], we have introduced two interfaces for queries, <code>Query</code> and <code>SimpleQuery</code>. We discuss the interfaces and their implementations below, starting from the most generic of the two.
 +
 +
== Generic Queries ==
 +
 +
<code>Query</code> defines a read-only API, exposing only the textual expression of the query. 
 +
 +
<source lang="java5">
 +
 +
public interface Query {
 +
String expression();
 +
}
 +
 +
</source>
 +
 +
The interface is thus only suitable to clients that consume queries. The <code>DiscoveryClient</code> which submits queries to the <code>Information Collector</code> is one such client. More generally, this is a good type to move queries from the place of construction to the place of consumption.
 +
 +
The simplest <code>Query</code> implementation is no more than a box for the textual expression of the query:
 +
 +
<code>Query q = new QueryBox("….a query…");</code>
 +
 +
We resort to box a query whenever we cannot build it with higher-level facilities, such as those discussed below. Normally, this indicates that the query is too complex to be constructed in code. Then we can always write it as a string, accepting full exposure to the details of the target database. For <code>ic-client</code>, this means to know the details of the database in which the <code>Information Collector</code> stores resource descriptions.
 +
 +
==Templated Queries==
 +
 +
<code>QueryTemplate</code> is a first implementation of <code>Query</code> that introduces the notion of query building. The idea is to derive a query from a template by filling named holes with equally named parameters. Who instantiates a <code>QueryTemplate</code> provides the template:
 +
 +
<code>QueryTemplate template = new QueryTemplate("….template…");</code>
 +
 +
and who uses the instance fills the holes, ignoring other details of the query:
 +
 +
<code>template.addParameter("name","value");</code>
 +
 +
Then, whenever a consumer invokes <code>expression()</code> on the <code>QueryTemplate</code>, the parameters are interpolated and the complete query expression returned.
 +
 +
The approach makes sense whenever there is a natural separation between object creators and object consumers. The first defines the template and sees the full structure of the target database, but the latter works with a simpler picture. The target use case is for library code built on top of the <code>ic-client</code>. The <code>ic-client</code> itself defines all the predefined queries for resource types as <code>QueryTemplate</code>s.
 +
 +
If we are not writing library code, however, <code>QueryTemplate</code>s are of little use and we can move on to the facilities discussed [[#Simple Queries|next]]. Otherwise, let us look at templates in more details.
 +
 +
Templates are strings with empty XML elements, optionally with a def attribute. Here's a template for an unlikely query language:
 +
 +
<code>all results that satisfy <cond1/> or <cond2 def='that'/> </extra></code>
 +
 +
Whenever <code>expression()</code> is invoked, the empty elements in the template are replaced according to the first rule that applies among the following:
 +
 +
* by the value of an equally named parameter, if one exists
 +
* by the value of the def attribute, if one exists
 +
*by the empty string
 +
 
 +
For example, after adding the single parameter <code>cond1="this"</code> to the template above, <code>expression()</code> returns the string:
 +
 +
<code>all results that satisfy this or that</code>
 +
 +
<code>QueryTemplate</code>s are mainly intended for subclassing rather than directly for client use. The subclass is expected to present a more typed interface to clients, where the parameters are added behind dedicated setters, e.g.:
 +
 +
<source lang=java5>
 +
 +
public class MyQuery extends QueryTemplate {
 +
 +
public MyQuery() {
 +
super("..mytemplate");
 +
}
 +
        ….
 +
setCond(String cond1) { …addParameter("cond1",cond1)…..}
 +
setCond2(String cond2) { …addParameter("cond2",cond2)…..}
 +
setExtra(String value) {…addParameter("extra",extra)…..}
 +
        …...
 +
}
 +
 +
</source>
 +
 +
Indeed, we discuss [[#Simple Queries|below]] one such subclass and the API that it presents to clients.
 +
 +
 +
Note finally that, for added flexibility, <code>QueryTemplate</code> defines a constructor that allows a subclass to provide an initial set of parameters:
 +
 +
<code>QueryTemplate(String template, Map<String,String> parameters);</code>
 +
 +
Thus a family of subclasses can share the same generic template, partially interpolate it at construction time, and offer many different specialisations to their clients. We can see this approach in action in the next section.
 +
 +
==Simple Queries==
 +
 +
As shown in our [[#First Examples||firsts examples]], a <code>SimpleQuery</code> allows clients to customise a query in terms of namespaces, conditions, and result expressions, elements that tend to recur across many query languages:
 +
 +
<source lang="java5">
 +
public interface SimpleQuery extends Query {
 +
 +
void addCondition(String condition);
 +
void addNamespace(String prefix, URI uri);
 +
void setResult(String expression);
 +
void clearConditions();
 +
}
 +
 +
</source>
 +
 +
Naturally, the <code>ic-client</code> includes an implementation of <code>SimpleQuery</code> for the XQuery language, the language of the <code>Information Collector</code>. <code>XQuery</code> implements the interface over a simple query template, extending the <code>QueryTemplate</code> discussed [[#Templated Queries|above]] for the purpose:
 +
 +
<code>public class XQuery extends QueryTemplate implements SimpleQuery {…}</code>
 +
 +
The template declares a number of namesapaces and a single variable, <code>$result</code>. the <code>ic-client</code> then pre-defines a number of <code>XQuery</code> instances where the template is specialised so that <code>$result</code> ranges over different resource types. When we invoke the method <code>queryFor()</code> of the <code>ICFactory</code> we get back the predefined <code>XQuery</code> for a given resource type.
 +
 +
In conclusion, we two main options for building queries:
 +
 +
* we can use pre-defined queries and customise them, as in the examples above;
 +
* we can directly box more complex textual expressions inside QueryBoxes
 +
 +
In addition, if we are writing libraries:
 +
 +
* we can define our own query templates and present them as <code>QueryTemplate</code> instances to our own clients, typically under ad-hoc APIs like <code>SimpleQuery</code>'s;
  
 
= Query Submission =
 
= Query Submission =

Revision as of 11:39, 24 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. Once the results are back we can navigate through the ServiceEndpoint instances to get to the information we need. For this, we need to acquire familiarity with the resource model, nothing that some documentation and a good IDE cannot help us with.

Thus interaction with the ic-client is two-phased. In the first phase we build a query, here a predefined one for a given resource type. In the second phase 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 appears 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 now plain strings our choice of parser is to have none at all. Instead of the method ICFactory#clientFor(Class), we use the method ICFactory#client():

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. The condition 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 schema of service endpoint descriptions to formulate expressions, and adhere to the (documented) convention to use $resource as a variable ranging over target resources.

In the next example, we move to middle ground: rather than looking for whole resource descriptions or individual strings we focus on selected parts of descriptions, e.g. we retrieve all the available access information. We thus return to needing result parsing, 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()....
}

It's now clear that the resource model classes that we pass drive a generic parser embedded in the client. The classes of the model are in fact decorated with JAXB annotations, and these annotations define the binding of classes from XML.


In our last example, we show how different parts of service endpoint descriptions, say access points and identifiers, can be composed together to form the desired results. We then show how these ad-hoc combination can be 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 we give results have custom shape, hence we cannot cherry pick the class to be used for parsing from the resource model. Rather, we define it as our PerfectResult, a very simple bean which we decorate with annotations to drive the underlying JAXB parser. Note that the bean reuses AccessPoint from the resource model, we do not need to reinvent the wheel. We then pass the bean to the discovery client and collect results as its instances.

Queries

In the examples above, we have introduced two interfaces for queries, Query and SimpleQuery. We discuss the interfaces and their implementations below, starting from the most generic of the two.

Generic Queries

Query defines a read-only API, exposing only the textual expression of the query.

public interface Query {
 String expression();
}

The interface is thus only suitable to clients that consume queries. The DiscoveryClient which submits queries to the Information Collector is one such client. More generally, this is a good type to move queries from the place of construction to the place of consumption.

The simplest Query implementation is no more than a box for the textual expression of the query:

Query q = new QueryBox("….a query…");

We resort to box a query whenever we cannot build it with higher-level facilities, such as those discussed below. Normally, this indicates that the query is too complex to be constructed in code. Then we can always write it as a string, accepting full exposure to the details of the target database. For ic-client, this means to know the details of the database in which the Information Collector stores resource descriptions.

Templated Queries

QueryTemplate is a first implementation of Query that introduces the notion of query building. The idea is to derive a query from a template by filling named holes with equally named parameters. Who instantiates a QueryTemplate provides the template:

QueryTemplate template = new QueryTemplate("….template…");

and who uses the instance fills the holes, ignoring other details of the query:

template.addParameter("name","value");

Then, whenever a consumer invokes expression() on the QueryTemplate, the parameters are interpolated and the complete query expression returned.

The approach makes sense whenever there is a natural separation between object creators and object consumers. The first defines the template and sees the full structure of the target database, but the latter works with a simpler picture. The target use case is for library code built on top of the ic-client. The ic-client itself defines all the predefined queries for resource types as QueryTemplates.

If we are not writing library code, however, QueryTemplates are of little use and we can move on to the facilities discussed next. Otherwise, let us look at templates in more details.

Templates are strings with empty XML elements, optionally with a def attribute. Here's a template for an unlikely query language:

all results that satisfy <cond1/> or <cond2 def='that'/> </extra>

Whenever expression() is invoked, the empty elements in the template are replaced according to the first rule that applies among the following:

  • by the value of an equally named parameter, if one exists
  • by the value of the def attribute, if one exists
  • by the empty string

For example, after adding the single parameter cond1="this" to the template above, expression() returns the string:

all results that satisfy this or that

QueryTemplates are mainly intended for subclassing rather than directly for client use. The subclass is expected to present a more typed interface to clients, where the parameters are added behind dedicated setters, e.g.:

public class MyQuery extends QueryTemplate {
 
	public MyQuery() {
		super("..mytemplate");
	}
        ….
	setCond(String cond1) { …addParameter("cond1",cond1)…..}
	setCond2(String cond2) { …addParameter("cond2",cond2)…..}
	setExtra(String value) {…addParameter("extra",extra)…..}
        …...
}

Indeed, we discuss below one such subclass and the API that it presents to clients.


Note finally that, for added flexibility, QueryTemplate defines a constructor that allows a subclass to provide an initial set of parameters:

QueryTemplate(String template, Map<String,String> parameters);

Thus a family of subclasses can share the same generic template, partially interpolate it at construction time, and offer many different specialisations to their clients. We can see this approach in action in the next section.

Simple Queries

As shown in our |firsts examples, a SimpleQuery allows clients to customise a query in terms of namespaces, conditions, and result expressions, elements that tend to recur across many query languages:

public interface SimpleQuery extends Query {
 
 void addCondition(String condition);
 void addNamespace(String prefix, URI uri);
 void setResult(String expression);
 void clearConditions();
}

Naturally, the ic-client includes an implementation of SimpleQuery for the XQuery language, the language of the Information Collector. XQuery implements the interface over a simple query template, extending the QueryTemplate discussed above for the purpose:

public class XQuery extends QueryTemplate implements SimpleQuery {…}

The template declares a number of namesapaces and a single variable, $result. the ic-client then pre-defines a number of XQuery instances where the template is specialised so that $result ranges over different resource types. When we invoke the method queryFor() of the ICFactory we get back the predefined XQuery for a given resource type.

In conclusion, we two main options for building queries:

  • we can use pre-defined queries and customise them, as in the examples above;
  • we can directly box more complex textual expressions inside QueryBoxes

In addition, if we are writing libraries:

  • we can define our own query templates and present them as QueryTemplate instances to our own clients, typically under ad-hoc APIs like SimpleQuery's;

Query Submission