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-b396298e98cfmaster
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 @@
|
||||
|
@ -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…
Reference in New Issue