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. * *

* * 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 the type of service addresses * @param the type of service stubs * * @see Query * @see EndpointCache */ public class DiscoveryDelegate extends AbstractDelegate> { 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 config) { super(config); } //helper private boolean isAnchored() { return config().hasProperty(Property.sticky_session) ? config().property(Property.sticky_session,Boolean.class): false; } @Override public V make(Call call) throws Exception { ProxyPlugin plugin = config().plugin(); Query query = config().query(); EndpointCache 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 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 boolean isUnrecoverable(Exception e) { try { return e.getClass().isAnnotationPresent(Unrecoverable.class); } catch (Exception ex) { throw new RuntimeException(ex); } } // helper private List filterResults(A cached, List results) { List endpoints = new ArrayList(); for (A result : results) if (!result.equals(cached)) endpoints.add(result); return endpoints; } }