diff --git a/hapi-fhir-base/.classpath b/hapi-fhir-base/.classpath
index 550e3f2ae04..123df06e860 100644
--- a/hapi-fhir-base/.classpath
+++ b/hapi-fhir-base/.classpath
@@ -6,6 +6,7 @@
+
diff --git a/hapi-fhir-base/.settings/org.eclipse.wst.common.component b/hapi-fhir-base/.settings/org.eclipse.wst.common.component
index 1a94c8cf11d..30be1c9ba76 100644
--- a/hapi-fhir-base/.settings/org.eclipse.wst.common.component
+++ b/hapi-fhir-base/.settings/org.eclipse.wst.common.component
@@ -2,5 +2,6 @@
+
diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index 11efafb28c6..82f53182d9b 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -76,7 +76,7 @@
org.slf4jslf4j-api
- 1.7.6
+ ${slf4j_version}ch.qos.logback
diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml
index 31707633742..b04ebac4ec1 100644
--- a/hapi-fhir-base/src/changes/changes.xml
+++ b/hapi-fhir-base/src/changes/changes.xml
@@ -14,6 +14,19 @@
Search parameters are not properly escaped and unescaped. E.g. for a token parameter such as
"&identifier=system|codepart1\|codepart2"
+
+ Add support for OPTIONS verb (which returns the server conformance statement)
+
+
+ Add support for CORS headers in server
+
+
+ Bump SLF4j dependency to latest version (1.7.7)
+
+
+ Add interceptor framework for clients (annotation based and generic), and add interceptors
+ for configurable logging, capturing requests and responses, and HTTP basic auth.
+
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java
index 75c2afb2b8f..82a7d403e08 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/BaseResource.java
@@ -127,7 +127,7 @@ public abstract class BaseResource extends BaseElement implements IResource {
*/
@Override
protected boolean isBaseEmpty() {
- return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText);
+ return super.isBaseEmpty() && ElementUtil.isEmpty(myLanguage, myText, myId);
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 04f2c940c94..226cc89f696 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -179,7 +179,8 @@ public class JsonParser extends BaseParser implements IParser {
for (BundleEntry nextEntry : theBundle.getEntries()) {
eventWriter.writeStartObject();
- if (nextEntry.getDeletedAt() !=null&&nextEntry.getDeletedAt().isEmpty()==false) {
+ boolean deleted = nextEntry.getDeletedAt() !=null&&nextEntry.getDeletedAt().isEmpty()==false;
+ if (deleted) {
writeTagWithTextNode(eventWriter, "deleted", nextEntry.getDeletedAt());
}
writeTagWithTextNode(eventWriter, "title", nextEntry.getTitle());
@@ -210,7 +211,7 @@ public class JsonParser extends BaseParser implements IParser {
writeAuthor(nextEntry, eventWriter);
IResource resource = nextEntry.getResource();
- if (resource != null && !resource.isEmpty()) {
+ if (resource != null && !resource.isEmpty() && !deleted) {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(resource);
encodeResourceToJsonStreamWriter(resDef, resource, eventWriter, "content", false);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
index 704a1f7c0ef..ba5e38ea176 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
@@ -199,7 +199,7 @@ public class XmlParser extends BaseParser implements IParser {
}
IResource resource = nextEntry.getResource();
- if (resource != null && !resource.isEmpty()) {
+ if (resource != null && !resource.isEmpty() && !deleted) {
eventWriter.writeStartElement("content");
eventWriter.writeAttribute("type", "text/xml");
encodeResourceToXmlStreamWriter(resource, eventWriter, false);
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 9c0c9e82832..811a06d823e 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
@@ -27,6 +27,7 @@ import java.util.Map;
import org.apache.http.client.HttpClient;
+import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
@@ -51,6 +52,8 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
myMethodToLambda.put(theClientType.getMethod("setEncoding", EncodingEnum.class), new SetEncodingLambda());
myMethodToLambda.put(theClientType.getMethod("setPrettyPrint", boolean.class), new SetPrettyPrintLambda());
+ myMethodToLambda.put(theClientType.getMethod("registerInterceptor", IClientInterceptor.class), new RegisterInterceptorLambda());
+ myMethodToLambda.put(theClientType.getMethod("unregisterInterceptor", IClientInterceptor.class), new UnregisterInterceptorLambda());
} catch (NoSuchMethodException e) {
throw new ConfigurationException("Failed to find methods on client. This is a HAPI bug!", e);
@@ -105,5 +108,22 @@ public class ClientInvocationHandler extends BaseClient implements InvocationHan
return null;
}
}
+ private class UnregisterInterceptorLambda implements ILambda {
+ @Override
+ public Object handle(Object[] theArgs) {
+ IClientInterceptor interceptor = (IClientInterceptor) theArgs[0];
+ unregisterInterceptor(interceptor);
+ return null;
+ }
+ }
+
+ private class RegisterInterceptorLambda implements ILambda {
+ @Override
+ public Object handle(Object[] theArgs) {
+ IClientInterceptor interceptor = (IClientInterceptor) theArgs[0];
+ registerInterceptor(interceptor);
+ return null;
+ }
+ }
}
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 67e3588c867..d72c60f5fd2 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
@@ -26,12 +26,14 @@ import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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;
@@ -49,6 +51,8 @@ import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.gclient.IClientExecutable;
+import ca.uhn.fhir.rest.gclient.ICreate;
+import ca.uhn.fhir.rest.gclient.ICreateTyped;
import ca.uhn.fhir.rest.gclient.ICriterion;
import ca.uhn.fhir.rest.gclient.ICriterionInternal;
import ca.uhn.fhir.rest.gclient.IGetPage;
@@ -77,6 +81,7 @@ import ca.uhn.fhir.rest.method.ValidateMethodBinding;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class GenericClient extends BaseClient implements IGenericClient {
@@ -106,6 +111,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
+ @Override
+ public ICreate create() {
+ return new CreateInternal();
+ }
+
@Override
public MethodOutcome create(IResource theResource) {
BaseHttpClientInvocation invocation = CreateMethodBinding.createCreateInvocation(theResource, myContext);
@@ -150,11 +160,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return new GetTagsInternal();
}
- @Override
- public ITransaction transaction() {
- return new TransactionInternal();
- }
-
@Override
public Bundle history(final Class theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) {
String resourceName = theType != null ? toResourceName(theType) : null;
@@ -179,12 +184,17 @@ public class GenericClient extends BaseClient implements IGenericClient {
return myLogRequestAndResponse;
}
+ @Override
+ public IGetPage loadPage() {
+ return new LoadPageInternal();
+ }
+
@Override
public T read(final Class theType, IdDt theId) {
if (theId == null || theId.hasIdPart() == false) {
throw new IllegalArgumentException("theId does not contain a valid ID, is: " + theId);
}
-
+
HttpGetClientInvocation invocation = ReadMethodBinding.createReadInvocation(theId, toResourceName(theType));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
@@ -241,6 +251,15 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLogRequestAndResponse = theLogRequestAndResponse;
}
+ private String toResourceName(Class extends IResource> theType) {
+ return myContext.getResourceDefinition(theType).getName();
+ }
+
+ @Override
+ public ITransaction transaction() {
+ return new TransactionInternal();
+ }
+
@Override
public List transaction(List theResources) {
BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(theResources, myContext);
@@ -305,15 +324,18 @@ public class GenericClient extends BaseClient implements IGenericClient {
return vread(theType, new IdDt(theId), new IdDt(theVersionId));
}
- private String toResourceName(Class extends IResource> theType) {
- return myContext.getResourceDefinition(theType).getName();
- }
-
private abstract class BaseClientExecutable, Y> implements IClientExecutable {
private EncodingEnum myParamEncoding;
private Boolean myPrettyPrint;
private boolean myQueryLogRequestAndResponse;
+ protected void addParam(Map> params, String parameterName, String parameterValue) {
+ if (!params.containsKey(parameterName)) {
+ params.put(parameterName, new ArrayList());
+ }
+ params.get(parameterName).add(parameterValue);
+ }
+
@SuppressWarnings("unchecked")
@Override
public T andLogRequestAndResponse(boolean theLogRequestAndResponse) {
@@ -328,26 +350,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
+ @SuppressWarnings("unchecked")
@Override
public T encodedXml() {
myParamEncoding = EncodingEnum.XML;
return (T) this;
}
- @SuppressWarnings("unchecked")
- @Override
- public T prettyPrint() {
- myPrettyPrint = true;
- return (T) this;
- }
-
- protected void addParam(Map> params, String parameterName, String parameterValue) {
- if (!params.containsKey(parameterName)) {
- params.put(parameterName, new ArrayList());
- }
- params.get(parameterName).add(parameterValue);
- }
-
protected Z invoke(Map> theParams, IClientResponseHandler theHandler, BaseHttpClientInvocation theInvocation) {
if (myParamEncoding != null) {
theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
@@ -365,6 +374,13 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public T prettyPrint() {
+ myPrettyPrint = true;
+ return (T) this;
+ }
+
}
private final class BundleResponseHandler implements IClientResponseHandler {
@@ -376,7 +392,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
+ public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
+ BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@@ -385,19 +402,71 @@ public class GenericClient extends BaseClient implements IGenericClient {
return parser.parseBundle(myType, theResponseReader);
}
}
-
- private final class ResourceListResponseHandler implements IClientResponseHandler> {
- private Class extends IResource> myType;
+ private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped {
+
+ private String myId;
+ private IResource myResource;
+ private String myResourceBody;
+
+ @Override
+ public MethodOutcome execute() {
+ if (myResource == null) {
+ EncodingEnum encoding = null;
+ for (int i = 0; i < myResourceBody.length() && encoding == null; i++) {
+ switch (myResourceBody.charAt(i)) {
+ case '<':
+ encoding = EncodingEnum.XML;
+ break;
+ case '{':
+ encoding = EncodingEnum.JSON;
+ break;
+ }
+ }
+ if (encoding == null) {
+ throw new InvalidRequestException("FHIR client can't determine resource encoding");
+ }
+ myResource = encoding.newParser(myContext).parseResource(myResourceBody);
+ }
+
+ BaseHttpClientInvocation invocation = CreateMethodBinding.createCreateInvocation(myResource,myResourceBody, myId, myContext);
+
+ RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
+ final String resourceName = def.getName();
+
+ OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
+
+ Map> params = new HashMap>();
+ return invoke(params, binding, invocation);
- public ResourceListResponseHandler(Class extends IResource> theType) {
- myType = theType;
}
@Override
- public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
- return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
+ public ICreateTyped resource(IResource theResource) {
+ Validate.notNull(theResource, "Resource can not be null");
+ myResource = theResource;
+ return this;
}
+
+ @Override
+ public ICreateTyped resource(String theResourceBody) {
+ Validate.notBlank(theResourceBody, "Body can not be null or blank");
+ myResourceBody = theResourceBody;
+ return this;
+ }
+
+ @Override
+ public CreateInternal withId(IdDt theId) {
+ myId = theId.getIdPart();
+ return this;
+ }
+
+ @Override
+ public CreateInternal withId(String theId) {
+ myId = theId;
+ return this;
+ }
+
}
private class ForInternal extends BaseClientExecutable implements IQuery {
@@ -496,10 +565,31 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
+ private class GetPageInternal extends BaseClientExecutable implements IGetPageTyped {
+
+ private String myUrl;
+
+ public GetPageInternal(String theUrl) {
+ myUrl = theUrl;
+ }
+
+ @Override
+ public Bundle execute() {
+
+ BundleResponseHandler binding = new BundleResponseHandler(null);
+ HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl);
+
+ Map> params = null;
+ return invoke(params, binding, invocation);
+
+ }
+
+ }
+
private class GetTagsInternal extends BaseClientExecutable implements IGetTags {
- private String myResourceName;
private String myId;
+ private String myResourceName;
private String myVersionId;
@Override
@@ -537,14 +627,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
- private void setResourceClass(Class extends IResource> theClass) {
- if (theClass != null) {
- myResourceName = myContext.getResourceDefinition(theClass).getName();
- } else {
- myResourceName = null;
- }
- }
-
@Override
public IGetTags forResource(Class extends IResource> theClass, String theId) {
setResourceClass(theClass);
@@ -560,6 +642,33 @@ public class GenericClient extends BaseClient implements IGenericClient {
return this;
}
+ private void setResourceClass(Class extends IResource> theClass) {
+ if (theClass != null) {
+ myResourceName = myContext.getResourceDefinition(theClass).getName();
+ } else {
+ myResourceName = null;
+ }
+ }
+
+ }
+
+ private final class LoadPageInternal implements IGetPage {
+
+ @Override
+ public IGetPageTyped next(Bundle theBundle) {
+ return new GetPageInternal(theBundle.getLinkNext().getValue());
+ }
+
+ @Override
+ public IGetPageTyped previous(Bundle theBundle) {
+ return new GetPageInternal(theBundle.getLinkPrevious().getValue());
+ }
+
+ @Override
+ public IGetPageTyped url(String thePageUrl) {
+ return new GetPageInternal(thePageUrl);
+ }
+
}
private final class OutcomeResponseHandler implements IClientResponseHandler {
@@ -570,7 +679,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
- public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
+ public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
+ BaseServerResponseException {
MethodOutcome response = BaseOutcomeReturningMethodBinding.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
@@ -595,14 +705,29 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
+ private final class ResourceListResponseHandler implements IClientResponseHandler> {
+
+ private Class extends IResource> myType;
+
+ public ResourceListResponseHandler(Class extends IResource> theType) {
+ myType = theType;
+ }
+
+ @Override
+ public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
+ BaseServerResponseException {
+ return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
+ }
+ }
+
private final class ResourceResponseHandler implements IClientResponseHandler {
- private Class myType;
private IdDt myId;
+ private Class myType;
public ResourceResponseHandler(Class theType, IdDt theId) {
myType = theType;
- myId=theId;
+ myId = theId;
}
@Override
@@ -613,11 +738,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
IParser parser = respType.newParser(myContext);
T retVal = parser.parseResource(myType, theResponseReader);
-
+
if (myId != null) {
retVal.setId(myId);
}
-
+
return retVal;
}
}
@@ -666,7 +791,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class TagListResponseHandler implements IClientResponseHandler {
@Override
- public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException {
+ public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException,
+ BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@@ -676,94 +802,47 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
}
- @Override
- public IGetPage loadPage() {
- return new LoadPageInternal();
- }
+ private final class TransactionExecutable extends BaseClientExecutable, T> implements ITransactionTyped {
- private final class LoadPageInternal implements IGetPage {
+ private Bundle myBundle;
+ private List myResources;
- @Override
- public IGetPageTyped previous(Bundle theBundle) {
- return new GetPageInternal(theBundle.getLinkPrevious().getValue());
+ public TransactionExecutable(Bundle theResources) {
+ myBundle = theResources;
}
- @Override
- public IGetPageTyped next(Bundle theBundle) {
- return new GetPageInternal(theBundle.getLinkNext().getValue());
+ public TransactionExecutable(List theResources) {
+ myResources = theResources;
}
+ @SuppressWarnings("unchecked")
@Override
- public IGetPageTyped url(String thePageUrl) {
- return new GetPageInternal(thePageUrl);
+ public T execute() {
+ if (myResources != null) {
+ ResourceListResponseHandler binding = new ResourceListResponseHandler(null);
+ BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
+ Map> params = null;
+ return (T) invoke(params, binding, invocation);
+ } else {
+ BundleResponseHandler binding = new BundleResponseHandler(null);
+ BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext);
+ Map> params = null;
+ return (T) invoke(params, binding, invocation);
+ }
}
}
private final class TransactionInternal implements ITransaction {
- @Override
- public ITransactionTyped> withResources(List theResources) {
- return new TransactionExecutable>(theResources);
- }
-
@Override
public ITransactionTyped withBundle(Bundle theResources) {
return new TransactionExecutable(theResources);
}
-
- }
-
-
- private final class TransactionExecutable extends BaseClientExecutable, T> implements ITransactionTyped{
-
- private List myResources;
- private Bundle myBundle;
-
- public TransactionExecutable(List theResources) {
- myResources=theResources;
- }
-
- public TransactionExecutable(Bundle theResources) {
- myBundle=theResources;
- }
-
- @SuppressWarnings("unchecked")
@Override
- public T execute() {
- if (myResources!=null) {
- ResourceListResponseHandler binding = new ResourceListResponseHandler(null);
- BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myResources, myContext);
- Map> params = null;
- return (T) invoke(params, binding, invocation);
- }else {
- BundleResponseHandler binding = new BundleResponseHandler(null);
- BaseHttpClientInvocation invocation = TransactionMethodBinding.createTransactionInvocation(myBundle, myContext);
- Map> params = null;
- return (T) invoke(params, binding, invocation);
- }
- }
-
- }
-
- private class GetPageInternal extends BaseClientExecutable implements IGetPageTyped {
-
- private String myUrl;
-
- public GetPageInternal(String theUrl) {
- myUrl = theUrl;
- }
-
- @Override
- public Bundle execute() {
-
- BundleResponseHandler binding = new BundleResponseHandler(null);
- HttpSimpleGetClientInvocation invocation = new HttpSimpleGetClientInvocation(myUrl);
-
- Map> params = null;
- return invoke(params, binding, invocation);
-
+ public ITransactionTyped> withResources(List theResources) {
+ return new TransactionExecutable>(theResources);
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/HttpBasicAuthInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/HttpBasicAuthInterceptor.java
index 56062675dd4..1bfb506f45d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/HttpBasicAuthInterceptor.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/HttpBasicAuthInterceptor.java
@@ -1,5 +1,19 @@
package ca.uhn.fhir.rest.client;
+import java.io.IOException;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.auth.AuthState;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.protocol.HttpContext;
+
+import ca.uhn.fhir.rest.client.api.IBasicClient;
+
/*
* #%L
* HAPI FHIR Library
@@ -20,27 +34,11 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
-import java.io.IOException;
-
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.auth.AuthState;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.impl.auth.BasicScheme;
-import org.apache.http.protocol.HttpContext;
/**
- * HTTP interceptor to be used for adding HTTP basic auth username/password tokens
- * to requests
- *
- * See the HAPI Documentation
- * for information on how to use this class.
- *
+ * @deprecated Use {@link ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor} instead. Note that that class is a HAPI client interceptor instead of being a commons-httpclient interceptor, so you register it to your client instance once it's created using {@link IGenericClient#registerInterceptor(IClientInterceptor)} or {@link IBasicClient#registerInterceptor(IClientInterceptor)} instead
*/
-public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
+public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
private String myUsername;
private String myPassword;
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 7029cc05589..a1472b2f4f2 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
@@ -20,13 +20,21 @@ package ca.uhn.fhir.rest.client;
* #L%
*/
+import java.io.IOException;
+
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
public interface IClientInterceptor {
+ /**
+ * Fired by the client just before invoking the HTTP client request
+ */
void interceptRequest(HttpRequestBase theRequest);
- void interceptResponse(HttpResponse theRequest);
+ /**
+ * Fired by the client upon receiving an HTTP response, prior to processing that response
+ */
+ void interceptResponse(HttpResponse theResponse) throws IOException;
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
index ec73de11f1b..980b0b7c201 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java
@@ -30,13 +30,25 @@ import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.client.api.IRestfulClient;
+import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.IGetPage;
import ca.uhn.fhir.rest.gclient.IGetTags;
import ca.uhn.fhir.rest.gclient.ITransaction;
import ca.uhn.fhir.rest.gclient.IUntypedQuery;
public interface IGenericClient {
+ /**
+ * Register a new interceptor for this client. An interceptor can be used to add additional
+ * logging, or add security headers, or pre-process responses, etc.
+ */
+ void registerInterceptor(IClientInterceptor theInterceptor);
+ /**
+ * Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
+ */
+ void unregisterInterceptor(IClientInterceptor theInterceptor);
+
/**
* Retrieves and returns the server conformance statement
*/
@@ -243,4 +255,9 @@ public interface IGenericClient {
*/
ITransaction transaction();
+ /**
+ * Fluent method for the "create" operation, which creates a new resource instance on the server
+ */
+ ICreate create();
+
}
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 405025f5634..abdaa658994 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,9 +21,11 @@ package ca.uhn.fhir.rest.client.api;
*/
+import org.apache.commons.lang3.Validate;
import org.apache.http.client.HttpClient;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.client.IClientInterceptor;
import ca.uhn.fhir.rest.server.EncodingEnum;
public interface IRestfulClient {
@@ -64,6 +66,15 @@ public interface IRestfulClient {
*/
String getServerBase();
-
+ /**
+ * Register a new interceptor for this client. An interceptor can be used to add additional
+ * logging, or add security headers, or pre-process responses, etc.
+ */
+ void registerInterceptor(IClientInterceptor theInterceptor);
+
+ /**
+ * Remove an intercaptor that was previously registered using {@link IRestfulClient#registerInterceptor(IClientInterceptor)}
+ */
+ void unregisterInterceptor(IClientInterceptor theInterceptor);
}
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
new file mode 100644
index 00000000000..f63a8e43d20
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/BasicAuthInterceptor.java
@@ -0,0 +1,73 @@
+package ca.uhn.fhir.rest.client.interceptor;
+
+/*
+ * #%L
+ * HAPI FHIR Library
+ * %%
+ * Copyright (C) 2014 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.UnsupportedEncodingException;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+
+import ca.uhn.fhir.rest.client.IClientInterceptor;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+
+/**
+ * HTTP interceptor to be used for adding HTTP basic auth username/password tokens
+ * to requests
+ *
+ * See the HAPI Documentation
+ * for information on how to use this class.
+ *
+ */
+public class BasicAuthInterceptor implements IClientInterceptor {
+
+ private String myUsername;
+ private String myPassword;
+
+ public BasicAuthInterceptor(String theUsername, String thePassword) {
+ super();
+ myUsername = theUsername;
+ myPassword = thePassword;
+ }
+
+ @Override
+ public void interceptRequest(HttpRequestBase theRequest) {
+ String authorizationUnescaped = StringUtils.defaultString(myUsername) + ":" + StringUtils.defaultString(myPassword);
+ String encoded;
+ try {
+ encoded = Base64.encodeBase64String(authorizationUnescaped.getBytes("ISO-8859-1"));
+ } catch (UnsupportedEncodingException e) {
+ throw new InternalErrorException("Could not find US-ASCII encoding. This shouldn't happen!");
+ }
+ theRequest.addHeader(Constants.HEADER_AUTHORIZATION, ("Basic " + encoded));
+ }
+
+ @Override
+ public void interceptResponse(HttpResponse theResponse) throws IOException {
+ // 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
new file mode 100644
index 00000000000..db000733db1
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/CapturingInterceptor.java
@@ -0,0 +1,43 @@
+package ca.uhn.fhir.rest.client.interceptor;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+
+import ca.uhn.fhir.rest.client.IClientInterceptor;
+
+/**
+ * Client interceptor which simply captures request and response objects and stored them so that they can be inspected after a client
+ * call has returned
+ */
+public class CapturingInterceptor implements IClientInterceptor {
+
+ private HttpRequestBase myLastRequest;
+ private HttpResponse myLastResponse;
+
+ /**
+ * Clear the last request and response values
+ */
+ public void clear() {
+ myLastRequest = null;
+ myLastResponse = null;
+ }
+
+ public HttpRequestBase getLastRequest() {
+ return myLastRequest;
+ }
+
+ public HttpResponse getLastResponse() {
+ return myLastResponse;
+ }
+
+ @Override
+ public void interceptRequest(HttpRequestBase theRequest) {
+ myLastRequest = theRequest;
+ }
+
+ @Override
+ public void interceptResponse(HttpResponse theRequest) {
+ myLastResponse = theRequest;
+ }
+
+}
\ No newline at end of file
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
new file mode 100644
index 00000000000..c957beb91ce
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/interceptor/LoggingInterceptor.java
@@ -0,0 +1,184 @@
+package ca.uhn.fhir.rest.client.interceptor;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.entity.HttpEntityWrapper;
+
+import ca.uhn.fhir.rest.client.IClientInterceptor;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+
+public class LoggingInterceptor implements IClientInterceptor {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
+ private boolean myLogRequestSummary = true;
+ private boolean myLogResponseSummary = true;
+ private boolean myLogResponseBody = false;
+ private boolean myLogRequestBody = false;
+ private boolean myLogResponseHeaders=false;
+ private boolean myLogRequestHeaders=false;
+
+ /**
+ * Constructor
+ */
+ public LoggingInterceptor() {
+ super();
+ }
+
+ /**
+ * Constructor
+ *
+ * @param theVerbose If set to true, all logging is enabled
+ */
+ public LoggingInterceptor(boolean theVerbose) {
+ if (theVerbose) {
+ setLogRequestBody(true);
+ setLogRequestSummary(true);
+ setLogResponseBody(true);
+ setLogResponseSummary(true);
+ setLogRequestHeaders(true);
+ setLogResponseHeaders(true);
+ }
+ }
+
+ @Override
+ public void interceptRequest(HttpRequestBase theRequest) {
+ if (myLogRequestSummary) {
+ ourLog.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');
+ }
+ }
+ ourLog.info("Client request headers:\n{}", b.toString());
+ }
+
+ if (myLogRequestBody) {
+ if (theRequest instanceof HttpEntityEnclosingRequest) {
+ HttpEntity entity = ((HttpEntityEnclosingRequest) theRequest).getEntity();
+ if (entity.isRepeatable()) {
+ try {
+ String content = IOUtils.toString(entity.getContent());
+ ourLog.info("Client request body:\n{}", content);
+ } catch (IllegalStateException e) {
+ ourLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
+ } catch (IOException e) {
+ ourLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void interceptResponse(HttpResponse theResponse) throws IOException {
+ if (myLogResponseSummary) {
+ String message = "HTTP " + theResponse.getStatusLine().getStatusCode() + " " + theResponse.getStatusLine().getReasonPhrase();
+ ourLog.info("Client response: {}", message);
+ }
+
+ if (myLogRequestHeaders) {
+ StringBuilder b = new StringBuilder();
+ 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');
+ }
+ }
+ ourLog.info("Client response headers:\n{}", b.toString());
+ }
+
+ if (myLogResponseBody) {
+ HttpEntity respEntity = theResponse.getEntity();
+ final byte[] bytes;
+ try {
+ bytes = IOUtils.toByteArray(respEntity.getContent());
+ } catch (IllegalStateException e) {
+ throw new InternalErrorException(e);
+ }
+
+ ourLog.info("Client response body:\n{}", new String(bytes));
+
+ theResponse.setEntity(new MyEntityWrapper(respEntity, bytes));
+ }
+ }
+
+ 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);
+ }
+
+ }
+
+ /**
+ * Should a summary (one line) for each request be logged, containing the URL and other information
+ */
+ public void setLogRequestSummary(boolean theValue) {
+ myLogRequestSummary = theValue;
+ }
+
+ /**
+ * Should a summary (one line) for each request be logged, containing the URL and other information
+ */
+ public void setLogResponseSummary(boolean theValue) {
+ myLogResponseSummary = theValue;
+ }
+
+ /**
+ * Should headers for each request be logged, containing the URL and other information
+ */
+ public void setLogRequestHeaders(boolean theValue) {
+ myLogRequestHeaders = theValue;
+ }
+
+ /**
+ * Should headers for each request be logged, containing the URL and other information
+ */
+ public void setLogResponseHeaders(boolean theValue) {
+ myLogResponseHeaders = theValue;
+ }
+
+ /**
+ * Should a summary (one line) for each request be logged, containing the URL and other information
+ */
+ public void setLogRequestBody(boolean theValue) {
+ myLogRequestBody = theValue;
+ }
+
+ /**
+ * Should a summary (one line) for each request be logged, containing the URL and other information
+ */
+ public void setLogResponseBody(boolean theValue) {
+ myLogResponseBody = theValue;
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreate.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreate.java
new file mode 100644
index 00000000000..8b16ed3d09a
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreate.java
@@ -0,0 +1,31 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR Library
+ * %%
+ * Copyright (C) 2014 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.model.api.IResource;
+
+public interface ICreate {
+
+ ICreateTyped resource(IResource theResource);
+
+ ICreateTyped resource(String theResourceAsText);
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java
new file mode 100644
index 00000000000..8fd1d56a728
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICreateTyped.java
@@ -0,0 +1,40 @@
+package ca.uhn.fhir.rest.gclient;
+
+/*
+ * #%L
+ * HAPI FHIR Library
+ * %%
+ * Copyright (C) 2014 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.model.primitive.IdDt;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+
+public interface ICreateTyped extends IClientExecutable {
+
+ /**
+ * If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
+ * need to invoke this method, so that the server will assign the ID itself.
+ */
+ ICreateTyped withId(String theId);
+
+ /**
+ * If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
+ * need to invoke this method, so that the server will assign the ID itself.
+ */
+ ICreateTyped withId(IdDt theId);
+
+}
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 fa84a7ff0c8..8fc5fcfd82a 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
@@ -50,7 +50,8 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
private final TagList myTagList;
private final List myResources;
private final Bundle myBundle;
-
+ private final String myContents;
+
public BaseHttpClientInvocationWithContents(FhirContext theContext, IResource theResource, String theUrlExtension) {
super();
myContext = theContext;
@@ -59,6 +60,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myTagList = null;
myResources = null;
myBundle = null;
+ myContents = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, TagList theTagList, String... theUrlExtension) {
@@ -72,6 +74,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myTagList = theTagList;
myResources = null;
myBundle = null;
+ myContents = null;
myUrlExtension = StringUtils.join(theUrlExtension, '/');
}
@@ -83,6 +86,7 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myUrlExtension = null;
myResources = theResources;
myBundle = null;
+ myContents = null;
}
public BaseHttpClientInvocationWithContents(FhirContext theContext, Bundle theBundle) {
@@ -92,8 +96,20 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
myUrlExtension = null;
myResources = null;
myBundle = theBundle;
+ myContents = null;
}
+ public BaseHttpClientInvocationWithContents(FhirContext theContext, String theContents, String theUrlExtension) {
+ myContext = theContext;
+ myResource = null;
+ myTagList = null;
+ myUrlExtension = theUrlExtension;
+ myResources = null;
+ myBundle = null;
+ myContents = theContents;
+ }
+
+
@Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) throws DataFormatException {
StringBuilder b = new StringBuilder();
@@ -131,6 +147,8 @@ public abstract class BaseHttpClientInvocationWithContents extends BaseHttpClien
} else if (myResources != null) {
Bundle bundle = RestfulServer.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size());
contents = parser.encodeBundleToString(bundle);
+ } else if (myContents != null) {
+ contents = myContents;
} else {
contents = parser.encodeResourceToString(myResource);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java
index e084de69268..15519f34770 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java
@@ -20,10 +20,13 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
+import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
@@ -55,12 +58,24 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
return Collections.singleton(RequestType.POST);
}
+ @Override
+ protected IResource parseIncomingServerResource(Request theRequest) throws IOException {
+ IResource retVal = super.parseIncomingServerResource(theRequest);
+
+ if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
+ retVal.setId(theRequest.getId());
+ }
+
+ return retVal;
+ }
+
+
@Override
protected BaseHttpClientInvocation createClientInvocation(Object[] theArgs, IResource theResource) {
FhirContext context = getContext();
BaseHttpClientInvocation retVal = createCreateInvocation(theResource, context);
-
+
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
@@ -72,16 +87,28 @@ public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithRe
}
public static HttpPostClientInvocation createCreateInvocation(IResource theResource, FhirContext theContext) {
+ return createCreateInvocation(theResource, null,null, theContext);
+ }
+
+ public static HttpPostClientInvocation createCreateInvocation(IResource theResource, String theResourceBody, String theId, FhirContext theContext) {
RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
String resourceName = def.getName();
-
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(resourceName);
+ if (StringUtils.isNotBlank(theId)) {
+ urlExtension.append('/');
+ urlExtension.append(theId);
+ }
- HttpPostClientInvocation retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
+ HttpPostClientInvocation retVal;
+ if (StringUtils.isBlank(theResourceBody)) {
+ retVal = new HttpPostClientInvocation(theContext, theResource, urlExtension.toString());
+ }else {
+ retVal = new HttpPostClientInvocation(theContext, theResourceBody, urlExtension.toString());
+ }
addTagsToPostOrPut(theResource, retVal);
-
+
return retVal;
}
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 7fc83f4cab6..2ab7222e8c0 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
@@ -51,6 +51,10 @@ public class HttpPostClientInvocation extends BaseHttpClientInvocationWithConten
super(theContext,theBundle);
}
+ public HttpPostClientInvocation(FhirContext theContext, String theContents, String theUrlExtension) {
+ super(theContext,theContents, theUrlExtension);
+ }
+
@Override
protected HttpPost createRequest(String url, AbstractHttpEntity theEntity) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
index 2d0d088ab18..030237da2fa 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
@@ -28,6 +28,10 @@ import java.util.Set;
public class Constants {
+ public static final String HEADER_CORS_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
+ public static final String HEADER_CORS_ALLOW_METHODS = "Access-Control-Allow-Methods";
+ public static final String HEADER_CORS_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
+
public static final String CHARSET_UTF_8 = "UTF-8";
public static final String CT_ATOM_XML = "application/atom+xml";
public static final String CT_FHIR_JSON = "application/json+fhir";
@@ -88,6 +92,8 @@ public class Constants {
public static final String ENCODING_GZIP = "gzip";
public static final String HEADER_LOCATION = "Location";
public static final String HEADER_LOCATION_LC = HEADER_LOCATION.toLowerCase();
+ public static final String HEADERVALUE_CORS_ALLOW_METHODS_ALL = "GET, POST, PUT, DELETE";
+ public static final String HEADER_AUTHORIZATION = "Authorization";
static {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index c923c6af42e..13cc91d798e 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -79,6 +79,7 @@ import ca.uhn.fhir.util.VersionUtil;
public class RestfulServer extends HttpServlet {
+
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServer.class);
private static final long serialVersionUID = 1L;
@@ -99,6 +100,20 @@ public class RestfulServer extends HttpServlet {
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
+ private String myCorsAllowDomain;
+
+ public String getCorsAllowDomain() {
+ return myCorsAllowDomain;
+ }
+
+ /**
+ * If set to anything other than null (which is the default), the server will return CORS (Cross Origin Resource Sharing) headers with the given domain string.
+ *
+ * A value of "*" indicates that the server allows access to all domains (which may be appropriate in development situations but is generally not appropriate in production)
+ */
+ public void setCorsAllowDomain(String theCorsAllowDomain) {
+ myCorsAllowDomain = theCorsAllowDomain;
+ }
/**
* Constructor
@@ -120,6 +135,13 @@ public class RestfulServer extends HttpServlet {
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
+
+ if (isNotBlank(myCorsAllowDomain)) {
+ theHttpResponse.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, myCorsAllowDomain);
+ theHttpResponse.addHeader(Constants.HEADER_CORS_ALLOW_METHODS, Constants.HEADERVALUE_CORS_ALLOW_METHODS_ALL);
+ theHttpResponse.addHeader(Constants.HEADER_CORS_EXPOSE_HEADERS, Constants.HEADER_CONTENT_LOCATION);
+ }
+
}
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
@@ -352,7 +374,7 @@ public class RestfulServer extends HttpServlet {
int start = Math.min(offsetI, resultList.size() - 1);
- EncodingEnum responseEncoding = determineRequestEncoding(theRequest);
+ EncodingEnum responseEncoding = determineResponseEncoding(theRequest.getServletRequest());
boolean prettyPrint = prettyPrintResponse(theRequest);
boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest());
NarrativeModeEnum narrativeMode = determineNarrativeMode(theRequest);
@@ -418,7 +440,7 @@ public class RestfulServer extends HttpServlet {
ResourceBinding resourceBinding = null;
BaseMethodBinding> resourceMethod = null;
- if ("metadata".equals(resourceName)) {
+ if ("metadata".equals(resourceName) || theRequestType == RequestType.OPTIONS) {
resourceMethod = myServerConformanceMethod;
} else if (resourceName == null) {
resourceBinding = myNullResourceBinding;
diff --git a/hapi-fhir-base/src/site/example/java/example/ClientExamples.java b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
index d6a66bf7ecf..ad8b1e2bbef 100644
--- a/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
+++ b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java
@@ -3,10 +3,11 @@ package example;
import org.apache.http.impl.client.HttpClientBuilder;
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.rest.client.HttpBasicAuthInterceptor;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
+import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor;
+import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.EncodingEnum;
public class ClientExamples {
@@ -17,29 +18,50 @@ public class ClientExamples {
@SuppressWarnings("unused")
public void createSecurity() {
-{
//START SNIPPET: security
// Create a context and get the client factory so it can be configured
FhirContext ctx = new FhirContext();
IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
-// Create an HTTP Client Builder
-HttpClientBuilder builder = HttpClientBuilder.create();
-
-// This interceptor adds HTTP username/password to every request
+//Create an HTTP basic auth interceptor
String username = "foobar";
String password = "boobear";
-builder.addInterceptorFirst(new HttpBasicAuthInterceptor(username, password));
+BasicAuthInterceptor authInterceptor = new BasicAuthInterceptor(username, password);
-// Use the new HTTP client builder
-clientFactory.setHttpClient(builder.build());
-
-// This factory is applied to both styles of client
+// Register the interceptor with your client (either style)
IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir");
+annotationClient.registerInterceptor(authInterceptor);
+
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
+annotationClient.registerInterceptor(authInterceptor);
//END SNIPPET: security
}
+@SuppressWarnings("unused")
+public void createLogging() {
+{
+//START SNIPPET: logging
+//Create a context and get the client factory so it can be configured
+FhirContext ctx = new FhirContext();
+IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
+
+//Create a logging interceptor
+LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
+
+// Optionally you may configure the interceptor (by default only summary info is logged)
+loggingInterceptor.setLogRequestSummary(true);
+loggingInterceptor.setLogRequestBody(true);
+
+//Register the interceptor with your client (either style)
+IPatientClient annotationClient = ctx.newRestfulClient(IPatientClient.class, "http://localhost:9999/fhir");
+annotationClient.registerInterceptor(loggingInterceptor);
+
+IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
+annotationClient.registerInterceptor(loggingInterceptor);
+//END SNIPPET: logging
+}
+
+
/******************************/
{
diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml
index 144def9e1e0..f3edce85428 100644
--- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml
+++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml
@@ -165,6 +165,28 @@
+
+
+
+ Restful client interfaces that you create will also extend
+ the interface
+ IRestfulClient,
+ which comes with some helpful methods for configuring the way that
+ the client will interact with the server.
+
+
+ The following snippet shows how to configure the cliet to explicitly
+ request JSON or XML responses, and how to request "pretty printed" responses
+ on servers that support this (HAPI based servers currently).
+
+
+
+
+
+
+
+
+
@@ -205,6 +227,21 @@
on the RestfulClientFactory.
+
+
+
+ Both generic clients and annotation-driven clients support
+ Client Interceptors,
+ which may be registered in order to provide specific behaviour to each
+ client request.
+
+
+
+ The following section shows some sample interceptors which may be used.
+
+
+
+
@@ -219,23 +256,15 @@
-
+
- Restful client interfaces that you create will also extend
- the interface
- IRestfulClient,
- which comes with some helpful methods for configuring the way that
- the client will interact with the server.
+ The following example shows how to configure your client to
+ use a specific username and password in every request.
-
- The following snippet shows how to configure the cliet to explicitly
- request JSON or XML responses, and how to request "pretty printed" responses
- on servers that support this (HAPI based servers currently).
-
- Create/Update an instance of the resource. If no ID is specified,
- a new resource will be created. If an ID is specified, the existing
- resource with that ID will be updated.
+ Create an instance of the resource. Generally you do not need to specify an ID
+ but you may force the server to use a specific ID by including one.