eu.fbk.knowledgestore.client.Client Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ks-client Show documentation
Show all versions of ks-client Show documentation
KnowledgeStore Java Client
The newest version!
package eu.fbk.knowledgestore.client;
import com.google.common.base.Charsets;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.escape.Escaper;
import com.google.common.io.BaseEncoding;
import com.google.common.net.HttpHeaders;
import com.google.common.net.UrlEscapers;
import eu.fbk.knowledgestore.AbstractKnowledgeStore;
import eu.fbk.knowledgestore.AbstractSession;
import eu.fbk.knowledgestore.Outcome;
import eu.fbk.knowledgestore.Outcome.Status;
import eu.fbk.knowledgestore.Session;
import eu.fbk.knowledgestore.data.*;
import eu.fbk.knowledgestore.internal.Util;
import eu.fbk.knowledgestore.internal.jaxrs.Protocol;
import eu.fbk.knowledgestore.internal.jaxrs.Serializer;
import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
import eu.fbk.knowledgestore.vocabulary.NIE;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.glassfish.jersey.apache.connector.ApacheClientProperties;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.message.GZipEncoder;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.query.BindingSet;
import org.openrdf.rio.RDFFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.core.*;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
// TODO: decide where to place the Configuration class
public final class Client extends AbstractKnowledgeStore {
private static final Logger LOGGER = LoggerFactory.getLogger(Client.class);
private static final String USER_AGENT = String.format(
"KnowledgeStore/%s Apache-HttpClient/%s",
Util.getVersion("eu.fbk.knowledgestore", "ks-core", "devel"),
Util.getVersion("org.apache.httpcomponents", "httpclient", "unknown"));
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
private static final String MIME_TYPE_RDF = "application/x-tql"; // "text/turtle";
private static final String MIME_TYPE_TUPLE = "text/tab-separated-values";
private static final String MIME_TYPE_BOOLEAN = "text/boolean";
private static final int DEFAULT_MAX_CONNECTIONS = 2;
private static final boolean DEFAULT_VALIDATE_SERVER = true;
private static final int DEFAULT_CONNECTION_TIMEOUT = 1000; // 1 sec
private static final int DEFAULT_SOCKET_TIMEOUT = 10000; // 10 sec
private static final boolean DEFAULT_COMPRESSION_ENABLED = LoggerFactory.getLogger(
"org.apache.http.wire").isDebugEnabled();;
private final String serverURL;
private final boolean compressionEnabled;
private final HttpClientConnectionManager connectionManager;
private final javax.ws.rs.client.Client client;
private final Map targets; // path -> URI
private Client(final Builder builder) {
String url = Preconditions.checkNotNull(builder.serverURL);
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
final int timeout;
timeout = MoreObjects.firstNonNull(builder.connectionTimeout, DEFAULT_CONNECTION_TIMEOUT);
Preconditions.checkArgument(timeout >= 0, "Invalid connection timeout %d", timeout);
final int socketTimeout;
socketTimeout = MoreObjects.firstNonNull(builder.socketTimeout, DEFAULT_SOCKET_TIMEOUT);
Preconditions.checkArgument(socketTimeout >= 0, "Invalid connection timeout %d", socketTimeout);
this.serverURL = url;
this.compressionEnabled = MoreObjects.firstNonNull(builder.compressionEnabled,
DEFAULT_COMPRESSION_ENABLED);
this.connectionManager = createConnectionManager(
MoreObjects.firstNonNull(builder.maxConnections, DEFAULT_MAX_CONNECTIONS),
MoreObjects.firstNonNull(builder.validateServer, DEFAULT_VALIDATE_SERVER));
this.client = createJaxrsClient(this.connectionManager, timeout, socketTimeout, builder.proxy);
this.targets = Maps.newConcurrentMap();
}
public synchronized String getServerURL() {
checkNotClosed();
return this.serverURL;
}
@Override
protected Session doNewSession(@Nullable final String username, @Nullable final String password) {
return new SessionImpl(username, password);
}
@Override
protected void doClose() {
try {
this.client.close();
} finally {
this.connectionManager.shutdown();
}
}
private static PoolingHttpClientConnectionManager createConnectionManager(
final int maxConnections, final boolean validateServer) {
// Setup SSLContext and HostnameVerifier based on validateServer parameter
final SSLContext sslContext;
HostnameVerifier hostVerifier;
try {
if (validateServer) {
sslContext = SSLContext.getDefault();
hostVerifier = new DefaultHostnameVerifier();
} else {
sslContext = SSLContext.getInstance(Protocol.HTTPS_PROTOCOLS[0]);
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} }, null);
hostVerifier = NoopHostnameVerifier.INSTANCE;
}
} catch (final Throwable ex) {
throw new RuntimeException("SSL configuration failed", ex);
}
// Create HTTP connection factory
final ConnectionSocketFactory httpConnectionFactory = PlainConnectionSocketFactory
.getSocketFactory();
// Create HTTPS connection factory
final ConnectionSocketFactory httpsConnectionFactory = new SSLConnectionSocketFactory(
sslContext, Protocol.HTTPS_PROTOCOLS, Protocol.HTTPS_CIPHER_SUITES, hostVerifier);
// Create pooled connection manager
final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(
RegistryBuilder.create()
.register("http", httpConnectionFactory)
.register("https", httpsConnectionFactory).build());
// Setup max concurrent connections
manager.setMaxTotal(maxConnections);
manager.setDefaultMaxPerRoute(maxConnections);
manager.setValidateAfterInactivity(1000); // validate connection after 1s idle
return manager;
}
private static javax.ws.rs.client.Client createJaxrsClient(
final HttpClientConnectionManager connectionManager, final int connectionTimeout,
final int socketTimeout, @Nullable final ProxyConfig proxy) {
// Configure requests
final RequestConfig requestConfig = RequestConfig.custom()//
.setExpectContinueEnabled(false) //
.setRedirectsEnabled(false) //
.setConnectionRequestTimeout(connectionTimeout) //
.setConnectTimeout(connectionTimeout) //
.setSocketTimeout(socketTimeout)
.build();
// Configure client
final ClientConfig config = new ClientConfig();
config.connectorProvider(new ApacheConnectorProvider());
config.property(ApacheClientProperties.CONNECTION_MANAGER, connectionManager);
config.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig);
config.property(ApacheClientProperties.DISABLE_COOKIES, true); // not needed
config.property(ClientProperties.REQUEST_ENTITY_PROCESSING,
RequestEntityProcessing.CHUNKED); // required to stream data to the server
if (proxy != null) {
config.property(ClientProperties.PROXY_URI, proxy.getURL());
config.property(ClientProperties.PROXY_USERNAME, proxy.getUsername());
config.property(ClientProperties.PROXY_PASSWORD, proxy.getPassword());
}
// Register filter and custom serializer
config.register(Serializer.class);
config.register(GZipEncoder.class);
// Create and return a configured JAX-RS client
return ClientBuilder.newClient(config);
}
private final class SessionImpl extends AbstractSession {
private final String authorization;
SessionImpl(@Nullable final String username, @Nullable final String password) {
super(Data.newNamespaceMap(Data.newNamespaceMap(), Data.getNamespaceMap()), username,
password);
final String actualUsername = MoreObjects.firstNonNull(username, "");
final String actualPassword = MoreObjects.firstNonNull(password, "");
final String authorizationString = actualUsername + ":" + actualPassword;
final byte[] authorizationBytes = authorizationString.getBytes(Charsets.ISO_8859_1);
this.authorization = "Basic " + BaseEncoding.base64().encode(authorizationBytes);
}
@Override
protected Status doFail(final Throwable ex, final AtomicReference message)
throws Throwable {
if (ex instanceof WebApplicationException) {
final Response response = ((WebApplicationException) ex).getResponse();
try {
final RDFFormat format = RDFFormat.forMIMEType(response.getMediaType()
.toString());
final Outcome outcome = Outcome.decode(
RDFUtil.readRDF((InputStream) response.getEntity(), format, null,
null, false), false).getUnique();
message.set(outcome.getMessage());
return outcome.getStatus();
} catch (final Throwable ex2) {
LOGGER.error("Unable to decode error body", ex2);
return Status.valueOf(response.getStatus());
} finally {
response.close();
}
} else if (ex instanceof ResponseProcessingException) {
final Response response = ((ResponseProcessingException) ex).getResponse();
try {
final StringBuilder builder = new StringBuilder(
"Client side error (server response: ");
builder.append(response.getStatus());
if (response.hasEntity()) {
final String etag = response.getHeaderString(HttpHeaders.ETAG);
builder.append(", ").append(etag != null ? etag : response.getMediaType());
final Date lastModified = response.getLastModified();
if (lastModified != null) {
synchronized (DATE_FORMAT) {
builder.append(", ").append(DATE_FORMAT.format(lastModified));
}
}
}
message.set(builder.toString());
return Status.valueOf(response.getStatus());
} finally {
response.close();
}
} else {
return super.doFail(ex, message);
}
}
@Override
@Nullable
protected Representation doDownload(@Nullable final Long timeout, final URI id,
@Nullable final Set mimeTypes, final boolean useCaches) throws Throwable {
final String query = query(Protocol.PARAMETER_ID, id);
final Map headers = Maps.newHashMap();
if (mimeTypes != null) {
headers.put(HttpHeaders.ACCEPT, mimeTypes);
}
if (!useCaches) {
final CacheControl cacheControl = new CacheControl();
cacheControl.setNoStore(true);
headers.put(HttpHeaders.CACHE_CONTROL, cacheControl);
}
try {
return invoke(HttpMethod.GET, Protocol.PATH_REPRESENTATIONS, query, headers, null,
new GenericType(Representation.class), timeout);
} catch (final WebApplicationException ex) {
if (ex.getResponse().getStatus() == 404) {
ex.getResponse().close();
return null;
}
throw ex;
}
}
@Override
protected Outcome doUpload(@Nullable final Long timeout, final URI id,
final Representation representation) throws Exception {
final String path = Protocol.PATH_REPRESENTATIONS;
final String query = query(Protocol.PARAMETER_ID, id);
final Entity> entity = representation == null ? null : entity(representation);
return invoke(HttpMethod.PUT, path, query, null, entity, Protocol.STREAM_OF_OUTCOMES,
timeout).getUnique();
}
@Override
protected long doCount(@Nullable final Long timeout, final URI type,
@Nullable final XPath condition, @Nullable final Set ids) throws Throwable {
final String path = Protocol.pathFor(type) + "/" + Protocol.SUBPATH_COUNT;
final String query = query(Protocol.PARAMETER_CONDITION, condition,
Protocol.PARAMETER_ID, ids);
final Statement result = invoke(HttpMethod.GET, path, query, null, null,
Protocol.STREAM_OF_STATEMENTS, timeout).getUnique();
return Data.convert(result.getObject(), Long.class);
}
@Override
protected Stream doRetrieve(@Nullable final Long timeout, final URI type,
@Nullable final XPath condition, @Nullable final Set ids,
@Nullable final Set properties, @Nullable final Long offset,
@Nullable final Long limit) throws Throwable {
final String path = Protocol.pathFor(type);
final String query = query(//
Protocol.PARAMETER_CONDITION, condition, //
Protocol.PARAMETER_ID, ids, //
Protocol.PARAMETER_PROPERTY, properties, //
Protocol.PARAMETER_OFFSET, offset, //
Protocol.PARAMETER_LIMIT, limit);
final Stream result = invoke(HttpMethod.GET, path, query, null, null,
Protocol.STREAM_OF_RECORDS, timeout);
result.setProperty("types", ImmutableSet.of(type));
return result;
}
@Override
protected void doCreate(@Nullable final Long timeout, final URI type,
final Stream extends Record> records, final Handler super Outcome> handler)
throws Exception {
final String path = Protocol.pathFor(type) + "/" + Protocol.SUBPATH_CREATE;
final Entity> entity = entity(records, type);
final Stream result = invoke(HttpMethod.POST, path, null, null, entity,
Protocol.STREAM_OF_OUTCOMES, timeout);
result.toHandler(handler);
}
@Override
protected void doMerge(@Nullable final Long timeout, final URI type,
final Stream extends Record> records, final Criteria criteria,
final Handler super Outcome> handler) throws Exception {
final String path = Protocol.pathFor(type) + "/" + Protocol.SUBPATH_MERGE;
final String query = query(Protocol.PARAMETER_CRITERIA, criteria);
final Entity> entity = entity(records, type);
final Stream result = invoke(HttpMethod.POST, path, query, null, entity,
Protocol.STREAM_OF_OUTCOMES, timeout);
result.toHandler(handler);
}
@Override
protected void doUpdate(@Nullable final Long timeout, final URI type,
final XPath condition, final Set ids, final Record record,
final Criteria criteria, final Handler super Outcome> handler) throws Exception {
final String path = Protocol.pathFor(type) + "/" + Protocol.SUBPATH_UPDATE;
final String query = query(//
Protocol.PARAMETER_CRITERIA, criteria, //
Protocol.PARAMETER_CONDITION, condition, //
Protocol.PARAMETER_ID, ids);
final Entity> entity = entity(Stream.create(record), type);
final Stream result = invoke(HttpMethod.POST, path, query, null, entity,
Protocol.STREAM_OF_OUTCOMES, timeout);
result.toHandler(handler);
}
@Override
protected void doDelete(@Nullable final Long timeout, final URI type,
final XPath condition, final Set ids, final Handler super Outcome> handler)
throws Exception {
final String path = Protocol.pathFor(type) + "/" + Protocol.SUBPATH_DELETE;
final String query = query(//
Protocol.PARAMETER_CONDITION, condition, //
Protocol.PARAMETER_ID, ids);
final Stream result = invoke(HttpMethod.POST, path, query, null, null,
Protocol.STREAM_OF_OUTCOMES, timeout);
result.toHandler(handler);
}
@Override
protected Stream doMatch(@Nullable final Long timeout,
final Map conditions, final Map> ids,
final Map> properties) throws Exception {
// TODO
throw new UnsupportedOperationException();
}
@SuppressWarnings("unchecked")
@Override
protected Stream doSparql(@Nullable final Long timeout, final Class type,
final String expression, final Set defaultGraphs, final Set namedGraphs)
throws Exception {
final String path = Protocol.PATH_SPARQL;
final String query = query(//
Protocol.PARAMETER_QUERY, expression, //
Protocol.PARAMETER_DEFAULT_GRAPH, defaultGraphs, //
Protocol.PARAMETER_NAMED_GRAPH, namedGraphs);
GenericType> responseType;
if (type == Statement.class) {
responseType = Protocol.STREAM_OF_STATEMENTS;
} else if (type == BindingSet.class) {
responseType = Protocol.STREAM_OF_TUPLES;
} else if (type == Boolean.class) {
responseType = Protocol.STREAM_OF_BOOLEANS;
} else {
throw new Error("Unexpected result type: " + type);
}
return (Stream) invoke(HttpMethod.GET, path, query, null, null, responseType,
timeout);
}
@Override
protected Outcome doSparqlUpdate(@Nullable Long timeout, @Nullable Stream extends Statement> statements) throws Throwable {
final String path = Protocol.PATH_UPDATE;
final GenericEntity> entity = new GenericEntity>((Stream) statements, Protocol.STREAM_OF_STATEMENTS.getType());
Entity> entityEntity = Entity.entity(entity, new Variant(MediaType.valueOf(MIME_TYPE_RDF), (String) null, Client.this.compressionEnabled ? "gzip" : "identity"));
return invoke(HttpMethod.POST, path, null, null, entityEntity, Protocol.STREAM_OF_OUTCOMES, timeout).getUnique();
}
@Override
protected Outcome doSparqlDelete(@Nullable Long timeout, @Nullable Stream extends Statement> statements) throws Throwable {
final String path = Protocol.PATH_DELETE;
final GenericEntity> entity = new GenericEntity>((Stream) statements, Protocol.STREAM_OF_STATEMENTS.getType());
Entity> entityEntity = Entity.entity(entity, new Variant(MediaType.valueOf(MIME_TYPE_RDF), (String) null, Client.this.compressionEnabled ? "gzip" : "identity"));
return invoke(HttpMethod.POST, path, null, null, entityEntity, Protocol.STREAM_OF_OUTCOMES, timeout).getUnique();
}
private String query(final Object... queryNameValues) {
final StringBuilder builder = new StringBuilder();
final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
String separator = "?";
for (int i = 0; i < queryNameValues.length; i += 2) {
final Object name = queryNameValues[i].toString();
final Object value = queryNameValues[i + 1];
if (value == null) {
continue;
}
final Iterable> iterable = value instanceof Iterable> ? (Iterable>) value
: ImmutableSet.of(value);
for (final Object element : iterable) {
if (element == null) {
continue;
}
String encoded;
if (element instanceof Value && !name.equals(Protocol.PARAMETER_DEFAULT_GRAPH)
&& !name.equals(Protocol.PARAMETER_NAMED_GRAPH)) {
encoded = Data.toString(element, Data.getNamespaceMap());
} else {
encoded = element.toString();
}
builder.append(separator).append(name).append("=");
builder.append(escaper.escape(encoded));
separator = "&";
}
}
return builder.toString();
}
private Entity entity(final Representation representation) {
final String mimeType = representation.getMetadata().getUnique(NIE.MIME_TYPE,
String.class, MediaType.APPLICATION_OCTET_STREAM);
final Variant variant = new Variant(MediaType.valueOf(mimeType), (String) null,
Client.this.compressionEnabled ? "gzip" : "identity");
return Entity.entity(representation, variant);
}
@SuppressWarnings("unchecked")
private Entity>> entity(
final Stream extends Record> records, final URI type) {
records.setProperty("types", ImmutableSet.of(type));
final GenericEntity> entity = new GenericEntity>(
(Stream) records, Protocol.STREAM_OF_RECORDS.getType());
return Entity.entity(entity, new Variant(MediaType.valueOf(MIME_TYPE_RDF),
(String) null, Client.this.compressionEnabled ? "gzip" : "identity"));
}
private T invoke(final String method, final String path, @Nullable final String query,
@Nullable final Map headers,
@Nullable final Entity> requestEntity, final GenericType responseType,
@Nullable final Long timeout) {
// Determine target URI based on path and stored redirections
final String action = method + ":" + path;
final String target = Client.this.targets.get(action);
final String uri = target != null ? target : Client.this.serverURL + "/" + path;
// Do a probe first in case we don't know whether the method / URI pair is restricted
String actualQuery = query;
Entity> actualRequestEntity = requestEntity;
if (target == null) {
actualQuery = Strings.isNullOrEmpty(query) ? "?probe=true" : query + "&probe=true";
if (requestEntity != null) {
final Variant variant = requestEntity.getVariant();
actualRequestEntity = Entity.entity(new byte[0],
new Variant(variant.getMediaType(), (String) null, "identity"));
}
}
// Encode timeout
if (timeout != null) {
final long timeoutInSeconds = Math.max(1, timeout / 1000);
actualQuery = Strings.isNullOrEmpty(actualQuery) ? "?timeout=" + timeoutInSeconds
: actualQuery + "&timeout=" + timeoutInSeconds;
}
// Determine Accept MIME type based on expected (Java) response type
String acceptType = MediaType.WILDCARD;
if (responseType.equals(Protocol.STREAM_OF_RECORDS)
|| responseType.equals(Protocol.STREAM_OF_OUTCOMES)
|| responseType.equals(Protocol.STREAM_OF_STATEMENTS)) {
acceptType = MIME_TYPE_RDF;
} else if (responseType.equals(Protocol.STREAM_OF_TUPLES)) {
acceptType = MIME_TYPE_TUPLE;
} else if (responseType.equals(Protocol.STREAM_OF_BOOLEANS)) {
acceptType = MIME_TYPE_BOOLEAN;
}
// Create an invocation builder for the target URI + query string
final Invocation.Builder invoker = Client.this.client.target(
actualQuery == null ? uri : uri + actualQuery).request(acceptType);
// Add custom headers, if any.
if (headers != null) {
for (final Map.Entry entry : headers.entrySet()) {
invoker.header(entry.getKey(), entry.getValue());
}
}
// Add invocation ID and User-Agent headers
invoker.header(HttpHeaders.USER_AGENT, USER_AGENT);
invoker.header(Protocol.HEADER_INVOCATION, getInvocationID().stringValue());
// Reject response compression, if disabled
invoker.header(HttpHeaders.ACCEPT_ENCODING,
Client.this.compressionEnabled ? "gzip, deflate, identity" : "identity");
// Add credentials IFF the HTTPS scheme is used
if (uri.startsWith("https")) {
invoker.header(HttpHeaders.AUTHORIZATION, this.authorization);
}
// Log the request
if (LOGGER.isDebugEnabled()) {
final StringBuilder builder = new StringBuilder("Http: ");
builder.append(method).append(' ')
.append(actualQuery == null ? uri : uri + actualQuery);
if (actualRequestEntity != null) {
Type type = actualRequestEntity.getEntity().getClass();
if (type.equals(GenericEntity.class)) {
type = ((GenericEntity>) actualRequestEntity.getEntity()).getType();
}
builder.append(' ').append(Util.formatType(type));
builder.append(' ').append(actualRequestEntity.getMediaType());
}
if (getUsername() != null) {
builder.append(' ').append(getUsername());
}
LOGGER.debug(builder.toString());
}
// Perform the request
final long timestamp = System.currentTimeMillis();
final Response response = actualRequestEntity == null ? invoker.method(method) : //
invoker.method(method, actualRequestEntity);
final long elapsed = System.currentTimeMillis() - timestamp;
// Log the response
if (LOGGER.isDebugEnabled()) {
final StringBuilder builder = new StringBuilder("Http: ");
builder.append(response.getStatus());
if (response.hasEntity()) {
final String etag = response.getHeaderString(HttpHeaders.ETAG);
builder.append(", ").append(etag != null ? etag : response.getMediaType());
final Date lastModified = response.getLastModified();
if (lastModified != null) {
synchronized (DATE_FORMAT) {
builder.append(", ").append(DATE_FORMAT.format(lastModified));
}
}
}
builder.append(", ").append(elapsed).append(" ms");
LOGGER.debug(builder.toString());
}
// On redirection, close response, update targets map and try again
final int status = response.getStatus();
if (status == 302 || status == 307 || status == 308) {
response.close();
String newURI = response.getHeaderString(HttpHeaders.LOCATION);
final int index = newURI.indexOf('?');
newURI = index < 0 ? newURI : newURI.substring(0, index);
Client.this.targets.put(action, newURI);
LOGGER.debug("Http: stored redirection: {} -> {}", path, newURI);
return invoke(method, path, query, headers, requestEntity, responseType, timeout);
}
// Otherwise, update targets map and either return response or fail
Client.this.targets.put(action, uri);
if (status / 100 == 2) {
if (Representation.class.isAssignableFrom(responseType.getRawType())) {
response.bufferEntity();
}
final T result = response.readEntity(responseType);
if (result instanceof Stream>) {
((Stream>) result).onClose(new Runnable() {
@Override
public void run() {
response.close();
}
});
}
return result;
} else {
Util.closeQuietly(response);
throw new WebApplicationException(response);
}
}
}
public static Builder builder(final String serverURL) {
return new Builder(serverURL);
}
public static class Builder {
String serverURL;
@Nullable
Integer maxConnections;
@Nullable
Integer connectionTimeout;
@Nullable
Integer socketTimeout;
@Nullable
Boolean compressionEnabled;
@Nullable
Boolean validateServer;
@Nullable
ProxyConfig proxy;
Builder(final String serverURL) {
this.serverURL = Preconditions.checkNotNull(serverURL);
}
public Builder maxConnections(@Nullable final Integer maxConnections) {
this.maxConnections = maxConnections;
return this;
}
public Builder connectionTimeout(@Nullable final Integer connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
public Builder socketTimeout(@Nullable final Integer socketTimeout) {
this.socketTimeout = socketTimeout;
return this;
}
public Builder compressionEnabled(@Nullable final Boolean compressionEnabled) {
this.compressionEnabled = compressionEnabled;
return this;
}
public Builder validateServer(@Nullable final Boolean validateServer) {
this.validateServer = validateServer;
return this;
}
public Builder proxy(@Nullable final ProxyConfig proxy) {
this.proxy = proxy;
return this;
}
public Client build() {
return new Client(this);
}
}
}