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 new file mode 100644 index 00000000000..eb2e393ecb5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/HttpBasicAuthInterceptor.java @@ -0,0 +1,44 @@ +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; + +/** + * HTTP interceptor to be used for adding HTTP basic auth username/password tokens + * to requests + *

+ * See the + *

+ */ +public class HttpBasicAuthInterceptor implements HttpRequestInterceptor { + + private String myUsername; + private String myPassword; + + public HttpBasicAuthInterceptor(String theUsername, String thePassword) { + super(); + myUsername = theUsername; + myPassword = thePassword; + } + + @Override + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE); + + if (authState.getAuthScheme() == null) { + Credentials creds = new UsernamePasswordCredentials(myUsername, myPassword); + authState.update(new BasicScheme(), creds); + } + + } + +} \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index 7f2b12ecf48..d75c475fcad 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -69,7 +69,7 @@ public abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends if (param == null) { continue; } - params[i] = param.translateQueryParametersIntoServerArgument(theRequest.getParameters(), resource); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, resource); } addParametersForServerRequest(theRequest, params); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 3238780a6b6..2089bed62c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.method; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.PrintWriter; @@ -130,7 +130,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi throw new IllegalStateException("Should not get here!"); } - public abstract List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException; + public abstract List invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException; @Override public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { @@ -165,7 +165,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi requestIsBrowser = true; } - List result = invokeServer(getProvider(), theRequest.getId(), theRequest.getVersion(), theRequest.getParameters()); + List result = invokeServer(getProvider(), theRequest); switch (getReturnType()) { case BUNDLE: streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode); 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 dacff818beb..c7a411fb81a 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 @@ -3,7 +3,6 @@ package ca.uhn.fhir.rest.method; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; -import java.util.Map; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; @@ -11,7 +10,6 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -39,7 +37,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding } @Override - public List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, + public List invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException { IResource conf; try { 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 882568898ab..0cd69c004ee 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 @@ -1,11 +1,10 @@ package ca.uhn.fhir.rest.method; -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -151,10 +150,10 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException { + public List invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException { Object[] args = new Object[getMethod().getParameterTypes().length]; if (myCountParamIndex != null) { - String[] countValues = theParameterValues.remove(Constants.PARAM_COUNT); + String[] countValues = theRequest.getParameters().remove(Constants.PARAM_COUNT); if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) { try { args[myCountParamIndex] = new IntegerDt(countValues[0]); @@ -164,7 +163,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } } if (mySinceParamIndex != null) { - String[] sinceValues = theParameterValues.remove(Constants.PARAM_SINCE); + String[] sinceValues = theRequest.getParameters().remove(Constants.PARAM_SINCE); if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) { try { args[mySinceParamIndex] = new InstantDt(sinceValues[0]); @@ -175,7 +174,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } if (myIdParamIndex!=null) { - args[myIdParamIndex] = theId; + args[myIdParamIndex] = theRequest.getId(); } Object response; 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 e63e4ec80d9..0c1812aaadc 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 @@ -2,7 +2,6 @@ package ca.uhn.fhir.rest.method; import java.lang.reflect.Method; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; @@ -88,11 +87,11 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException { + public List invokeServer(Object theResourceProvider,Request theRequest) throws InvalidRequestException, InternalErrorException { Object[] params = new Object[myParameterCount]; - params[myIdIndex] = theId; + params[myIdIndex] = theRequest.getId(); if (myVersionIdIndex != null) { - params[myVersionIdIndex] = theVersionId; + params[myVersionIdIndex] = theRequest.getId(); } Object response; 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 a1806fa30b5..3b080c006bc 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 @@ -15,10 +15,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; -import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.client.GetClientInvocation; import ca.uhn.fhir.rest.param.IParameter; -import ca.uhn.fhir.rest.param.IQueryParameter; +import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -85,20 +84,21 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public List invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map parameterValues) throws InvalidRequestException, + public List invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException { - assert theId == null; - assert theVersionId == null; + assert theRequest.getId() == null; + assert theRequest.getVersion() == null; Object[] params = new Object[myParameters.size()]; for (int i = 0; i < myParameters.size(); i++) { IParameter param = myParameters.get(i); - params[i] = param.translateQueryParametersIntoServerArgument(parameterValues, null); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null); } Object response; try { - response = this.getMethod().invoke(theResourceProvider, params); + Method method = this.getMethod(); + response = method.invoke(theResourceProvider, params); } catch (IllegalAccessException e) { throw new InternalErrorException(e); } catch (IllegalArgumentException e) { @@ -140,10 +140,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { Set methodParamsTemp = new HashSet(); for (int i = 0; i < this.myParameters.size(); i++) { - if (!(myParameters.get(i) instanceof IQueryParameter)) { + if (!(myParameters.get(i) instanceof BaseQueryParameter)) { continue; } - IQueryParameter temp = (IQueryParameter) myParameters.get(i); + BaseQueryParameter temp = (BaseQueryParameter) myParameters.get(i); methodParamsTemp.add(temp.getName()); if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) { ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), temp.getName()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java new file mode 100644 index 00000000000..90de02cb218 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java @@ -0,0 +1,26 @@ +package ca.uhn.fhir.rest.method; + +import java.util.List; +import java.util.Map; + +import ca.uhn.fhir.rest.param.IParameter; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +class ServletRequestParameter implements IParameter { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class); + + @Override + public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map> theTargetQueryArguments) throws InternalErrorException { + /* + * Does nothing, since we just ignore HttpServletRequest arguments + */ + ourLog.trace("Ignoring HttpServletRequest argument: {}", theSourceClientArgument); + } + + @Override + public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { + return theRequest.getServletRequest(); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java index 05c96caef3c..99c8a543d70 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { +class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { private Integer myIdParameterIndex; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java index b618873ac13..e26370e39da 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java @@ -10,6 +10,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.PathSpecification; @@ -88,66 +91,73 @@ class Util { int paramIndex = 0; for (Annotation[] annotations : method.getParameterAnnotations()) { boolean haveHandledMethod = false; - for (int i = 0; i < annotations.length; i++) { - Annotation nextAnnotation = annotations[i]; - Class parameterType = parameterTypes[paramIndex]; - Class> outerCollectionType = null; - Class> innerCollectionType = null; - - if (Collection.class.isAssignableFrom(parameterType)) { - innerCollectionType = (Class>) parameterType; - parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex); - } - - if (Collection.class.isAssignableFrom(parameterType)) { - outerCollectionType = innerCollectionType; - innerCollectionType = (Class>) parameterType; - parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex); - } - - IParameter param; - if (nextAnnotation instanceof RequiredParam) { - SearchParameter parameter = new SearchParameter(); - parameter.setName(((RequiredParam) nextAnnotation).name()); - parameter.setRequired(true); - parameter.setType(parameterType, innerCollectionType, outerCollectionType); - param = parameter; - } else if (nextAnnotation instanceof OptionalParam) { - SearchParameter parameter = new SearchParameter(); - parameter.setName(((OptionalParam) nextAnnotation).name()); - parameter.setRequired(false); - parameter.setType(parameterType, innerCollectionType, innerCollectionType); - param = parameter; - } else if (nextAnnotation instanceof IncludeParam) { - if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) { - throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" - + PathSpecification.class.getSimpleName() + ">"); - } - Class> instantiableCollectionType = (Class>) CollectionBinder.getInstantiableCollectionType( - innerCollectionType, "Method '" + method.getName() + "'"); - - param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType); - } else if (nextAnnotation instanceof ResourceParam) { - if (!IResource.class.isAssignableFrom(parameterType)) { - throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() - + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName()); - } - param = new ResourceParameter((Class) parameterType); - } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { - param = null; - } else { - continue; - } - - haveHandledMethod = true; + Class parameterType = parameterTypes[paramIndex]; + if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) { + ServletRequestParameter param = new ServletRequestParameter(); parameters.add(param); + } else { + for (int i = 0; i < annotations.length; i++) { + Annotation nextAnnotation = annotations[i]; - } + Class> outerCollectionType = null; + Class> innerCollectionType = null; - if (!haveHandledMethod) { - throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName() - + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter"); + if (Collection.class.isAssignableFrom(parameterType)) { + innerCollectionType = (Class>) parameterType; + parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex); + } + + if (Collection.class.isAssignableFrom(parameterType)) { + outerCollectionType = innerCollectionType; + innerCollectionType = (Class>) parameterType; + parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex); + } + + IParameter param; + if (nextAnnotation instanceof RequiredParam) { + SearchParameter parameter = new SearchParameter(); + parameter.setName(((RequiredParam) nextAnnotation).name()); + parameter.setRequired(true); + parameter.setType(parameterType, innerCollectionType, outerCollectionType); + param = parameter; + } else if (nextAnnotation instanceof OptionalParam) { + SearchParameter parameter = new SearchParameter(); + parameter.setName(((OptionalParam) nextAnnotation).name()); + parameter.setRequired(false); + parameter.setType(parameterType, innerCollectionType, outerCollectionType); + param = parameter; + } else if (nextAnnotation instanceof IncludeParam) { + if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) { + throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + + PathSpecification.class.getSimpleName() + ">"); + } + Class> instantiableCollectionType = (Class>) CollectionBinder.getInstantiableCollectionType( + innerCollectionType, "Method '" + method.getName() + "'"); + + param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType); + } else if (nextAnnotation instanceof ResourceParam) { + if (!IResource.class.isAssignableFrom(parameterType)) { + throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName()); + } + param = new ResourceParameter((Class) parameterType); + } else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) { + param = null; + } else { + continue; + } + + haveHandledMethod = true; + parameters.add(param); + break; + + } + + if (!haveHandledMethod) { + throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName() + + "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter"); + } } paramIndex++; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IQueryParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java similarity index 87% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IQueryParameter.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java index 2e7cb767949..684b36fd75f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IQueryParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java @@ -6,11 +6,12 @@ import java.util.List; import java.util.Map; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; +import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.util.QueryUtil; -public abstract class IQueryParameter implements IParameter { +public abstract class BaseQueryParameter implements IParameter { public abstract List> encode(Object theObject) throws InternalErrorException; @@ -54,8 +55,8 @@ public abstract class IQueryParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(Map theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException { - String[] value = theQueryParameters.get(getName()); + public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { + String[] value = theRequest.getParameters().get(getName()); if (value == null || value.length == 0) { if (handlesMissing()) { return parse(new ArrayList>(0)); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParameter.java index 60d05b833cd..eae424ca8d5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IParameter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -16,12 +17,12 @@ public interface IParameter { * This server method method takes the data received by the server in an incoming request, and translates that data into a single argument for a server method invocation. Note that all * received data is passed to this method, but the expectation is that not necessarily that all data is used by every parameter. * - * @param theQueryParameters - * The query params, e.g. ?family=smith&given=john + * @param theRequest + * The incoming request object * @param theRequestContents * The parsed contents of the incoming request. E.g. if the request was an HTTP POST with a resource in the body, this argument would contain the parsed {@link IResource} instance. * @return Returns the argument object as it will be passed to the {@link IResourceProvider} method. */ - Object translateQueryParametersIntoServerArgument(Map theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException; + Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java index cfccf341f4d..048b72aac97 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java @@ -13,7 +13,7 @@ import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class IncludeParameter extends IQueryParameter { +public class IncludeParameter extends BaseQueryParameter { private Class> myInstantiableCollectionType; private HashSet myAllow; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java index b7472aa2e3f..0d95716a3e9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Map; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.method.Request; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -22,7 +23,7 @@ public class ResourceParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(Map theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException { IResource resource = (IResource) theRequestContents; return resource; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java index 26b4081c5d8..6b5a84803f2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/SearchParameter.java @@ -16,7 +16,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; /** * Created by dsotnikov on 2/25/2014. */ -public class SearchParameter extends IQueryParameter { +public class SearchParameter extends BaseQueryParameter { private String name; private IParamBinder myParamBinder; 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 5f5ce22a8c7..b3d433b447c 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 @@ -162,6 +162,7 @@ public class RestfulServer extends HttpServlet { } ourLog.info("Got {} resource providers", typeToProvider.size()); for (IResourceProvider provider : typeToProvider.values()) { + assertProviderIsValid(provider); findResourceMethods(provider); } } @@ -169,6 +170,7 @@ public class RestfulServer extends HttpServlet { Collection providers = getProviders(); if (providers != null) { for (Object next : providers) { + assertProviderIsValid(next); findResourceMethods(next); } } @@ -184,6 +186,12 @@ public class RestfulServer extends HttpServlet { ourLog.info("A FHIR has been lit on this server"); } + private void assertProviderIsValid(Object theNext) throws ConfigurationException { + if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) { + throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Must be public"); + } + } + public boolean isUseBrowserFriendlyContentTypes() { return myUseBrowserFriendlyContentTypes; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java index 0b23e25d591..bc7f1e115e6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java @@ -22,7 +22,7 @@ import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.param.IParameter; -import ca.uhn.fhir.rest.param.IQueryParameter; +import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.server.ResourceBinding; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.util.ExtensionConstants; @@ -86,11 +86,11 @@ public class ServerConformanceProvider { RestResourceSearchParam searchParam = null; StringDt searchParamChain = null; for (IParameter nextParameterObj : params) { - if (!(nextParameterObj instanceof IQueryParameter)) { + if (!(nextParameterObj instanceof BaseQueryParameter)) { continue; } - IQueryParameter nextParameter = (IQueryParameter)nextParameterObj; + BaseQueryParameter nextParameter = (BaseQueryParameter)nextParameterObj; if (nextParameter.getName().startsWith("_")) { continue; } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narrative.css b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narrative.css index 8175c763521..4fe7d485d5b 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narrative.css +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narrative.css @@ -15,7 +15,7 @@ DIV.hapiHeaderText { TABLE.hapiTableOfValues THEAD TR TD { background: #A0A0F0; color: #000080; - font-size: 1.em; + font-size: 1.2em; font-weight: bold; padding: 5px; } diff --git a/hapi-fhir-base/src/site/example/java/example/ClientExamples.java b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java new file mode 100644 index 00000000000..1407d3a6270 --- /dev/null +++ b/hapi-fhir-base/src/site/example/java/example/ClientExamples.java @@ -0,0 +1,40 @@ +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.IRestfulClientFactory; +import ca.uhn.fhir.rest.client.api.IBasicClient; + +public class ClientExamples { + + public interface PatientClient extends IBasicClient { + // nothing yet + } + + @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 +String username = "foobar"; +String password = "boobear"; +builder.addInterceptorFirst(new HttpBasicAuthInterceptor(username, password)); + +// Use the new HTTP client builder +clientFactory.setHttpClient(builder.build()); + +// Actually create a client instance +PatientClient client = ctx.newRestfulClient(PatientClient.class, "http://localhost:9999/"); +//END SNIPPET: security + + } + +} 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 9c3076e2f62..870e59bc3c3 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml @@ -1,5 +1,6 @@ - + RESTful Client - HAPI FHIR @@ -10,97 +11,152 @@
- + - +

- HAPI provides a built-in mechanism for connecting to FHIR RESTful servers. - The HAPI RESTful client is designed to be easy to set up and to allow strong - compile-time type checking wherever possible. + HAPI provides a built-in mechanism for connecting to FHIR RESTful + servers. + The HAPI RESTful client is designed to be easy to set up and + to allow strong + compile-time type checking wherever possible.

- Setup is mostly done using simple annotations, which means that it should - be possible to create a FHIR compliant server quickly and easily. Once again, - this design is intended to be similar to that of JAX-WS, so users of that - specification should be comfortable with this one. + Setup is mostly done using simple annotations, which means that it + should + be possible to create a FHIR compliant server quickly and + easily. Once again, + this design is intended to be similar to that of + JAX-WS, so users of that + specification should be comfortable with + this one.

- + - +

The first step in creating a FHIR RESTful Client is to define a - restful client interface. + restful client interface.

- +

- A restful client interface class must extend the - IRestfulClient interface, + A restful client interface class must extend the + IRestfulClient + interface, and will contain one or more methods which have been - annotated with special annotations indicating which RESTful operation - that method supports. Below is a simple example of a resource provider + annotated with special annotations indicating which RESTful + operation + that method supports. Below is a simple example of a + resource provider which supports the read operation (i.e. retrieve a single resource by ID) as well as the search - operation (i.e. find any resources matching a given criteria) for a specific + operation (i.e. find any resources matching a given criteria) for a + specific search criteria.

- +

- You may notice that this interface looks a lot like the Resource Provider - which is defined for use by the RESTful server. In fact, it supports all - of the same annotations and is essentially identical, other than the + You may notice that this interface looks a lot like the Resource + Provider + which is defined for use by the RESTful server. In fact, it + supports all + of the same annotations and is essentially identical, + other than the fact that for a client you must use an interface but for a server you must use a concrete class with method implementations.

- + - +

You will probably want to add more methods - to your client interface. See - RESTful Operations for + to your client interface. + See + RESTful Operations + for lots more examples of how to add methods for various operations.

- +

Once your client interface is created, all that is left is to create a FhirContext and instantiate the client and you are - ready to start using it. + ready to + start using it.

- + - + -
- + +

The following is a complete example showing a RESTful client - using HAPI FHIR. + using + HAPI FHIR.

- + - - + +
- + +
+ +

+ The client uses Apache HTTP Client + as a provider. The HTTP Client is very powerful, but can be a bit tricky to configure. +

+ +

+ In many cases, the default configuration should suffice. However, if you require anything + more sophisticated (username/password, HTTP proxy settings, etc.) you will need + to configure the underlying client. +

+ +

+ The underlying client configuration is provided by setting an + HttpClient + on the RestfulClientFactory. +

+ + + +

+ The following example shows how to configure your client to + use a specific username and password in every request. +

+ + + + + + +
+ +
+
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientIntegrationTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientIntegrationTest.java new file mode 100644 index 00000000000..6a151f078d8 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientIntegrationTest.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.rest.client; + +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyDiagnosticReportResourceProvider; +import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyPatientResourceProvider; +import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyRestfulServer; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.provider.ServerProfileProvider; +import ca.uhn.fhir.testutil.RandomServerPortProvider; + +public class ClientIntegrationTest { + + private int myPort; + private Server myServer; + private FhirContext myCtx; + private MyPatientResourceProvider myPatientProvider; + + @Before + public void before() { + myPort = RandomServerPortProvider.findFreePort(); + myServer = new Server(myPort); + myCtx = new FhirContext(Patient.class); + + myPatientProvider = new MyPatientResourceProvider(); + + ServletHandler proxyHandler = new ServletHandler(); + RestfulServer servlet = new RestfulServer(); + servlet.setResourceProviders(myPatientProvider); + ServletHolder servletHolder = new ServletHolder(servlet); + proxyHandler.addServletWithMapping(servletHolder, "/*"); + myServer.setHandler(proxyHandler); + + } + + @Test + public void testClientSecurity() throws Exception { +// BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); +// UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("foobar", "boobear"); +// AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT); +// credentialsProvider.setCredentials(scope, credentials); +// builder.setDefaultCredentialsProvider(credentialsProvider); +// + + myServer.start(); + + FhirContext ctx = new FhirContext(); + + HttpClientBuilder builder = HttpClientBuilder.create(); +// PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); +// builder.setConnectionManager(connectionManager); + builder.addInterceptorFirst(new HttpBasicAuthInterceptor("foobar", "boobear")); + + CloseableHttpClient httpClient = builder.build(); + ctx.getRestfulClientFactory().setHttpClient(httpClient); + + PatientClient client = ctx.newRestfulClient(PatientClient.class, "http://localhost:" + myPort + "/"); + + List actualPatients = client.searchForPatients(new StringDt("AAAABBBB")); + assertEquals(1, actualPatients.size()); + assertEquals("AAAABBBB", actualPatients.get(0).getNameFirstRep().getFamilyAsSingleString()); + + assertEquals("Basic Zm9vYmFyOmJvb2JlYXI=", myPatientProvider.getAuthorizationHeader()); + } + + + @After + public void after() throws Exception { + myServer.stop(); + } + + + public static class MyPatientResourceProvider implements IResourceProvider { + private String myAuthorizationHeader; + + public String getAuthorizationHeader() { + return myAuthorizationHeader; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Search + public List searchForPatients(@RequiredParam(name="fooParam") StringDt theFooParam, HttpServletRequest theRequest) { + + myAuthorizationHeader = theRequest.getHeader("authorization"); + + Patient retVal = new Patient(); + retVal.addName().addFamily(theFooParam.getValue()); + return Collections.singletonList(retVal); + } + + + } + + + private static interface PatientClient extends IBasicClient { + + @Search + public List searchForPatients(@RequiredParam(name="fooParam") StringDt theFooParam); + + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java index 3ff21f74805..ea09be0e458 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java @@ -52,9 +52,9 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; public class ClientTest { + private FhirContext ctx; private HttpClient httpClient; private HttpResponse httpResponse; - private FhirContext ctx; // atom-document-large.xml @@ -68,6 +68,133 @@ public class ClientTest { httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); } + private String getPatientFeedWithOneResult() { + //@formatter:off + String msg = "\n" + + "\n" + + "<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" + + "<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" + + "<published>2014-03-11T16:35:07-04:00</published>\n" + + "<author>\n" + + "<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" + + "</author>\n" + + "<entry>\n" + + "<content type=\"text/xml\">" + + "<Patient xmlns=\"http://hl7.org/fhir\">" + + "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>" + + "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>" + + "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>" + + "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>" + + "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>" + + "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>" + + "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />" + + "</Patient>" + + "</content>\n" + + " </entry>\n" + + "</feed>"; + //@formatter:on + return msg; + } + + @Test + public void testCreate() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200")); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + MethodOutcome response = client.createPatient(patient); + + assertEquals(HttpPost.class, capt.getValue().getClass()); + HttpPost post = (HttpPost) capt.getValue(); + assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); + assertEquals("100", response.getId().getValue()); + assertEquals("200", response.getVersionId().getValue()); + } + + @Test + public void testCreateBad() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8"))); + + try { + ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), StringContains.containsString("foobar")); + } + } + + @Test + public void testDelete() throws Exception { + + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setDetails("Hello"); + String resp = new FhirContext().newXmlParser().encodeResourceToString(oo); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + MethodOutcome response = client.deletePatient(new IdDt("1234")); + + assertEquals(HttpDelete.class, capt.getValue().getClass()); + assertEquals("http://foo/Patient/1234", capt.getValue().getURI().toString()); + assertEquals("Hello", response.getOperationOutcome().getIssueFirstRep().getDetails().getValue()); + } + + @Test + public void testDeleteNoResponse() throws Exception { + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 204, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + client.deleteDiagnosticReport(new IdDt("1234")); + + assertEquals(HttpDelete.class, capt.getValue().getClass()); + assertEquals("http://foo/DiagnosticReport/1234", capt.getValue().getURI().toString()); + } + + @Test + public void testGetConformance() throws Exception { + + String msg = IOUtils.toString(ClientTest.class.getResourceAsStream("/example-metadata.xml")); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + Conformance response = client.getServerConformanceStatement(); + + assertEquals("http://foo/metadata", capt.getValue().getURI().toString()); + assertEquals("Health Intersections", response.getPublisher().getValue()); + + } + @Test public void testHistoryResourceInstance() throws Exception { @@ -90,35 +217,35 @@ public class ClientTest { // Older resource { - BundleEntry olderEntry = response.getEntries().get(0); - assertEquals("222", olderEntry.getId().getValue()); - assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = olderEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(20000L)); - InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = olderEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } // Newer resource { - BundleEntry newerEntry = response.getEntries().get(1); - assertEquals("222", newerEntry.getId().getValue()); - assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = newerEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(30000L)); - InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = newerEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } } @@ -144,38 +271,38 @@ public class ClientTest { // Older resource { - BundleEntry olderEntry = response.getEntries().get(0); - assertEquals("222", olderEntry.getId().getValue()); - assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = olderEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(20000L)); - InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = olderEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } // Newer resource { - BundleEntry newerEntry = response.getEntries().get(1); - assertEquals("222", newerEntry.getId().getValue()); - assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = newerEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(30000L)); - InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = newerEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } } - + @Test public void testHistoryServer() throws Exception { @@ -198,35 +325,35 @@ public class ClientTest { // Older resource { - BundleEntry olderEntry = response.getEntries().get(0); - assertEquals("222", olderEntry.getId().getValue()); - assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = olderEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(20000L)); - InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = olderEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry olderEntry = response.getEntries().get(0); + assertEquals("222", olderEntry.getId().getValue()); + assertThat(olderEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/1")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = olderEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(20000L)); + InstantDt updActualRes = (InstantDt) olderEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = olderEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } // Newer resource { - BundleEntry newerEntry = response.getEntries().get(1); - assertEquals("222", newerEntry.getId().getValue()); - assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); - InstantDt pubExpected = new InstantDt(new Date(10000L)); - InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); - InstantDt pubActualBundle = newerEntry.getPublished(); - assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); - assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); - InstantDt updExpected = new InstantDt(new Date(30000L)); - InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - InstantDt updActualBundle = newerEntry.getUpdated(); - assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); - assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); + BundleEntry newerEntry = response.getEntries().get(1); + assertEquals("222", newerEntry.getId().getValue()); + assertThat(newerEntry.getLinkSelf().getValue(), StringEndsWith.endsWith("/Patient/222/_history/2")); + InstantDt pubExpected = new InstantDt(new Date(10000L)); + InstantDt pubActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.PUBLISHED); + InstantDt pubActualBundle = newerEntry.getPublished(); + assertEquals(pubExpected.getValueAsString(), pubActualRes.getValueAsString()); + assertEquals(pubExpected.getValueAsString(), pubActualBundle.getValueAsString()); + InstantDt updExpected = new InstantDt(new Date(30000L)); + InstantDt updActualRes = (InstantDt) newerEntry.getResource().getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + InstantDt updActualBundle = newerEntry.getUpdated(); + assertEquals(updExpected.getValueAsString(), updActualRes.getValueAsString()); + assertEquals(updExpected.getValueAsString(), updActualBundle.getValueAsString()); } } @@ -260,194 +387,10 @@ public class ClientTest { } - @Test - public void testCreate() throws Exception { + public void testSearchByDateRange() throws Exception { - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200")); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - MethodOutcome response = client.createPatient(patient); - - assertEquals(HttpPost.class, capt.getValue().getClass()); - HttpPost post = (HttpPost) capt.getValue(); - assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); - assertEquals("100", response.getId().getValue()); - assertEquals("200", response.getVersionId().getValue()); - } - - - @Test - public void testDelete() throws Exception { - - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().setDetails("Hello"); - String resp = new FhirContext().newXmlParser().encodeResourceToString(oo); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(resp), Charset.forName("UTF-8"))); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - MethodOutcome response = client.deletePatient(new IdDt("1234")); - - assertEquals(HttpDelete.class, capt.getValue().getClass()); - assertEquals("http://foo/Patient/1234", capt.getValue().getURI().toString()); - assertEquals("Hello", response.getOperationOutcome().getIssueFirstRep().getDetails().getValue()); - } - - @Test - public void testDeleteNoResponse() throws Exception { - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 204, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.deleteDiagnosticReport(new IdDt("1234")); - - assertEquals(HttpDelete.class, capt.getValue().getClass()); - assertEquals("http://foo/DiagnosticReport/1234", capt.getValue().getURI().toString()); - } - - - @Test - public void testUpdate() throws Exception { - - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200")); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - MethodOutcome response = client.updatePatient(new IdDt("100"), patient); - - assertEquals(HttpPut.class, capt.getValue().getClass()); - HttpPut post = (HttpPut) capt.getValue(); - assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100")); - assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); - assertEquals("100", response.getId().getValue()); - assertEquals("200", response.getVersionId().getValue()); - } - - /** - * Return a FHIR content type, but no content and make sure we handle this without crashing - */ - @Test - public void testUpdateWithEmptyResponse() throws Exception { - - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200")); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.updatePatient(new IdDt("100"), new IdDt("200"), patient); - } - - @Test - public void testUpdateWithVersion() throws Exception { - - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200")); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - MethodOutcome response = client.updatePatient(new IdDt("100"), new IdDt("200"), patient); - - assertEquals(HttpPut.class, capt.getValue().getClass()); - HttpPut post = (HttpPut) capt.getValue(); - assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100")); - assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); - assertThat(post.getFirstHeader("Content-Location").getValue(), StringEndsWith.endsWith("/Patient/100/_history/200")); - assertEquals("100", response.getId().getValue()); - assertEquals("200", response.getVersionId().getValue()); - } - - - @Test(expected=ResourceVersionConflictException.class) - public void testUpdateWithResourceConflict() throws Exception { - - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_409_CONFLICT, "Conflict")); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.updatePatient(new IdDt("100"), new IdDt("200"), patient); - } - - - @Test - public void testCreateBad() throws Exception { - - Patient patient = new Patient(); - patient.addIdentifier("urn:foo", "123"); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 400, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8"))); - - try { - ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient); - fail(); - }catch (InvalidRequestException e) { - assertThat(e.getMessage(), StringContains.containsString("foobar")); - } - } - - private Header[] toHeaderArray(String theName, String theValue) { - return new Header[] {new BasicHeader(theName, theValue)}; - } - - @Test - public void testVRead() throws Exception { - - //@formatter:off - String msg = "<Patient xmlns=\"http://hl7.org/fhir\">" - + "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>" - + "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>" - + "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>" - + "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>" - + "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>" - + "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>" - + "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />" - + "</Patient>"; - //@formatter:on + String msg = getPatientFeedWithOneResult(); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); @@ -456,11 +399,31 @@ public class ClientTest { when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - // Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123")); - Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999")); + DateRangeParam param = new DateRangeParam(); + param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01")); + param.setUpperBound(new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, "2021-01-01")); + List<Patient> response = client.getPatientByDateRange(param); - assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString()); - assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue()); + assertEquals("http://foo/Patient?dateRange=%3E%3D2011-01-01&dateRange=%3C%3D2021-01-01", capt.getValue().getURI().toString()); + + } + + @Test + public void testSearchByDob() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + List<Patient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02")); + + assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString()); + assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue()); } @@ -482,70 +445,7 @@ public class ClientTest { assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue()); } - - - @Test - public void testSearchByDateRange() throws Exception { - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - DateRangeParam param = new DateRangeParam(); - param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01")); - param.setUpperBound(new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, "2021-01-01")); - List<Patient> response = client.getPatientByDateRange(param); - - assertEquals("http://foo/Patient?dateRange=%3E%3D2011-01-01&dateRange=%3C%3D2021-01-01", capt.getValue().getURI().toString()); - - } - - - - - - @Test - public void testSearchByDob() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - List<Patient> response = client.getPatientByDob(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-02")); - - assertEquals("http://foo/Patient?birthdate=%3E%3D2011-01-02", capt.getValue().getURI().toString()); - assertEquals("PRP1660", response.get(0).getIdentifier().get(0).getValue().getValue()); - - } - - @Test - public void testSearchWithIncludes() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.getPatientWithIncludes(new StringDt("aaa"), Arrays.asList(new PathSpecification[] {new PathSpecification("inc1"), new PathSpecification("inc2")})); - - assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1&_include=inc2", capt.getValue().getURI().toString()); - - } - @Test public void testSearchComposite() throws Exception { @@ -566,11 +466,11 @@ public class ClientTest { assertEquals("http://foo/Patient?ids=foo%7Cbar%2Cbaz%7Cboz", capt.getValue().getURI().toString()); } - - @Test - public void testGetConformance() throws Exception { - String msg = IOUtils.toString(ClientTest.class.getResourceAsStream("/example-metadata.xml")); + @Test + public void testSearchNamedQueryNoParams() throws Exception { + + String msg = getPatientFeedWithOneResult(); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); @@ -579,10 +479,45 @@ public class ClientTest { when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - Conformance response = client.getServerConformanceStatement(); + client.getPatientNoParams(); - assertEquals("http://foo/metadata", capt.getValue().getURI().toString()); - assertEquals("Health Intersections", response.getPublisher().getValue()); + assertEquals("http://foo/Patient?_query=someQueryNoParams", capt.getValue().getURI().toString()); + + } + + @Test + public void testSearchNamedQueryOneParam() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + client.getPatientOneParam(new StringDt("BB")); + + assertEquals("http://foo/Patient?_query=someQueryOneParam¶m1=BB", capt.getValue().getURI().toString()); + + } + + @Test + public void testSearchWithIncludes() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + client.getPatientWithIncludes(new StringDt("aaa"), Arrays.asList(new PathSpecification[] { new PathSpecification("inc1"), new PathSpecification("inc2") })); + + assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1&_include=inc2", capt.getValue().getURI().toString()); } @@ -619,54 +554,95 @@ public class ClientTest { } @Test - public void testSearchNamedQueryOneParam() throws Exception { + public void testUpdate() throws Exception { - String msg = getPatientFeedWithOneResult(); + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200")); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.getPatientOneParam(new StringDt("BB")); - - assertEquals("http://foo/Patient?_query=someQueryOneParam¶m1=BB", capt.getValue().getURI().toString()); + MethodOutcome response = client.updatePatient(new IdDt("100"), patient); + assertEquals(HttpPut.class, capt.getValue().getClass()); + HttpPut post = (HttpPut) capt.getValue(); + assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100")); + assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); + assertEquals("100", response.getId().getValue()); + assertEquals("200", response.getVersionId().getValue()); } - + + /** + * Return a FHIR content type, but no content and make sure we handle this without crashing + */ @Test - public void testSearchNamedQueryNoParams() throws Exception { + public void testUpdateWithEmptyResponse() throws Exception { - String msg = getPatientFeedWithOneResult(); + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); when(httpClient.execute(capt.capture())).thenReturn(httpResponse); - when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200")); ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); - client.getPatientNoParams(); - - assertEquals("http://foo/Patient?_query=someQueryNoParams", capt.getValue().getURI().toString()); - + client.updatePatient(new IdDt("100"), new IdDt("200"), patient); } - - private String getPatientFeedWithOneResult() { + + @Test(expected = ResourceVersionConflictException.class) + public void testUpdateWithResourceConflict() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_409_CONFLICT, "Conflict")); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + client.updatePatient(new IdDt("100"), new IdDt("200"), patient); + } + + @Test + public void testUpdateWithVersion() throws Exception { + + Patient patient = new Patient(); + patient.addIdentifier("urn:foo", "123"); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200")); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + MethodOutcome response = client.updatePatient(new IdDt("100"), new IdDt("200"), patient); + + assertEquals(HttpPut.class, capt.getValue().getClass()); + HttpPut post = (HttpPut) capt.getValue(); + assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100")); + assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient")); + assertThat(post.getFirstHeader("Content-Location").getValue(), StringEndsWith.endsWith("/Patient/100/_history/200")); + assertEquals("100", response.getId().getValue()); + assertEquals("200", response.getVersionId().getValue()); + } + + @Test + public void testVRead() throws Exception { + //@formatter:off - String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" + - "<title/>\n" + - "<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" + - "<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" + - "<published>2014-03-11T16:35:07-04:00</published>\n" + - "<author>\n" + - "<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" + - "</author>\n" + - "<entry>\n" + - "<content type=\"text/xml\">" - + "<Patient xmlns=\"http://hl7.org/fhir\">" + String msg = "<Patient xmlns=\"http://hl7.org/fhir\">" + "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>" + "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>" + "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>" @@ -674,11 +650,25 @@ public class ClientTest { + "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>" + "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>" + "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />" - + "</Patient>" - + "</content>\n" - + " </entry>\n" - + "</feed>"; + + "</Patient>"; //@formatter:on - return msg; + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(httpClient.execute(capt.capture())).thenReturn(httpResponse); + when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo"); + // Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123")); + Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999")); + + assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString()); + assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue()); + + } + + private Header[] toHeaderArray(String theName, String theValue) { + return new Header[] { new BasicHeader(theName, theValue) }; } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java index 059d64d1f84..78cf1bdb6d8 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerInvalidDefinitionTest.java @@ -30,6 +30,20 @@ public class ServerInvalidDefinitionTest { } } + @Test + public void testPrivateResourceProvider() { + RestfulServer srv = new RestfulServer(); + srv.setResourceProviders(new PrivateResourceProvider()); + + try { + srv.init(); + fail(); + } catch (ServletException e) { + assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException")); + assertThat(e.getCause().toString(), StringContains.containsString("public")); + } + } + /** * Normal, should initialize properly */ @@ -40,8 +54,23 @@ public class ServerInvalidDefinitionTest { srv.init(); } + private static class PrivateResourceProvider implements IResourceProvider + { + + @Override + public Class<? extends IResource> getResourceType() { + return Patient.class; + } + + @SuppressWarnings("unused") + @Read + public Patient read(@IdParam IdDt theId) { + return null; + } + + } - private static class NonInstantiableTypeForResourceProvider implements IResourceProvider + public static class NonInstantiableTypeForResourceProvider implements IResourceProvider { @Override @@ -57,7 +86,7 @@ public class ServerInvalidDefinitionTest { } - private static class InstantiableTypeForResourceProvider implements IResourceProvider + public static class InstantiableTypeForResourceProvider implements IResourceProvider { @Override