/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.transport.jaxrs; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeField; import org.fudgemsg.FudgeMsg; import org.fudgemsg.MutableFudgeMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.transport.EndPointDescriptionProvider; import com.opengamma.util.ArgumentChecker; import com.sun.jersey.api.client.Client; /** * An implementation of {@link EndPointDescriptionProvider} that describes URLs */ public class UriEndPointDescriptionProvider implements EndPointDescriptionProvider { private static final Logger s_logger = LoggerFactory.getLogger(UriEndPointDescriptionProvider.class); /** * Type of connection. Always {@link #TYPE_VALUE}. */ public static final String TYPE_KEY = "type"; /** * Value of the type of connection. */ public static final String TYPE_VALUE = "Uri"; /** * URL. */ public static final String URI_KEY = "uri"; private final List<String> _uris; public UriEndPointDescriptionProvider(final String uri) { _uris = Collections.singletonList(uri); } public UriEndPointDescriptionProvider(final List<String> uris) { _uris = new ArrayList<String>(uris); } @Override public FudgeMsg getEndPointDescription(final FudgeContext fudgeContext) { final MutableFudgeMsg msg = fudgeContext.newMessage(); msg.add(TYPE_KEY, TYPE_VALUE); for (final String url : _uris) { msg.add(URI_KEY, url); } return msg; } /** * Default message production allows simple use in a configuration resource. * * @param fudgeContext the Fudge context * @return the end point description message, as returned by {@link #getEndPointDescription} */ public FudgeMsg toFudgeMsg(final FudgeContext fudgeContext) { return getEndPointDescription(fudgeContext); } /** * Validation service to extract a URI that can be contacted. E.g. if the remote description publishes a number of alternative ones for failover/redundancy purposes. */ public static final class Validater { private static final URI NULL_URI = URI.create("null:0"); private int _timeout = 10000; // Default 10s timeout private volatile Client _client; private final Executor _executor; private final URI _baseURI; private Validater(final Executor executorService, final URI baseURI) { _executor = executorService; _baseURI = baseURI; } // Caller must hold the monitor private void configureClient(final Client client) { client.setReadTimeout(_timeout); client.setConnectTimeout(_timeout); } private Client getClient() { Client client = _client; if (client == null) { synchronized (this) { client = _client; if (client == null) { client = Client.create(); configureClient(client); _client = client; } } } return client; } // Caller must hold the monitor private void createOrConfigureClient() { final Client client = _client; if (client == null) { getClient(); } else { configureClient(client); } } public synchronized void setTimeout(final int timeout) { _timeout = timeout; createOrConfigureClient(); } private boolean validateType(final FudgeMsg endPoint) { for (final FudgeField typeField : endPoint.getAllByName(TYPE_KEY)) { if (TYPE_VALUE.equals(typeField.getValue())) { return true; } } return false; } private URI getAccessibleURI(final FudgeMsg endPoint, final FudgeField uriField) { try { final String uriString = endPoint.getFieldValue(String.class, uriField); final URI uri = (_baseURI != null) ? _baseURI.resolve(uriString) : new URI(uriString); final int status = getClient().resource(uri).head().getStatus(); s_logger.debug("{} returned {}", uri, status); switch (status) { case 200: case 405: return uri; } } catch (final Exception ex) { s_logger.warn("URI {} not accessible", uriField); s_logger.debug("Exception caught", ex); } return NULL_URI; } public URI getAccessibleURI(final FudgeMsg endPoint) { ArgumentChecker.notNull(endPoint, "endPoint"); if (!validateType(endPoint)) { throw new IllegalArgumentException("End point is not a REST target - " + endPoint); } final List<FudgeField> uriFields = endPoint.getAllByName(URI_KEY); URI uri = NULL_URI; if (uriFields.size() > 1) { final BlockingQueue<URI> result = new LinkedBlockingQueue<URI>(); int count = uriFields.size(); for (final FudgeField uriField : uriFields) { _executor.execute(new Runnable() { @Override public void run() { result.add(getAccessibleURI(endPoint, uriField)); } }); } do { try { uri = result.poll(_timeout * 2, TimeUnit.MILLISECONDS); } catch (final InterruptedException ex) { throw new OpenGammaRuntimeException("Interrupted", ex); } } while ((uri == NULL_URI) && (--count > 0)); } else if (uriFields.size() == 1) { uri = getAccessibleURI(endPoint, uriFields.get(0)); } if (uri == NULL_URI) { s_logger.error("No accessible URIs found in {}", endPoint); return null; } else { s_logger.info("Using {}", uri); return uri; } } public Collection<String> getAllURIStrings(final FudgeMsg endPoint) { ArgumentChecker.notNull(endPoint, "endPoint"); if (!validateType(endPoint)) { return Collections.emptySet(); } final Collection<FudgeField> uriFields = endPoint.getAllByName(URI_KEY); final List<String> results = new ArrayList<String>(uriFields.size()); for (final FudgeField uriField : uriFields) { final String str = endPoint.getFieldValue(String.class, uriField); if (str != null) { results.add(str); } } return results; } public URI getAccessibleURI(final FudgeContext fudgeContext, final EndPointDescriptionProvider endPointProvider) { return getAccessibleURI(endPointProvider.getEndPointDescription(fudgeContext)); } } /** * Creates a new validater. * * @param executorService the executor to use for parallel resolution of targets * @param baseURI the base URL that the original end point was described by (e.g. if it contains a relative reference) * @return the validater */ public static Validater validater(final Executor executorService, final URI baseURI) { return new Validater(executorService, baseURI); } /** * Extracts a URI from a description message that responds to a http request. * * @param executorService the executor to use for parallel resolution of targets * @param baseURI the base URI that the original end point was described by (e.g. if it contains a relative reference) * @param endPoint the end point description message * @return a URI that responds, or null if there is none */ public static URI getAccessibleURI(final Executor executorService, final URI baseURI, final FudgeMsg endPoint) { return validater(executorService, baseURI).getAccessibleURI(endPoint); } /** * Extracts a URI from a description message provider. * * @param executorService the executor to use for parallel resolution of targets * @param fudgeContext the Fudge context to use for working with the end point description message * @param baseURI the base URI that the original end point was described by (e.g. if it contains a relative reference) * @param endPointProvider the end point description provider * @return a URI that responds, or null if there is none */ public static URI getAccessibleURI(final Executor executorService, final FudgeContext fudgeContext, final URI baseURI, final EndPointDescriptionProvider endPointProvider) { ArgumentChecker.notNull(endPointProvider, "endPointProvider"); return getAccessibleURI(executorService, baseURI, endPointProvider.getEndPointDescription(fudgeContext)); } /** * Extracts a URI from a description message provider that operates over the network. * * @param executorService the executor to use for parallel resolution of targets * @param fudgeContext the Fudge context to use for working with the end point description message * @param endPointProvider the end point description provider * @return a URI that responds, or null if there is none */ public static URI getAccessibleURI(final Executor executorService, final FudgeContext fudgeContext, final RemoteEndPointDescriptionProvider endPointProvider) { ArgumentChecker.notNull(endPointProvider, "endPointProvider"); return getAccessibleURI(executorService, fudgeContext, endPointProvider.getUri(), endPointProvider); } }