diff --git a/examples/src/main/java/example/ClientExamples.java b/examples/src/main/java/example/ClientExamples.java index 5f191520a59..e36a8139fc9 100644 --- a/examples/src/main/java/example/ClientExamples.java +++ b/examples/src/main/java/example/ClientExamples.java @@ -3,11 +3,11 @@ package example; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IRestfulClientFactory; +import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor; import ca.uhn.fhir.rest.client.interceptor.CookieInterceptor; -import ca.uhn.fhir.rest.client.interceptor.GZipContentInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.EncodingEnum; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index aa6c93ca8ef..ec3aa00dcbc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -47,7 +47,7 @@ import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.parser.XmlParser; import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IRestfulClientFactory; -import ca.uhn.fhir.rest.client.RestfulClientFactory; +import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; @@ -298,9 +298,22 @@ public class FhirContext { return myIdToResourceDefinition.values(); } + /** + * Set the restful client factory + * @param theRestfulClientFactory + */ + public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) { + this.myRestfulClientFactory = theRestfulClientFactory; + } + + /** + * Get the restful client factory. If no factory has been set, this will be initialized with + * a new ApacheRestfulClientFactory. + * @return the factory used to create the restful clients + */ public IRestfulClientFactory getRestfulClientFactory() { if (myRestfulClientFactory == null) { - myRestfulClientFactory = new RestfulClientFactory(this); + myRestfulClientFactory = new ApacheRestfulClientFactory(this); } return myRestfulClientFactory; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java index 50797f698a6..74adf164721 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseClient.java @@ -25,10 +25,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -40,14 +38,6 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -62,6 +52,9 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; @@ -79,19 +72,19 @@ public abstract class BaseClient implements IRestfulClient { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseClient.class); - private final HttpClient myClient; + private final IHttpClient myClient; private boolean myDontValidateConformance; private EncodingEnum myEncoding = null; // default unspecified (will be XML) private final RestfulClientFactory myFactory; private List myInterceptors = new ArrayList(); private boolean myKeepResponses = false; - private HttpResponse myLastResponse; + private IHttpResponse myLastResponse; private String myLastResponseBody; private Boolean myPrettyPrint = false; private SummaryEnum mySummary; private final String myUrlBase; - BaseClient(HttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) { + BaseClient(IHttpClient theClient, String theUrlBase, RestfulClientFactory theFactory) { super(); myClient = theClient; myUrlBase = theUrlBase; @@ -130,7 +123,7 @@ public abstract class BaseClient implements IRestfulClient { * {@inheritDoc} */ @Override - public HttpClient getHttpClient() { + public IHttpClient getHttpClient() { return myClient; } @@ -141,7 +134,7 @@ public abstract class BaseClient implements IRestfulClient { /** * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! */ - public HttpResponse getLastResponse() { + public IHttpResponse getLastResponse() { return myLastResponse; } @@ -193,46 +186,43 @@ public abstract class BaseClient implements IRestfulClient { // TODO: handle non 2xx status codes by throwing the correct exception, // and ensure it's passed upwards - HttpRequestBase httpRequest; - HttpResponse response; + IHttpRequest httpRequest; + IHttpResponse response = null; try { - Map> params = createExtraParams(); + Map> params = createExtraParams(); - if (theEncoding == EncodingEnum.XML) { - params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); - } else if (theEncoding == EncodingEnum.JSON) { - params.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); - } + if (theEncoding == EncodingEnum.XML) { + params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml")); + } else if (theEncoding == EncodingEnum.JSON) { + params.put(Constants.PARAM_FORMAT, Collections.singletonList("json")); + } - if (theSummaryMode != null) { - params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode())); - } else if (mySummary != null) { - params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode())); - } + if (theSummaryMode != null) { + params.put(Constants.PARAM_SUMMARY, Collections.singletonList(theSummaryMode.getCode())); + } else if (mySummary != null) { + params.put(Constants.PARAM_SUMMARY, Collections.singletonList(mySummary.getCode())); + } - if (thePrettyPrint == Boolean.TRUE) { - params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE)); - } + if (thePrettyPrint == Boolean.TRUE) { + params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE)); + } - if (theSubsetElements != null && theSubsetElements.isEmpty() == false) { - params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ','))); - } + if (theSubsetElements != null && theSubsetElements.isEmpty() == false) { + params.put(Constants.PARAM_ELEMENTS, Collections.singletonList(StringUtils.join(theSubsetElements, ','))); + } - EncodingEnum encoding = getEncoding(); - if (theEncoding != null) { - encoding = theEncoding; - } + EncodingEnum encoding = getEncoding(); + if (theEncoding != null) { + encoding = theEncoding; + } httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint); if (theLogRequestAndResponse) { ourLog.info("Client invoking: {}", httpRequest); - if (httpRequest instanceof HttpEntityEnclosingRequest) { - HttpEntity entity = ((HttpEntityEnclosingRequest) httpRequest).getEntity(); - if (entity.isRepeatable()) { - String content = IOUtils.toString(entity.getContent()); - ourLog.info("Client request body: {}", content); - } + String body = httpRequest.getRequestBodyFromStream(); + if(body != null) { + ourLog.info("Client request body: {}", body); } } @@ -240,45 +230,26 @@ public abstract class BaseClient implements IRestfulClient { nextInterceptor.interceptRequest(httpRequest); } - response = myClient.execute(httpRequest); + response = httpRequest.execute(); for (IClientInterceptor nextInterceptor : myInterceptors) { nextInterceptor.interceptResponse(response); } - } catch (DataFormatException e) { - throw new FhirClientConnectionException(e); - } catch (IOException e) { - throw new FhirClientConnectionException(e); - } - - try { String mimeType; - if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatusLine().getStatusCode()) { + if (Constants.STATUS_HTTP_204_NO_CONTENT == response.getStatus()) { mimeType = null; } else { - ContentType ct = ContentType.get(response.getEntity()); - mimeType = ct != null ? ct.getMimeType() : null; + mimeType = response.getMimeType(); } - Map> headers = new HashMap>(); - if (response.getAllHeaders() != null) { - for (Header next : response.getAllHeaders()) { - String name = next.getName().toLowerCase(); - List list = headers.get(name); - if (list == null) { - list = new ArrayList(); - headers.put(name, list); - } - list.add(next.getValue()); - } - } + Map> headers = response.getAllHeaders(); - if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() > 299) { + if (response.getStatus() < 200 || response.getStatus() > 299) { String body = null; Reader reader = null; try { - reader = createReaderFromResponse(response); + reader = response.createReader(); body = IOUtils.toString(reader); } catch (Exception e) { ourLog.debug("Failed to read input stream", e); @@ -286,7 +257,7 @@ public abstract class BaseClient implements IRestfulClient { IOUtils.closeQuietly(reader); } - String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); + String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); IBaseOperationOutcome oo = null; if (Constants.CT_TEXT.equals(mimeType)) { message = message + ": " + body; @@ -309,7 +280,7 @@ public abstract class BaseClient implements IRestfulClient { keepResponseAndLogIt(theLogRequestAndResponse, response, body); - BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatusLine().getStatusCode(), message); + BaseServerResponseException exception = BaseServerResponseException.newInstance(response.getStatus(), message); exception.setOperationOutcome(oo); if (body != null) { @@ -321,7 +292,7 @@ public abstract class BaseClient implements IRestfulClient { if (binding instanceof IClientResponseHandlerHandlesBinary) { IClientResponseHandlerHandlesBinary handlesBinary = (IClientResponseHandlerHandlesBinary) binding; if (handlesBinary.isBinary()) { - InputStream reader = response.getEntity().getContent(); + InputStream reader = response.readEntity(); try { if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) { @@ -330,7 +301,7 @@ public abstract class BaseClient implements IRestfulClient { myLastResponse = response; myLastResponseBody = null; } - String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); + String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); if (theLogRequestAndResponse) { ourLog.info("Client response: {} - {} bytes", message, responseBytes.length); } else { @@ -339,14 +310,14 @@ public abstract class BaseClient implements IRestfulClient { reader = new ByteArrayInputStream(responseBytes); } - return handlesBinary.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers); + return handlesBinary.invokeClient(mimeType, reader, response.getStatus(), headers); } finally { IOUtils.closeQuietly(reader); } } } - Reader reader = createReaderFromResponse(response); + Reader reader = response.createReader(); if (ourLog.isTraceEnabled() || myKeepResponses || theLogRequestAndResponse) { String responseString = IOUtils.toString(reader); @@ -355,22 +326,24 @@ public abstract class BaseClient implements IRestfulClient { } try { - return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers); + return binding.invokeClient(mimeType, reader, response.getStatus(), headers); } finally { IOUtils.closeQuietly(reader); } + } catch (DataFormatException e) { + throw new FhirClientConnectionException(e); } catch (IllegalStateException e) { throw new FhirClientConnectionException(e); } catch (IOException e) { throw new FhirClientConnectionException(e); + } catch(RuntimeException e) { + throw e; + } catch (Exception e) { + throw new FhirClientConnectionException(e); } finally { - if (response instanceof CloseableHttpResponse) { - try { - ((CloseableHttpResponse) response).close(); - } catch (IOException e) { - ourLog.debug("Failed to close response", e); - } + if (response != null) { + response.close(); } } } @@ -391,13 +364,13 @@ public abstract class BaseClient implements IRestfulClient { return Boolean.TRUE.equals(myPrettyPrint); } - private void keepResponseAndLogIt(boolean theLogRequestAndResponse, HttpResponse response, String responseString) { + private void keepResponseAndLogIt(boolean theLogRequestAndResponse, IHttpResponse response, String responseString) { if (myKeepResponses) { myLastResponse = response; myLastResponseBody = responseString; } if (theLogRequestAndResponse) { - String message = "HTTP " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase(); + String message = "HTTP " + response.getStatus() + " " + response.getStatusInfo(); if (StringUtils.isNotBlank(responseString)) { ourLog.info("Client response: {}\n{}", message, responseString); } else { @@ -444,7 +417,7 @@ public abstract class BaseClient implements IRestfulClient { /** * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! */ - public void setLastResponse(HttpResponse theLastResponse) { + public void setLastResponse(IHttpResponse theLastResponse) { myLastResponse = theLastResponse; } @@ -477,30 +450,9 @@ public abstract class BaseClient implements IRestfulClient { myInterceptors.remove(theInterceptor); } - public static Reader createReaderFromResponse(HttpResponse theResponse) throws IllegalStateException, IOException { - HttpEntity entity = theResponse.getEntity(); - if (entity == null) { - return new StringReader(""); - } - Charset charset = null; - if (entity.getContentType() != null && entity.getContentType().getElements() != null && entity.getContentType().getElements().length > 0) { - ContentType ct = ContentType.get(entity); - charset = ct.getCharset(); - } - if (charset == null) { - if (Constants.STATUS_HTTP_204_NO_CONTENT != theResponse.getStatusLine().getStatusCode()) { - ourLog.warn("Response did not specify a charset."); - } - charset = Charset.forName("UTF-8"); - } - - Reader reader = new InputStreamReader(theResponse.getEntity().getContent(), charset); - return reader; - } - @Override public T fetchResourceFromUrl(Class theResourceType, String theUrl) { - BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(theUrl); + BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl); ResourceResponseHandler binding = new ResourceResponseHandler(theResourceType, null, false); return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java index 9541202b24f..ef1ad077126 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/BaseHttpClientInvocation.java @@ -27,23 +27,25 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.http.Header; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.message.BasicHeader; - -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.server.EncodingEnum; -import ca.uhn.fhir.util.VersionUtil; public abstract class BaseHttpClientInvocation { - private List
myHeaders; + private final List
myHeaders; + private final FhirContext myContext; + + public BaseHttpClientInvocation(FhirContext myContext) { + this.myContext = myContext; + this.myHeaders = new ArrayList
(); + } public void addHeader(String theName, String theValue) { - if (myHeaders == null) { - myHeaders = new ArrayList
(); - } - myHeaders.add(new BasicHeader(theName, theValue)); + myHeaders.add(new Header(theName, theValue)); } /** @@ -57,15 +59,15 @@ public abstract class BaseHttpClientInvocation { * The encoding to use for any serialized content sent to the * server */ - public abstract HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint); + public abstract IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint); - protected static void appendExtraParamsWithQuestionMark(Map> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { + public static void appendExtraParamsWithQuestionMark(Map> theExtraParams, StringBuilder theUrlBuilder, boolean theWithQuestionMark) { if (theExtraParams == null) { return; } boolean first = theWithQuestionMark; - if (theExtraParams != null && theExtraParams.isEmpty() == false) { + if (theExtraParams.isEmpty() == false) { for (Entry> next : theExtraParams.entrySet()) { for (String nextValue : next.getValue()) { if (first) { @@ -86,25 +88,43 @@ public abstract class BaseHttpClientInvocation { } } - public void addHeadersToRequest(HttpRequestBase theHttpRequest, EncodingEnum theEncoding) { - if (myHeaders != null) { - for (Header next : myHeaders) { - theHttpRequest.addHeader(next); - } - } - - String versionString = VersionUtil.getVersion(); - theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + versionString + " (FHIR Client)"); - theHttpRequest.addHeader("Accept-Charset", "utf-8"); - theHttpRequest.addHeader("Accept-Encoding", "gzip"); - - if (theEncoding == null) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON); - } else if (theEncoding == EncodingEnum.JSON) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); - } else if (theEncoding == EncodingEnum.XML) { - theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML); - } + /** + * Get the restfull client factory + * @return + */ + public IRestfulClientFactory getRestfulClientFactory() { + return myContext.getRestfulClientFactory(); + } + + /** + * Create an HTTP request for the given url, encoding and request-type + * + * @param theUrl + * The complete FHIR url to which the http request will be sent + * @param theEncoding + * The encoding to use for any serialized content sent to the + * server + * @param theRequestType + * the type of HTTP request (GET, DELETE, ..) + */ + protected IHttpRequest createHttpRequest(String theUrl, EncodingEnum theEncoding, RequestTypeEnum theRequestType) { + IHttpClient httpClient = getRestfulClientFactory().getHttpClient(new StringBuilder(theUrl), null, null, theRequestType, myHeaders); + return httpClient.createGetRequest(theEncoding); + } + + /** + * Returns the http headers to be sent with the request + */ + public List
getHeaders() { + return myHeaders; + } + + /** + * Returns the FHIR context associated with this client + * @return the myContext + */ + public FhirContext getFhirContext() { + return myContext; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java index 00d5b1c6f71..b5bb778c7dc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java @@ -24,20 +24,19 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; -import org.apache.http.client.HttpClient; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.client.ClientInvocationHandlerFactory.ILambda; +import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.method.BaseMethodBinding; -class ClientInvocationHandler extends BaseClient implements InvocationHandler { +public class ClientInvocationHandler extends BaseClient implements InvocationHandler { private final Map> myBindings; private final Map myMethodToReturnValue; private FhirContext myContext; private Map myMethodToLambda; - public ClientInvocationHandler(HttpClient theClient, FhirContext theContext, String theUrlBase, Map theMethodToReturnValue, Map> theBindings, Map theMethodToLambda, RestfulClientFactory theFactory) { + public ClientInvocationHandler(IHttpClient theClient, FhirContext theContext, String theUrlBase, Map theMethodToReturnValue, Map> theBindings, Map theMethodToLambda, RestfulClientFactory theFactory) { super(theClient, theUrlBase, theFactory); myContext = theContext; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandlerFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandlerFactory.java index 4363a4cb7bb..62cf0d40df4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandlerFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandlerFactory.java @@ -24,26 +24,26 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; -import org.apache.http.client.HttpClient; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.EncodingEnum; -class ClientInvocationHandlerFactory { +public class ClientInvocationHandlerFactory { private final Map> myBindings = new HashMap>(); - private final HttpClient myClient; + private final IHttpClient myClient; private final FhirContext myContext; private final Map myMethodToLambda = new HashMap(); private final Map myMethodToReturnValue = new HashMap(); private final String myUrlBase; - public ClientInvocationHandlerFactory(HttpClient theClient, FhirContext theContext, String theUrlBase, Class theClientType) { + public ClientInvocationHandlerFactory(IHttpClient theClient, FhirContext theContext, String theUrlBase, Class theClientType) { myClient = theClient; myUrlBase = theUrlBase; myContext = theContext; @@ -75,7 +75,7 @@ class ClientInvocationHandlerFactory { return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda, theRestfulClientFactory); } - interface ILambda { + public interface ILambda { Object handle(ClientInvocationHandler theTarget, Object[] theArgs); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 311b18d5648..65fabec095d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -38,8 +38,6 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseConformance; @@ -72,6 +70,8 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.ICreate; @@ -157,13 +157,13 @@ public class GenericClient extends BaseClient implements IGenericClient { private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); private FhirContext myContext; - private HttpRequestBase myLastRequest; + private IHttpRequest myLastRequest; private boolean myLogRequestAndResponse; /** * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! */ - public GenericClient(FhirContext theContext, HttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) { + public GenericClient(FhirContext theContext, IHttpClient theHttpClient, String theServerBase, RestfulClientFactory theFactory) { super(theHttpClient, theServerBase, theFactory); myContext = theContext; } @@ -174,7 +174,7 @@ public class GenericClient extends BaseClient implements IGenericClient { throw new IllegalArgumentException("Must call conformance(" + IBaseConformance.class.getSimpleName() + ") instead of conformance() for HL7.org structures"); } - HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); + HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(getFhirContext()); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } @@ -216,7 +216,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public MethodOutcome delete(final Class theType, IdDt theId) { - HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType))); + HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), theId.withResourceType(toResourceName(theType))); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } @@ -243,15 +243,15 @@ public class GenericClient extends BaseClient implements IGenericClient { HttpGetClientInvocation invocation; if (id.hasBaseUrl()) { if (theVRead) { - invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id); + invocation = ReadMethodBinding.createAbsoluteVReadInvocation(getFhirContext(), id); } else { - invocation = ReadMethodBinding.createAbsoluteReadInvocation(id); + invocation = ReadMethodBinding.createAbsoluteReadInvocation(getFhirContext(), id); } } else { if (theVRead) { - invocation = ReadMethodBinding.createVReadInvocation(id, resName); + invocation = ReadMethodBinding.createVReadInvocation(getFhirContext(), id, resName); } else { - invocation = ReadMethodBinding.createReadInvocation(id, resName); + invocation = ReadMethodBinding.createReadInvocation(getFhirContext(), id, resName); } } if (isKeepResponses()) { @@ -305,7 +305,7 @@ public class GenericClient extends BaseClient implements IGenericClient { return myContext; } - public HttpRequestBase getLastRequest() { + public IHttpRequest getLastRequest() { return myLastRequest; } @@ -330,7 +330,7 @@ public class GenericClient extends BaseClient implements IGenericClient { public Bundle history(final Class theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) { String resourceName = theType != null ? toResourceName(theType) : null; String id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt.getValue() : null; - HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit); + HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, theSince, theLimit); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding(), isPrettyPrint()); } @@ -468,7 +468,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public Bundle search(final Class theType, UriDt theUrl) { - BaseHttpClientInvocation invocation = new HttpGetClientInvocation(theUrl.getValueAsString()); + BaseHttpClientInvocation invocation = new HttpGetClientInvocation(getFhirContext(), theUrl.getValueAsString()); return invokeClient(myContext, new BundleResponseHandler(theType), invocation); } @@ -480,7 +480,7 @@ public class GenericClient extends BaseClient implements IGenericClient { /** * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! */ - public void setLastRequest(HttpRequestBase theLastRequest) { + public void setLastRequest(IHttpRequest theLastRequest) { myLastRequest = theLastRequest; } @@ -839,12 +839,12 @@ public class GenericClient extends BaseClient implements IGenericClient { public BaseOperationOutcome execute() { HttpDeleteClientInvocation invocation; if (myId != null) { - invocation = DeleteMethodBinding.createDeleteInvocation(myId); + invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myId); } else if (myCriterionList != null) { Map> params = myCriterionList.toParamList(); - invocation = DeleteMethodBinding.createDeleteInvocation(myResourceType, params); + invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), myResourceType, params); } else { - invocation = DeleteMethodBinding.createDeleteInvocation(mySearchUrl); + invocation = DeleteMethodBinding.createDeleteInvocation(getFhirContext(), mySearchUrl); } OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler(); Map> params = new HashMap>(); @@ -926,7 +926,7 @@ public class GenericClient extends BaseClient implements IGenericClient { @Override public Object execute() { ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null); - HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); + HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(getFhirContext()); return super.invoke(null, binding, invocation); } @@ -965,7 +965,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } else { binding = new ResourceResponseHandler(myBundleType, null); } - HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl); + HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myContext, myUrl); Map> params = null; return invoke(params, binding, invocation); @@ -1002,7 +1002,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } urlFragments.add(Constants.PARAM_TAGS); - HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, urlFragments); + HttpGetClientInvocation invocation = new HttpGetClientInvocation(myContext, params, urlFragments); return invoke(params, binding, invocation); @@ -1083,7 +1083,7 @@ public class GenericClient extends BaseClient implements IGenericClient { id = null; } - HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, mySince, myCount); + HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(myContext, resourceName, id, mySince, myCount); IClientResponseHandler handler; if (myReturnType != null) { @@ -1813,7 +1813,7 @@ public class GenericClient extends BaseClient implements IGenericClient { BaseHttpClientInvocation invocation; if (mySearchUrl != null) { - invocation = SearchMethodBinding.createSearchInvocation(mySearchUrl, params); + invocation = SearchMethodBinding.createSearchInvocation(myContext, mySearchUrl, params); } else { invocation = SearchMethodBinding.createSearchInvocation(myContext, myResourceName, params, resourceId, myCompartmentName, mySearchStyle); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IClientInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IClientInterceptor.java index aedd237e762..b1d1ec73e7c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IClientInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IClientInterceptor.java @@ -22,19 +22,19 @@ package ca.uhn.fhir.rest.client; import java.io.IOException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; public interface IClientInterceptor { /** * Fired by the client just before invoking the HTTP client request */ - void interceptRequest(HttpRequestBase theRequest); + void interceptRequest(IHttpRequest theRequest); /** * Fired by the client upon receiving an HTTP response, prior to processing that response */ - void interceptResponse(HttpResponse theResponse) throws IOException; + void interceptResponse(IHttpResponse theResponse) throws IOException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java index ce02bf0348f..2d2516e2d4e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IRestfulClientFactory.java @@ -20,9 +20,13 @@ package ca.uhn.fhir.rest.client; * #L% */ -import org.apache.http.client.HttpClient; +import java.util.List; +import java.util.Map; import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; public interface IRestfulClientFactory { @@ -76,11 +80,20 @@ public interface IRestfulClientFactory { int getConnectTimeout(); /** - * Returns the Apache HTTP client instance. This method will not return null. - * - * @see #setHttpClient(HttpClient) + * Returns the HTTP client instance. This method will not return null. + * @param theUrl + * The complete FHIR url to which the http request will be sent + * @param theIfNoneExistParams + * The params for header "If-None-Exist" as a hashmap + * @param theIfNoneExistString + * The param for header "If-None-Exist" as a string + * @param theRequestType + * the type of HTTP request (GET, DELETE, ..) + * @param theHeaders + * the headers to be sent together with the http request + * @return the HTTP client instance */ - HttpClient getHttpClient(); + IHttpClient getHttpClient(StringBuilder theUrl, Map> theIfNoneExistParams, String theIfNoneExistString, RequestTypeEnum theRequestType, List
theHeaders); /** * @deprecated Use {@link #getServerValidationMode()} instead @@ -172,7 +185,7 @@ public interface IRestfulClientFactory { * @param theHttpClient * An HTTP client instance to use, or null */ - void setHttpClient(HttpClient theHttpClient); + void setHttpClient(T theHttpClient); /** * Sets the HTTP proxy to use for outgoing connections @@ -234,4 +247,12 @@ public interface IRestfulClientFactory { *

*/ void setPoolMaxPerRoute(int thePoolMaxPerRoute); + + void validateServerBase(String theServerBase, IHttpClient theHttpClient, BaseClient theClient); + + /** + * This method is internal to HAPI - It may change in future versions, use with caution. + */ + void validateServerBaseIfConfiguredToDoSo(String theServerBase, IHttpClient theHttpClient, BaseClient theClient); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java index cb83ec68e32..ccbc2c1be90 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java @@ -28,27 +28,16 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.FhirClientInappropriateForServerException; @@ -56,18 +45,18 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.FhirTerser; -public class RestfulClientFactory implements IRestfulClientFactory { +public abstract class RestfulClientFactory implements IRestfulClientFactory { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulClientFactory.class); private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT; private FhirContext myContext; - private HttpClient myHttpClient; private Map, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap, ClientInvocationHandlerFactory>(); - private HttpHost myProxy; private ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE; private int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT; private Set myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet()); + private String myProxyUsername; + private String myProxyPassword; private int myPoolMaxTotal = DEFAULT_POOL_MAX; private int myPoolMaxPerRoute = DEFAULT_POOL_MAX_PER_ROUTE; @@ -97,45 +86,21 @@ public class RestfulClientFactory implements IRestfulClientFactory { return myConnectTimeout; } - @Override - public synchronized HttpClient getHttpClient() { - if (myHttpClient == null) { - - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); - connectionManager.setMaxTotal(myPoolMaxTotal); - connectionManager.setDefaultMaxPerRoute(myPoolMaxPerRoute); - - //@formatter:off - RequestConfig defaultRequestConfig = RequestConfig.custom() - .setSocketTimeout(mySocketTimeout) - .setConnectTimeout(myConnectTimeout) - .setConnectionRequestTimeout(myConnectionRequestTimeout) - .setStaleConnectionCheckEnabled(true) - .setProxy(myProxy) - .build(); - - HttpClientBuilder builder = HttpClients.custom() - .setConnectionManager(connectionManager) - .setDefaultRequestConfig(defaultRequestConfig) - .disableCookieManagement(); - - if (myProxy != null && StringUtils.isNotBlank(myProxyUsername) && StringUtils.isNotBlank(myProxyPassword)) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()), new UsernamePasswordCredentials(myProxyUsername, myProxyPassword)); - builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - builder.setDefaultCredentialsProvider(credsProvider); - } - - myHttpClient = builder.build(); - //@formatter:on - - } - - return myHttpClient; + /** + * Return the proxy username to authenticate with the HTTP proxy + * @param The proxy username + */ + protected String getProxyUsername() { + return myProxyUsername; } - private String myProxyUsername; - private String myProxyPassword; + /** + * Return the proxy password to authenticate with the HTTP proxy + * @param The proxy password + */ + protected String getProxyPassword() { + return myProxyPassword; + } @Override public void setProxyCredentials(String theUsername, String thePassword) { @@ -189,7 +154,7 @@ public class RestfulClientFactory implements IRestfulClientFactory { ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType); if (invocationHandler == null) { - HttpClient httpClient = getHttpClient(); + IHttpClient httpClient = getHttpClient(theServerBase); invocationHandler = new ClientInvocationHandlerFactory(httpClient, myContext, theServerBase, theClientType); for (Method nextMethod : theClientType.getMethods()) { BaseMethodBinding binding = BaseMethodBinding.bindMethod(nextMethod, myContext, null); @@ -205,14 +170,12 @@ public class RestfulClientFactory implements IRestfulClientFactory { @Override public synchronized IGenericClient newGenericClient(String theServerBase) { - HttpClient httpClient = getHttpClient(); + IHttpClient httpClient = getHttpClient(theServerBase); return new GenericClient(myContext, httpClient, theServerBase, this); } - /** - * This method is internal to HAPI - It may change in future versions, use with caution. - */ - public void validateServerBaseIfConfiguredToDoSo(String theServerBase, HttpClient theHttpClient, BaseClient theClient) { + @Override + public void validateServerBaseIfConfiguredToDoSo(String theServerBase, IHttpClient theHttpClient, BaseClient theClient) { String serverBase = normalizeBaseUrlForMap(theServerBase); switch (myServerValidationMode) { @@ -238,13 +201,13 @@ public class RestfulClientFactory implements IRestfulClientFactory { @Override public synchronized void setConnectionRequestTimeout(int theConnectionRequestTimeout) { myConnectionRequestTimeout = theConnectionRequestTimeout; - myHttpClient = null; + resetHttpClient(); } @Override public synchronized void setConnectTimeout(int theConnectTimeout) { myConnectTimeout = theConnectTimeout; - myHttpClient = null; + resetHttpClient(); } /** @@ -256,26 +219,13 @@ public class RestfulClientFactory implements IRestfulClientFactory { } myContext = theContext; } - + /** - * Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to - * null, which is the default, a new HTTP client with default settings will be created. - * - * @param theHttpClient - * An HTTP client instance to use, or null + * Return the fhir context + * @return the fhir context */ - @Override - public synchronized void setHttpClient(HttpClient theHttpClient) { - myHttpClient = theHttpClient; - } - - @Override - public void setProxy(String theHost, Integer thePort) { - if (theHost != null) { - myProxy = new HttpHost(theHost, thePort, "http"); - } else { - myProxy = null; - } + public FhirContext getFhirContext() { + return myContext; } @Override @@ -287,24 +237,39 @@ public class RestfulClientFactory implements IRestfulClientFactory { @Override public synchronized void setSocketTimeout(int theSocketTimeout) { mySocketTimeout = theSocketTimeout; - myHttpClient = null; + resetHttpClient(); } @Override public synchronized void setPoolMaxTotal(int thePoolMaxTotal) { myPoolMaxTotal = thePoolMaxTotal; - myHttpClient = null; + resetHttpClient(); } @Override public synchronized void setPoolMaxPerRoute(int thePoolMaxPerRoute) { myPoolMaxPerRoute = thePoolMaxPerRoute; - myHttpClient = null; + resetHttpClient(); + } + + /** + * Instantiates a new client invocation handler + * @param theClient + * the client which will invoke the call + * @param theUrlBase + * the url base + * @param theMethodToReturnValue + * @param theBindings + * @param theMethodToLambda + * @return a newly created client invocation handler + */ + ClientInvocationHandler newInvocationHandler(IHttpClient theClient, String theUrlBase, Map theMethodToReturnValue, Map> theBindings, Map theMethodToLambda) { + return new ClientInvocationHandler(theClient, getFhirContext(), theUrlBase.toString(), theMethodToReturnValue, + theBindings, theMethodToLambda, this); } - @SuppressWarnings("unchecked") - void validateServerBase(String theServerBase, HttpClient theHttpClient, BaseClient theClient) { - + @Override + public void validateServerBase(String theServerBase, IHttpClient theHttpClient, BaseClient theClient) { GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this); client.setEncoding(theClient.getEncoding()); for (IClientInterceptor interceptor : theClient.getInterceptors()) { @@ -363,5 +328,18 @@ public class RestfulClientFactory implements IRestfulClientFactory { public void setServerValidationModeEnum(ServerValidationModeEnum theServerValidationMode) { setServerValidationMode(theServerValidationMode); } + + /** + * Get the http client for the given server base + * @param theServerBase the server base + * @return the http client + */ + protected abstract IHttpClient getHttpClient(String theServerBase); + + /** + * Reset the http client. This method is used when parameters have been set and a + * new http client needs to be created + */ + protected abstract void resetHttpClient(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpClient.java new file mode 100644 index 00000000000..0e6a1d3a02f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpClient.java @@ -0,0 +1,202 @@ +package ca.uhn.fhir.rest.client.apache; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpOptions; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.message.BasicNameValuePair; +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.VersionUtil; + +/** + * A Http Client based on Apache. This is an adapter around the class + * {@link org.apache.http.client.HttpClient HttpClient} + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class ApacheHttpClient implements IHttpClient { + + private HttpClient myClient; + private List
myHeaders; + private StringBuilder myUrl; + private Map> myIfNoneExistParams; + private String myIfNoneExistString; + private RequestTypeEnum myRequestType; + + public ApacheHttpClient(HttpClient theClient, StringBuilder theUrl, Map> theIfNoneExistParams, + String theIfNoneExistString, RequestTypeEnum theRequestType, List
theHeaders) { + this.myClient = theClient; + this.myUrl = theUrl; + this.myIfNoneExistParams = theIfNoneExistParams; + this.myIfNoneExistString = theIfNoneExistString; + this.myRequestType = theRequestType; + this.myHeaders = theHeaders; + } + + @Override + public IHttpRequest createByteRequest(String theContents, String theContentType, EncodingEnum theEncoding) { + /* + * We aren't using a StringEntity here because the constructors + * supported by Android aren't available in non-Android, and vice versa. + * Since we add the content type header manually, it makes no difference + * which one we use anyhow. + */ + ByteArrayEntity entity = new ByteArrayEntity(theContents.getBytes(Constants.CHARSET_UTF8)); + ApacheHttpRequest retVal = createHttpRequest(entity); + addHeadersToRequest(retVal, theEncoding); + retVal.addHeader(Constants.HEADER_CONTENT_TYPE, theContentType + Constants.HEADER_SUFFIX_CT_UTF_8); + return retVal; + } + + @Override + public IHttpRequest createParamRequest(Map> theParams, EncodingEnum theEncoding) { + List parameters = new ArrayList(); + for (Entry> nextParam : theParams.entrySet()) { + List value = nextParam.getValue(); + for (String s : value) { + parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); + } + } + try { + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "UTF-8"); + return createHttpRequest(entity); + } catch (UnsupportedEncodingException e) { + throw new InternalErrorException("Server does not support UTF-8 (should not happen)", e); + } + } + + @Override + public IHttpRequest createBinaryRequest(IBaseBinary theBinary) { + /* + * Note: Be careful about changing which constructor we use for + * ByteArrayEntity, as Android's version of HTTPClient doesn't support + * the newer ones for whatever reason. + */ + ByteArrayEntity entity = new ByteArrayEntity(theBinary.getContent()); + entity.setContentType(theBinary.getContentType()); + return createHttpRequest(entity); + } + + @Override + public IHttpRequest createGetRequest(EncodingEnum theEncoding) { + ApacheHttpRequest retVal = createHttpRequest(null); + addHeadersToRequest(retVal, theEncoding); + return retVal; + } + + public void addHeadersToRequest(ApacheHttpRequest theHttpRequest, EncodingEnum theEncoding) { + if (myHeaders != null) { + for (Header next : myHeaders) { + theHttpRequest.addHeader(next.getName(), next.getValue()); + } + } + + theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"); + theHttpRequest.addHeader("Accept-Charset", "utf-8"); + theHttpRequest.addHeader("Accept-Encoding", "gzip"); + + if (theEncoding == null) { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_ALL); + } else if (theEncoding == EncodingEnum.JSON) { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON); + } else if (theEncoding == EncodingEnum.XML) { + theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML); + } + } + + private ApacheHttpRequest createHttpRequest(HttpEntity theEntity) { + HttpRequestBase request = constructRequestBase(theEntity); + ApacheHttpRequest result = new ApacheHttpRequest(myClient, request); + addHeaderIfNoneExist(result); + return result; + } + + private void addHeaderIfNoneExist(IHttpRequest result) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(myUrl); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); + result.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + + if (myIfNoneExistString != null) { + StringBuilder b = newHeaderBuilder(myUrl); + b.append(b.indexOf("?") == -1 ? '?' : '&'); + b.append(myIfNoneExistString.substring(myIfNoneExistString.indexOf('?') + 1)); + result.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + } + + private StringBuilder newHeaderBuilder(StringBuilder theUrlBase) { + StringBuilder b = new StringBuilder(); + b.append(theUrlBase); + if (theUrlBase.length() > 0 && theUrlBase.charAt(theUrlBase.length() - 1) == '/') { + b.deleteCharAt(b.length() - 1); + } + return b; + } + + private HttpRequestBase constructRequestBase(HttpEntity theEntity) { + String url = myUrl.toString(); + switch (myRequestType) { + case DELETE: + return new HttpDelete(url); + case OPTIONS: + return new HttpOptions(url); + case POST: + HttpPost httpPost = new HttpPost(url); + httpPost.setEntity(theEntity); + return httpPost; + case PUT: + HttpPut httpPut = new HttpPut(url); + httpPut.setEntity(theEntity); + return httpPut; + case GET: + default: + return new HttpGet(url); + } + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java new file mode 100644 index 00000000000..8a35e334b48 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpRequest.java @@ -0,0 +1,101 @@ +package ca.uhn.fhir.rest.client.apache; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; + +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; + +/** + * A Http Request based on Apache. This is an adapter around the class + * {@link org.apache.http.client.methods.HttpRequestBase HttpRequestBase} + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class ApacheHttpRequest implements IHttpRequest { + + private HttpRequestBase myRequest; + private HttpClient myClient; + + public ApacheHttpRequest(HttpClient theClient, HttpRequestBase theApacheRequest) { + this.myClient = theClient; + this.myRequest = theApacheRequest; + } + + @Override + public void addHeader(String theName, String theValue) { + myRequest.addHeader(theName, theValue); + } + + /** + * Get the ApacheRequest + * @return the ApacheRequest + */ + public HttpRequestBase getApacheRequest() { + return myRequest; + } + + @Override + public IHttpResponse execute() throws IOException { + return new ApacheHttpResponse(myClient.execute(myRequest)); + } + + @Override + public Map> getAllHeaders() { + Map> result = new HashMap>(); + for (Header header : myRequest.getAllHeaders()) { + if (!result.containsKey(header.getName())) { + result.put(header.getName(), new LinkedList()); + } + result.get(header.getName()).add(header.getValue()); + } + return result; + } + + @Override + public String toString() { + return myRequest.toString(); + } + + @Override + public String getRequestBodyFromStream() throws IOException { + if (myRequest instanceof HttpEntityEnclosingRequest) { + HttpEntity entity = ((HttpEntityEnclosingRequest) myRequest).getEntity(); + if (entity.isRepeatable()) { + return IOUtils.toString(entity.getContent()); + } + } + return null; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java new file mode 100644 index 00000000000..10713b9e96d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheHttpResponse.java @@ -0,0 +1,163 @@ +package ca.uhn.fhir.rest.client.apache; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.ContentType; + +import ca.uhn.fhir.rest.client.api.IHttpResponse; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; + +/** + * A Http Response based on Apache. This is an adapter around the class + * {@link org.apache.http.HttpResponse HttpResponse} + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class ApacheHttpResponse implements IHttpResponse { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ApacheHttpResponse.class); + + private boolean myEntityBuffered = false; + private final HttpResponse myResponse; + private byte[] myEntityBytes; + + public ApacheHttpResponse(HttpResponse theResponse) { + this.myResponse = theResponse; + } + + @Override + public HttpResponse getResponse() { + return myResponse; + } + + @Override + public int getStatus() { + return myResponse.getStatusLine().getStatusCode(); + } + + @Override + public String getMimeType() { + ContentType ct = ContentType.get(myResponse.getEntity()); + return ct != null ? ct.getMimeType() : null; + } + + @Override + public Map> getAllHeaders() { + Map> headers = new HashMap>(); + if (myResponse.getAllHeaders() != null) { + for (Header next : myResponse.getAllHeaders()) { + String name = next.getName().toLowerCase(); + List list = headers.get(name); + if (list == null) { + list = new ArrayList(); + headers.put(name, list); + } + list.add(next.getValue()); + } + + } + return headers; + } + + @Override + public String getStatusInfo() { + return myResponse.getStatusLine().getReasonPhrase(); + } + + @Override + public Reader createReader() throws IOException { + HttpEntity entity = myResponse.getEntity(); + if (entity == null) { + return new StringReader(""); + } + Charset charset = null; + if (entity.getContentType() != null && entity.getContentType().getElements() != null + && entity.getContentType().getElements().length > 0) { + ContentType ct = ContentType.get(entity); + charset = ct.getCharset(); + } + if (charset == null) { + if (Constants.STATUS_HTTP_204_NO_CONTENT != myResponse.getStatusLine().getStatusCode()) { + ourLog.warn("Response did not specify a charset."); + } + charset = Charset.forName("UTF-8"); + } + + Reader reader = new InputStreamReader(readEntity(), charset); + return reader; + } + + @Override + public InputStream readEntity() throws IOException { + if (this.myEntityBuffered) { + return new ByteArrayInputStream(myEntityBytes); + } else if (myResponse.getEntity() != null) { + return myResponse.getEntity().getContent(); + } else { + return null; + } + } + + @Override + public void close() { + if (myResponse instanceof CloseableHttpResponse) { + try { + ((CloseableHttpResponse) myResponse).close(); + } catch (IOException e) { + ourLog.debug("Failed to close response", e); + } + } + } + + @Override + public void bufferEntitity() throws IOException { + if (myEntityBuffered) { + return; + } + InputStream respEntity = readEntity(); + if (respEntity != null) { + this.myEntityBuffered = true; + try { + this.myEntityBytes = IOUtils.toByteArray(respEntity); + } catch (IllegalStateException e) { + throw new InternalErrorException(e); + } + } + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheRestfulClientFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheRestfulClientFactory.java new file mode 100644 index 00000000000..ff1fda0e066 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/ApacheRestfulClientFactory.java @@ -0,0 +1,139 @@ +package ca.uhn.fhir.rest.client.apache; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.RestfulClientFactory; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; + +/** + * A Restful Factory to create clients, requests and responses based on the Apache httpclient library. + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class ApacheRestfulClientFactory extends RestfulClientFactory { + + private HttpClient myHttpClient; + private HttpHost myProxy; + + /** + * Constructor + */ + public ApacheRestfulClientFactory() { + } + + /** + * Constructor + * + * @param theContext + * The context + */ + public ApacheRestfulClientFactory(FhirContext theContext) { + super(theContext); + } + + public synchronized HttpClient getNativeHttpClient() { + if (myHttpClient == null) { + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, + TimeUnit.MILLISECONDS); + connectionManager.setMaxTotal(getPoolMaxTotal()); + connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute()); + + // @formatter:off + RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(getSocketTimeout()) + .setConnectTimeout(getConnectTimeout()).setConnectionRequestTimeout(getConnectionRequestTimeout()) + .setStaleConnectionCheckEnabled(true).setProxy(myProxy).build(); + + HttpClientBuilder builder = HttpClients.custom().setConnectionManager(connectionManager) + .setDefaultRequestConfig(defaultRequestConfig).disableCookieManagement(); + + if (myProxy != null && StringUtils.isNotBlank(getProxyUsername()) && StringUtils.isNotBlank(getProxyPassword())) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()), + new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword())); + builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + builder.setDefaultCredentialsProvider(credsProvider); + } + + myHttpClient = builder.build(); + // @formatter:on + + } + + return myHttpClient; + } + + @Override + public IHttpClient getHttpClient(StringBuilder theUrl, Map> theIfNoneExistParams, + String theIfNoneExistString, RequestTypeEnum theRequestType, List
theHeaders) { + return new ApacheHttpClient(getNativeHttpClient(), theUrl, theIfNoneExistParams, theIfNoneExistString, theRequestType, + theHeaders); + } + + @Override + public void setProxy(String theHost, Integer thePort) { + if (theHost != null) { + myProxy = new HttpHost(theHost, thePort, "http"); + } else { + myProxy = null; + } + } + + /** + * Only allows to set an instance of type org.apache.http.client.HttpClient + * @see ca.uhn.fhir.rest.client.IRestfulClientFactory#setHttpClient(ca.uhn.fhir.rest.client.api.IHttpClient) + */ + @Override + public synchronized void setHttpClient(Object theHttpClient) { + this.myHttpClient = (HttpClient) theHttpClient; + } + + @Override + protected ApacheHttpClient getHttpClient(String theServerBase) { + return new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(theServerBase), null, null, null, null); + } + + @Override + protected void resetHttpClient() { + this.myHttpClient = null; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/GZipContentInterceptor.java similarity index 85% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/GZipContentInterceptor.java index d5818645149..610447f68d0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/apache/GZipContentInterceptor.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.rest.client.interceptor; +package ca.uhn.fhir.rest.client.apache; /* * #%L @@ -26,11 +26,12 @@ import java.util.zip.GZIPOutputStream; import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.ByteArrayEntity; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.Constants; /** @@ -42,7 +43,8 @@ public class GZipContentInterceptor implements IClientInterceptor { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GZipContentInterceptor.class); @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequestInterface) { + HttpRequestBase theRequest = ((ApacheHttpRequest) theRequestInterface).getApacheRequest(); if (theRequest instanceof HttpEntityEnclosingRequest) { Header[] encodingHeaders = theRequest.getHeaders(Constants.HEADER_CONTENT_ENCODING); if (encodingHeaders == null || encodingHeaders.length == 0) { @@ -69,7 +71,7 @@ public class GZipContentInterceptor implements IClientInterceptor { } @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { + public void interceptResponse(IHttpResponse theResponse) throws IOException { // nothing } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/Header.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/Header.java new file mode 100644 index 00000000000..d121005a094 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/Header.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.rest.client.api; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +/** + * Represents an HTTP header field. + */ +public class Header { + + public final String myName; + public final String myValue; + + public Header(String myName, String myValue) { + this.myName = myName; + this.myValue = myValue; + } + + /** + * Get the name of the Header. + * + * @return the name of the Header, never {@code null} + */ + public String getName() { + return myName; + } + + /** + * Get the value of the Header. + * + * @return the value of the Header, may be {@code null} + */ + public String getValue() { + return myValue; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java new file mode 100644 index 00000000000..00750cc4877 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java @@ -0,0 +1,66 @@ +package ca.uhn.fhir.rest.client.api; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.List; +import java.util.Map; + +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.rest.server.EncodingEnum; + +/** + * A HTTP Client interface. + */ +public interface IHttpClient { + + /** + * Create a byte request + * @param theContents the contents + * @param theContentType the contentType + * @param theEncoding the encoding + * @return the http request to be executed + */ + IHttpRequest createByteRequest(String theContents, String theContentType, EncodingEnum theEncoding); + + /** + * Create a parameter request + * @param theParams the parameters + * @param theEncoding the encoding + * @return the http request to be executed + */ + IHttpRequest createParamRequest(Map> theParams, EncodingEnum theEncoding); + + /** + * Create a binary request + * @param theBinary the binary + * @return the http request to be executed + */ + IHttpRequest createBinaryRequest(IBaseBinary theBinary); + + /** + * Create a normal http get request + * @param theEncoding the request encoding + * @return the http request to be executed + */ + IHttpRequest createGetRequest(EncodingEnum theEncoding); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java new file mode 100644 index 00000000000..ceb54ad576f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpRequest.java @@ -0,0 +1,59 @@ +package ca.uhn.fhir.rest.client.api; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Http Request. Allows addition of headers and execution of the request. + */ +public interface IHttpRequest { + + /** + * Add a header to the request + * @param theName the header name + * @param theValue the header value + */ + public void addHeader(String theName, String theValue); + + /** + * Execute the request + * @return the response + * @throws IOException + */ + public IHttpResponse execute() throws IOException; + + /** + * @return all request headers in lower case + */ + public Map> getAllHeaders(); + + /** + * Return the requestbody as a string. + * If this is not supported by the underlying technology, null is returned + * @return a string representation of the request or null if not supported or empty. + * @throws IOException + */ + public String getRequestBodyFromStream() throws IOException; + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java new file mode 100644 index 00000000000..68876f51817 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpResponse.java @@ -0,0 +1,102 @@ +package ca.uhn.fhir.rest.client.api; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.List; +import java.util.Map; + +/** + * An interface around the HTTP Response. + */ +public interface IHttpResponse { + + /** + * Get the status code associated with the response. + * @return the response status code. + */ + public int getStatus(); + + /** + * @return the native response, depending on the client library used + */ + Object getResponse(); + + /** + * Extracts {@code Content-Type} value from the response exactly as + * specified by the {@code Content-Type} header. Returns {@code null} + * if not specified. + */ + public String getMimeType(); + + /** + * Get map of the response headers and corresponding string values. + * @return response headers as a map header keys and they values. + */ + public Map> getAllHeaders(); + + /** + * Get the response status information reason phrase associated with the response. + * @return the reason phrase. + */ + public String getStatusInfo(); + + /** + * Returna reader for the response entity + */ + public Reader createReader() throws IOException; + + /** + * Read the message entity input stream as an InputStream. + */ + public InputStream readEntity() throws IOException; + + /** + * Close the response + */ + public void close(); + + /** + * Buffer the message entity data. + *

+ * In case the message entity is backed by an unconsumed entity input stream, + * all the bytes of the original entity input stream are read and stored in a + * local buffer. The original entity input stream is consumed. + *

+ *

+ * In case the response entity instance is not backed by an unconsumed input stream + * an invocation of {@code bufferEntity} method is ignored and the method returns. + *

+ *

+ * This operation is idempotent, i.e. it can be invoked multiple times with + * the same effect which also means that calling the {@code bufferEntity()} + * method on an already buffered (and thus closed) message instance is legal + * and has no further effect. + *

+ *

+ * Buffering the message entity data allows for multiple invocations of + * {@code readEntity(...)} methods on the response instance. + */ + void bufferEntitity() throws IOException; + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IRestfulClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IRestfulClient.java index ab54bc0bd35..7b7bc2039ba 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IRestfulClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IRestfulClient.java @@ -21,7 +21,6 @@ package ca.uhn.fhir.rest.client.api; */ -import org.apache.http.client.HttpClient; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; @@ -51,7 +50,7 @@ public interface IRestfulClient { * Do not call this method in client code. It is a part of the internal HAPI API and * is subject to change! */ - HttpClient getHttpClient(); + IHttpClient getHttpClient(); /** * Base URL for the server, with no trailing "/" diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java index 05fa7f91f57..00b3d8826bd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java @@ -25,10 +25,10 @@ import java.io.UnsupportedEncodingException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -52,7 +52,7 @@ public class BasicAuthInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { String authorizationUnescaped = StringUtils.defaultString(myUsername) + ":" + StringUtils.defaultString(myPassword); String encoded; try { @@ -64,7 +64,7 @@ public class BasicAuthInterceptor implements IClientInterceptor { } @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { + public void interceptResponse(IHttpResponse theResponse) throws IOException { // nothing } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java index 437067a5628..4dc60bde084 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BearerTokenAuthInterceptor.java @@ -21,10 +21,10 @@ package ca.uhn.fhir.rest.client.interceptor; */ import org.apache.commons.lang3.Validate; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.CoverageIgnore; @@ -71,12 +71,12 @@ public class BearerTokenAuthInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { theRequest.addHeader(Constants.HEADER_AUTHORIZATION, (Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + myToken)); } @Override - public void interceptResponse(HttpResponse theResponse) { + public void interceptResponse(IHttpResponse theResponse) { // nothing } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java index 4077b1a6e3a..1d1978631e1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java @@ -20,10 +20,9 @@ package ca.uhn.fhir.rest.client.interceptor; * #L% */ -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; - import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; /** * Client interceptor which simply captures request and response objects and stores them so that they can be inspected after a client @@ -31,8 +30,8 @@ import ca.uhn.fhir.rest.client.IClientInterceptor; */ public class CapturingInterceptor implements IClientInterceptor { - private HttpRequestBase myLastRequest; - private HttpResponse myLastResponse; + private IHttpRequest myLastRequest; + private IHttpResponse myLastResponse; /** * Clear the last request and response values @@ -42,21 +41,21 @@ public class CapturingInterceptor implements IClientInterceptor { myLastResponse = null; } - public HttpRequestBase getLastRequest() { + public IHttpRequest getLastRequest() { return myLastRequest; } - public HttpResponse getLastResponse() { + public IHttpResponse getLastResponse() { return myLastResponse; } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { myLastRequest = theRequest; } @Override - public void interceptResponse(HttpResponse theRequest) { + public void interceptResponse(IHttpResponse theRequest) { myLastResponse = theRequest; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CookieInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CookieInterceptor.java index 7e5658c33a6..c7221b4cf5e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CookieInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CookieInterceptor.java @@ -20,10 +20,9 @@ package ca.uhn.fhir.rest.client.interceptor; * #L% */ -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; - import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.Constants; /** @@ -42,12 +41,12 @@ public class CookieInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { theRequest.addHeader(Constants.HEADER_COOKIE, sessionCookie); //$NON-NLS-1$ } @Override - public void interceptResponse(HttpResponse theResponse) { + public void interceptResponse(IHttpResponse theResponse) { // nothing } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java index 69e46eb5222..e325f551024 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java @@ -20,22 +20,19 @@ package ca.uhn.fhir.rest.client.interceptor; * #L% */ -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.HttpEntityWrapper; import org.slf4j.Logger; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; public class LoggingInterceptor implements IClientInterceptor { @@ -74,59 +71,39 @@ public class LoggingInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { if (myLogRequestSummary) { myLog.info("Client request: {}", theRequest); } if (myLogRequestHeaders) { - StringBuilder b = new StringBuilder(); - for (int i = 0; i < theRequest.getAllHeaders().length; i++) { - Header next = theRequest.getAllHeaders()[i]; - b.append(next.getName() + ": " + next.getValue()); - if (i + 1 < theRequest.getAllHeaders().length) { - b.append('\n'); - } - } + StringBuilder b = headersToString(theRequest.getAllHeaders()); myLog.info("Client request headers:\n{}", b.toString()); } if (myLogRequestBody) { - if (theRequest instanceof HttpEntityEnclosingRequest) { - HttpEntity entity = ((HttpEntityEnclosingRequest) theRequest).getEntity(); - if (entity.isRepeatable()) { - try { - String content = IOUtils.toString(entity.getContent()); - myLog.info("Client request body:\n{}", content); - } catch (IllegalStateException e) { - myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); - } catch (IOException e) { - myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); - } + try { + String content = theRequest.getRequestBodyFromStream(); + if (content != null) { + myLog.info("Client request body:\n{}", content); } + } catch (IllegalStateException e) { + myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); + } catch (IOException e) { + myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); } } - } @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { + public void interceptResponse(IHttpResponse theResponse) throws IOException { if (myLogResponseSummary) { - String message = "HTTP " + theResponse.getStatusLine().getStatusCode() + " " + theResponse.getStatusLine().getReasonPhrase(); + String message = "HTTP " + theResponse.getStatus() + " " + theResponse.getStatusInfo(); myLog.info("Client response: {}", message); } if (myLogResponseHeaders) { - StringBuilder b = new StringBuilder(); - if (theResponse.getAllHeaders() != null) { - for (int i = 0; i < theResponse.getAllHeaders().length; i++) { - Header next = theResponse.getAllHeaders()[i]; - b.append(next.getName() + ": " + next.getValue()); - if (i + 1 < theResponse.getAllHeaders().length) { - b.append('\n'); - } - } - } + StringBuilder b = headersToString(theResponse.getAllHeaders()); // if (theResponse.getEntity() != null && theResponse.getEntity().getContentEncoding() != null) { // Header next = theResponse.getEntity().getContentEncoding(); // b.append(next.getName() + ": " + next.getValue()); @@ -143,23 +120,46 @@ public class LoggingInterceptor implements IClientInterceptor { } if (myLogResponseBody) { - HttpEntity respEntity = theResponse.getEntity(); - if (respEntity != null) { - final byte[] bytes; - try { - bytes = IOUtils.toByteArray(respEntity.getContent()); - } catch (IllegalStateException e) { - throw new InternalErrorException(e); - } - - myLog.info("Client response body:\n{}", new String(bytes, "UTF-8")); - theResponse.setEntity(new MyEntityWrapper(respEntity, bytes)); - } else { - myLog.info("Client response body: (none)"); + theResponse.bufferEntitity(); + InputStream respEntity = null; + try { + respEntity = theResponse.readEntity(); + if (respEntity != null) { + final byte[] bytes; + try { + bytes = IOUtils.toByteArray(respEntity); + } catch (IllegalStateException e) { + throw new InternalErrorException(e); + } + myLog.info("Client response body:\n{}", new String(bytes, "UTF-8")); + } else { + myLog.info("Client response body: (none)"); + } + } finally { + IOUtils.closeQuietly(respEntity); } } } + private StringBuilder headersToString(Map> theHeaders) { + StringBuilder b = new StringBuilder(); + if (theHeaders != null && !theHeaders.isEmpty()) { + Iterator nameEntries = theHeaders.keySet().iterator(); + while(nameEntries.hasNext()) { + String key = nameEntries.next(); + Iterator values = theHeaders.get(key).iterator(); + while(values.hasNext()) { + String value = values.next(); + b.append(key + ": " + value); + if (nameEntries.hasNext() || values.hasNext()) { + b.append('\n'); + } + } + } + } + return b; + } + /** * Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect * logs to a differently named logger instead. @@ -214,25 +214,4 @@ public class LoggingInterceptor implements IClientInterceptor { myLogResponseSummary = theValue; } - private static class MyEntityWrapper extends HttpEntityWrapper { - - private byte[] myBytes; - - public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) { - super(theWrappedEntity); - myBytes = theBytes; - } - - @Override - public InputStream getContent() throws IOException { - return new ByteArrayInputStream(myBytes); - } - - @Override - public void writeTo(OutputStream theOutstream) throws IOException { - theOutstream.write(myBytes); - } - - } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/UserInfoInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/UserInfoInterceptor.java index 72e21414f22..fbf4764c7b1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/UserInfoInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/UserInfoInterceptor.java @@ -22,10 +22,9 @@ package ca.uhn.fhir.rest.client.interceptor; import java.io.IOException; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpRequestBase; - import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; /** * HTTP interceptor to be used for adding HTTP headers containing user identifying info for auditing purposes to the request @@ -48,14 +47,14 @@ public class UserInfoInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { if(myUserId != null) theRequest.addHeader(HEADER_USER_ID, myUserId); if(myUserName != null) theRequest.addHeader(HEADER_USER_NAME, myUserName); if(myAppName != null) theRequest.addHeader(HEADER_APPLICATION_NAME, myAppName); } @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { + public void interceptResponse(IHttpResponse theResponse) throws IOException { // nothing } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/AddTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/AddTagsMethodBinding.java index 4984e9c79e5..d833212cec5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/AddTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/AddTagsMethodBinding.java @@ -28,8 +28,8 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding { - public AddTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, AddTags theAnnotation) { - super(theMethod, theConetxt, theProvider, theAnnotation.type()); + public AddTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, AddTags theAnnotation) { + super(theMethod, theContext, theProvider, theAnnotation.type()); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java index 1a5cf33c0b6..ff1ae23510b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java @@ -56,8 +56,8 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding private String myResourceName; private Integer myTagListParamIndex; - public BaseAddOrDeleteTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, Class theTypeFromMethodAnnotation) { - super(theMethod, theConetxt, theProvider); + public BaseAddOrDeleteTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, Class theTypeFromMethodAnnotation) { + super(theMethod, theContext, theProvider); if (theProvider instanceof IResourceProvider) { myType = ((IResourceProvider) theProvider).getResourceType(); @@ -69,7 +69,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding throw new ConfigurationException("Method '" + theMethod.getName() + "' does not specify a resource type, but has an @" + IdParam.class.getSimpleName() + " parameter. Please specity a resource type in the method annotation on this method"); } - myResourceName = theConetxt.getResourceDefinition(myType).getName(); + myResourceName = theContext.getResourceDefinition(myType).getName(); myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod, getContext()); myVersionIdParamIndex = MethodUtil.findVersionIdParameterIndex(theMethod); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 4fc82f143e5..02469a665dc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -23,19 +23,10 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #L% */ -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.AbstractHttpEntity; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.message.BasicNameValuePair; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -47,10 +38,11 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** * @author James Agnew @@ -63,7 +55,6 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca private final BundleTypeEnum myBundleType; private final String myContents; private boolean myContentsIsBundle; - private final FhirContext myContext; private Map> myIfNoneExistParams; private String myIfNoneExistString; private boolean myOmitResourceId = false; @@ -74,7 +65,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca private final String myUrlPath; public BaseHttpClientInvocationWithContents(FhirContext theContext, Bundle theBundle) { - myContext = theContext; + super(theContext); myResource = null; myTagList = null; myUrlPath = null; @@ -84,8 +75,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } - public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, Map> theParams, String... theUrlPath) { - myContext = theContext; + public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, + Map> theParams, String... theUrlPath) { + super(theContext); myResource = theResource; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -98,8 +90,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, String theUrlPath) { - super(); - myContext = theContext; + super(theContext); myResource = theResource; myUrlPath = theUrlPath; myTagList = null; @@ -109,8 +100,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } - public BaseHttpClientInvocationWithContents(FhirContext theContext, List theResources, BundleTypeEnum theBundleType) { - myContext = theContext; + public BaseHttpClientInvocationWithContents(FhirContext theContext, List theResources, + BundleTypeEnum theBundleType) { + super(theContext); myResource = null; myTagList = null; myUrlPath = null; @@ -120,8 +112,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = theBundleType; } - public BaseHttpClientInvocationWithContents(FhirContext theContext, Map> theParams, String... theUrlPath) { - myContext = theContext; + public BaseHttpClientInvocationWithContents(FhirContext theContext, Map> theParams, + String... theUrlPath) { + super(theContext); myResource = null; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -133,8 +126,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } - public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlPath) { - myContext = theContext; + public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, boolean theIsBundle, + String theUrlPath) { + super(theContext); myResource = null; myTagList = null; myUrlPath = theUrlPath; @@ -145,8 +139,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myBundleType = null; } - public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, Map> theParams, String... theUrlPath) { - myContext = theContext; + public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, + Map> theParams, String... theUrlPath) { + super(theContext); myResource = null; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -159,13 +154,12 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, TagList theTagList, String... theUrlPath) { - super(); + super(theContext); if (theTagList == null) { throw new NullPointerException("Tag list must not be null"); } myResource = null; - myContext = theContext; myTagList = theTagList; myResources = null; myBundle = null; @@ -175,23 +169,8 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myUrlPath = StringUtils.join(theUrlPath, '/'); } - private void addMatchHeaders(HttpRequestBase theHttpRequest, StringBuilder theUrlBase) { - if (myIfNoneExistParams != null) { - StringBuilder b = newHeaderBuilder(theUrlBase); - appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); - theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); - } - - if (myIfNoneExistString != null) { - StringBuilder b = newHeaderBuilder(theUrlBase); - b.append(b.indexOf("?") == -1 ? '?' : '&'); - b.append(myIfNoneExistString.substring(myIfNoneExistString.indexOf('?') + 1)); - theHttpRequest.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); - } - } - @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException { + public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException { StringBuilder url = new StringBuilder(); if (myUrlPath == null) { @@ -207,43 +186,46 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } appendExtraParamsWithQuestionMark(theExtraParams, url, url.indexOf("?") == -1); + IHttpClient httpClient = getRestfulClientFactory().getHttpClient(url, myIfNoneExistParams, myIfNoneExistString, getRequestType(), getHeaders()); if (myResource != null && IBaseBinary.class.isAssignableFrom(myResource.getClass())) { - IBaseBinary binary = (IBaseBinary) myResource; - if (isBlank(binary.getContentType()) || EncodingEnum.forContentTypeStrict(binary.getContentType()) != null) { - ourLog.trace("Binary has Content-Type {}, encoding as a FHIR resource instead of raw", binary.getContentType()); + return httpClient.createBinaryRequest((IBaseBinary) myResource); + } else { + EncodingEnum encoding = theEncoding; + if (myContents != null) { + encoding = MethodUtil.detectEncoding(myContents); + } + + if (encoding == null) { + encoding = EncodingEnum.XML; + } + + if (myParams != null) { + return httpClient.createParamRequest(myParams, encoding); } else { - /* - * Note: Be careful about changing which constructor we use for ByteArrayEntity, - * as Android's version of HTTPClient doesn't support the newer ones for - * whatever reason. - */ - ByteArrayEntity entity = new ByteArrayEntity(binary.getContent()); - - HttpRequestBase retVal = createRequest(url, entity); - addMatchHeaders(retVal, url); - super.addHeadersToRequest(retVal, null); - if (isNotBlank(binary.getContentType())) { - retVal.addHeader(Constants.HEADER_CONTENT_TYPE, binary.getContentType()); - } - return retVal; + String contents = parseContents(thePrettyPrint, encoding); + String contentType = getContentType(encoding); + return httpClient.createByteRequest(contents, contentType, encoding); } } + } - IParser parser; - String contentType; - EncodingEnum encoding = null; - encoding = theEncoding; - - if (myContents != null) { - encoding = MethodUtil.detectEncoding(myContents); + private String getContentType(EncodingEnum encoding) { + if (myBundle != null || (getFhirContext().getVersion().getVersion() == FhirVersionEnum.DSTU1 + && ((myContents != null && myContentsIsBundle) || myResources != null))) { + return encoding.getBundleContentType(); + } else { + return encoding.getResourceContentType(); } + } + + private String parseContents(Boolean thePrettyPrint, EncodingEnum encoding) { + IParser parser; if (encoding == EncodingEnum.JSON) { - parser = myContext.newJsonParser(); + parser = getFhirContext().newJsonParser(); } else { - encoding = EncodingEnum.XML; - parser = myContext.newXmlParser(); + parser = getFhirContext().newXmlParser(); } if (thePrettyPrint != null) { @@ -251,84 +233,27 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } parser.setOmitResourceId(myOmitResourceId); - - AbstractHttpEntity entity; - if (myParams != null) { - contentType = null; - List parameters = new ArrayList(); - for (Entry> nextParam : myParams.entrySet()) { - List value = nextParam.getValue(); - for (String s : value) { - parameters.add(new BasicNameValuePair(nextParam.getKey(), s)); - } - } - try { - entity = new UrlEncodedFormEntity(parameters, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new InternalErrorException("Server does not support UTF-8 (should not happen)", e); - } - } else { - String contents; - if (myTagList != null) { - contents = parser.encodeTagListToString(myTagList); - contentType = encoding.getResourceContentType(); - } else if (myBundle != null) { - contents = parser.encodeBundleToString(myBundle); - contentType = encoding.getBundleContentType(); - } else if (myResources != null) { - IVersionSpecificBundleFactory bundleFactory = myContext.newBundleFactory(); - bundleFactory.initializeBundleFromResourceList("", myResources, "", "", myResources.size(), myBundleType); - Bundle bundle = bundleFactory.getDstu1Bundle(); - if (bundle != null) { - contents = parser.encodeBundleToString(bundle); - contentType = encoding.getBundleContentType(); - } else { - IBaseResource bundleRes = bundleFactory.getResourceBundle(); - contents = parser.encodeResourceToString(bundleRes); - contentType = encoding.getResourceContentType(); - } - } else if (myContents != null) { - contents = myContents; - if (myContentsIsBundle && myContext.getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { - contentType = encoding.getBundleContentType(); - } else { - contentType = encoding.getResourceContentType(); - } + if (myTagList != null) { + return parser.encodeTagListToString(myTagList); + } else if (myBundle != null) { + return parser.encodeBundleToString(myBundle); + } else if (myResources != null) { + IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory(); + bundleFactory.initializeBundleFromResourceList("", myResources, "", "", myResources.size(), myBundleType); + Bundle bundle = bundleFactory.getDstu1Bundle(); + if (bundle != null) { + return parser.encodeBundleToString(bundle); } else { - contents = parser.encodeResourceToString(myResource); - contentType = encoding.getResourceContentType(); + IBaseResource bundleRes = bundleFactory.getResourceBundle(); + return parser.encodeResourceToString(bundleRes); } - - /* - * We aren't using a StringEntity here because the constructors supported by - * Android aren't available in non-Android, and vice versa. Since we add the - * content type header manually, it makes no difference which one - * we use anyhow. - */ - entity = new ByteArrayEntity(contents.getBytes(Constants.CHARSET_UTF8)); + } else if (myContents != null) { + return myContents; + } else { + return parser.encodeResourceToString(myResource); } - - HttpRequestBase retVal = createRequest(url, entity); - super.addHeadersToRequest(retVal, encoding); - addMatchHeaders(retVal, url); - - if (contentType != null) { - retVal.addHeader(Constants.HEADER_CONTENT_TYPE, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); - } - - return retVal; } - protected abstract HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity); - - private StringBuilder newHeaderBuilder(StringBuilder theUrlBase) { - StringBuilder b = new StringBuilder(); - b.append(theUrlBase); - if (theUrlBase.length() > 0 && theUrlBase.charAt(theUrlBase.length() - 1) == '/') { - b.deleteCharAt(b.length() - 1); - } - return b; - } public void setIfNoneExistParams(Map> theIfNoneExist) { myIfNoneExistParams = theIfNoneExist; } @@ -341,4 +266,9 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myOmitResourceId = theOmitResourceId; } + /** + * Get the HTTP request type. + */ + protected abstract RequestTypeEnum getRequestType(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java index 34a617fd321..b98367caf19 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java @@ -60,7 +60,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding @Override public HttpGetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - HttpGetClientInvocation retVal = MethodUtil.createConformanceInvocation(); + HttpGetClientInvocation retVal = MethodUtil.createConformanceInvocation(getContext()); if (theArgs != null) { for (int idx = 0; idx < theArgs.length; idx++) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java index 12603ad0596..1642794a39a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java @@ -119,7 +119,7 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { throw new InvalidRequestException("ID parameter has the wrong resource type, expected '" + getResourceName() + "', found: " + idDt.getResourceType()); } - HttpDeleteClientInvocation retVal = createDeleteInvocation(idDt); + HttpDeleteClientInvocation retVal = createDeleteInvocation(getContext(), idDt); for (int idx = 0; idx < theArgs.length; idx++) { IParameter nextParam = getParameters().get(idx); @@ -129,8 +129,8 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { return retVal; } - public static HttpDeleteClientInvocation createDeleteInvocation(IIdType theId) { - HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theId); + public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, IIdType theId) { + HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theContext, theId); return retVal; } @@ -144,13 +144,13 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { return null; } - public static HttpDeleteClientInvocation createDeleteInvocation(String theSearchUrl) { - HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theSearchUrl); + public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, String theSearchUrl) { + HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theContext, theSearchUrl); return retVal; } - public static HttpDeleteClientInvocation createDeleteInvocation(String theResourceType, Map> theParams) { - return new HttpDeleteClientInvocation(theResourceType, theParams); + public static HttpDeleteClientInvocation createDeleteInvocation(FhirContext theContext, String theResourceType, Map> theParams) { + return new HttpDeleteClientInvocation(theContext, theResourceType, theParams); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteTagsMethodBinding.java index c1c22b9f63f..08491d306de 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteTagsMethodBinding.java @@ -28,8 +28,8 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding { - public DeleteTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, DeleteTags theDeleteTags) { - super(theMethod, theConetxt, theProvider, theDeleteTags.type()); + public DeleteTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, DeleteTags theDeleteTags) { + super(theMethod, theContext, theProvider, theDeleteTags.type()); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java index e5f3f7aeb86..ebae9055f85 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java @@ -48,8 +48,8 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi private HashSet myParamNames; private Integer myIdParamIndex; - public DynamicSearchMethodBinding(Class theReturnResourceType, Method theMethod, FhirContext theConetxt, IDynamicSearchResourceProvider theProvider) { - super(theReturnResourceType, theMethod, theConetxt, theProvider); + public DynamicSearchMethodBinding(Class theReturnResourceType, Method theMethod, FhirContext theContext, IDynamicSearchResourceProvider theProvider) { + super(theReturnResourceType, theMethod, theContext, theProvider); myProvider = theProvider; mySearchParameters = myProvider.getSearchParameters(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index bd80413bea6..00d55e79bbd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java @@ -54,8 +54,8 @@ public class GetTagsMethodBinding extends BaseMethodBinding { private Class myType; private Integer myVersionIdParamIndex; - public GetTagsMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider, GetTags theAnnotation) { - super(theMethod, theConetxt, theProvider); + public GetTagsMethodBinding(Method theMethod, FhirContext theContext, Object theProvider, GetTags theAnnotation) { + super(theMethod, theContext, theProvider); if (theProvider instanceof IResourceProvider) { myType = ((IResourceProvider) theProvider).getResourceType(); @@ -64,7 +64,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding { } if (!Modifier.isInterface(myType.getModifiers())) { - myResourceName = theConetxt.getResourceDefinition(myType).getName(); + myResourceName = theContext.getResourceDefinition(myType).getName(); } myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod, getContext()); @@ -127,17 +127,17 @@ public class GetTagsMethodBinding extends BaseMethodBinding { if (myType != IResource.class) { if (id != null) { if (versionId != null) { - retVal = new HttpGetClientInvocation(getResourceName(), id.getIdPart(), Constants.PARAM_HISTORY, versionId.getValue(), Constants.PARAM_TAGS); + retVal = new HttpGetClientInvocation(getContext(), getResourceName(), id.getIdPart(), Constants.PARAM_HISTORY, versionId.getValue(), Constants.PARAM_TAGS); } else if (id.hasVersionIdPart()) { - retVal = new HttpGetClientInvocation(getResourceName(), id.getIdPart(), Constants.PARAM_HISTORY, id.getVersionIdPart(), Constants.PARAM_TAGS); + retVal = new HttpGetClientInvocation(getContext(), getResourceName(), id.getIdPart(), Constants.PARAM_HISTORY, id.getVersionIdPart(), Constants.PARAM_TAGS); } else { - retVal = new HttpGetClientInvocation(getResourceName(), id.getIdPart(), Constants.PARAM_TAGS); + retVal = new HttpGetClientInvocation(getContext(), getResourceName(), id.getIdPart(), Constants.PARAM_TAGS); } } else { - retVal = new HttpGetClientInvocation(getResourceName(), Constants.PARAM_TAGS); + retVal = new HttpGetClientInvocation(getContext(), getResourceName(), Constants.PARAM_TAGS); } } else { - retVal = new HttpGetClientInvocation(Constants.PARAM_TAGS); + retVal = new HttpGetClientInvocation(getContext(), Constants.PARAM_TAGS); } if (theArgs != null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index 0d077d03a7e..5a026d90252 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -53,8 +53,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { private String myResourceName; private final RestOperationTypeEnum myResourceOperationType; - public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { - super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider); + public HistoryMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { + super(toReturnType(theMethod, theProvider), theMethod, theContext, theProvider); myIdParamIndex = MethodUtil.findIdParameterIndex(theMethod, getContext()); @@ -80,7 +80,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (type != IResource.class) { - myResourceName = theConetxt.getResourceDefinition(type).getName(); + myResourceName = theContext.getResourceDefinition(type).getName(); } else { myResourceName = null; } @@ -142,7 +142,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } String historyId = id != null ? id.getIdPart() : null; - HttpGetClientInvocation retVal = createHistoryInvocation(resourceName, historyId, null, null); + HttpGetClientInvocation retVal = createHistoryInvocation(getContext(), resourceName, historyId, null, null); if (theArgs != null) { for (int idx = 0; idx < theArgs.length; idx++) { @@ -206,7 +206,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { }; } - public static HttpGetClientInvocation createHistoryInvocation(String theResourceName, String theId, IPrimitiveType theSince, Integer theLimit) { + public static HttpGetClientInvocation createHistoryInvocation(FhirContext theContext, String theResourceName, String theId, IPrimitiveType theSince, Integer theLimit) { StringBuilder b = new StringBuilder(); if (theResourceName != null) { b.append(theResourceName); @@ -230,7 +230,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { b.append(Constants.PARAM_COUNT).append('=').append(theLimit); } - HttpGetClientInvocation retVal = new HttpGetClientInvocation(b.toString()); + HttpGetClientInvocation retVal = new HttpGetClientInvocation(theContext, b.toString()); return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java index 3621682cb3e..e7e869d8976 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpDeleteClientInvocation.java @@ -23,11 +23,12 @@ package ca.uhn.fhir.rest.method; import java.util.List; import java.util.Map; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpRequestBase; import org.hl7.fhir.instance.model.api.IIdType; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.server.EncodingEnum; public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { @@ -35,22 +36,24 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { private String myUrlPath; private Map> myParams; - public HttpDeleteClientInvocation(IIdType theId) { - super(); + public HttpDeleteClientInvocation(FhirContext theContext, IIdType theId) { + super(theContext); myUrlPath = theId.toUnqualifiedVersionless().getValue(); } - public HttpDeleteClientInvocation(String theSearchUrl) { + public HttpDeleteClientInvocation(FhirContext theContext, String theSearchUrl) { + super(theContext); myUrlPath = theSearchUrl; } - public HttpDeleteClientInvocation(String theResourceType, Map> theParams) { + public HttpDeleteClientInvocation(FhirContext theContext, String theResourceType, Map> theParams) { + super(theContext); myUrlPath = theResourceType; myParams = theParams; } @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { + public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); b.append(theUrlBase); if (!theUrlBase.endsWith("/")) { @@ -61,9 +64,7 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation { appendExtraParamsWithQuestionMark(myParams, b, b.indexOf("?") == -1); appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); - HttpDelete retVal = new HttpDelete(b.toString()); - super.addHeadersToRequest(retVal, theEncoding); - return retVal; + return createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.DELETE); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java index 1c4a2470a7b..5e2a1acda3f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpGetClientInvocation.java @@ -28,10 +28,12 @@ import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.methods.HttpGet; import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.server.EncodingEnum; /** @@ -43,28 +45,33 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { private final Map> myParameters; private final String myUrlPath; - public HttpGetClientInvocation(Map> theParameters, String... theUrlFragments) { + public HttpGetClientInvocation(FhirContext theContext, Map> theParameters, String... theUrlFragments) { + super(theContext); myParameters = theParameters; myUrlPath = StringUtils.join(theUrlFragments, '/'); } - public HttpGetClientInvocation(Map> theParameters, List theUrlFragments) { + public HttpGetClientInvocation(FhirContext theContext, Map> theParameters, List theUrlFragments) { + super(theContext); myParameters = theParameters; myUrlPath = StringUtils.join(theUrlFragments, '/'); } - public HttpGetClientInvocation(String theUrlPath) { + public HttpGetClientInvocation(FhirContext theContext, String theUrlPath) { + super(theContext); myParameters = new HashMap>(); myUrlPath = theUrlPath; } - public HttpGetClientInvocation(String... theUrlFragments) { + public HttpGetClientInvocation(FhirContext theContext, String... theUrlFragments) { + super(theContext); myParameters = new HashMap>(); myUrlPath = StringUtils.join(theUrlFragments, '/'); } - public HttpGetClientInvocation(List theUrlFragments) { + public HttpGetClientInvocation(FhirContext theContext, List theUrlFragments) { + super(theContext); myParameters = new HashMap>(); myUrlPath = StringUtils.join(theUrlFragments, '/'); } @@ -78,7 +85,7 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { } @Override - public HttpGet asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { + public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { StringBuilder b = new StringBuilder(); if (!myUrlPath.contains("://")) { @@ -102,10 +109,7 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation { appendExtraParamsWithQuestionMark(theExtraParams, b, first); - HttpGet retVal = new HttpGet(b.toString()); - super.addHeadersToRequest(retVal, theEncoding); - - return retVal; + return super.createHttpRequest(b.toString(), theEncoding, RequestTypeEnum.GET); } private boolean addQueryParameter(StringBuilder b, boolean first, String nextKey, String nextValue) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java index 4b46346d3f0..5426eda3aef 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPostClientInvocation.java @@ -23,14 +23,13 @@ package ca.uhn.fhir.rest.method; import java.util.List; import java.util.Map; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.AbstractHttpEntity; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.valueset.BundleTypeEnum; +import ca.uhn.fhir.rest.api.RequestTypeEnum; public class HttpPostClientInvocation extends BaseHttpClientInvocationWithContents { @@ -63,11 +62,8 @@ public class HttpPostClientInvocation extends BaseHttpClientInvocationWithConten super(theContext, theParams, theUrlExtension); } - @Override - protected HttpPost createRequest(StringBuilder theUrlBase, AbstractHttpEntity theEntity) { - HttpPost retVal = new HttpPost(theUrlBase.toString()); - retVal.setEntity(theEntity); - return retVal; + protected RequestTypeEnum getRequestType() { + return RequestTypeEnum.POST; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java index eaec5aab82a..201701759f6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpPutClientInvocation.java @@ -20,12 +20,10 @@ package ca.uhn.fhir.rest.method; * #L% */ -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.entity.AbstractHttpEntity; import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; public class HttpPutClientInvocation extends BaseHttpClientInvocationWithContents { @@ -38,10 +36,8 @@ public class HttpPutClientInvocation extends BaseHttpClientInvocationWithContent } @Override - protected HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity) { - HttpPut retVal = new HttpPut(theUrl.toString()); - retVal.setEntity(theEntity); - return retVal; + protected RequestTypeEnum getRequestType() { + return RequestTypeEnum.PUT; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java index cd2f1eff7a8..7371e772d53 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HttpSimpleGetClientInvocation.java @@ -23,25 +23,24 @@ package ca.uhn.fhir.rest.method; import java.util.List; import java.util.Map; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpRequestBase; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.server.EncodingEnum; public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation { private final String myUrl; - public HttpSimpleGetClientInvocation(String theUrlPath) { + public HttpSimpleGetClientInvocation(FhirContext theContext, String theUrlPath) { + super(theContext); myUrl = theUrlPath; } @Override - public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { - HttpGet retVal = new HttpGet(myUrl); - super.addHeadersToRequest(retVal, theEncoding); - return retVal; + public IHttpRequest asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { + return createHttpRequest(myUrl, theEncoding, RequestTypeEnum.GET); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index ef3dad739b1..5dfc7fa9b0e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.utils.DateUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -89,6 +88,7 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; import ca.uhn.fhir.rest.server.SearchParameterMap; +import ca.uhn.fhir.util.DateUtils; import ca.uhn.fhir.util.ReflectionUtil; /* @@ -146,8 +146,8 @@ public class MethodUtil { return value; } - public static HttpGetClientInvocation createConformanceInvocation() { - return new HttpGetClientInvocation("metadata"); + public static HttpGetClientInvocation createConformanceInvocation(FhirContext theContext) { + return new HttpGetClientInvocation(theContext, "metadata"); } public static HttpPostClientInvocation createCreateInvocation(IBaseResource theResource, FhirContext theContext) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java index 6c085f960dd..4ca0f1df3cc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java @@ -358,7 +358,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { IPrimitiveType primitive = (IPrimitiveType) value; params.get(nextName).add(primitive.getValueAsString()); } - return new HttpGetClientInvocation(params, b.toString()); + return new HttpGetClientInvocation(theContext, params, b.toString()); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java index 73b8e8e2932..0636c903b59 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java @@ -167,15 +167,15 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem if (myVersionIdIndex == null) { String resourceName = getResourceName(); if (id.hasVersionIdPart()) { - retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()), resourceName); + retVal = createVReadInvocation(getContext(), new IdDt(resourceName, id.getIdPart(), id.getVersionIdPart()), resourceName); } else { - retVal = createReadInvocation(id, resourceName); + retVal = createReadInvocation(getContext(), id, resourceName); } } else { IdDt vid = ((IdDt) theArgs[myVersionIdIndex]); String resourceName = getResourceName(); - retVal = createVReadInvocation(new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()), resourceName); + retVal = createVReadInvocation(getContext(), new IdDt(resourceName, id.getIdPart(), vid.getVersionIdPart()), resourceName); } for (int idx = 0; idx < theArgs.length; idx++) { @@ -249,20 +249,20 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem return mySupportsVersion || myVersionIdIndex != null; } - public static HttpGetClientInvocation createAbsoluteReadInvocation(IIdType theId) { - return new HttpGetClientInvocation(theId.toVersionless().getValue()); + public static HttpGetClientInvocation createAbsoluteReadInvocation(FhirContext theContext, IIdType theId) { + return new HttpGetClientInvocation(theContext, theId.toVersionless().getValue()); } - public static HttpGetClientInvocation createAbsoluteVReadInvocation(IIdType theId) { - return new HttpGetClientInvocation(theId.getValue()); + public static HttpGetClientInvocation createAbsoluteVReadInvocation(FhirContext theContext, IIdType theId) { + return new HttpGetClientInvocation(theContext, theId.getValue()); } - public static HttpGetClientInvocation createReadInvocation(IIdType theId, String theResourceName) { - return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart()).getValue()); + public static HttpGetClientInvocation createReadInvocation(FhirContext theContext, IIdType theId, String theResourceName) { + return new HttpGetClientInvocation(theContext, new IdDt(theResourceName, theId.getIdPart()).getValue()); } - public static HttpGetClientInvocation createVReadInvocation(IIdType theId, String theResourceName) { - return new HttpGetClientInvocation(new IdDt(theResourceName, theId.getIdPart(), theId.getVersionIdPart()).getValue()); + public static HttpGetClientInvocation createVReadInvocation(FhirContext theContext, IIdType theId, String theResourceName) { + return new HttpGetClientInvocation(theContext, new IdDt(theResourceName, theId.getIdPart(), theId.getVersionIdPart()).getValue()); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java index 4325c567dad..6debd4efd1b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; /* * #%L @@ -274,4 +275,9 @@ public abstract class RequestDetails { mySecondaryOperation = theSecondaryOperation; } + /** + * Return the charset as defined by the header contenttype. Return null if it is not set. + */ + public abstract Charset getCharset(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 75a8ddd1244..e710e80a0b4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -340,16 +340,16 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { case GET: default: if (compartmentSearch) { - invocation = new HttpGetClientInvocation(theParameters, theResourceName, theId.getIdPart(), theCompartmentName); + invocation = new HttpGetClientInvocation(theContext, theParameters, theResourceName, theId.getIdPart(), theCompartmentName); } else { - invocation = new HttpGetClientInvocation(theParameters, theResourceName); + invocation = new HttpGetClientInvocation(theContext, theParameters, theResourceName); } break; case GET_WITH_SEARCH: if (compartmentSearch) { - invocation = new HttpGetClientInvocation(theParameters, theResourceName, theId.getIdPart(), theCompartmentName, Constants.PARAM_SEARCH); + invocation = new HttpGetClientInvocation(theContext, theParameters, theResourceName, theId.getIdPart(), theCompartmentName, Constants.PARAM_SEARCH); } else { - invocation = new HttpGetClientInvocation(theParameters, theResourceName, Constants.PARAM_SEARCH); + invocation = new HttpGetClientInvocation(theContext, theParameters, theResourceName, Constants.PARAM_SEARCH); } break; case POST: @@ -456,8 +456,8 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } - public static BaseHttpClientInvocation createSearchInvocation(String theSearchUrl, Map> theParams) { - return new HttpGetClientInvocation(theParams, theSearchUrl); + public static BaseHttpClientInvocation createSearchInvocation(FhirContext theContext, String theSearchUrl, Map> theParams) { + return new HttpGetClientInvocation(theContext, theParams, theSearchUrl); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java index 51abc33a9c3..33ead0772c0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java @@ -53,8 +53,8 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding private int myTransactionParamIndex; private ParamStyle myTransactionParamStyle; - public TransactionMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) { - super(null, theMethod, theConetxt, theProvider); + public TransactionMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { + super(null, theMethod, theContext, theProvider); myTransactionParamIndex = -1; int index = 0; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java index 69a40d90930..b9295c7da69 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java @@ -20,7 +20,6 @@ package ca.uhn.fhir.rest.param; * #L% */ import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -35,7 +34,6 @@ import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; -import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -130,13 +128,7 @@ public class ResourceParameter implements IParameter { } public static Charset determineRequestCharset(RequestDetails theRequest) { - String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); - - Charset charset = null; - if (isNotBlank(ct)) { - ContentType parsedCt = ContentType.parse(ct); - charset = parsedCt.getCharset(); - } + Charset charset = theRequest.getCharset(); if (charset == null) { charset = Charset.forName("UTF-8"); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 83a0cc18212..10dbda5af1c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -43,7 +43,6 @@ import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; -import org.apache.http.client.utils.DateUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -69,6 +68,7 @@ import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SummaryEnumParameter; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.DateUtils; public class RestfulServerUtils { static final Pattern ACCEPT_HEADER_PATTERN = Pattern.compile("\\s*([a-zA-Z0-9+.*/-]+)\\s*(;\\s*([a-zA-Z]+)\\s*=\\s*([a-zA-Z0-9.]+)\\s*)?(,?)"); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java index 34c8db073d6..cc97eb5b3bd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java @@ -1,7 +1,5 @@ package ca.uhn.fhir.rest.server.servlet; -import java.io.ByteArrayInputStream; - /* * #%L * HAPI FHIR - Core Library @@ -21,10 +19,13 @@ import java.io.ByteArrayInputStream; * limitations under the License. * #L% */ +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.charset.Charset; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; @@ -37,6 +38,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.rest.api.RequestTypeEnum; @@ -176,4 +178,16 @@ public class ServletRequestDetails extends RequestDetails { return retVal; } + @Override + public Charset getCharset() { + String ct = getHeader(Constants.HEADER_CONTENT_TYPE); + + Charset charset = null; + if (isNotBlank(ct)) { + ContentType parsedCt = ContentType.parse(ct); + charset = parsedCt.getCharset(); + } + return charset; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java new file mode 100644 index 00000000000..3214988ddab --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/DateUtils.java @@ -0,0 +1,254 @@ +package ca.uhn.fhir.util; + +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +import java.lang.ref.SoftReference; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +/** + * A utility class for parsing and formatting HTTP dates as used in cookies and + * other headers. This class handles dates as defined by RFC 2616 section + * 3.3.1 as well as some other common non-standard formats. + * + * @since 4.3 + */ +public final class DateUtils { + + /** + * Date format pattern used to parse HTTP date headers in RFC 1123 format. + */ + public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in RFC 1036 format. + */ + public static final String PATTERN_RFC1036 = "EEE, dd-MMM-yy HH:mm:ss zzz"; + + /** + * Date format pattern used to parse HTTP date headers in ANSI C + * {@code asctime()} format. + */ + public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy"; + + private static final String[] DEFAULT_PATTERNS = new String[] { + PATTERN_RFC1123, + PATTERN_RFC1036, + PATTERN_ASCTIME + }; + + private static final Date DEFAULT_TWO_DIGIT_YEAR_START; + + public static final TimeZone GMT = TimeZone.getTimeZone("GMT"); + + static { + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(GMT); + calendar.set(2000, Calendar.JANUARY, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime(); + } + + /** + * Parses a date value. The formats used for parsing the date value are retrieved from + * the default http params. + * + * @param dateValue the date value to parse + * + * @return the parsed date or null if input could not be parsed + */ + public static Date parseDate(final String dateValue) { + return parseDate(dateValue, null, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * + * @return the parsed date or null if input could not be parsed + */ + public static Date parseDate(final String dateValue, final String[] dateFormats) { + return parseDate(dateValue, dateFormats, null); + } + + /** + * Parses the date value using the given date formats. + * + * @param dateValue the date value to parse + * @param dateFormats the date formats to use + * @param startDate During parsing, two digit years will be placed in the range + * {@code startDate} to {@code startDate + 100 years}. This value may + * be {@code null}. When {@code null} is given as a parameter, year + * {@code 2000} will be used. + * + * @return the parsed date or null if input could not be parsed + */ + public static Date parseDate( + final String dateValue, + final String[] dateFormats, + final Date startDate) { + notNull(dateValue, "Date value"); + final String[] localDateFormats = dateFormats != null ? dateFormats : DEFAULT_PATTERNS; + final Date localStartDate = startDate != null ? startDate : DEFAULT_TWO_DIGIT_YEAR_START; + String v = dateValue; + // trim single quotes around date if present + // see issue #5279 + if (v.length() > 1 && v.startsWith("'") && v.endsWith("'")) { + v = v.substring (1, v.length() - 1); + } + + for (final String dateFormat : localDateFormats) { + final SimpleDateFormat dateParser = DateFormatHolder.formatFor(dateFormat); + dateParser.set2DigitYearStart(localStartDate); + final ParsePosition pos = new ParsePosition(0); + final Date result = dateParser.parse(v, pos); + if (pos.getIndex() != 0) { + return result; + } + } + return null; + } + + /** + * Formats the given date according to the RFC 1123 pattern. + * + * @param date The date to format. + * @return An RFC 1123 formatted date string. + * + * @see #PATTERN_RFC1123 + */ + public static String formatDate(final Date date) { + return formatDate(date, PATTERN_RFC1123); + } + + /** + * Formats the given date according to the specified pattern. The pattern + * must conform to that used by the {@link SimpleDateFormat simple date + * format} class. + * + * @param date The date to format. + * @param pattern The pattern to use for formatting the date. + * @return A formatted date string. + * + * @throws IllegalArgumentException If the given date pattern is invalid. + * + * @see SimpleDateFormat + */ + public static String formatDate(final Date date, final String pattern) { + notNull(date, "Date"); + notNull(pattern, "Pattern"); + final SimpleDateFormat formatter = DateFormatHolder.formatFor(pattern); + return formatter.format(date); + } + + + public static T notNull(final T argument, final String name) { + if (argument == null) { + throw new IllegalArgumentException(name + " may not be null"); + } + return argument; + } + + /** + * Clears thread-local variable containing {@link java.text.DateFormat} cache. + * + * @since 4.3 + */ + public static void clearThreadLocal() { + DateFormatHolder.clearThreadLocal(); + } + + /** This class should not be instantiated. */ + private DateUtils() { + } + + /** + * A factory for {@link SimpleDateFormat}s. The instances are stored in a + * threadlocal way because SimpleDateFormat is not threadsafe as noted in + * {@link SimpleDateFormat its javadoc}. + * + */ + final static class DateFormatHolder { + + private static final ThreadLocal>> + THREADLOCAL_FORMATS = new ThreadLocal>>() { + + @Override + protected SoftReference> initialValue() { + return new SoftReference>( + new HashMap()); + } + + }; + + /** + * creates a {@link SimpleDateFormat} for the requested format string. + * + * @param pattern + * a non-{@code null} format String according to + * {@link SimpleDateFormat}. The format is not checked against + * {@code null} since all paths go through + * {@link DateUtils}. + * @return the requested format. This simple dateformat should not be used + * to {@link SimpleDateFormat#applyPattern(String) apply} to a + * different pattern. + */ + public static SimpleDateFormat formatFor(final String pattern) { + final SoftReference> ref = THREADLOCAL_FORMATS.get(); + Map formats = ref.get(); + if (formats == null) { + formats = new HashMap(); + THREADLOCAL_FORMATS.set( + new SoftReference>(formats)); + } + + SimpleDateFormat format = formats.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + formats.put(pattern, format); + } + + return format; + } + + public static void clearThreadLocal() { + THREADLOCAL_FORMATS.remove(); + } + + } + +} diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 7099ad70a4f..870e1dc4f7b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -208,7 +208,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli-jpaserver - 1.4-SNAPSHOT + 1.5-SNAPSHOT war true target/classes diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java index eab48cb8362..755fc9c1338 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java +++ b/hapi-fhir-cli/hapi-fhir-cli-app/src/main/java/ca/uhn/fhir/cli/ExampleDataUploader.java @@ -53,7 +53,7 @@ import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.client.IGenericClient; -import ca.uhn.fhir.rest.client.interceptor.GZipContentInterceptor; +import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor; import ca.uhn.fhir.util.ResourceReferenceInfo; public class ExampleDataUploader extends BaseCommand { diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 58ce4e2ca3a..9f751617fb9 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -24,6 +24,14 @@ commons-logging commons-logging + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpcore + diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpClient.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpClient.java new file mode 100644 index 00000000000..cb63c8c0481 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpClient.java @@ -0,0 +1,160 @@ +package ca.uhn.fhir.jaxrs.client; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation.Builder; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; + +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.util.VersionUtil; + +/** + * A Http Request based on JaxRs. This is an adapter around the class + * {@link javax.ws.rs.client.Client Client} + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class JaxRsHttpClient implements IHttpClient { + + private Client myClient; + private List

myHeaders; + private StringBuilder myUrl; + private Map> myIfNoneExistParams; + private String myIfNoneExistString; + private RequestTypeEnum myRequestType; + + public JaxRsHttpClient(Client theClient, StringBuilder theUrl, Map> theIfNoneExistParams, String theIfNoneExistString, + RequestTypeEnum theRequestType, List
theHeaders) { + this.myClient = theClient; + this.myUrl = theUrl; + this.myIfNoneExistParams = theIfNoneExistParams; + this.myIfNoneExistString = theIfNoneExistString; + this.myRequestType = theRequestType; + this.myHeaders = theHeaders; + } + + @Override + public IHttpRequest createByteRequest(String theContents, String theContentType, EncodingEnum theEncoding) { + Entity entity = Entity.entity(theContents, theContentType + Constants.HEADER_SUFFIX_CT_UTF_8); + JaxRsHttpRequest retVal = createHttpRequest(entity); + addHeadersToRequest(retVal, theEncoding); + retVal.addHeader(Constants.HEADER_CONTENT_TYPE, theContentType + Constants.HEADER_SUFFIX_CT_UTF_8); + return retVal; + } + + @Override + public IHttpRequest createParamRequest(Map> theParams, EncodingEnum theEncoding) { + MultivaluedMap map = new MultivaluedHashMap(); + for (Map.Entry> nextParam : theParams.entrySet()) { + List value = nextParam.getValue(); + for (String s : value) { + map.add(nextParam.getKey(), s); + } + } + Entity
entity = Entity.form(map); + JaxRsHttpRequest retVal = createHttpRequest(entity); + // addHeadersToRequest(retVal, encoding); + return retVal; + } + + @Override + public IHttpRequest createBinaryRequest(IBaseBinary theBinary) { + Entity entity = Entity.entity(theBinary.getContentAsBase64(), theBinary.getContentType()); + JaxRsHttpRequest retVal = createHttpRequest(entity); + return retVal; + } + + @Override + public IHttpRequest createGetRequest(EncodingEnum theEncoding) { + JaxRsHttpRequest result = createHttpRequest(null); + addHeadersToRequest(result, theEncoding); + return result; + } + + public void addHeadersToRequest(JaxRsHttpRequest theHttpRequest, EncodingEnum theEncoding) { + if (myHeaders != null) { + for (Header next : myHeaders) { + theHttpRequest.addHeader(next.getName(), next.getValue()); + } + } + + theHttpRequest.addHeader("User-Agent", "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"); + theHttpRequest.addHeader("Accept-Charset", "utf-8"); + + Builder request = theHttpRequest.getRequest(); + request.acceptEncoding("gzip"); + + if (theEncoding == null) { + request.accept(Constants.HEADER_ACCEPT_VALUE_ALL); + } else if (theEncoding == EncodingEnum.JSON) { + request.accept(Constants.CT_FHIR_JSON); + } else if (theEncoding == EncodingEnum.XML) { + request.accept(Constants.CT_FHIR_XML); + } + } + + private JaxRsHttpRequest createHttpRequest(Entity entity) { + Builder request = myClient.target(myUrl.toString()).request(); + JaxRsHttpRequest result = new JaxRsHttpRequest(request, myRequestType, entity); + addHeaderIfNoneExist(result); + return result; + } + + private void addHeaderIfNoneExist(IHttpRequest result) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(myUrl); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); + result.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + + if (myIfNoneExistString != null) { + StringBuilder b = newHeaderBuilder(myUrl); + b.append(b.indexOf("?") == -1 ? '?' : '&'); + b.append(myIfNoneExistString.substring(myIfNoneExistString.indexOf('?') + 1)); + result.addHeader(Constants.HEADER_IF_NONE_EXIST, b.toString()); + } + } + + private StringBuilder newHeaderBuilder(StringBuilder theUrlBase) { + StringBuilder b = new StringBuilder(); + b.append(theUrlBase); + if (theUrlBase.length() > 0 && theUrlBase.charAt(theUrlBase.length() - 1) == '/') { + b.deleteCharAt(b.length() - 1); + } + return b; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java new file mode 100644 index 00000000000..728fbab969e --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequest.java @@ -0,0 +1,106 @@ +package ca.uhn.fhir.jaxrs.client; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; + +/** + * A Http Request based on JaxRs. This is an adapter around the class + * {@link javax.ws.rs.client.Invocation Invocation} + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class JaxRsHttpRequest implements IHttpRequest { + + private Invocation.Builder myRequest; + private RequestTypeEnum myRequestType; + private Entity myEntity; + private final Map> myHeaders = new HashMap>(); + + public JaxRsHttpRequest(Invocation.Builder theRequest, RequestTypeEnum theRequestType, Entity theEntity) { + this.myRequest = theRequest; + this.myRequestType = theRequestType; + this.myEntity = theEntity; + } + + @Override + public void addHeader(String theName, String theValue) { + if (!myHeaders.containsKey(theName)) { + myHeaders.put(theName, new LinkedList()); + } + myHeaders.get(theName).add(theValue); + getRequest().header(theName, theValue); + } + + /** + * Get the Request + * @return the Request + */ + public Invocation.Builder getRequest() { + return myRequest; + } + + /** + * Get the Request Type + * @return the request type + */ + public RequestTypeEnum getRequestType() { + return myRequestType == null ? RequestTypeEnum.GET : myRequestType; + } + + /** + * Get the Entity + * @return the entity + */ + public Entity getEntity() { + return myEntity; + } + + @Override + public IHttpResponse execute() { + Invocation invocation = getRequest().build(getRequestType().name(), getEntity()); + Response response = invocation.invoke(); + return new JaxRsHttpResponse(response); + } + + @Override + public Map> getAllHeaders() { + return this.myHeaders; + } + + @Override + public String getRequestBodyFromStream() { + // not supported + return null; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java new file mode 100644 index 00000000000..f6327db9317 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java @@ -0,0 +1,111 @@ +package ca.uhn.fhir.jaxrs.client; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.rest.client.api.IHttpResponse; + +/** + * A Http Response based on JaxRs. This is an adapter around the class {@link javax.ws.rs.core.Response Response} + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class JaxRsHttpResponse implements IHttpResponse { + + private final Response myResponse; + private boolean myBufferedEntity = false; + + public JaxRsHttpResponse(Response theResponse) { + this.myResponse = theResponse; + } + + @Override + public Response getResponse() { + return myResponse; + } + + @Override + public int getStatus() { + return myResponse.getStatus(); + } + + @Override + public String getMimeType() { + MediaType mediaType = myResponse.getMediaType(); + //Keep only type and subtype and do not include the parameters such as charset + return new MediaType(mediaType.getType(), mediaType.getSubtype()).toString(); + } + + @Override + public Map> getAllHeaders() { + Map> theHeaders = new ConcurrentHashMap>(); + for (Entry> iterable_element : myResponse.getStringHeaders().entrySet()) { + theHeaders.put(iterable_element.getKey().toLowerCase(), iterable_element.getValue()); + } + return theHeaders; + } + + @Override + public String getStatusInfo() { + return myResponse.getStatusInfo().getReasonPhrase(); + } + + @Override + public Reader createReader() { + if (!myBufferedEntity && !myResponse.hasEntity()) { + return new StringReader(""); + } else { + return new StringReader(myResponse.readEntity(String.class)); + } + } + + @Override + public InputStream readEntity() { + return myResponse.readEntity(java.io.InputStream.class); + } + + @Override + public void bufferEntitity() { + if(!myBufferedEntity && myResponse.hasEntity()) { + myBufferedEntity = true; + myResponse.bufferEntity(); + } else { + myResponse.bufferEntity(); + } + } + + + @Override + public void close() { + // automatically done by jax-rs + } + + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsRestfulClientFactory.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsRestfulClientFactory.java new file mode 100644 index 00000000000..8654a73763b --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsRestfulClientFactory.java @@ -0,0 +1,97 @@ +package ca.uhn.fhir.jaxrs.client; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.RestfulClientFactory; +import ca.uhn.fhir.rest.client.api.Header; +import ca.uhn.fhir.rest.client.api.IHttpClient; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import java.util.List; +import java.util.Map; + +/** + * A Restful Client Factory, based on Jax Rs + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare + */ +public class JaxRsRestfulClientFactory extends RestfulClientFactory { + + private Client myNativeClient; + + /** + * Constructor + */ + public JaxRsRestfulClientFactory() { + } + + /** + * Constructor + * @param theFhirContext The context + */ + public JaxRsRestfulClientFactory(FhirContext theFhirContext) { + super(theFhirContext); + } + + public synchronized Client getNativeClientClient() { + if (myNativeClient == null) { + ClientBuilder builder = ClientBuilder.newBuilder(); + myNativeClient = builder.build(); + } + + return myNativeClient; + } + + @Override + public IHttpClient getHttpClient(StringBuilder url, Map> theIfNoneExistParams, + String theIfNoneExistString, RequestTypeEnum theRequestType, List
theHeaders) { + return new JaxRsHttpClient(getNativeClientClient(), url, theIfNoneExistParams, theIfNoneExistString, theRequestType, + theHeaders); + } + + @Override + public void setProxy(String theHost, Integer thePort) { + throw new UnsupportedOperationException("Proxies are not supported yet"); + } + + /** + * Only accept clients of type javax.ws.rs.client.Client + * @param theHttpClient + */ + @Override + public synchronized void setHttpClient(Object theHttpClient) { + this.myNativeClient = (Client) theHttpClient; + } + + @Override + protected JaxRsHttpClient getHttpClient(String theServerBase) { + return new JaxRsHttpClient(getNativeClientClient(), new StringBuilder(theServerBase), null, null, null, null); + } + + @Override + protected void resetHttpClient() { + this.myNativeClient = null; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index fa61f76c342..7299af3f488 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -23,10 +23,12 @@ package ca.uhn.fhir.jaxrs.server.util; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.charset.Charset; import java.util.Collections; import java.util.List; import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; import org.apache.commons.lang3.StringUtils; @@ -149,8 +151,8 @@ public class JaxRsRequest extends RequestDetails { } } - private String theResourceString; - private HttpHeaders headers; + private String myResourceString; + private HttpHeaders myHeaders; private AbstractJaxRsProvider myServer; /** @@ -162,8 +164,8 @@ public class JaxRsRequest extends RequestDetails { */ public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { - this.headers = server.getHeaders(); - this.theResourceString = resourceString; + this.myHeaders = server.getHeaders(); + this.myResourceString = resourceString; this.setRestOperationType(restOperation); setServer(server); setFhirServerBase(server.getBaseForServer()); @@ -192,7 +194,7 @@ public class JaxRsRequest extends RequestDetails { @Override public List getHeaders(String name) { - List requestHeader = headers.getRequestHeader(name); + List requestHeader = myHeaders.getRequestHeader(name); return requestHeader == null ? Collections. emptyList() : requestHeader; } @@ -203,7 +205,7 @@ public class JaxRsRequest extends RequestDetails { @Override protected byte[] getByteStreamRequestContents() { - return StringUtils.defaultIfEmpty(theResourceString, "") + return StringUtils.defaultIfEmpty(myResourceString, "") .getBytes(ResourceParameter.determineRequestCharset(this)); } @@ -226,4 +228,18 @@ public class JaxRsRequest extends RequestDetails { // not yet implemented throw new UnsupportedOperationException(); } + + @Override + public Charset getCharset() { + String charset = null; + + if(myHeaders.getMediaType() != null && myHeaders.getMediaType().getParameters() != null) { + charset = myHeaders.getMediaType().getParameters().get(MediaType.CHARSET_PARAMETER); + } + if(charset != null) { + return Charset.forName(charset); + } else { + return null; + } + } } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index e39e8f60ec1..bf913645b02 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -38,6 +38,7 @@ import ca.uhn.fhir.jaxrs.server.test.RandomServerPortProvider; import ca.uhn.fhir.jaxrs.server.test.TestJaxRsConformanceRestProvider; import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPageProvider; import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProvider; +import ca.uhn.fhir.jaxrs.client.JaxRsRestfulClientFactory; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; @@ -104,6 +105,7 @@ public class AbstractJaxRsResourceProviderTest { jettyServer.start(); + ourCtx.setRestfulClientFactory(new JaxRsRestfulClientFactory(ourCtx)); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); serverBase = "http://localhost:" + ourPort + "/"; diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index 66d9bcbb5eb..ff5d65b4b8e 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jaxrs.server.example; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; diff --git a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java index d22015b3d69..bf56633d613 100644 --- a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java +++ b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java @@ -18,6 +18,7 @@ import org.junit.Ignore; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.client.JaxRsRestfulClientFactory; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -66,6 +67,7 @@ public class JaxRsPatientProviderTest { //@formatter:on jettyServer.start(); + ourCtx.setRestfulClientFactory(new JaxRsRestfulClientFactory(ourCtx)); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientDstu1Test.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientDstu1Test.java index 3983599b8ba..6147653faf0 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientDstu1Test.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientDstu1Test.java @@ -24,12 +24,14 @@ import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; import org.apache.http.Header; +import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicStatusLine; @@ -66,6 +68,7 @@ import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.client.apache.ApacheHttpRequest; import ca.uhn.fhir.rest.client.api.IBasicClient; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; import ca.uhn.fhir.rest.param.CompositeParam; @@ -150,7 +153,7 @@ public class ClientDstu1Test { MethodOutcome response = client.createPatient(patient); - assertEquals(interceptor.getLastRequest().getURI().toASCIIString(), "http://foo/Patient"); + assertEquals(((ApacheHttpRequest) interceptor.getLastRequest()).getApacheRequest().getURI().toASCIIString(), "http://foo/Patient"); assertEquals(HttpPost.class, capt.getValue().getClass()); HttpPost post = (HttpPost) capt.getValue(); diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java index cf8bb1deaac..e7f1bbacfae 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/LoggingInterceptorTest.java @@ -82,7 +82,7 @@ public class LoggingInterceptorTest { public boolean matches(final Object argument) { String formattedMessage = ((LoggingEvent) argument).getFormattedMessage(); System.out.println("Verifying: " + formattedMessage); - return formattedMessage.replace("; ", ";").replace("UTF", "utf").contains("Content-Type: application/xml+fhir;charset=utf-8"); + return formattedMessage.replace("; ", ";").toLowerCase().contains("Content-Type: application/xml+fhir;charset=utf-8".toLowerCase()); } })); } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/interceptor/CompressOutgoingContentInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/interceptor/CompressOutgoingContentInterceptorTest.java index 44134c4b3c1..39af449b633 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/interceptor/CompressOutgoingContentInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/interceptor/CompressOutgoingContentInterceptorTest.java @@ -20,6 +20,7 @@ import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.IResourceProvider; diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java index 035c3d9456a..3b12e61abaf 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java @@ -52,6 +52,8 @@ import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.rest.client.GenericClient; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.to.model.HomeRequest; @@ -290,7 +292,7 @@ public class BaseController { returnsResource = ResultType.NONE; ourLog.warn("Failed to invoke server", e); - if (theClient.getLastResponse() == null) { + if (e != null) { theModel.put("errorMsg", "Error: " + e.getMessage()); } @@ -653,17 +655,17 @@ public class BaseController { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { assert myLastRequest == null; - myLastRequest = theRequest; + myLastRequest = (HttpRequestBase) theRequest; } @Override - public void interceptResponse(HttpResponse theResponse) throws IOException { + public void interceptResponse(IHttpResponse theResponse) throws IOException { assert myLastResponse == null; - myLastResponse = theResponse; + myLastResponse = (HttpResponse) theResponse; - HttpEntity respEntity = theResponse.getEntity(); + HttpEntity respEntity = myLastResponse.getEntity(); if (respEntity != null) { final byte[] bytes; try { @@ -673,7 +675,7 @@ public class BaseController { } myResponseBody = new String(bytes, "UTF-8"); - theResponse.setEntity(new MyEntityWrapper(respEntity, bytes)); + myLastResponse.setEntity(new MyEntityWrapper(respEntity, bytes)); } } diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java index 70948b0f21c..1b607a0a66d 100644 --- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java +++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java @@ -15,6 +15,8 @@ import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.GenericClient; import ca.uhn.fhir.rest.client.IClientInterceptor; import ca.uhn.fhir.rest.client.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.api.IHttpRequest; +import ca.uhn.fhir.rest.client.api.IHttpResponse; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy; import ca.uhn.fhir.to.Controller; @@ -163,12 +165,12 @@ public class HomeRequest { retVal.registerInterceptor(new IClientInterceptor() { @Override - public void interceptResponse(HttpResponse theRequest) { + public void interceptResponse(IHttpResponse theRequest) { // nothing } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequest theRequest) { if (isNotBlank(remoteAddr)) { theRequest.addHeader("x-forwarded-for", remoteAddr); }