branch for 2.0.x releases (first release in gCube 2.10.0)

git-svn-id: http://svn.research-infrastructures.eu/public/d4science/gcube/branches/common/common-clients/2.0@57618 82a268e6-3cf1-43bd-a215-b396298e98cf
master
fabio.simeoni 12 years ago
commit 6b65acf8a8

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>common-clients</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

@ -0,0 +1,6 @@
gCube System - License
------------------------------------------------------------
The gCube/gCore software is licensed as Free Open Source software conveying to the EUPL (http://ec.europa.eu/idabc/eupl).
The software and documentation is provided by its authors/distributors "as is" and no expressed or
implied warranty is given for its use, quality or fitness for a particular case.

@ -0,0 +1,2 @@
* Fabio Simeoni (fabio.simeoni@fao.org), FAO of the UN, Italy
* Rena Tsantouli (e.tsantoylh@di.uoa.gr), University of Athens, Greece

@ -0,0 +1,38 @@
The gCube System - ${name}
----------------------
This work has been partially supported by the following European projects: DILIGENT (FP6-2003-IST-2), D4Science (FP7-INFRA-2007-1.2.2),
D4Science-II (FP7-INFRA-2008-1.2.2), iMarine (FP7-INFRASTRUCTURES-2011-2), and EUBrazilOpenBio (FP7-ICT-2011-EU-Brazil).
Authors
-------
* Fabio Simeoni (fabio.simeoni@fao.org), FAO of the UN, Italy.
Version and Release Date
------------------------
${version}
Description
-----------
${description}
Download information
--------------------
Source code is available from SVN:
${scm.url}
Binaries can be downloaded from:
Documentation
-------------
Documentation is available on-line from the Projects Documentation Wiki:
https://https://gcube.wiki.gcube-system.org/gcube/index.php/Integration_and_Interoperability_Facilities_Framework:_Client_Libraries_Framework
Licensing
---------
This software is licensed under the terms you may find in the file named "LICENSE" in this directory.

@ -0,0 +1,8 @@
<ReleaseNotes>
<Changeset component="${build.finalName}" date="2012-04-27">
<Change>First Release</Change>
</Changeset>
<Changeset component="${build.finalName}" date="2012-09-01">
<Change>Rewritten as a framework for the implementation of client libraries that comply with the CL Design Model</Change>
</Changeset>
</ReleaseNotes>

@ -0,0 +1,42 @@
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>servicearchive</id>
<formats>
<format>tar.gz</format>
</formats>
<baseDirectory>/</baseDirectory>
<fileSets>
<fileSet>
<directory>${distroDirectory}</directory>
<outputDirectory>/</outputDirectory>
<useDefaultExcludes>true</useDefaultExcludes>
<includes>
<include>README</include>
<include>LICENSE</include>
<include>INSTALL</include>
<include>MAINTAINERS</include>
<include>changelog.xml</include>
</includes>
<fileMode>755</fileMode>
<filtered>true</filtered>
</fileSet>
</fileSets>
<files>
<file>
<source>${distroDirectory}/profile.xml</source>
<outputDirectory>/</outputDirectory>
<filtered>true</filtered>
</file>
<file>
<source>target/${build.finalName}.jar</source>
<outputDirectory>/${artifactId}</outputDirectory>
</file>
<file>
<source>${distroDirectory}/svnpath.txt</source>
<outputDirectory>/${artifactId}</outputDirectory>
<filtered>true</filtered>
</file>
</files>
</assembly>

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ID />
<Type>Service</Type>
<Profile>
<Description>${description}</Description>
<Class>Common</Class>
<Name>${artifactId}</Name>
<Version>1.0.0</Version>
<Packages>
<Software>
<Name>${artifactId}</Name>
<Version>${version}</Version>
<MavenCoordinates>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
</MavenCoordinates>
<Files>
<File>${build.finalName}.jar</File>
</Files>
</Software>
</Packages>
</Profile>
</Resource>

@ -0,0 +1 @@
${scm.url}

@ -0,0 +1,117 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>maven-parent</artifactId>
<groupId>org.gcube.tools</groupId>
<version>1.0.0</version>
<relativePath />
</parent>
<groupId>org.gcube.core</groupId>
<artifactId>common-clients</artifactId>
<version>2.0.0-SNAPSHOT</version>
<name>Common Clients</name>
<description>A framework for client APIs</description>
<properties>
<distroDirectory>distro</distroDirectory>
</properties>
<scm>
<connection>scm:svn:http://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/common-clients</connection>
<developerConnection>scm:svn:https://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/common-clients</developerConnection>
<url>http://svn.d4science.research-infrastructures.eu/gcube/trunk/Common/common-clients</url>
</scm>
<dependencies>
<dependency>
<groupId>org.gcube.core</groupId>
<artifactId>common-scope</artifactId>
<version>[1.0.0-SNAPSHOT,2.0.0-SNAPSHOT)</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.8.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.5</version>
<executions>
<execution>
<id>copy-profile</id>
<phase>install</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target</outputDirectory>
<resources>
<resource>
<directory>${distroDirectory}</directory>
<filtering>true</filtering>
<includes>
<include>profile.xml</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${distroDirectory}/descriptor.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>servicearchive</id>
<phase>install</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,26 @@
package org.gcube.common.clients;
/**
* A call to an endpoint of a given service.
*
* <p>
*
* Calls interact with service endpoints at addresses provided by clients.
*
* @author Fabio Simeoni
*
* @param <S> the type of service stubs
* @param <R> the type of values returned from the call
*
*/
public interface Call<S, R> {
/**
* Calls a given service endpoint.
*
* @param address a proxy of the endpoint
* @return the value returned by the call
* @throws Exception if the call fails
*/
R call(S endpoint) throws Exception;
}

@ -0,0 +1,130 @@
package org.gcube.common.clients.builders;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.config.DiscoveryConfig;
import org.gcube.common.clients.config.EndpointConfig;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.delegates.DirectDelegate;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
import org.gcube.common.clients.delegates.ProxyDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
/**
* Partial implementation of proxy builders.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
* @param <P> the type of service proxies
*/
public abstract class AbstractBuilder<A,S,P> {
/**
* Default proxy timeout.
*/
public static final int defaultTimeout = (int)TimeUnit.SECONDS.toMillis(10);
private final ProxyPlugin<A,S,P> plugin;
private Query<A> query;
private W3CEndpointReference address;
private final EndpointCache<A> cache;
private final Map<String,Object> properties = new HashMap<String, Object>();
/**
* Constructs an instance with a given {@link ProxyPlugin}, and {@link EndpointCache}, and zero or more default {@link Property}s.
* @param plugin the plugin
* @param cache the cache
* @param properties the properties
*/
protected AbstractBuilder(ProxyPlugin<A,S,P> plugin,EndpointCache<A> cache,Property<?> ... properties) {
this.plugin=plugin;
this.cache=cache;
//sets default timeout, may be overridden by custom properties below
setTimeout(defaultTimeout);
//add custom properties
for (Property<?> property : properties)
addProperty(property);
}
/**
* Returns the {@link ProxyPlugin}.
* @return the plugin
*/
protected ProxyPlugin<A,S,P> plugin() {
return plugin;
}
/**
* Sets the query.
* @param query the query
*/
protected void setQuery(Query<A> query) {
this.query = query;
}
/**
* Sets the timeout.
* @param timeout the timout
*/
protected void setTimeout(int timeout) {
addProperty(Property.timeout(timeout));
}
/**
* Sets the address.
* @param address the address
*/
protected void setAddress(W3CEndpointReference address) {
this.address=address;
}
/**
* Adds a custom property.
* @param property the property
*/
protected void addProperty(Property<?> property) {
properties.put(property.name(),property.value());
}
//shared among subclasses
public P build() {
ProxyDelegate<S> delegate = null;
if (address==null) {
DiscoveryConfig<A,S> config =
new DiscoveryConfig<A,S>(plugin,query,cache);
for (Entry<String,Object> prop : properties.entrySet())
config.addProperty(prop.getKey(),prop.getValue());
delegate = new DiscoveryDelegate<A,S>(config);
}
else {
EndpointConfig<A,S> config = new EndpointConfig<A,S>(plugin,convertAddress(address));
for (Entry<String,Object> prop : properties.entrySet())
config.addProperty(prop.getKey(),prop.getValue());
delegate = new DirectDelegate<A,S>(config);
}
return plugin.newProxy(delegate);
}
/**
* Converts a {@link W3CEndpointReference} in a service address.
* @param address the address as a {@link W3CEndpointReference}
* @return the converted address
*/
protected abstract A convertAddress(W3CEndpointReference address);
protected abstract String contextPath();
}

@ -0,0 +1,77 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.gcube.common.clients.builders.SingletonBuilderAPI.Builder;
import org.gcube.common.clients.builders.SingletonBuilderAPI.FinalClause;
import org.gcube.common.clients.builders.SingletonBuilderAPI.SecondClause;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
/**
* Partial implementation of proxy builders for singleton services, i.e. stateful services with a known single instance.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
* @param <P> the type of service proxies
*/
public abstract class AbstractSingletonBuilder<A,S,P> extends AbstractBuilder<A,S,P> implements Builder<A,P>,SecondClause<P>,FinalClause<P> {
/**
* Constructs an instance with a given {@link ProxyPlugin}, and {@link EndpointCache}, and zero or more default {@link Property}s.
* @param plugin the plugin
* @param plugin the cache
* @param properties the properties
*/
protected AbstractSingletonBuilder(ProxyPlugin<A,S,P> plugin, EndpointCache<A> cache,Property<?> ... properties) {
super(plugin,cache,properties);
}
@Override
public SecondClause<P> matching(Query<A> query) {
setQuery(query);
return this;
};
@Override
public SecondClause<P> at(String host, int port) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), host, port));
return this;
}
@Override
public SecondClause<P> at(URL address) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), address));
return this;
}
@Override
public SecondClause<P> at(URI address) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), address));
return this;
}
@Override
public FinalClause<P> withTimeout(int duration, TimeUnit unit) {
setTimeout((int) unit.toMillis(duration));
return this;
}
@Override
public SecondClause<P> with(Property<?> property) {
addProperty(property);
return this;
}
@Override
public <T> SecondClause<P> with(String name, T value) {
addProperty(new Property<T>(name, value));
return this;
}
}

@ -0,0 +1,85 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import org.gcube.common.clients.builders.StatefulBuilderAPI.Builder;
import org.gcube.common.clients.builders.StatefulBuilderAPI.FinalClause;
import org.gcube.common.clients.builders.StatefulBuilderAPI.SecondClause;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
/**
* Partial implementation of proxy builders for stateful services.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
* @param <P> the type of service proxies
*/
public abstract class AbstractStatefulBuilder<A,S,P> extends AbstractBuilder<A,S,P> implements Builder<A,P>,SecondClause<P>,FinalClause<P> {
/**
* Constructs an instance with a given {@link ProxyPlugin}, and {@link EndpointCache}, and zero or more default {@link Property}s.
* @param plugin the plugin
* @param plugin the cache
* @param properties the properties
*/
protected AbstractStatefulBuilder(ProxyPlugin<A,S,P> plugin, EndpointCache<A> cache,Property<?> ... properties) {
super(plugin,cache,properties);
}
@Override
public SecondClause<P> matching(Query<A> query) {
setQuery(query);
return this;
};
@Override
public SecondClause<P> at(W3CEndpointReference address) {
setAddress(address);
return this;
}
@Override
public SecondClause<P> at(String key, String host, int port) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), plugin().namespace(), key, host, port));
return this;
}
@Override
public SecondClause<P> at(String key, URL address) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), plugin().namespace(), key, address));
return this;
}
@Override
public SecondClause<P> at(String key, URI address) {
this.setAddress(AddressingUtils.address(contextPath(),plugin().name(), plugin().namespace(), key, address));
return this;
}
@Override
public FinalClause<P> withTimeout(int duration, TimeUnit unit) {
setTimeout((int) unit.toMillis(duration));
return this;
}
@Override
public SecondClause<P> with(Property<?> property) {
addProperty(property);
return this;
}
@Override
public <T> SecondClause<P> with(String name, T value) {
addProperty(new Property<T>(name, value));
return this;
}
}

@ -0,0 +1,73 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.gcube.common.clients.builders.StatelessBuilderAPI.Builder;
import org.gcube.common.clients.builders.StatelessBuilderAPI.FinalClause;
import org.gcube.common.clients.builders.StatelessBuilderAPI.SecondClause;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
/**
* Partial implementation of proxy builders for stateless services.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
* @param <P> the type of service proxies
*/
public abstract class AbstractStatelessBuilder<A,S,P> extends AbstractBuilder<A,S,P> implements Builder<P>,SecondClause<P>,FinalClause<P> {
/**
* Constructs an instance with a given {@link ProxyPlugin}, an {@link EndpointCache}, a {@link Query}, and zero or more default {@link Property}s.
* @param plugin the plugin
* @param the cache
* @param query the query
* @param properties the properties
*/
protected AbstractStatelessBuilder(ProxyPlugin<A,S,P> plugin,EndpointCache<A> cache,Query<A> query,Property<?> ... properties) {
super(plugin,cache,properties);
setQuery(query);
}
@Override
public SecondClause<P> at(String host, int port) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), host, port));
return this;
}
@Override
public SecondClause<P> at(URL address) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), address));
return this;
}
@Override
public SecondClause<P> at(URI address) {
setAddress(AddressingUtils.address(contextPath(),plugin().name(), address));
return this;
}
@Override
public FinalClause<P> withTimeout(int duration, TimeUnit unit) {
setTimeout((int) unit.toMillis(duration));
return this;
}
@Override
public Builder<P> with(Property<?> property) {
addProperty(property);
return this;
}
@Override
public <T> Builder<P> with(String name, T value) {
addProperty(new Property<T>(name, value));
return this;
}
}

@ -0,0 +1,153 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Factory methods for addresses of service endpoints and service instances.
*
* @author Fabio Simeoni
*
*/
public class AddressingUtils {
public static final String keyElement="ResourceKey";
private static final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
private static final String scheme_prefix = "http://";
private static final String keyElementPrefix = "key";
static {
factory.setNamespaceAware(true);
}
/**
* Return the address of a service endpoint.
* @param contextPath the context path of the service
* @param service the name of the service
* @param host the host of the endpoint
* @param port the port of the endpoint
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service,String host, int port) throws IllegalArgumentException {
W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
builder.address(join(contextPath,service,host,port));
return builder.build();
}
/**
* Return the address of a service endpoint.
* @param contextPath the context path of the service
* @param service the name of the service
* @param address the address of the endpoint as a {@link URL}
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service,URL address) throws IllegalArgumentException {
return address(contextPath,service, address.getHost(),portFrom(address));
}
/**
* Return the address of a service endpoint.
* @param contextPath the context path of the service
* @param service the name of the service
* @param address the address of the endpoint as a {@link URI}
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service,URI address) throws IllegalArgumentException {
return address(contextPath,service, address.getHost(),portFrom(address));
}
/**
* Returns the address of a service instance.
* @param contextPath the context path of the service
* @param service the name of the service
* @param namespace the namespace of the service
* @param key the key of the instance
* @param host the host of the instance
* @param port the port of the instance
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service, String namespace, String key, String host, int port) throws IllegalArgumentException {
W3CEndpointReferenceBuilder builder = new W3CEndpointReferenceBuilder();
builder.address(join(contextPath,service,host,port));
builder.referenceParameter(key(namespace,key));
return builder.build();
}
/**
* Returns the address of a service instance.
* @param contextPath the context path of the service
* @param service the name of the service
* @param namespace the namespace of the service
* @param key the key of the instance
* @param address the address of the endpoint as a {@link URL}.
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service, String namespace, String key, URL address) throws IllegalArgumentException {
return address(contextPath,service,namespace,key,address.getHost(),portFrom(address));
}
/**
* Returns the address of a service instance.
* @param contextPath the context path of the service
* @param service the name of the service
* @param namespace the namespace of the service
* @param key the key of the instance
* @param address the address of the endpoint as a {@link URI}.
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the inputs
*/
public static W3CEndpointReference address(String contextPath,String service, String namespace, String key, URI address) throws IllegalArgumentException {
return address(contextPath,service,namespace,key,address.getHost(),portFrom(address));
}
//helper
private static String join(String path,String service,String host, int port) {
//some tolerance
if (host.startsWith(scheme_prefix))
host = host.substring(scheme_prefix.length(),host.length());
String address = scheme_prefix + host + ":" + port + path + service;
return address;
}
//helper
public static Element key(String namespace,String value) {
try {
Document document = factory.newDocumentBuilder().newDocument();
Element key = document.createElementNS(namespace,keyElementPrefix+":"+keyElement);
key.setAttribute("xmlns:"+keyElementPrefix,namespace);
key.appendChild(document.createTextNode(value));
return key;
}
catch(Exception e) {
throw new RuntimeException("programming error in AddressingUtils#key");
}
}
//helper
private static int portFrom(URI address) {
return address.getPort()!=-1?address.getPort():80;
}
//helper
private static int portFrom(URL address) {
return portFrom(URI.create(address.toExternalForm()));
}
}

@ -0,0 +1,121 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.queries.Query;
/**
* Defines a DSL for proxy builders of singleton services, i.e. stateful services with a known single instance.
*
* @author Fabio Simeoni
*
*/
public class SingletonBuilderAPI {
/**
* The first clause of the DSL.
*
* @author Fabio Simeoni
*
* @param<A> the type of service addresses
* @param <P> the type of service proxies
*/
public static interface Builder<A,P> {
/**
* Configures a query for service instances.
* @param query the query
* @return further configuration options
*/
public SecondClause<P> matching(Query<A> query);
/**
* Configures the address of a given service endpoint.
* @param address the address of the endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(URL address) throws IllegalArgumentException;
/**
* Configures the address of a given service endpoint.
* @param address the address of the endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(URI address) throws IllegalArgumentException;
/**
* Configures the address of a given service instance.
* @param address the address of the corresponding service endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(String host, int port) throws IllegalArgumentException;
}
/**
* The second clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface SecondClause<P> {
/**
* Configures the timeout for the proxy.
* @param timeoutDuration the duration of the timeout
* @param timeoutUnit the time unit of the timeout
* @return further configuration options
*/
public FinalClause<P> withTimeout(int timeoutDuration, TimeUnit timeoutUnit);
/**
* Set a configuration property for the proxy.
* @param name the name of the property
* @param value the value of the property
* @return further configuration options
*
* @param <T> the type of the property value
*/
public <T> SecondClause<P> with(String name, T value);
/**
* Set a configuration property for the proxy.
* @param name the property
* @return further configuration options
*/
public SecondClause<P> with(Property<?> property);
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
/**
* The final clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface FinalClause<P> {
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
}

@ -0,0 +1,134 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.queries.Query;
/**
* Defines a DSL for proxy builders of stateful services.
*
* @author Fabio Simeoni
*
*/
public class StatefulBuilderAPI {
/**
* The first clause of the DSL.
*
* @author Fabio Simeoni
*
* @param<A> the type of service addresses
* @param <P> the type of service proxies
*/
public static interface Builder<A,P> {
/**
* Configures a query for service instances.
* @param query the query
* @return further configuration options
*/
public SecondClause<P> matching(Query<A> query);
/**
* Configures the address of a given service instance.
* @param address the address
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(W3CEndpointReference address) throws IllegalArgumentException;
/**
* Configures the address of a given service instance.
* @param the key of the instance
* @param host the host of the corresponding service endpoint
* @param port the port of the corresponding service endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(String key, String host, int port) throws IllegalArgumentException;
/**
* Configures the address of a given service instance.
* @param the key of the instance
* @param address the address of the corresponding service endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(String key, URL url) throws IllegalArgumentException;
/**
* Configures the address of a given service instance.
* @param the key of the instance
* @param address the address of the corresponding service endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(String key, URI url) throws IllegalArgumentException;
}
/**
* The second clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface SecondClause<P> {
/**
* Configures the timeout for the proxy.
* @param timeoutDuration the duration of the timeout
* @param timeoutUnit the time unit of the timeout
* @return further configuration options
*/
public FinalClause<P> withTimeout(int timeoutDuration, TimeUnit timeoutUnit);
/**
* Set a configuration property for the proxy.
* @param name the name of the property
* @param value the value of the property
* @return further configuration options
*
* @param <T> the type of the property value
*/
public <T> SecondClause<P> with(String name, T value);
/**
* Set a configuration property for the proxy.
* @param name the property
* @return further configuration options
*/
public SecondClause<P> with(Property<?> property);
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
/**
* The final clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface FinalClause<P> {
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
}

@ -0,0 +1,129 @@
package org.gcube.common.clients.builders;
import java.net.URI;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import org.gcube.common.clients.config.Property;
/**
* Defines a DSL for proxy builders of stateless services.
*
* @author Fabio Simeoni
*
*/
public class StatelessBuilderAPI {
/**
* The first clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface Builder<P> {
/**
* Configures the address of a given service instance.
* @param address the address of the corresponding service endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(String host, int port) throws IllegalArgumentException;
/**
* Configures the address of a given service endpoint.
* @param address the address of the endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(URL address) throws IllegalArgumentException;
/**
* Configures the address of a given service endpoint.
* @param address the address of the endpoint
* @return further configuration options
* @throws IllegalArgumentException if the address is invalid
*/
public SecondClause<P> at(URI address) throws IllegalArgumentException;
/**
* Configures the timeout for the proxy.
* @param timeoutDuration the duration of the timeout
* @param timeoutUnit the time unit of the timeout
* @return further configuration options
*/
public FinalClause<P> withTimeout(int timeoutDuration, TimeUnit timeoutUnit);
/**
* Set a configuration property for the proxy.
* @param name the name of the property
* @param value the value of the property
* @return further configuration options
*
* @param <T> the type of the property value
*/
public <T> Builder<P> with(String name, T value);
/**
* Set a configuration property for the proxy.
* @param name the property
* @return further configuration options
*/
public Builder<P> with(Property<?> property);
/**
* Configures the timeout for the proxy.
* @param timeoutDuration the duration of the timeout
* @param timeoutUnit the time unit of the timeout
* @return further configuration options
*/
public P build();
}
/**
* The second clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface SecondClause<P> {
/**
* Configures the timeout for the proxy.
* @param timeoutDuration the duration of the timeout
* @param timeoutUnit the time unit of the timeout
* @return further configuration options
*/
public FinalClause<P> withTimeout(int timeoutDuration, TimeUnit timeoutUnit);
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
/**
* The final clause of the DSL.
*
* @author Fabio Simeoni
*
* @param <P> the type of service proxies
*/
public static interface FinalClause<P> {
/**
* Returns a configured proxy.
* @return the proxy
*/
public P build();
}
}

@ -0,0 +1,63 @@
package org.gcube.common.clients.cache;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of {@link EndpointCache}.
*
* @author Fabio Simeoni
*
* @param <A> the type of the service addresses
*
* @see DiscoveryDelegate
*/
public class DefaultEndpointCache<A> implements EndpointCache<A> {
private static Logger logger = LoggerFactory.getLogger(DefaultEndpointCache.class);
/** Service map. */
private Map<Key,A> cache = Collections.synchronizedMap(new HashMap<Key,A>());
@Override
public void clear(Key key) throws IllegalArgumentException {
assertnotNull(key,"key");
logger.debug("clearing cache {} for {}",this,key);
cache.put(key, null);
}
@Override
public A get(Key key) throws IllegalArgumentException {
assertnotNull(key,"key");
return cache.get(key);
}
@Override
public void put(Key key, A address) throws IllegalArgumentException {
assertnotNull(key,"key");
assertnotNull(address,"address");
logger.debug("caching {} for {}",address,key);
cache.put(key,address);
}
//helper
private void assertnotNull(Object object, String msg) throws IllegalArgumentException {
if (object==null)
throw new IllegalArgumentException(msg+" is null");
}
}

@ -0,0 +1,41 @@
package org.gcube.common.clients.cache;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
/**
* A cross-service cache of endpoint addresses used by {@link DiscoveryDelegate}s.
*
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
*
* @see Key
* @see DiscoveryDelegate
*
*/
public interface EndpointCache<A> {
/**
* Resets the cache for a given {@link Key}.
* @param key the key
* @throws IllegalArgumentException if the key is <code>null</code>
*/
void clear(Key key) throws IllegalArgumentException;
/**
* Returns the address cached for a given {@link Key}
* @param key the key
* @return the endpoint address, or <code>null</code> if there is no endpoint address cached for the service
* @throws IllegalArgumentException if the key is <code>null</code>
*/
A get(Key key) throws IllegalArgumentException;
/**
* Caches an endpoint address for a given {@link Key}
* @param key the key
* @param address the address
* @throws IllegalArgumentException if the key or the address are <code>null</code>
*/
void put(Key Key,A address) throws IllegalArgumentException;
}

@ -0,0 +1,81 @@
package org.gcube.common.clients.cache;
import org.gcube.common.clients.queries.Query;
import org.gcube.common.scope.api.ScopeProvider;
/**
* Keys for cross-service {@link EndpointCache}s comprised of a service name, a {@link Query}, and a scope.
*
* <p>
* Keys are <em>value objects</em> with informative string representations.
*
* @author Fabio Simeoni
*
*/
public final class Key {
private final String name;
private final Query<?> query;
private final String scope;
/**
* Creates a {@link Key} with a given service name and {@link Query}
* @param name the name
* @param query the query
* @return the key
*/
public static Key key(String name, Query<?> query) {
return new Key(name, query);
}
//private
private Key(String name, Query<?> query) {
this.name = name;
this.query = query;
this.scope = ScopeProvider.instance.get();
}
@Override
public String toString() {
return "Key [name=" + name + ", query=" + query + ", scope=" + scope + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((query == null) ? 0 : query.hashCode());
result = prime * result + ((scope == null) ? 0 : scope.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Key other = (Key) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (query == null) {
if (other.query != null)
return false;
} else if (!query.equals(other.query))
return false;
if (scope == null) {
if (other.scope != null)
return false;
} else if (!scope.equals(other.scope))
return false;
return true;
}
}

@ -0,0 +1,69 @@
package org.gcube.common.clients.config;
import java.util.HashMap;
import java.util.Map;
import org.gcube.common.clients.delegates.ProxyPlugin;
/**
* Partial implementation of {@link ProxyConfig}.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
*/
public abstract class AbstractConfig<A,S> implements ProxyConfig<A,S> {
private final ProxyPlugin<A,S,?> plugin;
private final Map<String,Object> properties = new HashMap<String, Object>();
/**
* Creates an instance with a given {@link ProxyPlugin}.
* @param plugin the plugin
*/
protected AbstractConfig(ProxyPlugin<A,S,?> plugin) {
this.plugin = plugin;
}
@Override
public ProxyPlugin<A,S,?> plugin() {
return plugin;
}
@Override
public long timeout() throws IllegalArgumentException {
if (!hasProperty(Property.timeout))
throw new IllegalArgumentException("timeout property is undefined");
else
return property(Property.timeout,Long.class);
}
@Override
public <T> void addProperty(String name, T value) {
properties.put(name, value);
}
@Override
public void addProperty(Property<?> property) {
properties.put(property.name(),property.value());
}
@Override
public boolean hasProperty(String property) {
return properties.containsKey(property);
}
@Override
public <T> T property(String property, Class<T> clazz) throws IllegalStateException, IllegalArgumentException {
if (!hasProperty(property))
throw new IllegalStateException(property+" is unknown");
try {
return clazz.cast(properties.get(property));
}
catch(Exception e) {
throw new IllegalArgumentException("could not retrieve "+property+" as "+clazz,e);
}
}
}

@ -0,0 +1,51 @@
package org.gcube.common.clients.config;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
/**
* The configuration of a proxy created in discovery mode.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
*
* @see DiscoveryDelegate
*
*/
public class DiscoveryConfig<A,S> extends AbstractConfig<A,S> {
private final Query<A> query;
private final EndpointCache<A> cache;
/**
* Creates an instance with a given {@link ProxyPlugin}, {@link Query} and call timeout.
* @param plugin the plugin
* @param query the query
* @param the timeout
*/
public DiscoveryConfig(ProxyPlugin<A,S,?> plugin,Query<A> query, EndpointCache<A> cache) {
super(plugin);
this.query=query;
this.cache=cache;
}
/**
* Returns the address cache used by the proxy.
* @return the cache
*/
public EndpointCache<A> cache() {
return cache;
}
/**
* Returns the query used by the proxy.
* @return the query
*/
public Query<A> query() {
return query;
}
}

@ -0,0 +1,28 @@
package org.gcube.common.clients.config;
import org.gcube.common.clients.delegates.DirectDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
/**
* The configuration of a proxy created in direct mode.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
*
* @see DirectDelegate
*/
public class EndpointConfig<A,S> extends AbstractConfig<A,S> {
private final A address;
public EndpointConfig(ProxyPlugin<A,S,?> plugin, A address) {
super(plugin);
this.address=address;
}
public A address() {
return address;
}
}

@ -0,0 +1,87 @@
package org.gcube.common.clients.config;
import java.util.concurrent.TimeUnit;
import org.gcube.common.clients.builders.AbstractStatefulBuilder;
import org.gcube.common.clients.builders.AbstractStatelessBuilder;
/**
* A custom configuration property for proxies.
*
* @author Fabio Simeoni
*
* @see AbstractStatefulBuilder
* @see AbstractStatelessBuilder
*
* @param <T> the type of the property value
*/
public class Property<T> {
/**
* The name of the call timeout {@link Property}.
*/
public static final String timeout = "timeout";
/**
* The name of the sticky session property {@link Property}.
*/
public static final String sticky_session = "sticky_session";
/**
* Return the call timeout {@link Property}.
* @param value the property value
* @return the property
*/
public static Property<Long> timeout(long value) {
return new Property<Long>(timeout,value);
}
/**
* Return the call timeout {@link Property}.
* @param duration the duration of the timeout
* @param unit the time unit of the timeout
* @return the property
*/
public static Property<Long> timeout(long duration, TimeUnit unit) {
return new Property<Long>(timeout,unit.toMillis(duration));
}
/**
* Return the sticky session {@link Property}.
* @param value the property value
* @return the property
*/
public static Property<Boolean> sticky_session(boolean value) {
return new Property<Boolean>(sticky_session,value);
}
private final String name;
private final T value;
/**
* Creates an instance with a name and a value.
* @param name the name
* @param value the value
*/
public Property(String name, T value) {
this.name=name;
this.value=value;
}
/**
* Returns the name of the property.
* @return the name
*/
public String name() {
return name;
}
/**
* Returns the value of the property.
* @return the value
*/
public T value() {
return value;
}
}

@ -0,0 +1,62 @@
package org.gcube.common.clients.config;
import org.gcube.common.clients.delegates.ProxyPlugin;
/**
* The configuration of service proxies.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
*/
public interface ProxyConfig<A,S> {
/**
* Returns the timeout.
* @return the timeout
*/
public long timeout();
/**
* Returns the {@link ProxyPlugin}.
* @return the plugin
*/
public ProxyPlugin<A,S,?> plugin();
/**
* Adds a custom property to the configuration.
* @param name the name of the property
* @param value the value of the property
*
* @param <T> the type of the property value
*/
public <T> void addProperty(String name, T value);
/**
* Adds a custom property to the configuration.
* @param property the property
*/
public void addProperty(Property<?> property);
/**
* Returns <code>true</code> if the configuration includes a given custom property.
* @param property the name of the property
* @return
*/
public boolean hasProperty(String property);
/**
* Returns the value of a given custom property.
* @param property the name of the property
* @param clazz the type of the property value
* @return the property value
* @throws IllegalStateException if the property is not included in the configuration
* @throws IllegalArgumentException if the property exists but its value has a different value
*
* * @param <T> the type of the property value
*/
<T> T property(String property, Class<T> clazz) throws IllegalStateException, IllegalArgumentException;
}

@ -0,0 +1,35 @@
package org.gcube.common.clients.delegates;
import org.gcube.common.clients.config.ProxyConfig;
/**
* Partial implementation of {@link ProxyDelegate}s
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <S> the type of service stubs
* @param <C> the type of {@link ProxyConfig} used by the delegate
*/
public abstract class AbstractDelegate<A,S,C extends ProxyConfig<A,S>> implements ProxyDelegate<S> {
private final C config;
/**
* Constructs an instance with a given configuration
* @param config the configuration
*/
public AbstractDelegate(C config) {
this.config=config;
}
@Override
public C config() {
return config;
}
@Override
public String toString() {
return config().plugin().name()+"'s proxy";
}
}

@ -0,0 +1,205 @@
package org.gcube.common.clients.delegates;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.config.ProxyConfig;
import org.gcube.common.scope.api.ScopeProvider;
/**
* A {@link ProxyDelegate} that delivers the outcome of {@link Call}s asynchronously, either through polling or
* notifications.
* <p>
* The delegates use {@link ExecutorService}s to make calls in separate threads. If required, clients may provide their own
* {@link ExecutorService}s at the point of call submission.
*
* @author Fabio Simeoni
*
* @param <S> the type of service stubs
*/
public class AsyncProxyDelegate<S> implements ProxyDelegate<S> {
// we try to cope with demand within holding on to threads that may never be used
private final static ExecutorService service = Executors.newCachedThreadPool();
// quits the default service when JVM does
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
service.shutdown();
}
});
}
// the inner synchronous delegate
private final ProxyDelegate<S> inner;
/**
* Creates an instance with a (synchronous) {@link ProxyDelegate}
*
* @param delegate the delegate
*/
public AsyncProxyDelegate(ProxyDelegate<S> delegate) {
this.inner = delegate;
}
@Override
public <V> V make(Call<S, V> call) throws Exception {
return inner.make(call);
}
@Override
public ProxyConfig<?, S> config() {
return inner.config();
}
/**
* Makes a {@link Call} to a service endpoint asynchronously, returning a {@link Future} that clients can use to
* poll for and obtain the call outcome, or to cancel the call (assuming that the call is designed for cancellation
* or has not been made yet).
*
* @param call the {@link Call} to be made asynchronously
* @return the {@link Future} of the {@link Call} outcome
*
* @param <V> the type of the value returned from the {@link Call}
*
* @throws RejectedExecutionException if the call cannot not be submitted for asynchronous execution
*/
public <V> Future<V> makeAsync(Call<S, V> call) throws RejectedExecutionException {
return makeAsync(call, service);
}
/**
* Makes a {@link Call} to a service endpoint asynchronously, returning a {@link Future} that clients can use to
* poll for and obtain the call outcome, or to cancel the call (assuming that the call is designed for cancellation
* or has not been made yet).
*
* @param call the {@link Call} to be executed asynchronously
* @param service a {@link ExecutorService} to which the {@link Call} should be submitted for execution
*
* @return the {@link Future} of the {@link Call} outcome
*
* @param <V> the type of the value returned from the {@link Call}
*
* @throws RejectedExecutionException if the call cannot not be submitted for asynchronous execution
*
*/
public <V> Future<V> makeAsync(final Call<S, V> call, ExecutorService service) throws RejectedExecutionException {
final String callScope = ScopeProvider.instance.get();
// create task from call
Callable<V> callTask = new Callable<V>() {
@Override
public V call() throws Exception {
ScopeProvider.instance.set(callScope);
return inner.make(call);
}
};
// submit task
return service.submit(callTask);
}
/**
* Makes a {@link Call} to a service endpoint asynchronously, notifying a {@link Callback} of its outcome. Returns a
* {@link Future} that clients can use to cancel the execution of the call (assuming that the call is designed for
* cancellation or has not been made yet).
*
* @param call the {@link Call}
* @param callback the {@link Callback}
*
* @return the {@link Future} of call submission
*
* @throws RejectedExecutionException if the call cannot not be submitted for asynchronous execution
*/
public <V> Future<?> makeAsync(final Call<S, V> call, final Callback<V> callback) throws RejectedExecutionException {
return makeAsync(call, callback, service);
}
/**
* Makes a {@link Call} to a service endpoint asynchronously, notifying a {@link Callback} of its outcome. Returns a
* {@link Future} that clients can use to cancel the execution of the call (assuming that the call is designed for
* cancellation or has not been made yet).
*
* @param call the {@link Call}
* @param callback the {@link Callback}
* @param service the {@link ExecutorService} that executes the call
*
* @return the {@link Future} of call submission
*
* @throws RejectedExecutionException if the call cannot not be submitted for asynchronous execution
*/
public <V> Future<?> makeAsync(final Call<S, V> call, final Callback<V> callback, ExecutorService service)
throws RejectedExecutionException {
// submit call
final Future<V> callFuture = makeAsync(call, service);
// create a task that blocks waiting on outome
Runnable waitingTask = new Runnable() {
@Override
public void run() {
try {
long timeout = callback.timeout();
V outcome = null;
// honour callback timeout
if (timeout==0)
//not only may clients want to wait indefinitely, they may have set the timeout on proxy
outcome = callFuture.get();
else
outcome = callFuture.get(timeout, TimeUnit.MILLISECONDS);
// notify callback
callback.done(outcome);
} catch (InterruptedException e) {
// we assume client has cancelled task, hence do not notify it of its own actions
// but we reset the flag for other consumers, such as the executor service
// so clients do not need to worry about it.
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
// notify callback of underlying failure
// by now, it will have already beeen converted
callback.onFailure(e.getCause());
} catch (TimeoutException e) {
// notify callback the required timeout has expired
callback.onFailure(e);
// attempt to cancel the call, in case it's designed for it
callFuture.cancel(true);
}
}
};
// submits waiting task
service.submit(waitingTask);
// return call task future, rather than waiting task
return callFuture;
}
}

@ -0,0 +1,43 @@
package org.gcube.common.clients.delegates;
import java.util.concurrent.TimeoutException;
import org.gcube.common.clients.Call;
/**
* Asynchronous {@link Call} listeners.
*
* @author Fabio Simeoni
*
* @param <V> the type of value returned by the call
*
* @see Call
*/
public interface Callback<V> {
/**
* Invoked when the value returned by the call is available.
* @param value the value
*/
void done(V value);
/**
* Invoked when the call does not complete successfully.
* <p>
* Failures may be generated by a {@link Call}s, or by the expiration of timeouts set on their
* asynchronous execution. In the latter case, the failures are {@link TimeoutException}s.
*
* @param failure the failure
*/
void onFailure(Throwable failure);
/**
* The time to wait on the value returned by the call.
* @return the timeout
*/
long timeout();
}

@ -0,0 +1,60 @@
package org.gcube.common.clients.delegates;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.config.EndpointConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link ProxyDelegate} that sends {@link Call}s to service endpoints at known addresses.
* <p>
* This is a no-op {@link ProxyDelegate}, i.e. it executes the {@link Call} interface and converts its faults.
* It exists to support uniform programming against the {@link ProxyDelegate} interface.
*
* @author Fabio Simeoni
*
* @param <A> the of service addresses
* @param <S> the type of service proxies
*
*/
public final class DirectDelegate<A,S> extends AbstractDelegate<A,S,EndpointConfig<A,S>> implements ProxyDelegate<S> {
private static Logger log = LoggerFactory.getLogger(DirectDelegate.class);
/**
* Creates an instance with a {@link ProxyPlugin} and an endpoint address.
* @param plugin the plugin
* @param address the address
*/
public DirectDelegate(EndpointConfig<A,S> config) {
super(config);
}
@Override
public <V> V make(Call<S, V> call) throws Exception {
ProxyPlugin<A,S,?> plugin = config().plugin();
A address = config().address();
log.info("calling {} @ {}",plugin.name(),address);
S stub =null;
try {
stub = plugin.resolve(address,config());
}
catch(Exception e) {
throw new IllegalStateException("could not resolve "+address,e);
}
try {
return call.call(stub);
}
catch(Exception fault) {
throw plugin.convert(fault,config());
}
}
}

@ -0,0 +1,180 @@
package org.gcube.common.clients.delegates;
import static org.gcube.common.clients.cache.Key.*;
import java.util.ArrayList;
import java.util.List;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.cache.Key;
import org.gcube.common.clients.config.DiscoveryConfig;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.exceptions.DiscoveryException;
import org.gcube.common.clients.queries.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A {@link ProxyDelegate} that discovers service endpoints.
*
* <p>
*
* The delegates attempt to make {@link Call}s to endpoints cached in an {@link EndpointCache}.
* If the calls fail, or the cache is empty, they execute a {@link Query} for endpoints and call the results in turn until the call succeeds or there are no
* more endpoints to call. If the call succeeds with one endpoint, the delegates cache the endpoint in the {@link EndpointCache}.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param<S> the type of service stubs
*
* @see Query
* @see EndpointCache
*/
public class DiscoveryDelegate<A,S> extends AbstractDelegate<A,S,DiscoveryConfig<A,S>> {
private static Logger log = LoggerFactory.getLogger(DiscoveryDelegate.class);
/**
* Creates an instance with a {@link ProxyPlugin}, a {@link Query}, and an {@link EndpointCache}.
*
* @param plugin the plugin
* @param query the query
* @param cache the cache
*/
public DiscoveryDelegate(DiscoveryConfig<A,S> config) {
super(config);
}
//helper
private boolean isAnchored() {
return config().hasProperty(Property.sticky_session) ?
config().property(Property.sticky_session,Boolean.class):
false;
}
@Override
public <V> V make(Call<S, V> call) throws Exception {
ProxyPlugin<A,S,?> plugin = config().plugin();
Query<A> query = config().query();
EndpointCache<A> cache = config().cache();
// create key in call scope
Key key = key(plugin.name(),query);
// try with each endpoint in turn
Exception lastFault = null;
// use cache first, if any
A lgeAddress = cache.get(key);
use_cache:if (lgeAddress != null)
try {
log.info("calling {} @ {} (cached)", plugin.name(),lgeAddress);
S lge = null;
try {
lge=plugin.resolve(lgeAddress,config());
}
catch(Exception e) {
log.error("could not resolve "+lgeAddress,e);
lastFault = e;
break use_cache;
}
return call.call(lge);
} catch (Exception fault) {
cache.clear(key);
fault = plugin.convert(fault,config());
if (isUnrecoverable(fault) || isAnchored()) // exit now
throw fault;
else
lastFault = fault; //move on to querying
}
List<A> results = null;
try {
log.info("executing query for {} endpoints: {}", plugin.name(),query);
// execute query
results = query.fire();
// exclude cached endpoint (we do not use 'remove' in case list implementation does not support it)
results = filterResults(lgeAddress, results);
if (results.size() == 0)
throw new DiscoveryException("no endpoints found for "+query);
}
catch(DiscoveryException fault) {
if (lastFault == null)
throw fault;
else
throw lastFault;
}
//try with each endpoint in turn
for (A address : results)
try {
log.info("calling {} @ {}", plugin.name(), address);
S stub = null;
try {
stub=plugin.resolve(address,config());
}
catch(Exception e) {
log.error("could not resolve "+address,e);
lastFault = e;
}
V result = call.call(stub);
cache.put(key, address);
return result;
} catch (Exception fault) {
lastFault= plugin.convert(fault,config());
if (isUnrecoverable(lastFault) || isAnchored()) // exit now
break;
}
throw lastFault;
}
//helper
private <CR> boolean isUnrecoverable(Exception e) {
try {
return e.getClass().isAnnotationPresent(Unrecoverable.class);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
// helper
private List<A> filterResults(A cached, List<A> results) {
List<A> endpoints = new ArrayList<A>();
for (A result : results)
if (!result.equals(cached))
endpoints.add(result);
return endpoints;
}
}

@ -0,0 +1,52 @@
package org.gcube.common.clients.delegates;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.builders.AbstractBuilder;
import org.gcube.common.clients.config.AbstractConfig;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.config.ProxyConfig;
/**
* A {@link ProxyDelegate} for mock testing.
*
* @author Fabio Simeoni
*
* @param <S> the type of service endpoint stubs used by {@link Call}s
*/
public class MockDelegate<S> implements ProxyDelegate<S> {
/**
* Creates an instance with a {@link ProxyPlugin} and a mock endpoints.
* @param plugin the plugin
* @param endpoint the endpoint
*/
public static <S> ProxyDelegate<S> mockDelegate(ProxyPlugin<?, S, ?> plugin,S endpoint) {
return new MockDelegate<S>(plugin, endpoint);
};
private ProxyConfig<?,S> config;
private S mockEndpoint;
@SuppressWarnings("all")
private MockDelegate(ProxyPlugin<?, S, ?> plugin,S endpoint) {
this.config = new AbstractConfig(plugin) {};
this.config().addProperty(Property.timeout(AbstractBuilder.defaultTimeout));
this.mockEndpoint=endpoint;
}
@Override
public <V> V make(Call<S, V> call) throws Exception {
try {
return call.call(mockEndpoint);
}
catch(Exception e) {
throw config.plugin().convert(e,config);
}
}
@Override
public ProxyConfig<?, S> config() {
return config;
}
}

@ -0,0 +1,38 @@
package org.gcube.common.clients.delegates;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.config.ProxyConfig;
/**
* Makes {@link Call}s to service endpoints on behalf a service proxy.
* <p>
* Delegates obtain the addresses of service endpoints according to some strategy, using information
* found in their {@link ProxyConfig}.
*
* @author Fabio Simeoni
*
* @param <S> the type of service stubs
*
* @see Call
*/
public interface ProxyDelegate<S> {
/**
* Makes a {@link Call} to a given service endpoint.
*
* @param call the call
* @param <V> the type of the value returned from the call
* @return the value returned from the call
* @throws Exception if the call fails
*
*/
<V> V make(Call<S, V> call) throws Exception;
/**
* Returns the configuration of the proxy.
* @return the configuration
*/
ProxyConfig<?,S> config();
}

@ -0,0 +1,56 @@
package org.gcube.common.clients.delegates;
import org.gcube.common.clients.Call;
import org.gcube.common.clients.config.ProxyConfig;
/**
* Provides information to customise the strategy of a {@link ProxyDelegate}.
*
* @author Fabio Simeoni
*
* @see Call
* @see ProxyDelegate
*/
public interface ProxyPlugin<A,S,P> {
/**
* Returns the name of the service.
*
* @return the name
*/
String name();
/**
* Returns the namespace of the service
* @return the namespace
*/
String namespace();
/**
* Converts a fault raised by a {@link Call} into a fault that can be recognised by {@link ProxyDelegate} clients.
*
* @param fault the original fault
* @return the converted fault
*/
Exception convert(Exception fault, ProxyConfig<?,?> config);
/**
* Returns a stub for a given service endpoint
* @param address the address of the endpoint
* @return the stub
* @throws Exception if the address cannot be resolved
*/
S resolve(A address, ProxyConfig<?,?> config) throws Exception;
/**
* Returns a proxy with a given delegate
* @param delegate the delegate
* @return the proxy
*/
P newProxy(ProxyDelegate<S> delegate);
}

@ -0,0 +1,24 @@
package org.gcube.common.clients.delegates;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.gcube.common.clients.Call;
/**
* Specified on {@link Call#call(Object)} to short-circuit endpoint iteration in the interaction strategy of {@link DiscoveryDelegate}s.
*
* @author Fabio Simeoni
* @see Call
* @see DiscoveryDelegate
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
@Documented
public @interface Unrecoverable {}

@ -0,0 +1,32 @@
package org.gcube.common.clients.exceptions;
/**
* Raised when services endpoints cannot be discovered.
*
* @author Fabio Simeoni
*
*/
public class DiscoveryException extends ServiceException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance with a message.
* @param msg the message
*/
public DiscoveryException(String msg) {
super(msg);
}
/**
* Creates an instance from a cause.
* @param cause the cause
*/
public DiscoveryException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,116 @@
package org.gcube.common.clients.exceptions;
/**
* A simple DSL for fault conversion.
*
* @author Fabio Simeoni
*
*/
public class FaultDSL {
/**
* Fault narrowing clause;
* @author Fabio Simeoni
*
*/
public static class AsClause {
final Throwable fault;
/**
* Creates an instance with the fault to narrow.
* @param fault the fault
*/
AsClause(Throwable fault) {
this.fault=fault;
}
/**
* Throws a {@link ServiceException} caused by a given fault
* @param fault the fault
* @return unused, allows clients to throw invocations of this method
*/
public ServiceException asServiceException() {
if (fault instanceof ServiceException)
throw (ServiceException) fault;
throw new ServiceException(fault);
}
/**
* Rethrows the fault with a narrower type, or wraps it in {@link ServiceException} if its type cannot be narrowed.
* @param clazz1 the narrower type
* @return unused, allows clients to throw invocations of this method
* @throws T1 the narrower type
*/
public <T1 extends Throwable> ServiceException as(Class<T1> clazz1) throws T1 {
if (clazz1.isInstance(fault)) throw clazz1.cast(fault);
else return asServiceException();
}
/**
* Rethrows the fault with a narrower type, or wraps it in {@link ServiceException} if its type cannot be narrowed.
* @param clazz1 the narrower type
* @param clazz2 an alternative narrower type
* @return unused, allows clients to throw invocations of this method
* @throws T1 the narrower type
* @throws T2 the second narrower type
*/
public <T1 extends Throwable, T2 extends Throwable> ServiceException as(Class<T1> clazz1,Class<T2> clazz2) throws T1,T2 {
if (clazz2.isInstance(fault)) throw clazz2.cast(fault);
else return as(clazz1);
}
/**
* Rethrows the fault with a narrower type, or wraps it in {@link ServiceException} if its type cannot be narrowed.
* @param clazz1 the narrower type
* @param clazz2 an alternative narrower type
* @param clazz3 an alternative narrower type
* @return unused, allows clients to throw invocations of this method
* @throws T1 the narrower type
* @throws T2 the second narrower type
* @throws T3 the second narrower type
*/
public <T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> ServiceException as(Class<T1> clazz1,Class<T2> clazz2, Class<T3> clazz3) throws T1,T2,T3 {
if (clazz3.isInstance(fault)) throw clazz3.cast(fault);
else return as(clazz1,clazz2);
}
/**
* Rethrows the fault with a narrower type, or wraps it in {@link ServiceException} if its type cannot be narrowed.
* @param clazz1 the narrower type
* @param clazz2 an alternative narrower type
* @param clazz3 an alternative narrower type
* @param clazz4 an alternative narrower type
* @return unused, allows clients to throw invocations of this method
* @throws T1 the narrower type
* @throws T2 the second narrower type
* @throws T3 the second narrower type
* @throws T4 the second narrower type
*/
public <T1 extends Throwable, T2 extends Throwable, T3 extends Throwable, T4 extends Throwable> ServiceException as(Class<T1> clazz1,Class<T2> clazz2, Class<T3> clazz3, Class<T4> clazz4) throws T1,T2,T3,T4 {
if (clazz4.isInstance(fault)) throw clazz4.cast(fault);
else return as(clazz1,clazz2,clazz3);
}
}
/**
* Indicates a fault to be rethrown with a narrower type.
* @param fault the fault
* @return the next clause in the sentence
*/
public static AsClause again(Throwable fault) {
return new AsClause(fault);
}
}

@ -0,0 +1,27 @@
package org.gcube.common.clients.exceptions;
/**
* A {@link ServiceException} raised when attempt to contact service endpoints outside the scope.
*
* @author Fabio Simeoni
*
*/
public class IllegalScopeException extends InvalidRequestException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance.
*/
public IllegalScopeException(){
super();
}
/**
* Creates an instance with a given message.
* @param msg the message
*/
public IllegalScopeException(String msg) {
super(msg);
}
}

@ -0,0 +1,48 @@
package org.gcube.common.clients.exceptions;
import org.gcube.common.clients.delegates.Unrecoverable;
/**
* A {@link ServiceException} raised when client requests are invalid for services.
*
* @author Fabio Simeoni
*
*/
@Unrecoverable
public class InvalidRequestException extends ServiceException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance.
*/
public InvalidRequestException(){
super();
}
/**
* Creates an instance with a given message.
* @param msg the message
*/
public InvalidRequestException(String msg) {
super(msg);
}
/**
* Creates an instance from an underlying cause.
*
* @param cause the cause
*/
public InvalidRequestException(Throwable cause) {
super(cause);
}
/** Creates an instance with a given message and an underlying cause.
*
* @param msg the message
* @param cause the cause
*/
public InvalidRequestException(String msg,Throwable cause) {
super(msg,cause);
}
}

@ -0,0 +1,20 @@
package org.gcube.common.clients.exceptions;
/**
* A {@link ServiceException} raised when services endpoints are not reachable.
*
* @author Fabio Simeoni
*
*/
public class NoSuchEndpointException extends ServiceException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance from an underlying cause.
* @param cause the cause
*/
public NoSuchEndpointException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,44 @@
package org.gcube.common.clients.exceptions;
/**
* An exceptions that occurs in the attempt to communicate with service endpoints.
*
* @author Fabio Simeoni
*
*/
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance.
*/
public ServiceException() {}
/**
* Creates an instance with a given message.
*
* @param msg the message
*/
public ServiceException(String msg) {
super(msg);
}
/**
* Creates an instance from an underlying cause.
*
* @param cause the cause
*/
public ServiceException(Throwable cause) {
super(cause);
}
/** Creates an instance with a given message and an underlying cause.
*
* @param msg the message
* @param cause the cause
*/
public ServiceException(String msg,Throwable cause) {
super(msg,cause);
}
}

@ -0,0 +1,28 @@
package org.gcube.common.clients.exceptions;
import org.gcube.common.clients.delegates.Unrecoverable;
/**
* Raised with requests to service operations that are not supported.
*
* @author Fabio Simeoni
*
*/
@Unrecoverable
public class UnsupportedOperationException extends InvalidRequestException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance.
*/
public UnsupportedOperationException() {}
/**
* Creates an instance with a message.
* @param msg the message
*/
public UnsupportedOperationException(String msg) {
super(msg);
}
}

@ -0,0 +1,45 @@
package org.gcube.common.clients.exceptions;
import org.gcube.common.clients.delegates.Unrecoverable;
/**
* Raised with requests to services which are not supported by the target plugin.
*
* @author Fabio Simeoni
*
*/
@Unrecoverable
public class UnsupportedRequestException extends InvalidRequestException {
private static final long serialVersionUID = 1L;
/**
* Creates an instance.
*/
public UnsupportedRequestException() {}
/**
* Creates an instance with a message.
* @param msg the message
*/
public UnsupportedRequestException(String msg) {
super(msg);
}
/**
* Creates an instance with a message and a cause.
* @param msg the message
* @param cause the cause
*/
public UnsupportedRequestException(String msg, Throwable cause) {
super(msg,cause);
}
/**
* Creates an instance with a cause.
* @param cause the cause
*/
public UnsupportedRequestException(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,134 @@
package org.gcube.common.clients.queries;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.exceptions.DiscoveryException;
/**
* Partial implementation of {@link Query}s.
*
* @author Fabio Simeoni
*
* @param <A> the type of service addresses
* @param <R> the type of query results
*/
public abstract class AbstractQuery<A,R> implements Query<A> {
private final ProxyPlugin<A,?,?> plugin;
private final Map<String,String> conditions = new HashMap<String, String>();
//default matcher does not filter out any result
private ResultMatcher<R> matcher = new ResultMatcher<R>() {
@Override public boolean match(R doc) {
return true;
}
};
/**
* Creates an instance with a {@link ISFacade}, a {@link PluginAdapter}, and a type of IS queries
* @param facade the facade
* @param plugin the plugin
* @param queryClass the query type
*/
protected AbstractQuery(ProxyPlugin<A,?,?> plugin) {
this.plugin = plugin;
}
/**
* Adds a condition to the query.
* @param property an expression that identifies a property of service endpoints
* @param value the value of the property
*/
public void addCondition(String property, String value) {
this.conditions.put(property,value);
}
/**
* Sets an {@link ResultMatcher} for the query.
* @param matcher the matcher.
*/
public void setMatcher(ResultMatcher<R> matcher) {
this.matcher=matcher;
}
@Override
public final List<A> fire() throws DiscoveryException {
//delegate actual execution to subclass-specific mechanisms
List<R> results = fire(conditions);
//from results to addresses
List<A> endpoints = new ArrayList<A>();
for (R result : results)
try {
if (matcher.match(result)) //should we include this? ask matcher
endpoints.add(address(result)); //extract address
}
catch(IllegalArgumentException e) {
//skip result, this is just a signal from subclasses
};
return endpoints;
}
/**
* Executes the query through implementation-specific means.
* @param conditions the conditions to apply on the query prior to its execution
* @return the query results
* @throws DiscoveryException if the query could not be executed
*/
protected abstract List<R> fire(Map<String,String> conditions) throws DiscoveryException;
/**
* Returns an endpoint address from a query result.
* @param result the result
* @return the address
* @throws IllegalArgumentException if an address cannot be derived from the result
*/
protected abstract A address(R result) throws IllegalArgumentException;
/**
* Returns the {@link ProxyPlugin}.
* @return the plugin
*/
protected ProxyPlugin<A,?,?> plugin() {
return plugin;
}
//queries are value objects based on properties
@Override
public final boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AbstractQuery<?,?> other = (AbstractQuery<?,?>) obj;
if (conditions == null) {
if (other.conditions != null)
return false;
} else if (!conditions.equals(other.conditions))
return false;
return true;
}
@Override
public final int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((conditions == null) ? 0 : conditions.hashCode());
return result;
}
@Override
public final String toString() {
return conditions.toString();
}
}

@ -0,0 +1,37 @@
package org.gcube.common.clients.queries;
import java.util.List;
import org.gcube.common.clients.exceptions.DiscoveryException;
/**
* A query for the endpoints of a given service.
*
* @author Fabio Simeoni
*
* @param <A> the type of service endpoint addresses
*/
public interface Query<A> {
/**
* Executes the query.
*
* @return the addresses of the discovered endpoints
* @throws DiscoveryException if query execution fails
*/
List<A> fire() throws DiscoveryException;
//emphasise
@Override
public boolean equals(Object query);
@Override
public int hashCode();
@Override
public String toString();
}

@ -0,0 +1,23 @@
package org.gcube.common.clients.queries;
/**
* A callback to filter out {@link Query} results.
*
* @author Fabio Simeoni
*
*
* @param <R> the type of query results
*
* @see AbstractQuery
*
*/
public interface ResultMatcher<R> {
/**
* Returns <code>true</code> if the result should be retained.
* @param result the result
* @return <code>true</code> if the result should be retained
*/
boolean match(R result);
}

@ -0,0 +1,270 @@
package org.gcube.common.clients;
import static java.util.concurrent.TimeUnit.*;
import static junit.framework.Assert.*;
import static org.gcube.common.clients.delegates.MockDelegate.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.gcube.common.clients.delegates.AsyncProxyDelegate;
import org.gcube.common.clients.delegates.Callback;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.scope.api.ScopeProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
@RunWith(MockitoJUnitRunner.class)
public class AsyncDelegateTest {
AsyncProxyDelegate<Object> delegate;
@Mock ProxyPlugin<Object,Object,?> plugin;
@Mock Object endpoint;
@Mock Call<Object,Object> call;
@Mock Object value;
@Mock Exception original;
@Mock Exception converted;
@Before
@SuppressWarnings("all")
public void setup() throws Exception {
//create subject-under-testing
delegate =new AsyncProxyDelegate<Object>(mockDelegate(plugin,endpoint));
//common configuration staging: mocking a delegate is not that immediate..
when(plugin.name()).thenReturn("some service");
when(plugin.convert(original,delegate.config())).thenReturn(converted);
}
@Test
public void asyncCallsReturnFutureValues() throws Exception {
//stage call
when(call.call(endpoint)).thenReturn(value);
Future<Object> future = delegate.makeAsync(call);
Object output = future.get();
assertEquals(value,output);
assertFalse(future.isCancelled());
assertTrue(future.isDone());
}
@Test
public void asyncCallsTimeout() throws Exception {
//stage call
Answer<?> slowly = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(300); //simulate longer process within call timeout
return value;
}
};
when(call.call(endpoint)).thenAnswer(slowly);
Future<Object> future = delegate.makeAsync(call);
try {
future.get(100,TimeUnit.MILLISECONDS);
fail();
}
catch(TimeoutException e) {}
}
@Test
public void asyncCallsExecuteInCallScope() throws Exception {
final String scope = "a/b/c";
ScopeProvider.instance.set(scope);
//stage call
Answer<?> checkingScope= new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
assertEquals(scope,ScopeProvider.instance.get());
return value;
}
};
when(call.call(endpoint)).thenAnswer(checkingScope);
Future<Object> future = delegate.makeAsync(call);
future.get();
}
@Test
public void asyncCallReturnConvertedFaultsAsInnerCauses() throws Exception {
//stage call
when(call.call(endpoint)).thenThrow(original);
Future<Object> future = delegate.makeAsync(call);
try {
future.get();
fail();
}
catch(Exception fault) {
assertEquals(converted,fault.getCause());
}
}
@Test
public void asyncCallsAreInterrupted() throws Exception {
final String scope = "a/b/c";
ScopeProvider.instance.set(scope);
//stage call
Answer<?> slowly = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(300); //simulate longer process within call timeout
return value;
}
};
when(call.call(endpoint)).thenAnswer(slowly);
final Future<Object> future = delegate.makeAsync(call);
new Thread() {
public void run() {
future.cancel(true);
};
}.start();
try {
future.get();
fail();
}
catch(CancellationException fault) {
assertTrue(future.isCancelled());
}
}
@Test
public void callbacksGetResults() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
//stage call
Answer<?> answer = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
latch.countDown();
return value;
}
};
when(call.call(endpoint)).thenAnswer(answer);
@SuppressWarnings("all")
Callback<Object> callback = mock(Callback.class);
Future<?> future = delegate.makeAsync(call,callback);
//make sure we test after call has been delivered
latch.await(1, SECONDS);
verify(callback).done(value);
assertTrue(future.isDone());
}
@Test
public void callbacksGetTimeoutErrors() throws Exception {
//stage call
Answer<?> slowly = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Thread.sleep(1000);
return value;
}
};
when(call.call(endpoint)).thenAnswer(slowly);
@SuppressWarnings("all")
Callback<Object> callback = mock(Callback.class);
when(callback.timeout()).thenReturn(50L);
Future<?> future = delegate.makeAsync(call,callback);
final CountDownLatch latch = new CountDownLatch(1);
Answer<?> unblock = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
latch.countDown();
return value;
}
};
doAnswer(unblock).when(callback).onFailure(any(TimeoutException.class));
//makes sure callback has been invoked
latch.await(1,SECONDS);
assertTrue(future.isCancelled());
}
@Test
public void callbacksGetFaults() throws Exception {
when(call.call(endpoint)).thenThrow(original);
@SuppressWarnings("all")
Callback<Object> callback = mock(Callback.class);
final CountDownLatch latch = new CountDownLatch(1);
Answer<?> unblock = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Throwable e = (Throwable) invocation.getArguments()[0];
assertEquals(converted,e);
//male sure this method has been invoked
latch.countDown();
return null;
}
};
doAnswer(unblock).when(callback).onFailure(any(Throwable.class));
delegate.makeAsync(call,callback);
//makes sure callback has been invoked
latch.await(1,SECONDS);
}
}

@ -0,0 +1,229 @@
package org.gcube.common.clients;
import static java.util.concurrent.TimeUnit.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.io.StringWriter;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import org.gcube.common.clients.builders.AbstractBuilder;
import org.gcube.common.clients.builders.AbstractStatefulBuilder;
import org.gcube.common.clients.builders.AbstractStatelessBuilder;
import org.gcube.common.clients.builders.AddressingUtils;
import org.gcube.common.clients.builders.StatefulBuilderAPI;
import org.gcube.common.clients.builders.StatelessBuilderAPI;
import org.gcube.common.clients.cache.DefaultEndpointCache;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.config.DiscoveryConfig;
import org.gcube.common.clients.config.EndpointConfig;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.config.ProxyConfig;
import org.gcube.common.clients.delegates.DirectDelegate;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
import org.gcube.common.clients.delegates.ProxyDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.queries.Query;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
@RunWith(MockitoJUnitRunner.class)
public class BuildersTest {
@Mock ProxyPlugin<W3CEndpointReference,Object,SampleProxy> plugin;
@Mock(name="some cache") EndpointCache<W3CEndpointReference> cache;
@Mock(name="sample query") Query<W3CEndpointReference> query;
URI uriAddress = URI.create("http://foobar.org");
Property<String> testProp = new Property<String>("name","value");
StatelessBuilderAPI.Builder<SampleProxy> statelessBuilder;
StatefulBuilderAPI.Builder<W3CEndpointReference,SampleProxy> statefulBuilder;
//we build these proxies that give access to delegate so that we can make assertions
class SampleProxy {
ProxyDelegate<Object> delegate;
public SampleProxy(ProxyDelegate<Object> delegate) {
this.delegate=delegate;
}
}
class SampleStatelessBuilder extends AbstractStatelessBuilder<W3CEndpointReference,Object,SampleProxy> {
public SampleStatelessBuilder(EndpointCache<W3CEndpointReference> cache,Query<W3CEndpointReference> query, Property<?>...properties) {
super(plugin, cache, query,properties);
}
@Override protected W3CEndpointReference convertAddress(W3CEndpointReference address) {
return address;
}
@Override
protected String contextPath() {
return "/context/path/";
}
};
class SampleStatefulBuilder extends AbstractStatefulBuilder<W3CEndpointReference,Object,SampleProxy> {
public SampleStatefulBuilder(EndpointCache<W3CEndpointReference> cache, Property<?>...properties) {
super(plugin,cache,properties);
}
@Override protected W3CEndpointReference convertAddress(W3CEndpointReference address) {
return address;
}
@Override
protected String contextPath() {
return "/context/path/";
}
};
@Before
@SuppressWarnings("all")
public void setup() throws Exception {
when(plugin.name()).thenReturn("someservice");
when(plugin.namespace()).thenReturn("http://acme.org");
when(plugin.newProxy(any(ProxyDelegate.class))).thenAnswer(new Answer<SampleProxy>() {
public SampleProxy answer(InvocationOnMock invocation) throws Throwable {
return new SampleProxy((ProxyDelegate) invocation.getArguments()[0]);
}
});
}
@Test
public void buildersBuildStatelessDirectProxy() {
statelessBuilder = new SampleStatelessBuilder(cache,query);
SampleProxy proxy = statelessBuilder.at(uriAddress).build();
ProxyDelegate<Object> delegate = proxy.delegate;
assertTrue(delegate instanceof DirectDelegate);
ProxyConfig<?,Object> config = delegate.config();
assertEquals(AbstractBuilder.defaultTimeout, config.timeout());
assertEquals(plugin, config.plugin());
assertTrue(config instanceof EndpointConfig);
@SuppressWarnings("unchecked")
EndpointConfig<W3CEndpointReference,Object> econfig = (EndpointConfig<W3CEndpointReference,Object>) config;
StringWriter w1 = new StringWriter();
StringWriter w2 = new StringWriter();
AddressingUtils.address("/context/path/",plugin.name(),uriAddress).writeTo(new StreamResult(w1));
econfig.address().writeTo(new StreamResult(w2));
assertEquals(w1.toString(),w2.toString());
}
@Test
public void buildersBuildStatefulDirectProxy() throws Exception {
//with new timeout default
statefulBuilder = new SampleStatefulBuilder(cache,Property.timeout(15,SECONDS));
//with client-driven timeout
SampleProxy proxy = statefulBuilder.at("key",uriAddress.toURL()).with(Property.timeout(20,SECONDS)).build();
ProxyDelegate<Object> delegate = proxy.delegate;
assertTrue(delegate instanceof DirectDelegate);
ProxyConfig<?,Object> config = delegate.config();
//client-driven timeout wins
assertEquals((int)TimeUnit.SECONDS.toMillis(20), config.timeout());
assertEquals(plugin, config.plugin());
assertTrue(config instanceof EndpointConfig);
@SuppressWarnings("unchecked")
EndpointConfig<W3CEndpointReference,Object> econfig = (EndpointConfig<W3CEndpointReference,Object>) config;
StringWriter w1 = new StringWriter();
StringWriter w2 = new StringWriter();
AddressingUtils.address("/context/path/",plugin.name(),plugin.namespace(),"key",uriAddress).writeTo(new StreamResult(w1));
econfig.address().writeTo(new StreamResult(w2));
System.out.println(w2.toString());
assertEquals(w1.toString(),w2.toString());
}
@Test
public void buildersBuildStatelessDiscoveryProxy() throws Exception {
EndpointCache<W3CEndpointReference> newCache = new DefaultEndpointCache<W3CEndpointReference>();
statelessBuilder = new SampleStatelessBuilder(newCache,query,Property.timeout(15,SECONDS),testProp);
SampleProxy proxy = statelessBuilder.build();
ProxyDelegate<Object> delegate = proxy.delegate;
assertTrue(delegate instanceof DiscoveryDelegate);
ProxyConfig<?,Object> config = delegate.config();
assertEquals(TimeUnit.SECONDS.toMillis(15), config.timeout());
assertTrue(config.hasProperty("name"));
assertEquals(config.property("name",String.class),"value");
assertEquals(plugin, config.plugin());
assertTrue(config instanceof DiscoveryConfig);
@SuppressWarnings("unchecked")
DiscoveryConfig<W3CEndpointReference,Object> dconfig = (DiscoveryConfig<W3CEndpointReference,Object>) config;
assertEquals(newCache, dconfig.cache());
assertEquals(query, dconfig.query());
}
@Test
public void buildersBuildStatefulDiscoveryProxy() {
statefulBuilder = new SampleStatefulBuilder(cache);
SampleProxy proxy = statefulBuilder.matching(query).with(testProp).withTimeout(15,SECONDS).build();
ProxyDelegate<Object> delegate = proxy.delegate;
assertTrue(delegate instanceof DiscoveryDelegate);
ProxyConfig<?,Object> config = delegate.config();
assertEquals(TimeUnit.SECONDS.toMillis(15), config.timeout());
assertTrue(config.hasProperty(testProp.name()));
assertEquals(config.property(testProp.name(),Object.class),testProp.value());
assertEquals(plugin, config.plugin());
assertTrue(config instanceof DiscoveryConfig);
@SuppressWarnings("unchecked")
DiscoveryConfig<W3CEndpointReference,Object> dconfig = (DiscoveryConfig<W3CEndpointReference,Object>) config;
assertEquals(query, dconfig.query());
}
}

@ -0,0 +1,79 @@
package org.gcube.common.clients;
import static junit.framework.Assert.*;
import static org.mockito.Mockito.*;
import org.gcube.common.clients.config.EndpointConfig;
import org.gcube.common.clients.delegates.DirectDelegate;
import org.gcube.common.clients.delegates.ProxyDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DirectDelegateTest {
@Mock Call<Object,Object> call;
@Mock ProxyPlugin<Object,Object,?> plugin;
@Mock EndpointConfig<Object,Object> config;
@Mock(name="some address") Object address;
@Mock Object endpoint;
@Mock Object value;
@Mock Exception original;
@Mock Exception converted;
ProxyDelegate<Object> delegate;
@Before
@SuppressWarnings("all")
public void setup() throws Exception {
//common configuration staging: mocking a delegate is not that immediate..
when(config.plugin()).thenReturn((ProxyPlugin) plugin);
when(config.address()).thenReturn(address);
when(plugin.name()).thenReturn("some service");
when(plugin.convert(original,config)).thenReturn(converted);
when(plugin.resolve(address,config)).thenReturn(endpoint);
//create subject-under-testing
delegate = new DirectDelegate<Object,Object>(config);
}
@Test
public void proxiesResolveAddressesAndMakeCalls() throws Exception {
//stage call
when(call.call(endpoint)).thenReturn(value);
Object output = delegate.make(call);
assertEquals(value,output);
//note: this ensures that proxy has resolved address
}
@Test
public void proxiesConvertAndReturnFaults() throws Exception {
//stage call
when(call.call(endpoint)).thenThrow(original);
//exercise client passes on failures
try {
delegate.make(call);
fail();
}
catch(Exception fault) {
assertEquals(converted,fault);
}
}
}

@ -0,0 +1,293 @@
package org.gcube.common.clients;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static junit.framework.Assert.*;
import static org.gcube.common.clients.cache.Key.*;
import static org.mockito.Mockito.*;
import org.gcube.common.clients.cache.DefaultEndpointCache;
import org.gcube.common.clients.cache.EndpointCache;
import org.gcube.common.clients.cache.Key;
import org.gcube.common.clients.config.DiscoveryConfig;
import org.gcube.common.clients.config.Property;
import org.gcube.common.clients.delegates.DiscoveryDelegate;
import org.gcube.common.clients.delegates.ProxyDelegate;
import org.gcube.common.clients.delegates.ProxyPlugin;
import org.gcube.common.clients.delegates.Unrecoverable;
import org.gcube.common.clients.exceptions.DiscoveryException;
import org.gcube.common.clients.queries.Query;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class DiscoveryDelegateTest {
@Mock Call<Object,Object> call;
@Mock ProxyPlugin<Object,Object,Object> plugin;
@Mock Query<Object> query;
@Mock DiscoveryConfig<Object,Object> config;
@Mock Object endpoint;
@Mock Object endpoint2;
@Mock(name="some address") Object address;
@Mock(name="some address2") Object address2;
@Mock(name="some value") Object value;
@Mock(name="some value2") Object value2;
@Mock(name="some recoverable fault") Exception recoverable;
@Mock(name="some unrecoverable fault") Exception unrecoverable;;
EndpointCache<Object> cache = new DefaultEndpointCache<Object>();
Key key;
ProxyDelegate<Object> delegate;
@Unrecoverable
public static class UnrecoverableFault extends Exception {
private static final long serialVersionUID = 1L;
}
@Before
@SuppressWarnings("all")
public void setup() throws Exception {
String serviceName = "some service";
//common staging for delegate
when(plugin.name()).thenReturn(serviceName);
when(plugin.convert(recoverable,config)).thenReturn(recoverable);
when(plugin.convert(unrecoverable,config)).thenReturn(new UnrecoverableFault());
when(config.plugin()).thenReturn((ProxyPlugin)plugin);
when(config.query()).thenReturn(query);
when(config.cache()).thenReturn(cache);
when(plugin.resolve(address,config)).thenReturn(endpoint);
when(plugin.resolve(address2,config)).thenReturn(endpoint2);
key = key(serviceName,query);
delegate = new DiscoveryDelegate<Object,Object>(config);
}
@Test
public void proxiesDiscoverCallAndCacheEndpoints() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address));
when(call.call(endpoint)).thenReturn(value);
//exercise client
Object output = delegate.make(call);
//address is discovered, resolved into an endpoint and endpoint is called
assertEquals(output,value);
//endpoint is cached
assertEquals(address,cache.get(key));
}
@Test
public void proxiesUseLGEBeforeExecutingQueries() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address));
when(call.call(endpoint)).thenReturn(value);
//cache LGE
cache.put(key, address);
//exercise client
delegate.make(call);
//query is never fired
verify(query,never()).fire();
}
@Test
public void proxiesStopAndClearCacheIfLGEFailsUnrecoverably() throws Exception {
//stage
when(call.call(endpoint)).thenThrow(unrecoverable);
//cache LGE
cache.put(key, address);
try {
//exercise client
delegate.make(call);
fail();
}
catch(UnrecoverableFault fault) {
//fault has been converted
}
//query was never fired
verify(query,never()).fire();
//cache has been cleared
assertNull(cache.get(key));
}
@Test
public void proxiesDiscoverOtherEndpointsWhenLGEFailsRecoverably() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address,address2));
when(call.call(endpoint)).thenThrow(recoverable);
when(call.call(endpoint2)).thenReturn(value2);
//cache LGE
cache.put(key, address);
//exercise client
Object output = delegate.make(call);
//LGE fails, query is executed, LGE is filtered from results
//and remaining endpoint is successfully invoked
assertEquals(output,value2);
//cache has new LGE
assertEquals(address2,cache.get(key));
}
@Test
public void proxiesReturnLGEFailureIfQueryFails() throws Exception {
//stage
when(query.fire()).thenThrow(new DiscoveryException("error"));
when(call.call(endpoint)).thenThrow(recoverable);
//cache LGE
cache.put(key, address);
//exercise client
try {
delegate.make(call);
fail();
}
catch(DiscoveryException unexpected) {
fail();
}
catch(Exception LGEfault) {
//fault has been converted
}
}
@Test
public void proxiesReturnLGEFailureIfQueryHasNoResults() throws Exception {
//stage
when(query.fire()).thenReturn(emptyList());
when(call.call(endpoint)).thenThrow(recoverable);
//cache LGE
cache.put(key, address);
//exercise client
try {
delegate.make(call);
fail();
}
catch(DiscoveryException unexpected) {
fail();
}
catch(Exception LGEfault) {
//fault has been converted
}
}
@Test
public void proxiesReturnQueryFailureIfLGEIsUndefined() throws Exception {
//stage
when(query.fire()).thenThrow(new DiscoveryException("error"));
//exercise client
try {
delegate.make(call);
fail();
}
catch(DiscoveryException fault) {}
}
@Test
public void proxiesReturnFailureIfQueryHasNoResults() throws Exception {
//stage
when(query.fire()).thenReturn(emptyList());
//exercise client
try {
delegate.make(call);
fail();
}
catch(DiscoveryException fault) {}
}
@Test
public void proxiesRecoverIfQueryResultFailsRecoverably() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address,address2));
when(call.call(endpoint)).thenThrow(recoverable);
when(call.call(endpoint2)).thenReturn(value2);
Object output = delegate.make(call);
assertEquals(output,value2);
}
@Test
public void proxiesStopIfQueryResultFailsUnrecoverably() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address,address2));
when(call.call(endpoint)).thenThrow(unrecoverable);
when(call.call(endpoint2)).thenReturn(value2);
try {
delegate.make(call);
fail();
}
catch(UnrecoverableFault fault){}
}
@Test
public void proxiesStopIfCallFailsRecoverablyButSessionIsSticky() throws Exception {
//stage
when(query.fire()).thenReturn(asList(address,address2));
when(call.call(endpoint)).thenThrow(recoverable);
when(call.call(endpoint2)).thenReturn(value2);
when(config.hasProperty(Property.sticky_session)).thenReturn(true);
when(config.property(Property.sticky_session,Boolean.class)).thenReturn(true);
try {
delegate.make(call);
fail();
}
catch(Exception fault){}
}
}
Loading…
Cancel
Save