/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2014 ForgeRock AS. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * http://forgerock.org/license/CDDLv1.0.html * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at http://forgerock.org/license/CDDLv1.0.html * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" */ package org.forgerock.openicf.misc.crest; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; import org.apache.http.ContentTooLongException; import org.apache.http.HttpEntity; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.annotation.Immutable; import org.apache.http.client.HttpResponseException; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.utils.URIBuilder; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.apache.http.nio.ContentDecoder; import org.apache.http.nio.IOControl; import org.apache.http.nio.entity.ContentBufferEntity; import org.apache.http.nio.protocol.AbstractAsyncResponseConsumer; import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; import org.apache.http.nio.util.HeapByteBufferAllocator; import org.apache.http.nio.util.SimpleInputBuffer; import org.apache.http.protocol.HttpContext; import org.apache.http.util.Asserts; import org.apache.http.util.EntityUtils; import org.forgerock.json.fluent.JsonPointer; import org.forgerock.json.fluent.JsonValue; import org.forgerock.json.fluent.JsonValueException; import org.forgerock.json.resource.ActionRequest; import org.forgerock.json.resource.Connection; import org.forgerock.json.resource.Context; import org.forgerock.json.resource.CreateRequest; import org.forgerock.json.resource.DeleteRequest; import org.forgerock.json.resource.FutureResult; import org.forgerock.json.resource.InternalServerErrorException; import org.forgerock.json.resource.PatchOperation; import org.forgerock.json.resource.PatchRequest; import org.forgerock.json.resource.QueryRequest; import org.forgerock.json.resource.QueryResult; import org.forgerock.json.resource.QueryResultHandler; import org.forgerock.json.resource.ReadRequest; import org.forgerock.json.resource.Request; import org.forgerock.json.resource.Resource; import org.forgerock.json.resource.ResourceException; import org.forgerock.json.resource.ResourceName; import org.forgerock.json.resource.ResultHandler; import org.forgerock.json.resource.ServiceUnavailableException; import org.forgerock.json.resource.SortKey; import org.forgerock.json.resource.UpdateRequest; /** * A NAME does ... * * @author Laszlo Hordos */ public abstract class AbstractRemoteConnection implements Connection { static final String ETAG_ANY = "*"; static final String PARAM_ACTION = param(ActionRequest.FIELD_ACTION); static final String PARAM_FIELDS = param(Request.FIELD_FIELDS); static final String PARAM_PAGE_SIZE = param(QueryRequest.FIELD_PAGE_SIZE); static final String PARAM_PAGED_RESULTS_COOKIE = param(QueryRequest.FIELD_PAGED_RESULTS_COOKIE); static final String PARAM_PAGED_RESULTS_OFFSET = param(QueryRequest.FIELD_PAGED_RESULTS_OFFSET); static final String PARAM_QUERY_EXPRESSION = param(QueryRequest.FIELD_QUERY_EXPRESSION); static final String PARAM_QUERY_FILTER = param(QueryRequest.FIELD_QUERY_FILTER); static final String PARAM_QUERY_ID = param(QueryRequest.FIELD_QUERY_ID); static final String PARAM_SORT_KEYS = param(QueryRequest.FIELD_SORT_KEYS); static final Pattern CONTENT_TYPE_REGEX = Pattern.compile( "^application/json([ ]*;[ ]*charset=utf-8)?$", Pattern.CASE_INSENSITIVE); private final ResourceName resourceName; private final HttpHost httpHost; protected AbstractRemoteConnection(ResourceName resourceName, HttpHost httpHost) { this.resourceName = resourceName; this.httpHost = httpHost; } public abstract <T> Future<T> execute(Context context, HttpUriRequest request, final HttpAsyncResponseConsumer<T> responseConsumer, FutureCallback<T> callback); /* public abstract FutureResult<JsonValue> execute(Context context, HttpUriRequest request, HttpAsyncResponseConsumer<JsonValue> responseConsumer, FutureCallback<JsonValue> callback); public abstract FutureResult<Resource> execute(Context context, HttpUriRequest request, HttpAsyncResponseConsumer<Resource> responseConsumer, FutureCallback<Resource> callback); public abstract FutureResult<QueryResult> execute(Context context, HttpUriRequest request, QueryResultHandler handler, HttpAsyncResponseConsumer<QueryResult> responseConsumer, FutureCallback<QueryResult> callback); */ protected abstract JsonValue parseJsonBody(final HttpEntity entity, final boolean allowEmpty) throws ResourceException; protected abstract QueryResult parseQueryResponse(final HttpResponse response, final QueryResultHandler handler) throws ResourceException; public ResourceName getResourceName() { return resourceName; } public HttpHost getHttpHost() { return httpHost; } @Override public void close() { // Do Nothing } @Override public boolean isValid() { return !isClosed(); } @Override public JsonValue action(final Context context, final ActionRequest request) throws ResourceException { final FutureResult<JsonValue> future = actionAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<JsonValue> actionAsync(final Context context, final ActionRequest request, final ResultHandler<? super JsonValue> handler) { try { final Future<JsonValue> result = execute(context, convert(request), new JsonValueResponseHandler(), new InternalFutureCallback<JsonValue>( (ResultHandler<JsonValue>) handler)); return new InternalFutureResult<JsonValue>(result); } catch (final Throwable t) { final ResourceException exception = adapt(t); if (null != handler) { handler.handleError(exception); } return new FailedFutureResult<JsonValue>(exception); } } @Override public QueryResult query(final Context context, final QueryRequest request, final QueryResultHandler handler) throws ResourceException { final FutureResult<QueryResult> future = queryAsync(context, request, handler); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public QueryResult query(final Context context, final QueryRequest request, final Collection<? super Resource> results) throws ResourceException { final QueryResultHandler handler = new QueryResultHandler() { @Override public void handleError(final ResourceException error) { // Ignore - handled by future. } @Override public boolean handleResource(final Resource resource) { results.add(resource); return true; } @Override public void handleResult(final QueryResult result) { // Ignore - handled by future. } }; return query(context, request, handler); } @Override public FutureResult<QueryResult> queryAsync(Context context, QueryRequest request, final QueryResultHandler handler) { try { final Future<QueryResult> result = execute(context, convert(request), new QueryResultResponseHandler(handler), new InternalFutureCallback<QueryResult>(handler)); return new InternalFutureResult<QueryResult>(result); } catch (final Throwable t) { final ResourceException exception = adapt(t); if (null != handler) { handler.handleError(exception); } return new FailedFutureResult<QueryResult>(exception); } } @Override public Resource create(final Context context, final CreateRequest request) throws ResourceException { final FutureResult<Resource> future = createAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<Resource> createAsync(Context context, CreateRequest request, ResultHandler<? super Resource> handler) { return handleRequestAsync(context, request, handler); } @Override public Resource delete(final Context context, final DeleteRequest request) throws ResourceException { final FutureResult<Resource> future = deleteAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<Resource> deleteAsync(Context context, DeleteRequest request, ResultHandler<? super Resource> handler) { return handleRequestAsync(context, request, handler); } @Override public Resource patch(final Context context, final PatchRequest request) throws ResourceException { final FutureResult<Resource> future = patchAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<Resource> patchAsync(Context context, PatchRequest request, ResultHandler<? super Resource> handler) { return handleRequestAsync(context, request, handler); } @Override public Resource read(final Context context, final ReadRequest request) throws ResourceException { final FutureResult<Resource> future = readAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<Resource> readAsync(Context context, ReadRequest request, ResultHandler<? super Resource> handler) { return handleRequestAsync(context, request, handler); } @Override public Resource update(final Context context, final UpdateRequest request) throws ResourceException { final FutureResult<Resource> future = updateAsync(context, request, null); try { return future.get(); } catch (final InterruptedException e) { throw interrupted(e); } finally { // Cancel the request if it hasn't completed. future.cancel(false); } } @Override public FutureResult<Resource> updateAsync(Context context, UpdateRequest request, ResultHandler<? super Resource> handler) { return handleRequestAsync(context, request, handler); } private HttpUriRequest convert(Request request) throws ResourceException { URIBuilder builder = getUriBuilder(request); HttpUriRequest rq = null; try { switch (request.getRequestType()) { case ACTION: { ActionRequest actionRequest = (ActionRequest) request; builder.setParameter(PARAM_ACTION, actionRequest.getAction()); for (Map.Entry<String, String> entry : actionRequest.getAdditionalParameters() .entrySet()) { builder.setParameter(entry.getKey(), entry.getValue()); } HttpPost httpRequest = new HttpPost(builder.build()); httpRequest.setEntity(new JsonEntity(actionRequest.getContent())); rq = httpRequest; rq.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); rq.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); break; } case CREATE: { CreateRequest createRequest = (CreateRequest) request; if (null == createRequest.getNewResourceId()) { builder.setParameter(PARAM_ACTION, ActionRequest.ACTION_ID_CREATE); HttpPost httpRequest = new HttpPost(builder.build()); httpRequest.setEntity(new JsonEntity(createRequest.getContent())); rq = httpRequest; } else { HttpPut httpRequest = new HttpPut(builder.build()); httpRequest.setEntity(new JsonEntity(createRequest.getContent())); httpRequest.setHeader(HttpHeaders.IF_NONE_MATCH, ETAG_ANY); rq = httpRequest; } rq.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); rq.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); break; } case UPDATE: { UpdateRequest updateRequest = (UpdateRequest) request; HttpPut httpRequest = new HttpPut(builder.build()); httpRequest.setEntity(new JsonEntity(updateRequest.getContent())); if (null != updateRequest.getRevision()) { httpRequest.setHeader(HttpHeaders.ETAG, updateRequest.getRevision()); } rq = httpRequest; rq.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); rq.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); break; } case PATCH: { PatchRequest patchRequest = (PatchRequest) request; List<Map<String, Object>> patch = new ArrayList<Map<String, Object>>(patchRequest.getPatchOperations().size()); for (PatchOperation operation : patchRequest.getPatchOperations()) { patch.add(operation.toJsonValue().asMap()); } HttpPatch httpRequest = new HttpPatch(builder.build()); httpRequest.setEntity(new JsonEntity(patch)); if (null != patchRequest.getRevision()) { httpRequest.setHeader(HttpHeaders.ETAG, patchRequest.getRevision()); } rq = httpRequest; rq.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString()); rq.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString()); break; } case DELETE: { DeleteRequest deleteRequest = (DeleteRequest) request; rq = new HttpDelete(builder.build()); if (null != deleteRequest.getRevision()) { rq.setHeader(HttpHeaders.ETAG, deleteRequest.getRevision()); } break; } case READ: { rq = new HttpGet(builder.build()); break; } case QUERY: { QueryRequest queryRequest = (QueryRequest) request; for (Map.Entry<String, String> entry : queryRequest.getAdditionalParameters() .entrySet()) { builder.setParameter(entry.getKey(), entry.getValue()); } if (null != queryRequest.getQueryId()) { builder.setParameter(PARAM_QUERY_ID, queryRequest.getQueryId()); } else if (null != queryRequest.getQueryFilter()) { builder.setParameter(PARAM_QUERY_FILTER, queryRequest.getQueryFilter() .toString()); } else if (null != queryRequest.getQueryExpression()) { builder.setParameter(PARAM_QUERY_EXPRESSION, queryRequest.getQueryExpression()); } if (0 < queryRequest.getPageSize()) { builder.setParameter(PARAM_PAGE_SIZE, Integer.toString(queryRequest .getPageSize())); if (null != queryRequest.getPagedResultsCookie()) { builder.setParameter(PARAM_PAGED_RESULTS_COOKIE, queryRequest .getPagedResultsCookie()); } builder.setParameter(PARAM_PAGED_RESULTS_OFFSET, Integer.toString(queryRequest .getPagedResultsOffset())); } for (SortKey key : queryRequest.getSortKeys()) { builder.setParameter(PARAM_SORT_KEYS, key.toString()); } rq = new HttpGet(builder.build()); break; } } } catch (URISyntaxException e) { throw new InternalServerErrorException(e); } return rq; } private URIBuilder getUriBuilder(Request request) { ResourceName resourceName = getResourceName().concat(request.getResourceNameObject()); if (request instanceof CreateRequest && null != ((CreateRequest) request).getNewResourceId()) { resourceName = resourceName.concat(((CreateRequest) request).getNewResourceId()); } URIBuilder builder = new URIBuilder().setScheme(getHttpHost().getSchemeName()).setHost( getHttpHost().getHostName()).setPort(getHttpHost().getPort()).setPath( "/" + resourceName.toString()); for (JsonPointer field : request.getFields()) { builder.addParameter(PARAM_FIELDS, field.toString()); } return builder; } private FutureResult<Resource> handleRequestAsync(final Context context, final Request request, final ResultHandler<? super Resource> handler) { try { final Future<Resource> result = execute(context, convert(request), new ResourceResponseHandler(), new InternalFutureCallback<Resource>((ResultHandler<Resource>) handler)); return new InternalFutureResult<Resource>(result); } catch (final Throwable t) { final ResourceException exception = adapt(t); if (null != handler) { handler.handleError(exception); } return new FailedFutureResult<Resource>(exception); } } // Handle thread interruption. private ResourceException interrupted(final InterruptedException e) { return new ServiceUnavailableException("Client thread interrupted", e); } // Internal Class definitions @Immutable static abstract public class AbstractJsonValueResponseHandler<T> extends AbstractAsyncResponseConsumer<T> { protected volatile HttpResponse response; private volatile SimpleInputBuffer buf; @Override protected void onResponseReceived(final HttpResponse response) throws IOException { this.response = response; } @Override protected void onEntityEnclosed(final HttpEntity entity, final ContentType contentType) throws IOException { long len = entity.getContentLength(); if (len > Integer.MAX_VALUE) { throw new ContentTooLongException("Entity content is too long: " + len); } if (len < 0) { len = 4096; } this.buf = new SimpleInputBuffer((int) len, new HeapByteBufferAllocator()); this.response.setEntity(new ContentBufferEntity(entity, this.buf)); } @Override protected void onContentReceived(final ContentDecoder decoder, final IOControl ioctrl) throws IOException { Asserts.notNull(this.buf, "Content buffer"); this.buf.consumeContent(decoder); } @Override protected void releaseResources() { this.response = null; this.buf = null; } } /** * A {@link org.apache.http.client.ResponseHandler} that returns the * response body as a JsonValue for successful (2xx) responses. If the * response code was >= 300, the response body is consumed and an * {@link HttpResponseResourceException} is thrown. * <p/> * If this is used with * {@link org.apache.http.client.HttpClient#execute(org.apache.http.client.methods.HttpUriRequest, org.apache.http.client.ResponseHandler)} * , HttpClient may handle redirects (3xx responses) internally. */ @Immutable public class JsonValueResponseHandler extends AbstractJsonValueResponseHandler<JsonValue> { /** * Returns the response body as a String if the response was successful * (a 2xx status code). If no response body exists, this returns null. * If the response was unsuccessful (>= 300 status code), throws an * {@link org.apache.http.client.HttpResponseException}. */ @Override protected JsonValue buildResult(HttpContext context) throws Exception { return parseResponse(response); } } /** * A {@link org.apache.http.client.ResponseHandler} that returns the * response body as a String for successful (2xx) responses. If the response * code was >= 300, the response body is consumed and an * {@link org.apache.http.client.HttpResponseException} is thrown. * <p/> * If this is used with * {@link org.apache.http.client.HttpClient#execute(org.apache.http.client.methods.HttpUriRequest, org.apache.http.client.ResponseHandler)} * , HttpClient may handle redirects (3xx responses) internally. */ @Immutable public class QueryResultResponseHandler extends AbstractJsonValueResponseHandler<QueryResult> { private final QueryResultHandler handler; public QueryResultResponseHandler(final QueryResultHandler handler) { this.handler = handler; } /** * Returns the response body as a String if the response was successful * (a 2xx status code). If no response body exists, this returns null. * If the response was unsuccessful (>= 300 status code), throws an * {@link org.apache.http.client.HttpResponseException}. */ public QueryResult buildResult(HttpContext context) throws Exception { return parseQueryResponse(response, handler); } } /** * A {@link org.apache.http.client.ResponseHandler} that returns the * response body as a Resource for successful (2xx) responses. If the * response code was >= 300, the response body is consumed and an * {@link HttpResponseResourceException} is thrown. * <p/> * If this is used with * {@link org.apache.http.client.HttpClient#execute(org.apache.http.client.methods.HttpUriRequest, org.apache.http.client.ResponseHandler)} * , HttpClient may handle redirects (3xx responses) internally. */ @Immutable public class ResourceResponseHandler extends AbstractJsonValueResponseHandler<Resource> { /** * Returns the response body as a String if the response was successful * (a 2xx status code). If no response body exists, this returns null. * If the response was unsuccessful (>= 300 status code), throws an * {@link org.apache.http.client.HttpResponseException}. */ public Resource buildResult(HttpContext context) throws Exception { return getAsResource(parseResponse(response)); } } /** * The HttpResponseResourceException wraps the {@link ResourceException} * into {@link org.apache.http.client.HttpResponseException}. * <p/> * This exception is used inside * {@link org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)}. */ public static class HttpResponseResourceException extends HttpResponseException { private static final long serialVersionUID = 1L; private final ResourceException cause; public HttpResponseResourceException(ResourceException wrapped) { super(wrapped.getCode(), wrapped.getReason()); cause = wrapped; } @Override public ResourceException getCause() { return cause; } } static private class InternalFutureCallback<R> implements FutureCallback<R> { private final ResultHandler<R> handler; protected InternalFutureCallback(final ResultHandler<R> handler) { this.handler = handler; } protected ResultHandler<R> getResultHandler() { return handler; } protected R adapt(R source) throws ResourceException { return source; } @Override public void completed(final R s) { final ResultHandler<R> handler = getResultHandler(); if (null != handler) { try { handler.handleResult(adapt(s)); } catch (final ResourceException e) { handler.handleError(e); } } } @Override public void failed(final Exception e) { final ResultHandler<R> handler = getResultHandler(); if (null != handler) { if (e instanceof HttpResponseResourceException) { handler.handleError(((HttpResponseResourceException) e).getCause()); } else { handler.handleError(new InternalServerErrorException(e)); } } } @Override public void cancelled() { final ResultHandler<R> handler = getResultHandler(); if (null != handler) { handler.handleError(new ServiceUnavailableException("Client thread interrupted")); } } } /** * @param < T > */ private static class InternalFutureResult<T> implements FutureResult<T> { private final Future<T> futureTask; protected InternalFutureResult(final Future<T> futureTask) { this.futureTask = futureTask; } protected Future<T> getResult() { return futureTask; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return getResult().cancel(mayInterruptIfRunning); } @Override public T get() throws ResourceException, InterruptedException { try { return getResult().get(); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof ResourceException) { throw (ResourceException) cause; } throw new InternalServerErrorException(e); } } @Override public T get(long timeout, TimeUnit unit) throws ResourceException, TimeoutException, InterruptedException { try { return getResult().get(timeout, unit); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof ResourceException) { throw (ResourceException) cause; } throw new InternalServerErrorException(e); } } @Override public boolean isCancelled() { return getResult().isCancelled(); } @Override public boolean isDone() { return getResult().isDone(); } } static class FailedFutureResult<V> implements FutureResult<V> { private ResourceException exception; FailedFutureResult(final ResourceException result) { this.exception = result; } @Override public boolean cancel(boolean mayInterruptIfRunning) { return false; // Cannot cancel. } @Override public V get() throws ResourceException, InterruptedException { throw exception; } @Override public V get(long timeout, TimeUnit unit) throws ResourceException, TimeoutException, InterruptedException { throw exception; } @Override public boolean isCancelled() { return false; } @Override public boolean isDone() { return true; } } // // Protected static methods // protected static Resource getAsResource(final JsonValue value) throws ResourceException { try { return new Resource(value.get(Resource.FIELD_CONTENT_ID).asString(), value .get(Resource.FIELD_CONTENT_REVISION).asString(), value); } catch (JsonValueException e) { // What shell we do if the response does not contains the _id throw new InternalServerErrorException(e); } } protected static ResourceException getAsResourceException(JsonValue resourceException) { ResourceException exception = null; if (resourceException.isMap()) { JsonValue code = resourceException.get(ResourceException.FIELD_CODE); if (code.isNumber()) { String message = resourceException.get(ResourceException.FIELD_MESSAGE).asString(); exception = ResourceException.getException(code.asInteger(), message); String reason = resourceException.get(ResourceException.FIELD_REASON).asString(); if (null != reason) { exception.setReason(reason); } JsonValue detail = resourceException.get(ResourceException.FIELD_DETAIL); if (!detail.isNull()) { exception.setDetail(detail); } } } return exception; } protected ResourceException isOK(StatusLine statusLine, HttpEntity entity) { ResourceException exception = null; if (statusLine.getStatusCode() >= 300) { String contentType = entity.getContentType().getValue(); if (contentType != null && CONTENT_TYPE_REGEX.matcher(contentType).matches()) { try { JsonValue resourceException = parseJsonBody(entity, true); exception = getAsResourceException(resourceException); } catch (ResourceException e) { /* ignore */ } } else { try { EntityUtils.consume(entity); } catch (IOException e) { /* ignore */ } } if (null == exception) { exception = ResourceException.getException(statusLine.getStatusCode(), statusLine .getReasonPhrase()); } } return exception; } protected JsonValue parseResponse(final HttpResponse response) throws ResourceException { final StatusLine statusLine = response.getStatusLine(); final HttpEntity entity = response.getEntity(); ResourceException exception = isOK(statusLine, entity); if (null != exception) { throw exception; } return parseJsonBody(entity, true); } protected static String param(final String field) { return "_" + field; } /** * Adapts an {@code Exception} to a {@code ResourceException}. * * @param t * The exception which caused the request to fail. * @return The equivalent resource exception. */ protected static ResourceException adapt(final Throwable t) { if (t instanceof ResourceException) { return (ResourceException) t; } else { return new InternalServerErrorException(t); } } }