From 1271c36d1b9818eb47094917300cccf1233d0904 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Thu, 7 Jan 2016 16:35:57 +0100 Subject: [PATCH] create a jax-rs client --- hapi-fhir-base-client/pom.xml | 147 ++++++++++ .../fhir/rest/client/ApacheHttpClient.java | 205 ++++++++++++++ .../rest/client/ApacheHttpRequestBase.java | 84 ++++++ .../fhir/rest/client/ApacheHttpResponse.java | 159 +++++++++++ .../client/ApacheRestfulClientFactory.java | 147 ++++++++++ .../interceptor/GZipContentInterceptor.java | 10 +- hapi-fhir-base/pom.xml | 2 +- .../java/ca/uhn/fhir/context/FhirContext.java | 19 +- .../uhn/fhir/rest/api/IHttpRequestBase.java | 53 ++++ .../ca/uhn/fhir/rest/client/BaseClient.java | 179 +++++------- .../rest/client/BaseHttpClientInvocation.java | 58 ++-- .../rest/client/ClientInvocationHandler.java | 7 +- .../ClientInvocationHandlerFactory.java | 14 +- .../uhn/fhir/rest/client/GenericClient.java | 44 +-- .../fhir/rest/client/IClientInterceptor.java | 7 +- .../uhn/fhir/rest/client/IHttpResponse.java | 109 ++++++++ .../rest/client/IRestfulClientFactory.java | 31 ++- .../rest/client/RestfulClientFactory.java | 125 +++------ .../uhn/fhir/rest/client/api/IHttpClient.java | 67 +++++ .../fhir/rest/client/api/IRestfulClient.java | 3 +- .../interceptor/BasicAuthInterceptor.java | 8 +- .../BearerTokenAuthInterceptor.java | 8 +- .../interceptor/CapturingInterceptor.java | 17 +- .../client/interceptor/CookieInterceptor.java | 9 +- .../interceptor/LoggingInterceptor.java | 91 +++--- .../interceptor/UserInfoInterceptor.java | 9 +- .../BaseHttpClientInvocationWithContents.java | 261 +++++++----------- .../rest/method/ConformanceMethodBinding.java | 2 +- .../fhir/rest/method/DeleteMethodBinding.java | 14 +- .../rest/method/GetTagsMethodBinding.java | 10 +- .../rest/method/HistoryMethodBinding.java | 6 +- .../method/HttpDeleteClientInvocation.java | 21 +- .../rest/method/HttpGetClientInvocation.java | 26 +- .../rest/method/HttpPostClientInvocation.java | 10 +- .../rest/method/HttpPutClientInvocation.java | 10 +- .../method/HttpSimpleGetClientInvocation.java | 12 +- .../ca/uhn/fhir/rest/method/MethodUtil.java | 4 +- .../rest/method/OperationMethodBinding.java | 2 +- .../fhir/rest/method/ReadMethodBinding.java | 22 +- .../fhir/rest/method/SearchMethodBinding.java | 12 +- .../fhir/jaxrs/client/JaxRsHttpClient.java | 156 +++++++++++ .../jaxrs/client/JaxRsHttpRequestBase.java | 95 +++++++ .../fhir/jaxrs/client/JaxRsHttpResponse.java | 113 ++++++++ .../client/JaxRsRestfulClientFactory.java | 108 ++++++++ .../AbstractJaxRsResourceProviderTest.java | 2 + .../uhn/fhir/rest/client/ClientDstu1Test.java | 4 +- .../rest/client/LoggingInterceptorTest.java | 2 +- .../java/ca/uhn/fhir/to/BaseController.java | 16 +- .../ca/uhn/fhir/to/model/HomeRequest.java | 6 +- pom.xml | 2 + 50 files changed, 1924 insertions(+), 604 deletions(-) create mode 100644 hapi-fhir-base-client/pom.xml create mode 100644 hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpClient.java create mode 100644 hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpRequestBase.java create mode 100644 hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpResponse.java create mode 100644 hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheRestfulClientFactory.java rename {hapi-fhir-base => hapi-fhir-base-client}/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java (85%) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/IHttpRequestBase.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IHttpResponse.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpClient.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequestBase.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsRestfulClientFactory.java diff --git a/hapi-fhir-base-client/pom.xml b/hapi-fhir-base-client/pom.xml new file mode 100644 index 00000000000..5ae04609c01 --- /dev/null +++ b/hapi-fhir-base-client/pom.xml @@ -0,0 +1,147 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 1.4-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + hapi-fhir-base + jar + + http://jamesagnew.github.io/hapi-fhir/ + + HAPI FHIR - Core Client + + + + ${project.groupId} + hapi-fhir-base-core + ${project.version} + + + + javax.json + javax.json-api + + + org.glassfish + javax.json + + + + + org.codehaus.woodstox + woodstox-core-asl + + + + + org.thymeleaf + thymeleaf + true + + + + + org.ebaysf.web + cors-filter + true + + + + + + com.phloc + phloc-schematron + true + + + com.phloc + phloc-commons + true + + + + + + + org.apache.commons + commons-lang3 + + + commons-codec + commons-codec + + + commons-io + commons-io + + + + + org.slf4j + slf4j-api + + + org.slf4j + jcl-over-slf4j + + + ch.qos.logback + logback-classic + true + + + + + org.apache.httpcomponents + httpclient + + + commons-logging + commons-logging + + + + + org.apache.httpcomponents + httpcore + + + + + + javax.servlet + javax.servlet-api + provided + + + + + + + + src/main/resources + true + + + + + diff --git a/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpClient.java b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpClient.java new file mode 100644 index 00000000000..2f57dac01c6 --- /dev/null +++ b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpClient.java @@ -0,0 +1,205 @@ +package ca.uhn.fhir.rest.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.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.http.Header; +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.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.IHttpClient; +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 { + + HttpClient myClient; + /** + * Get the myClient + * @return the myClient + */ + public HttpClient getMyClient() { + return myClient; + } + + private List
myHeaders; + private StringBuilder myUrl; + private Map> myIfNoneExistParams; + private String myIfNoneExistString; + private RequestTypeEnum myRequestType; + + public ApacheHttpClient(HttpClient myClient, StringBuilder url, Map> myIfNoneExistParams, String myIfNoneExistString, EncodingEnum theEncoding, RequestTypeEnum theRequestType, List
theHeaders) { + this.myHeaders = theHeaders; + this.myClient = myClient; + this.myUrl = url; + this.myIfNoneExistParams = myIfNoneExistParams; + this.myIfNoneExistString = myIfNoneExistString; + this.myRequestType = theRequestType; + } + + @Override + public IHttpRequestBase createByteRequest(String contents, String contentType, EncodingEnum encoding) { + /* + * 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(contents.getBytes(Constants.CHARSET_UTF8)); + HttpRequestBase retVal = createRequest(myUrl, entity); + addHeadersToRequest(retVal, encoding); + addMatchHeaders(retVal, myUrl); + retVal.addHeader(Constants.HEADER_CONTENT_TYPE, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); + return new ApacheHttpRequestBase(myClient, retVal); + } + + @Override + public IHttpRequestBase createParamRequest(Map> myParams, EncodingEnum encoding) { + 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 { + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "UTF-8"); + HttpRequestBase retVal = createRequest(myUrl, entity); + addMatchHeaders(retVal, myUrl); + return new ApacheHttpRequestBase(myClient, retVal); + } catch (UnsupportedEncodingException e) { + throw new InternalErrorException("Server does not support UTF-8 (should not happen)", e); + } + } + + @Override + public IHttpRequestBase createBinaryRequest(IBaseBinary binary) { + /* + * 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()); + entity.setContentType(binary.getContentType()); + HttpRequestBase retVal = createRequest(myUrl, entity); + addMatchHeaders(retVal, myUrl); + return new ApacheHttpRequestBase(myClient, retVal); + } + + @Override + public IHttpRequestBase createGetRequest(EncodingEnum theEncoding) { + HttpRequestBase retVal = createRequest(myUrl, null); + addHeadersToRequest(retVal, theEncoding); + addMatchHeaders(retVal, myUrl); + return new ApacheHttpRequestBase(myClient, retVal); + } + + public void addHeadersToRequest(HttpRequestBase 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 void addMatchHeaders(HttpRequestBase theHttpRequest, StringBuilder theUrlBase) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + BaseHttpClientInvocation.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()); + } + } + + 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 createRequest(StringBuilder url2, HttpEntity entity) { + String uri = url2.toString(); + switch(myRequestType) { + case DELETE : + return new HttpDelete(uri); + case OPTIONS : + return new HttpOptions(uri); + case POST : + HttpPost httpPost = new HttpPost(uri); + httpPost.setEntity(entity); + return httpPost; + case PUT : + HttpPut httpPut = new HttpPut(uri); + httpPut.setEntity(entity); + return httpPut; + case GET : + default: + return new HttpGet(uri); + } + } + +} diff --git a/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpRequestBase.java b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpRequestBase.java new file mode 100644 index 00000000000..0242358b7ee --- /dev/null +++ b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpRequestBase.java @@ -0,0 +1,84 @@ +package ca.uhn.fhir.rest.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.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.http.Header; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; + +import ca.uhn.fhir.rest.api.IHttpRequestBase; + +/** + * 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 ApacheHttpRequestBase implements IHttpRequestBase { + + private HttpRequestBase myRequest; + private HttpClient myClient; + + public ApacheHttpRequestBase(HttpClient theClient, HttpRequestBase theApacheRequest) { + this.myClient = theClient; + this.myRequest = theApacheRequest; + } + + @Override + public void addHeader(String theName, String theValue) { + getApacheRequest().addHeader(theName, theValue); + } + + /** + * Get the myApacheRequest + * @return the myApacheRequest + */ + public HttpRequestBase getApacheRequest() { + return myRequest; + } + + @Override + public IHttpResponse execute() throws IOException { + return new ApacheHttpResponse(myClient.execute(getApacheRequest())); + } + + @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(); + } + +} diff --git a/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpResponse.java b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpResponse.java new file mode 100644 index 00000000000..6c3e0640319 --- /dev/null +++ b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheHttpResponse.java @@ -0,0 +1,159 @@ +package ca.uhn.fhir.rest.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.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.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 UnsupportedOperationException, 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-client/src/main/java/ca/uhn/fhir/rest/client/ApacheRestfulClientFactory.java b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheRestfulClientFactory.java new file mode 100644 index 00000000000..0e70e6f6d58 --- /dev/null +++ b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/ApacheRestfulClientFactory.java @@ -0,0 +1,147 @@ +package ca.uhn.fhir.rest.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.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +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.api.IHttpClient; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.EncodingEnum; + +/** + * A Restful Factory to create clients, requests and responses based on Apache. + * @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 theFhirContext + * The context + */ + public ApacheRestfulClientFactory(FhirContext theFhirContext) { + super(theFhirContext); + } + + public synchronized HttpClient getNativeHttpClient() { + if (myHttpClient == null) { + + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); + + //@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; + } + + @Override + public ClientInvocationHandler newInvocationHandler(IHttpClient theClient, String myUrlBase, Map myMethodToReturnValue, Map> myBindings, Map myMethodToLambda) { + ApacheHttpClient apacheHttpClient = (ApacheHttpClient) theClient; + return new ClientInvocationHandler(apacheHttpClient, myContext, myUrlBase.toString(), myMethodToReturnValue, myBindings, myMethodToLambda, this); + } + + @Override + public IHttpClient getHttpClient(StringBuilder url, Map> myIfNoneExistParams, String myIfNoneExistString, + EncodingEnum theEncoding, RequestTypeEnum theRequestType, List
myHeaders) { + return new ApacheHttpClient(getNativeHttpClient(), url, myIfNoneExistParams, myIfNoneExistString, theEncoding, theRequestType, myHeaders); + } + + @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, 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-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/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-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java index c5b7803c647..58b0b76fc61 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java +++ b/hapi-fhir-base-client/src/main/java/ca/uhn/fhir/rest/client/interceptor/GZipContentInterceptor.java @@ -26,11 +26,13 @@ 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.api.IHttpRequestBase; +import ca.uhn.fhir.rest.client.ApacheHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.IHttpResponse; import ca.uhn.fhir.rest.server.Constants; /** @@ -42,7 +44,9 @@ 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(IHttpRequestBase theRequestInterface) { + HttpRequestBase theRequest = ((ApacheHttpRequestBase) theRequestInterface).getApacheRequest(); + if (theRequest instanceof HttpEntityEnclosingRequest) { Header[] encodingHeaders = theRequest.getHeaders(Constants.HEADER_CONTENT_ENCODING); if (encodingHeaders == null || encodingHeaders.length == 0) { @@ -69,7 +73,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/pom.xml b/hapi-fhir-base/pom.xml index adc58172609..db5211a5d03 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -9,7 +9,7 @@ ../hapi-deployable-pom/pom.xml - hapi-fhir-base + hapi-fhir-base-core jar http://jamesagnew.github.io/hapi-fhir/ 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 cf00fcf84d5..ef863012153 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 @@ -294,9 +294,26 @@ 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. The default method will create this based upon the apache restful client factory + * @return the factory used to create the restful clients + */ public IRestfulClientFactory getRestfulClientFactory() { if (myRestfulClientFactory == null) { - myRestfulClientFactory = new RestfulClientFactory(this); + try { + myRestfulClientFactory = (IRestfulClientFactory) Class.forName("ca.uhn.fhir.rest.client.ApacheRestfulClientFactory").getConstructor(getClass()).newInstance(this); + } + catch (Exception e) { + throw new RuntimeException("Could not create the RestfulClientFactory", e); + } } return myRestfulClientFactory; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/IHttpRequestBase.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/IHttpRequestBase.java new file mode 100644 index 00000000000..b7b0e0b8df1 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/IHttpRequestBase.java @@ -0,0 +1,53 @@ +package ca.uhn.fhir.rest.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; + +import ca.uhn.fhir.rest.client.IHttpResponse; + +/** + * Http Base Request. Allows addition of headers and execution of the request. + */ +public interface IHttpRequestBase { + + /** + * 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(); + +} 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 ab6cfa99033..d61a3277954 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 @@ -2,6 +2,10 @@ package ca.uhn.fhir.rest.client; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + /* * #%L * HAPI FHIR - Core Library @@ -22,13 +26,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; * #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.Collections; import java.util.HashMap; @@ -40,14 +39,8 @@ 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; @@ -61,7 +54,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.IHttpRequestBase; 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.client.exceptions.FhirClientConnectionException; import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException; @@ -79,19 +74,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 +125,7 @@ public abstract class BaseClient implements IRestfulClient { * {@inheritDoc} */ @Override - public HttpClient getHttpClient() { + public IHttpClient getHttpClient() { return myClient; } @@ -141,7 +136,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,35 +188,35 @@ 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; + IHttpRequestBase 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); @@ -235,50 +230,31 @@ public abstract class BaseClient implements IRestfulClient { } } } - for (IClientInterceptor nextInterceptor : myInterceptors) { - nextInterceptor.interceptRequest(httpRequest); + nextInterceptor.interceptRequest(httpRequest); + } + + response = httpRequest.execute(); + + for (IClientInterceptor nextInterceptor : myInterceptors) { + nextInterceptor.interceptResponse(response); } - response = myClient.execute(httpRequest); - - 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 +262,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 +285,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 +297,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 +306,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 +315,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 +331,23 @@ 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 +368,14 @@ 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 { @@ -406,7 +384,7 @@ public abstract class BaseClient implements IRestfulClient { } else { ourLog.trace("FHIR response:\n{}\n{}", response, responseString); } - } + } @Override public void registerInterceptor(IClientInterceptor theInterceptor) { @@ -444,7 +422,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 +455,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 b2487cf9c3f..66ff3ab9b40 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 @@ -28,16 +28,23 @@ 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.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.server.EncodingEnum; -import ca.uhn.fhir.util.VersionUtil; public abstract class BaseHttpClientInvocation { - private List
myHeaders; + protected List
myHeaders; + private final FhirContext myContext; + + public BaseHttpClientInvocation(FhirContext myContext) { + this.myContext = myContext; + } + public void addHeader(String theName, String theValue) { if (myHeaders == null) { @@ -57,15 +64,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 IHttpRequestBase 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,24 +93,25 @@ public abstract class BaseHttpClientInvocation { } } - public void addHeadersToRequest(HttpRequestBase theHttpRequest, EncodingEnum theEncoding) { - if (myHeaders != null) { - for (Header next : myHeaders) { - theHttpRequest.addHeader(next); - } - } - - 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); - } + /** + * Get thre restful client factory + * @return + */ + public IRestfulClientFactory getRestfulClientFactory() { + return myContext.getRestfulClientFactory(); + } + + public IHttpRequestBase createHttpRequest(String theUrl, EncodingEnum theEncoding, RequestTypeEnum theRequestType) { + IHttpClient httpClient = getRestfulClientFactory().getHttpClient(new StringBuilder(theUrl), null, null, theEncoding, theRequestType, myHeaders); + return httpClient.createGetRequest(theEncoding); + } + + /** + * Get the myContext + * @return the myContext + */ + public FhirContext getContext() { + 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 cedcbe24e59..39da118efeb 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 bb910f4d913..911260c7f00 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,29 +24,27 @@ 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 FhirContext myContext; + private final IHttpClient myClient; 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; try { myMethodToReturnValue.put(theClientType.getMethod("getFhirContext"), theContext); @@ -72,10 +70,10 @@ class ClientInvocationHandlerFactory { } ClientInvocationHandler newInvocationHandler(RestfulClientFactory theRestfulClientFactory) { - return new ClientInvocationHandler(myClient, myContext, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda, theRestfulClientFactory); + return theRestfulClientFactory.newInvocationHandler(myClient, myUrlBase, myMethodToReturnValue, myBindings, myMethodToLambda); } - 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 04f7fcbd25d..03c546662a5 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; @@ -69,9 +67,11 @@ import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.IHttpRequestBase; 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.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 IHttpRequestBase 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 IHttpRequestBase 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(IHttpRequestBase 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 21425efde76..6f2d9792010 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,18 @@ 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.api.IHttpRequestBase; public interface IClientInterceptor { /** * Fired by the client just before invoking the HTTP client request */ - void interceptRequest(HttpRequestBase theRequest); + void interceptRequest(IHttpRequestBase 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/IHttpResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IHttpResponse.java new file mode 100644 index 00000000000..dac704dda64 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IHttpResponse.java @@ -0,0 +1,109 @@ +package ca.uhn.fhir.rest.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.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.List; +import java.util.Map; + +public interface IHttpResponse { + + /** + * @return the response status number + */ + public int getStatus(); + + /** + * @return the raw response underlying response object + */ + Object getResponse(); + + /** + * @return the response mime type + */ + public String getMimeType(); + + /** + * @return all the headers + */ + public Map> getAllHeaders(); + + /** + * @return the status info + */ + public String getStatusInfo(); + + /** + * @return + * @throws IOException + */ + public Reader createReader() throws IOException; + + /** + * Read the entity as inputstream + * @return the inputstream + * @throws IOException + */ + 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 and automatically + * closed as part of the operation and the method returns {@code true}. + *

+ *

+ * 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 + * {@code false}. + *

+ *

+ * 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. Also, the result returned by the {@code bufferEntity()} + * method is consistent across all invocations of the method on the same + * {@code Response} instance. + *

+ *

+ * Buffering the message entity data allows for multiple invocations of + * {@code readEntity(...)} methods on the response instance. Note however, that + * once the response instance itself is {@link #close() closed}, the implementations + * are expected to release the buffered message entity data too. Therefore any subsequent + * attempts to read a message entity stream on such closed response will result in an + * {@link IllegalStateException} being thrown. + *

+ * @throws IOException + * @throws IllegalStateException in case the response has been {@link #close() closed}. + */ + void bufferEntitity() 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 6af4fed94a9..30a2a3f9b1c 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 @@ -1,5 +1,11 @@ package ca.uhn.fhir.rest.client; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; + +import org.apache.http.Header; + /* * #%L * HAPI FHIR - Core Library @@ -23,7 +29,11 @@ package ca.uhn.fhir.rest.client; import org.apache.http.client.HttpClient; import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +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; public interface IRestfulClientFactory { @@ -70,7 +80,7 @@ public interface IRestfulClientFactory { * * @see #setHttpClient(HttpClient) */ - HttpClient getHttpClient(); + IHttpClient getHttpClient(StringBuilder url, Map> myIfNoneExistParams, String myIfNoneExistString, EncodingEnum theEncoding, RequestTypeEnum theRequestType, List
myHeaders); /** * @deprecated Use {@link #getServerValidationMode()} instead @@ -120,6 +130,19 @@ public interface IRestfulClientFactory { * @return A newly created client */ IGenericClient newGenericClient(String theServerBase); + + /** + * Instantiates a new client invocation handler + * @param theClient + * the client which will invoke the call + * @param myUrlBase + * the url base + * @param myMethodToReturnValue + * @param myBindings + * @param myMethodToLambda + * @return a newly created client invocation handler + */ + ClientInvocationHandler newInvocationHandler(IHttpClient theClient, String myUrlBase, Map myMethodToReturnValue, Map> myBindings, Map myMethodToLambda); /** * Sets the connection request timeout, in milliseconds. This is the amount of time that the HTTPClient connection @@ -146,7 +169,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 @@ -193,4 +216,8 @@ public interface IRestfulClientFactory { */ void setSocketTimeout(int theSocketTimeout); + void validateServerBase(String theServerBase, IHttpClient theHttpClient, BaseClient theClient); + + 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 83643ba70c4..8d2e30648c4 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()); + protected int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; + protected int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT; + protected FhirContext myContext; + protected Map, ClientInvocationHandlerFactory> myInvocationHandlers = new HashMap, ClientInvocationHandlerFactory>(); + protected ServerValidationModeEnum myServerValidationMode = DEFAULT_SERVER_VALIDATION_MODE; + protected int mySocketTimeout = DEFAULT_SOCKET_TIMEOUT; + protected Set myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet()); + protected String myProxyUsername; + protected String myProxyPassword; /** * Constructor @@ -95,44 +84,6 @@ public class RestfulClientFactory implements IRestfulClientFactory { return myConnectTimeout; } - @Override - public synchronized HttpClient getHttpClient() { - if (myHttpClient == null) { - - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); - - //@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; - } - - private String myProxyUsername; - private String myProxyPassword; - @Override public void setProxyCredentials(String theUsername, String thePassword) { myProxyUsername=theUsername; @@ -175,7 +126,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); @@ -191,14 +142,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) { @@ -224,13 +173,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(); } /** @@ -243,27 +192,6 @@ 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 - */ - @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; - } - } - @Override public void setServerValidationMode(ServerValidationModeEnum theServerValidationMode) { Validate.notNull(theServerValidationMode, "theServerValidationMode may not be null"); @@ -273,11 +201,11 @@ public class RestfulClientFactory implements IRestfulClientFactory { @Override public synchronized void setSocketTimeout(int theSocketTimeout) { mySocketTimeout = theSocketTimeout; - myHttpClient = null; + resetHttpClient(); } - @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()); @@ -337,5 +265,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/api/IHttpClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java new file mode 100644 index 00000000000..894794a33bf --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/api/IHttpClient.java @@ -0,0 +1,67 @@ +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.api.IHttpRequestBase; +import ca.uhn.fhir.rest.server.EncodingEnum; + +/** + * A HTTP Client + */ +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 + */ + IHttpRequestBase 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 + */ + IHttpRequestBase createParamRequest(Map> theParams, EncodingEnum theEncoding); + + /** + * Create a binary request + * @param theBinary the binary + * @return the http request to be executed + */ + IHttpRequestBase createBinaryRequest(IBaseBinary theBinary); + + /** + * Create a normal http get request + * @param theEncoding the request encoding + * @return the http request to be executed + */ + IHttpRequestBase createGetRequest(EncodingEnum theEncoding); + +} 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 428f0622044..89cd9bb8fd9 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 752d60aa8b8..28d158d2775 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.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.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(IHttpRequestBase 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 29dcf899e78..6401838268b 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.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.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(IHttpRequestBase 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 6e19a4d01d5..e270ef20205 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.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.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 IHttpRequestBase 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 IHttpRequestBase getLastRequest() { return myLastRequest; } - public HttpResponse getLastResponse() { + public IHttpResponse getLastResponse() { return myLastResponse; } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequestBase 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 2180c19be45..1b596b45e8f 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.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.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(IHttpRequestBase 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 740957b517f..425ccd20fe3 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 @@ -24,6 +24,10 @@ 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 java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.Validate; @@ -35,7 +39,9 @@ import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.HttpEntityWrapper; import org.slf4j.Logger; +import ca.uhn.fhir.rest.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.IHttpResponse; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; public class LoggingInterceptor implements IClientInterceptor { @@ -74,20 +80,13 @@ public class LoggingInterceptor implements IClientInterceptor { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequestBase 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 = getHeaderString(theRequest.getAllHeaders()); myLog.info("Client request headers:\n{}", b.toString()); } @@ -110,23 +109,14 @@ public class LoggingInterceptor implements IClientInterceptor { } @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 = getHeaderString(theResponse.getAllHeaders()); // if (theResponse.getEntity() != null && theResponse.getEntity().getContentEncoding() != null) { // Header next = theResponse.getEntity().getContentEncoding(); // b.append(next.getName() + ": " + next.getValue()); @@ -143,23 +133,41 @@ public class LoggingInterceptor implements IClientInterceptor { } if (myLogResponseBody) { - HttpEntity respEntity = theResponse.getEntity(); + theResponse.bufferEntitity(); + InputStream respEntity = theResponse.readEntity(); 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)); + 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)"); } } } + private StringBuilder getHeaderString(Map> allHeaders) { + StringBuilder b = new StringBuilder(); + if (allHeaders != null && !allHeaders.isEmpty()) { + Iterator nameEntries = allHeaders.keySet().iterator(); + while(nameEntries.hasNext()) { + String key = nameEntries.next(); + Iterator values = allHeaders.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 +222,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 3ce4d21ad3f..a8d4c24c1db 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.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.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(IHttpRequestBase 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/BaseHttpClientInvocationWithContents.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java index 19a455cd382..ca34d47cb2e 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 @@ -20,19 +20,11 @@ package ca.uhn.fhir.rest.method; * #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.apache.http.Header; import org.hl7.fhir.instance.model.api.IBaseBinary; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -44,10 +36,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.api.IHttpRequestBase; +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 @@ -59,7 +52,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; @@ -70,7 +62,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; @@ -81,7 +73,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, IBaseResource theResource, Map> theParams, String... theUrlPath) { - myContext = theContext; + super(theContext); myResource = theResource; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -94,8 +86,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; @@ -106,7 +97,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, List theResources, BundleTypeEnum theBundleType) { - myContext = theContext; + super(theContext); myResource = null; myTagList = null; myUrlPath = null; @@ -117,7 +108,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, Map> theParams, String... theUrlPath) { - myContext = theContext; + super(theContext); myResource = null; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -130,7 +121,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, boolean theIsBundle, String theUrlPath) { - myContext = theContext; + super(theContext); myResource = null; myTagList = null; myUrlPath = theUrlPath; @@ -142,7 +133,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca } public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, Map> theParams, String... theUrlPath) { - myContext = theContext; + super(theContext); myResource = null; myTagList = null; myUrlPath = StringUtils.join(theUrlPath, '/'); @@ -155,13 +146,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; @@ -171,153 +161,76 @@ 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 { - StringBuilder url = new StringBuilder(); - - if (myUrlPath == null) { - url.append(theUrlBase); + public IHttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) throws DataFormatException { + IHttpClient httpClient = getHttpClient(theUrlBase, theEncoding, theExtraParams, myHeaders); + if (myResource != null && IBaseBinary.class.isAssignableFrom(myResource.getClass())) { + return httpClient.createBinaryRequest((IBaseBinary) myResource); } else { - if (!myUrlPath.contains("://")) { - url.append(theUrlBase); - if (!theUrlBase.endsWith("/")) { - url.append('/'); - } - } - url.append(myUrlPath); - } + EncodingEnum encoding = theEncoding; + if (myContents != null) { + encoding = MethodUtil.detectEncoding(myContents); + } - appendExtraParamsWithQuestionMark(theExtraParams, url, url.indexOf("?") == -1); - - if (myResource != null && IBaseBinary.class.isAssignableFrom(myResource.getClass())) { - IBaseBinary binary = (IBaseBinary) myResource; - - /* - * 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()); - entity.setContentType(binary.getContentType()); - HttpRequestBase retVal = createRequest(url, entity); - addMatchHeaders(retVal, url); - return retVal; - } - - IParser parser; - String contentType; - EncodingEnum encoding = null; - encoding = theEncoding; - - if (myContents != null) { - encoding = MethodUtil.detectEncoding(myContents); - } - - if (encoding == EncodingEnum.JSON) { - parser = myContext.newJsonParser(); - } else { - encoding = EncodingEnum.XML; - parser = myContext.newXmlParser(); - } - - if (thePrettyPrint != null) { - parser.setPrettyPrint(thePrettyPrint); - } - - 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(); - } - } else { - contents = parser.encodeResourceToString(myResource); - contentType = encoding.getResourceContentType(); - } - - /* - * 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)); - } - - 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; + if (encoding == null) { + encoding = EncodingEnum.XML; + } + + + if (myParams != null) { + return httpClient.createParamRequest(myParams, encoding); + } else { + String contents = parseContents(thePrettyPrint, encoding); + String contentType = getContentType(encoding); + return httpClient.createByteRequest(contents, contentType, encoding); + } + } } - protected abstract HttpRequestBase createRequest(StringBuilder theUrl, AbstractHttpEntity theEntity); + private String getContentType(EncodingEnum encoding) { + if(myBundle != null || + (getContext().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 = getContext().newJsonParser(); + } else { + parser = getContext().newXmlParser(); + } + + if (thePrettyPrint != null) { + parser.setPrettyPrint(thePrettyPrint); + } + + parser.setOmitResourceId(myOmitResourceId); + if (myTagList != null) { + return parser.encodeTagListToString(myTagList); + } else if (myBundle != null) { + return parser.encodeBundleToString(myBundle); + } else if (myResources != null) { + IVersionSpecificBundleFactory bundleFactory = getContext().newBundleFactory(); + bundleFactory.initializeBundleFromResourceList("", myResources, "", "", myResources.size(), myBundleType); + Bundle bundle = bundleFactory.getDstu1Bundle(); + if (bundle != null) { + return parser.encodeBundleToString(bundle); + } else { + IBaseResource bundleRes = bundleFactory.getResourceBundle(); + return parser.encodeResourceToString(bundleRes); + } + } else if (myContents != null) { + return myContents; + } else { + return parser.encodeResourceToString(myResource); + } + } - 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; } @@ -329,5 +242,27 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca public void setOmitResourceId(boolean theOmitResourceId) { myOmitResourceId = theOmitResourceId; } + + public IHttpClient getHttpClient(String theUrlBase, EncodingEnum theEncoding, Map> theExtraParams, List
myHeaders) { + //TODO move this to the factory + StringBuilder url = new StringBuilder(); + + if (myUrlPath == null) { + url.append(theUrlBase); + } else { + if (!myUrlPath.contains("://")) { + url.append(theUrlBase); + if (!theUrlBase.endsWith("/")) { + url.append('/'); + } + } + url.append(myUrlPath); + } + + appendExtraParamsWithQuestionMark(theExtraParams, url, url.indexOf("?") == -1); + return getRestfulClientFactory().getHttpClient(url, myIfNoneExistParams, myIfNoneExistString, theEncoding, getRequestType(), myHeaders); + } + + 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 7fa97abbc71..dd708a4c6f6 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 @@ -59,7 +59,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 2ea31baeed2..3b1ccdf0d0b 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/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index 6eb4e6e48df..3a4e16f7f13 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 @@ -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 d6a963cb9a3..528dedaa7d7 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 @@ -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 theFhirContext, 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(theFhirContext, 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 1e014f44da8..1dac697bddb 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,10 +23,11 @@ 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.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.EncodingEnum; @@ -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 IHttpRequestBase 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 de669820a9d..f20c88941f4 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,9 +28,11 @@ 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.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; 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 IHttpRequestBase 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 2c149900698..1c52bb733f6 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 2e2f3e363ed..ddf8b4f4c0d 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 1176fc21ffd..e327129ec81 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 @@ -26,6 +26,9 @@ 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.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.EncodingEnum; @@ -33,15 +36,14 @@ 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 IHttpRequestBase 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 1aa2bc303bb..a87a13d71b4 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 @@ -146,8 +146,8 @@ public class MethodUtil { return value; } - public static HttpGetClientInvocation createConformanceInvocation() { - return new HttpGetClientInvocation("metadata"); + public static HttpGetClientInvocation createConformanceInvocation(FhirContext theFhirContext) { + return new HttpGetClientInvocation(theFhirContext, "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 1cbd499c6aa..539e7b474f8 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 @@ -334,7 +334,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 8df59cd8b7e..2fbcb1ee9f9 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/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index 203aa0ad5a6..c5bef1d29a2 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-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..b00c489e009 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpClient.java @@ -0,0 +1,156 @@ +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.apache.http.Header; +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.rest.api.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; +import ca.uhn.fhir.rest.client.api.IHttpClient; +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 myClient, StringBuilder url, Map> myIfNoneExistParams, String myIfNoneExistString, EncodingEnum theEncoding, RequestTypeEnum theRequestType, List
theHeaders) { + this.myHeaders = theHeaders; + this.myClient = myClient; + this.myUrl = url; + this.myIfNoneExistParams = myIfNoneExistParams; + this.myIfNoneExistString = myIfNoneExistString; + this.myRequestType = theRequestType; + } + + @Override + public IHttpRequestBase createByteRequest(String contents, String contentType, EncodingEnum encoding) { + Builder retVal = createRequest(); + addHeadersToRequest(retVal, encoding); + addMatchHeaders(retVal, myUrl); + Entity entity = Entity.entity(contents, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); + retVal.header(Constants.HEADER_CONTENT_TYPE, contentType + Constants.HEADER_SUFFIX_CT_UTF_8); + return new JaxRsHttpRequestBase(retVal, myRequestType, entity); + } + + @Override + public IHttpRequestBase createParamRequest(Map> myParams, EncodingEnum encoding) { + MultivaluedMap map = new MultivaluedHashMap(); + for (Map.Entry> nextParam : myParams.entrySet()) { + List value = nextParam.getValue(); + for (String s : value) { + map.add(nextParam.getKey(), s); + } + } + Builder retVal = createRequest(); + //addHeadersToRequest(retVal, encoding); + addMatchHeaders(retVal, myUrl); + Entity
entity = Entity.form(map); + return new JaxRsHttpRequestBase(retVal, myRequestType, entity); + } + + @Override + public IHttpRequestBase createBinaryRequest(IBaseBinary binary) { + Entity entity = Entity.entity(binary.getContentAsBase64(), binary.getContentType()); + Builder retVal = createRequest(); + addMatchHeaders(retVal, myUrl); + return new JaxRsHttpRequestBase(retVal, myRequestType, entity); + } + + @Override + public IHttpRequestBase createGetRequest(EncodingEnum theEncoding) { + Builder builder = createRequest(); + addHeadersToRequest(builder, theEncoding); + addMatchHeaders(builder, myUrl); + return new JaxRsHttpRequestBase(builder, myRequestType, null); + } + + public void addHeadersToRequest(Builder builder, EncodingEnum theEncoding) { + if (myHeaders != null) { + for (Header next : myHeaders) { + builder.header(next.getName(), next.getValue()); + } + } + + builder.header("User-Agent", "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client)"); + builder.header("Accept-Charset", "utf-8"); + builder.acceptEncoding("gzip"); + + if (theEncoding == null) { + builder.accept(Constants.HEADER_ACCEPT_VALUE_ALL); + } else if (theEncoding == EncodingEnum.JSON) { + builder.accept(Constants.CT_FHIR_JSON); + } else if (theEncoding == EncodingEnum.XML) { + builder.accept(Constants.CT_FHIR_XML); + } + } + + private void addMatchHeaders(Builder theHttpRequest, StringBuilder theUrlBase) { + if (myIfNoneExistParams != null) { + StringBuilder b = newHeaderBuilder(theUrlBase); + BaseHttpClientInvocation.appendExtraParamsWithQuestionMark(myIfNoneExistParams, b, b.indexOf("?") == -1); + theHttpRequest.header(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.header(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 Builder createRequest() { + return myClient.target(myUrl.toString()).request(); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequestBase.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequestBase.java new file mode 100644 index 00000000000..4f1ec39f254 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpRequestBase.java @@ -0,0 +1,95 @@ +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 ca.uhn.fhir.rest.api.IHttpRequestBase; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.client.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 JaxRsHttpRequestBase implements IHttpRequestBase { + + private Invocation.Builder myRequest; + private RequestTypeEnum myRequestType; + private Entity myEntity; + private Map> myHeaders = new HashMap>(); + + public JaxRsHttpRequestBase(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() { + return new JaxRsHttpResponse(getRequest().build(getRequestType().name(), getEntity()).invoke()); + } + + @Override + public Map> getAllHeaders() { + return this.myHeaders; + } + +} 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..4bb56848c69 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsHttpResponse.java @@ -0,0 +1,113 @@ +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.IOException; +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.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.apache.http.entity.ContentType; + +import ca.uhn.fhir.rest.client.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() { + ContentType ct = ContentType.parse(myResponse.getHeaderString(HttpHeaders.CONTENT_TYPE)); + return ct != null ? ct.getMimeType() : null; + } + + @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() throws UnsupportedOperationException, IOException { + 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(myResponse.hasEntity()) { + myBufferedEntity = true; + myResponse.bufferEntity(); + } else { + myResponse.bufferEntity(); + } + } + + + @Override + public void close() { + // do nothing + } + + +} 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..f2e2b81318b --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/client/JaxRsRestfulClientFactory.java @@ -0,0 +1,108 @@ +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.ClientInvocationHandler; +import ca.uhn.fhir.rest.client.ClientInvocationHandlerFactory; +import ca.uhn.fhir.rest.client.RestfulClientFactory; +import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.EncodingEnum; +import org.apache.http.Header; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import java.lang.reflect.Method; +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 JaxRsHttpClient myHttpClient; + 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 ClientInvocationHandler newInvocationHandler(IHttpClient theClient, String myUrlBase, Map myMethodToReturnValue, Map> myBindings, Map myMethodToLambda) { + return new ClientInvocationHandler(myHttpClient, myContext, myUrlBase.toString(), myMethodToReturnValue, myBindings, myMethodToLambda, this); + } + + @Override + public IHttpClient getHttpClient(StringBuilder url, Map> myIfNoneExistParams, String myIfNoneExistString, + EncodingEnum theEncoding, RequestTypeEnum theRequestType, List
myHeaders) { + return new JaxRsHttpClient(getNativeClientClient(), url, myIfNoneExistParams, myIfNoneExistString, theEncoding, theRequestType, myHeaders); + } + + @Override + public void setProxy(String theHost, Integer thePort) { + throw new UnsupportedOperationException("Proxy setting is 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, null); + } + + @Override + protected void resetHttpClient() { + this.myHttpClient = 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..7ed27bc4383 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; @@ -106,6 +107,7 @@ public class AbstractJaxRsResourceProviderTest { ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + ourCtx.setRestfulClientFactory(new JaxRsRestfulClientFactory(ourCtx)); serverBase = "http://localhost:" + ourPort + "/"; client = ourCtx.newRestfulGenericClient(serverBase); client.setEncoding(EncodingEnum.JSON); 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..d89ba48c0d4 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; @@ -150,7 +152,7 @@ public class ClientDstu1Test { MethodOutcome response = client.createPatient(patient); - assertEquals(interceptor.getLastRequest().getURI().toASCIIString(), "http://foo/Patient"); + assertEquals(((ApacheHttpRequestBase) 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-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 ada85b3920d..db13f056d28 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 @@ -48,8 +48,10 @@ import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Conformance.Rest; import ca.uhn.fhir.model.primitive.DecimalDt; import ca.uhn.fhir.narrative.INarrativeGenerator; +import ca.uhn.fhir.rest.api.IHttpRequestBase; import ca.uhn.fhir.rest.client.GenericClient; import ca.uhn.fhir.rest.client.IClientInterceptor; +import ca.uhn.fhir.rest.client.IHttpResponse; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.to.model.HomeRequest; @@ -288,7 +290,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()); } @@ -647,17 +649,17 @@ public class BaseController { } @Override - public void interceptRequest(HttpRequestBase theRequest) { + public void interceptRequest(IHttpRequestBase 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 { @@ -667,7 +669,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..4a854c31f33 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 @@ -11,9 +11,11 @@ import org.springframework.web.bind.annotation.ModelAttribute; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.IHttpRequestBase; 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.IHttpResponse; import ca.uhn.fhir.rest.client.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy; @@ -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(IHttpRequestBase theRequest) { if (isNotBlank(remoteAddr)) { theRequest.addHeader("x-forwarded-for", remoteAddr); } diff --git a/pom.xml b/pom.xml index 9e71bfbfd1f..df172500968 100644 --- a/pom.xml +++ b/pom.xml @@ -1396,6 +1396,7 @@ SITE hapi-fhir-base + hapi-fhir-base-client hapi-fhir-structures-dstu hapi-fhir-structures-dstu2 hapi-fhir-structures-hl7org-dstu2 @@ -1432,6 +1433,7 @@ hapi-deployable-pom hapi-fhir-base + hapi-fhir-base-client hapi-fhir-base-test-mindeps-client hapi-fhir-base-test-mindeps-server