diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index a0b1410d9e3..246ce9a2d93 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -6,6 +6,11 @@ HAPI FHIR Changelog + + + Allow generic client + + having multiple ways of accomplishing the same thing. This means that a number of existing classes 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 2fa5a53c3ac..284bc4b72d9 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 @@ -74,11 +74,13 @@ import ca.uhn.fhir.rest.method.DeleteMethodBinding; import ca.uhn.fhir.rest.method.HistoryMethodBinding; import ca.uhn.fhir.rest.method.HttpDeleteClientInvocation; import ca.uhn.fhir.rest.method.HttpGetClientInvocation; +import ca.uhn.fhir.rest.method.HttpPostClientInvocation; import ca.uhn.fhir.rest.method.HttpSimpleGetClientInvocation; import ca.uhn.fhir.rest.method.IClientResponseHandler; import ca.uhn.fhir.rest.method.MethodUtil; import ca.uhn.fhir.rest.method.ReadMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.method.SearchStyleEnum; import ca.uhn.fhir.rest.method.TransactionMethodBinding; import ca.uhn.fhir.rest.method.ValidateMethodBinding; import ca.uhn.fhir.rest.server.Constants; @@ -420,7 +422,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); @@ -442,7 +445,7 @@ public class GenericClient extends BaseClient implements IGenericClient { myResource = parseResourceBody(myResourceBody); } myId = getPreferredId(myResource, myId); - + BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext); RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); @@ -455,7 +458,6 @@ public class GenericClient extends BaseClient implements IGenericClient { } - @Override public ICreateTyped resource(IResource theResource) { Validate.notNull(theResource, "Resource can not be null"); @@ -483,7 +485,7 @@ public class GenericClient extends BaseClient implements IGenericClient { } } - + private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped { private IdDt myId; @@ -498,10 +500,10 @@ public class GenericClient extends BaseClient implements IGenericClient { if (myId == null) { myId = myResource.getId(); } - if (myId==null || myId.hasIdPart() == false) { + if (myId == null || myId.hasIdPart() == false) { throw new InvalidRequestException("No ID supplied for resource to update, can not invoke server"); } - + BaseHttpClientInvocation invocation = MethodUtil.createUpdateInvocation(myResource, myResourceBody, myId, myContext); RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); @@ -533,8 +535,8 @@ public class GenericClient extends BaseClient implements IGenericClient { if (theId == null) { throw new NullPointerException("theId can not be null"); } - if (theId.hasIdPart()==false) { - throw new NullPointerException("theId must not be blank and must contain an ID, found: "+theId.getValue()); + if (theId.hasIdPart() == false) { + throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId.getValue()); } myId = theId; return this; @@ -546,9 +548,9 @@ public class GenericClient extends BaseClient implements IGenericClient { throw new NullPointerException("theId can not be null"); } if (isBlank(theId)) { - throw new NullPointerException("theId must not be blank and must contain an ID, found: "+theId); + throw new NullPointerException("theId must not be blank and must contain an ID, found: " + theId); } - myId=new IdDt(theId); + myId = new IdDt(theId); return this; } @@ -712,7 +714,8 @@ public class GenericClient extends BaseClient implements IGenericClient { private final class OperationOutcomeResponseHandler implements IClientResponseHandler { @Override - public OperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { + public OperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, + BaseServerResponseException { EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); if (respType == null) { return null; @@ -739,7 +742,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 = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders); return response; } @@ -754,7 +758,8 @@ public class GenericClient extends BaseClient implements IGenericClient { } @Override - public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { + public List invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, + BaseServerResponseException { return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources(); } } @@ -799,6 +804,8 @@ public class GenericClient extends BaseClient implements IGenericClient { private Class myResourceType; private List mySort = new ArrayList(); + private SearchStyleEnum mySearchStyle; + public SearchInternal() { myResourceType = null; myResourceName = null; @@ -838,7 +845,36 @@ public class GenericClient extends BaseClient implements IGenericClient { } BundleResponseHandler binding = new BundleResponseHandler(myResourceType); - HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, myResourceName); + + SearchStyleEnum searchStyle = mySearchStyle; + if (searchStyle == null) { + int length = 0; + for (Entry> nextEntry : params.entrySet()) { + length += nextEntry.getKey().length(); + for (String next : nextEntry.getValue()) { + length += next.length(); + } + } + + if (length < 5000) { + searchStyle = SearchStyleEnum.GET; + } else { + searchStyle = SearchStyleEnum.POST; + } + } + + BaseHttpClientInvocation invocation; + switch (searchStyle) { + case GET: + default: + invocation = new HttpGetClientInvocation(params, myResourceName); + break; + case GET_WITH_SEARCH: + invocation = new HttpGetClientInvocation(params, myResourceName, Constants.PARAM_SEARCH); + break; + case POST: + invocation = new HttpPostClientInvocation(myContext, params, myResourceName, Constants.PARAM_SEARCH); + } return invoke(params, binding, invocation); @@ -901,6 +937,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IQuery usingStyle(SearchStyleEnum theStyle) { + mySearchStyle = theStyle; + return this; + } + } private class SortInternal implements ISort { @@ -947,7 +989,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); @@ -1014,5 +1057,4 @@ public class GenericClient extends BaseClient implements IGenericClient { return new UpdateInternal(); } - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index db5847f7cb1..309cfb936a2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.rest.gclient; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.rest.method.SearchStyleEnum; public interface IQuery extends IClientExecutable { @@ -35,4 +36,13 @@ public interface IQuery extends IClientExecutable { IQuery limitTo(int theLimitTo); + /** + * Forces the query to perform the search using the given method (allowable methods are described in the + * FHIR Specification Section 2.1.11) + * + * @see SearchStyleEnum + * @since 0.6 + */ + IQuery usingStyle(SearchStyleEnum theStyle); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java index e7839663898..2c2c3077a7d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringClientParam.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.gclient; * #L% */ +import java.util.Arrays; +import java.util.List; + import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.server.Constants; @@ -41,10 +44,8 @@ public class StringClientParam implements IParam { return myParamName; } - /** - * The string matches the given value (servers will often, but are not required to) implement this as a left match, - * meaning that a value of "smi" would match "smi" and "smith". + * The string matches the given value (servers will often, but are not required to) implement this as a left match, meaning that a value of "smi" would match "smi" and "smith". */ public IStringMatch matches() { return new StringMatches(); @@ -59,10 +60,28 @@ public class StringClientParam implements IParam { public interface IStringMatch { + /** + * Requests that resources be returned which match the given value + */ ICriterion value(String theValue); + /** + * Requests that resources be returned which match ANY of the given values (this is an OR search). Note that to specify an AND search, simply add a subsequent {@link IQuery#where(ICriterion) + * where} criteria with the same parameter. + */ + ICriterion values(List theValues); + + /** + * Requests that resources be returned which match the given value + */ ICriterion value(StringDt theValue); + /** + * Requests that resources be returned which match ANY of the given values (this is an OR search). Note that to specify an AND search, simply add a subsequent {@link IQuery#where(ICriterion) + * where} criteria with the same parameter. + */ + ICriterion values(String... theValues); + } private class StringExactly implements IStringMatch { @@ -75,6 +94,16 @@ public class StringClientParam implements IParam { public ICriterion value(StringDt theValue) { return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue.getValue()); } + + @Override + public ICriterion values(List theValue) { + return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, theValue); + } + + @Override + public ICriterion values(String... theValues) { + return new StringCriterion(getParamName() + Constants.PARAMQUALIFIER_STRING_EXACT, Arrays.asList(theValues)); + } } private class StringMatches implements IStringMatch { @@ -87,6 +116,17 @@ public class StringClientParam implements IParam { public ICriterion value(StringDt theValue) { return new StringCriterion(getParamName(), theValue.getValue()); } + + @Override + public ICriterion values(List theValue) { + return new StringCriterion(getParamName(), theValue); + } + + @Override + public ICriterion values(String... theValues) { + return new StringCriterion(getParamName(), Arrays.asList(theValues)); + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java index 364f21e6099..40518e2e52a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringCriterion.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.rest.gclient; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + import ca.uhn.fhir.rest.param.ParameterUtil; /* @@ -28,8 +32,23 @@ class StringCriterion implements ICriterion, ICriterionInte private String myName; public StringCriterion(String theName, String theValue) { - myValue = theValue; myName=theName; + myValue = ParameterUtil.escape(theValue); + } + + public StringCriterion(String theName, List theValue) { + myName=theName; + StringBuilder b = new StringBuilder(); + for (String next : theValue) { + if (StringUtils.isBlank(next)) { + continue; + } + if (b.length() > 0) { + b.append(','); + } + b.append(ParameterUtil.escape(next)); + } + myValue = b.toString(); } @Override @@ -39,7 +58,7 @@ class StringCriterion implements ICriterion, ICriterionInte @Override public String getParameterValue() { - return ParameterUtil.escape(myValue); + return myValue; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java index d052b63c539..0388c398ac3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java @@ -1,6 +1,10 @@ package ca.uhn.fhir.rest.gclient; import static org.apache.commons.lang3.StringUtils.defaultString; + +import java.util.Arrays; +import java.util.List; + import ca.uhn.fhir.model.dstu.composite.IdentifierDt; /* @@ -66,7 +70,17 @@ public class TokenClientParam implements IParam { public ICriterion identifier(IdentifierDt theIdentifier) { return new TokenCriterion(getParamName(), theIdentifier.getSystem().getValueAsString(), theIdentifier.getValue().getValue()); } - }; + + @Override + public ICriterion identifiers(List theIdentifiers) { + return new TokenCriterion(getParamName(), theIdentifiers); + } + + @Override + public ICriterion identifiers(IdentifierDt... theIdentifiers) { + return new TokenCriterion(getParamName(), Arrays.asList(theIdentifiers)); + } +}; } public interface IMatches { @@ -118,6 +132,27 @@ public class TokenClientParam implements IParam { * @return A criterion */ ICriterion identifier(IdentifierDt theIdentifier); + + /** + * Creates a search criterion that matches against the given collection of identifiers (system and code if both are present, or whatever is present). + * In the query URL that is generated, identifiers will be joined with a ',' to create an OR query. + * + * @param theIdentifier + * The identifier + * @return A criterion + */ + ICriterion identifiers(List theIdentifiers); + + /** + * Creates a search criterion that matches against the given collection of identifiers (system and code if both are present, or whatever is present). + * In the query URL that is generated, identifiers will be joined with a ',' to create an OR query. + * + * @param theIdentifier + * The identifier + * @return A criterion + */ + ICriterion identifiers(IdentifierDt... theIdentifiers); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java index 973cd517174..9981148aeed 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenCriterion.java @@ -20,8 +20,11 @@ package ca.uhn.fhir.rest.gclient; * #L% */ +import java.util.List; + import org.apache.commons.lang3.StringUtils; +import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.rest.param.ParameterUtil; class TokenCriterion implements ICriterion, ICriterionInternal { @@ -31,15 +34,36 @@ class TokenCriterion implements ICriterion, ICriterionInternal public TokenCriterion(String theName, String theSystem, String theCode) { myName = theName; + myValue=toValue(theSystem, theCode); + } + + private String toValue(String theSystem, String theCode) { String system = ParameterUtil.escape(theSystem); String code = ParameterUtil.escape(theCode); + String value; if (StringUtils.isNotBlank(system)) { - myValue = system + "|" + StringUtils.defaultString(code); + value = system + "|" + StringUtils.defaultString(code); } else if (system == null) { - myValue = StringUtils.defaultString(code); + value = StringUtils.defaultString(code); } else { - myValue = "|" + StringUtils.defaultString(code); + value = "|" + StringUtils.defaultString(code); } + return value; + } + + public TokenCriterion(String theParamName, List theValue) { + myName=theParamName; + StringBuilder b = new StringBuilder(); + for (IdentifierDt next : theValue) { + if (next.getSystem().isEmpty() && next.getValue().isEmpty()) { + continue; + } + if (b.length() > 0) { + b.append(','); + } + b.append(toValue(next.getSystem().getValueAsString(), next.getValue().getValue())); + } + myValue = b.toString(); } @Override 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 88c22b2007c..252361aca22 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 @@ -22,15 +22,21 @@ package ca.uhn.fhir.rest.method; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.AbstractHttpEntity; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicNameValuePair; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; @@ -42,6 +48,7 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvocation { @@ -53,6 +60,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca private final Bundle myBundle; private final String myContents; private boolean myContentsIsBundle; + private Map> myParams; public BaseHttpClientInvocationWithContents(FhirContext theContext, IResource theResource, String theUrlExtension) { super(); @@ -112,6 +120,18 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca myContentsIsBundle = theIsBundle; } + public BaseHttpClientInvocationWithContents(FhirContext theContext, Map> theParams, String... theUrlExtension) { + myContext = theContext; + myResource = null; + myTagList = null; + myUrlExtension = StringUtils.join(theUrlExtension, '/'); + myResources = null; + myBundle = null; + myContents = null; + myContentsIsBundle = false; + myParams = theParams; + } + @Override public HttpRequestBase asHttpRequest(String theUrlBase, Map> theExtraParams, EncodingEnum theEncoding) throws DataFormatException { StringBuilder b = new StringBuilder(); @@ -145,31 +165,43 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca parser = myContext.newXmlParser(); } - String contents; - if (myTagList != null) { - contents = parser.encodeTagListToString(myTagList); - contentType = encoding.getResourceContentType(); - } else if (myBundle != null) { - contents = parser.encodeBundleToString(myBundle); - contentType = encoding.getBundleContentType(); - } else if (myResources != null) { - Bundle bundle = RestfulServer.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size()); - contents = parser.encodeBundleToString(bundle); - contentType = encoding.getBundleContentType(); - } else if (myContents != null) { - contents = myContents; - if (myContentsIsBundle) { - contentType = encoding.getBundleContentType(); - } else { - contentType = encoding.getResourceContentType(); + AbstractHttpEntity entity; + if (myParams != null) { + List parameters = new ArrayList(); + for (Entry> nextParam : myParams.entrySet()) { + parameters.add(new BasicNameValuePair(nextParam.getKey(), StringUtils.join(nextParam.getValue(), ','))); + } + try { + entity = new UrlEncodedFormEntity(parameters, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalErrorException("Server does not support UTF-8 (should not happen)", e); } } else { - contents = parser.encodeResourceToString(myResource); - contentType = encoding.getResourceContentType(); + String contents; + if (myTagList != null) { + contents = parser.encodeTagListToString(myTagList); + contentType = encoding.getResourceContentType(); + } else if (myBundle != null) { + contents = parser.encodeBundleToString(myBundle); + contentType = encoding.getBundleContentType(); + } else if (myResources != null) { + Bundle bundle = RestfulServer.createBundleFromResourceList(myContext, "", myResources, "", "", myResources.size()); + contents = parser.encodeBundleToString(bundle); + contentType = encoding.getBundleContentType(); + } else if (myContents != null) { + contents = myContents; + if (myContentsIsBundle) { + contentType = encoding.getBundleContentType(); + } else { + contentType = encoding.getResourceContentType(); + } + } else { + contents = parser.encodeResourceToString(myResource); + contentType = encoding.getResourceContentType(); + } + entity = new StringEntity(contents, ContentType.create(contentType, "UTF-8")); } - StringEntity entity = new StringEntity(contents, ContentType.create(contentType, "UTF-8")); - HttpRequestBase retVal = createRequest(url, entity); super.addHeadersToRequest(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 4a1a9c834b8..c1a65892ccc 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 @@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.method; */ import java.util.List; +import java.util.Map; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.AbstractHttpEntity; @@ -56,6 +57,11 @@ public class HttpPostClientInvocation extends BaseHttpClientInvocationWithConten } + public HttpPostClientInvocation(FhirContext theContext, Map> theParams, String... theUrlExtension) { + super(theContext, theParams, theUrlExtension); + } + + @Override protected HttpPost createRequest(String url, AbstractHttpEntity theEntity) { HttpPost retVal = new HttpPost(url); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParamBinder.java index ebd85ec7c4a..f043b83992d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParamBinder.java @@ -31,6 +31,6 @@ interface IParamBinder { List> encode(FhirContext theContext, Object theString) throws InternalErrorException; - Object parse(List theList) throws InternalErrorException, InvalidRequestException; + Object parse(String theName, List theList) throws InternalErrorException, InvalidRequestException; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java index 6dde522b265..f6eeb28be2f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterAndBinder.java @@ -43,7 +43,7 @@ final class QueryParameterAndBinder extends BaseBinder> im } @Override - public Object parse(List theString) throws InternalErrorException, InvalidRequestException { + public Object parse(String theName, List theString) throws InternalErrorException, InvalidRequestException { IQueryParameterAnd dt; try { dt = newInstance(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java index b0cb334395a..e9268c4eca4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterOrBinder.java @@ -44,7 +44,7 @@ final class QueryParameterOrBinder extends BaseBinder> impl } @Override - public Object parse(List theString) throws InternalErrorException, InvalidRequestException { + public Object parse(String theName, List theString) throws InternalErrorException, InvalidRequestException { IQueryParameterOr dt; try { dt = newInstance(); @@ -52,7 +52,7 @@ final class QueryParameterOrBinder extends BaseBinder> impl return dt; } if (theString.size() > 1) { - throw new InvalidRequestException("Multiple values detected"); + throw new InvalidRequestException("Multiple values detected for non-repeatable parameter '" + theName + "'. This server is not configured to allow multiple (AND/OR) values for this param."); } dt.setValuesAsQueryTokens(theString.get(0)); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java index 1b7b7c4f5e7..cef4cf03b45 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/QueryParameterTypeBinder.java @@ -46,7 +46,7 @@ final class QueryParameterTypeBinder extends BaseBinder imp } @Override - public Object parse(List theParams) throws InternalErrorException, InvalidRequestException { + public Object parse(String theName, List theParams) throws InternalErrorException, InvalidRequestException { String value = theParams.get(0).get(0); if (StringUtils.isBlank(value)) { return null; @@ -58,7 +58,7 @@ final class QueryParameterTypeBinder extends BaseBinder imp return dt; } if (theParams.size() > 1 || theParams.get(0).size() > 1) { - throw new InvalidRequestException("Multiple values detected"); + throw new InvalidRequestException("Multiple values detected for non-repeatable parameter '" + theName + "'. This server is not configured to allow multiple (AND/OR) values for this param."); } dt.setValueAsQueryToken(theParams.get(0).getQualifier(), value); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java index 6192a09957a..f5409f304d4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchParameter.java @@ -184,7 +184,7 @@ public class SearchParameter extends BaseQueryParameter { */ @Override public Object parse(List theString) throws InternalErrorException, InvalidRequestException { - return myParamBinder.parse(theString); + return myParamBinder.parse(getName(), theString); } public void setCompositeTypes(Class[] theCompositeTypes) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchStyleEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchStyleEnum.java new file mode 100644 index 00000000000..1ac8c4e7b5b --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchStyleEnum.java @@ -0,0 +1,53 @@ +package ca.uhn.fhir.rest.method; + +/* + * #%L + * HAPI FHIR - Core 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% + */ + +/** + * Enumerated type to represent the various allowable syntax for a search/query + * as described in the + * FHIR Specification Section 2.1.11 + */ +public enum SearchStyleEnum { + + /** + * This is the most common (and generally the default) behaviour. Performs the search using the style: + *
+ * GET [base]/[resource type]?[params] + */ + GET, + + /** + * This is the most common (and generally the default) behaviour. Performs the search using the style: + *
+ * GET [base]/[resource type]/_search?[params] + */ + GET_WITH_SEARCH, + + /** + * This is the most common (and generally the default) behaviour. Performs the search using the style: + *
+ * POST [base]/[resource type]/_search + *
+ * and the params in a form encoded POST body. + */ + POST + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/StringBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/StringBinder.java index 1175148e426..8839bdb9868 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/StringBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/StringBinder.java @@ -42,12 +42,12 @@ final class StringBinder implements IParamBinder { } @Override - public Object parse(List theParams) throws InternalErrorException, InvalidRequestException { + public Object parse(String theName, List theParams) throws InternalErrorException, InvalidRequestException { if (theParams.size() == 0 || theParams.get(0).size() == 0) { return ""; } if (theParams.size() > 1 || theParams.get(0).size() > 1) { - throw new InvalidRequestException("Multiple values detected"); + throw new InvalidRequestException("Multiple values detected for non-repeatable parameter '" + theName + "'. This server is not configured to allow multiple (AND) values for this param."); } return theParams.get(0).get(0); 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 05d42adb650..4f2c9b4b14b 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 @@ -149,7 +149,7 @@ public class RestfulServer extends HttpServlet { 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"); + throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class ust be public"); } } diff --git a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java index 0e70994bd67..97920283720 100644 --- a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java +++ b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java @@ -13,6 +13,7 @@ import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.method.SearchStyleEnum; public class GenericClientExample { @@ -100,10 +101,25 @@ Bundle response = client.search() .forResource(Patient.class) .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01")) .and(Patient.PROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health"))) - .andLogRequestAndResponse(true) .execute(); //END SNIPPET: search +//START SNIPPET: searchOr +response = client.search() + .forResource(Patient.class) + .where(Patient.FAMILY.matches().values("Smith", "Smyth")) + .execute(); +//END SNIPPET: searchOr + +//START SNIPPET: searchAnd +response = client.search() + .forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")) + .where(Patient.ADDRESS.matches().values("Ontario")) + .where(Patient.ADDRESS.matches().values("Canada")) + .execute(); +//END SNIPPET: searchAnd + //START SNIPPET: searchAdv response = client.search() .forResource(Patient.class) @@ -117,6 +133,15 @@ response = client.search() .execute(); //END SNIPPET: searchAdv +//START SNIPPET: searchPost +response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("Tester")) + .usingStyle(SearchStyleEnum.POST) + .execute(); +//END SNIPPET: searchPost + + //START SNIPPET: searchComposite response = client.search() .forResource("Observation") 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 8abdb98f29a..2a24d69c220 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml @@ -99,6 +99,30 @@ value="src/site/example/java/example/GenericClientExample.java" /> +

Search - Multi-valued Parameters (ANY/OR)

+

+ To search for a set of possible values where ANY should be matched, + you can provide multiple values to a parameter, as shown in the example below. + This leads to a URL resembling ?family=Smith,Smyth +

+ + + + + +

Search - Multi-valued Parameters (ALL/AND)

+

+ To search for a set of possible values where ALL should be matched, + you can provide multiple instances of a marameter, as shown in the example below. + This leads to a URL resembling ?address=Toronto&address=Ontario&address=Canada +

+ + + + +

Search - Paging

If the server supports paging results, the client has a page method @@ -123,7 +147,7 @@ value="src/site/example/java/example/GenericClientExample.java" /> -

Search - Query Options

+

Search - Other Query Options

The fluent search also has methods for sorting, limiting, specifying JSON encoding, etc. @@ -133,6 +157,19 @@ + +

Search - Using HTTP POST or GET with _search

+

+ The FHIR specification allows several styles of search (HTTP POST, a GET with _search at the end of the URL, etc.) + The usingStyle() method controls which style to use. By default, GET style is used + unless the client detects that the request would result in a very long URL (over 8000 chars) in which + case the client automatically switches to POST. +

+ + + + diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index 66d86cc3e3b..6080016d255 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -1,7 +1,11 @@ package ca.uhn.fhir.rest.client; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.StringReader; import java.net.URLEncoder; @@ -10,10 +14,12 @@ import java.util.Arrays; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; +import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; @@ -41,6 +47,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; +import ca.uhn.fhir.rest.method.SearchStyleEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -48,13 +55,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class GenericClientTest { private static FhirContext myCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientTest.class); private HttpClient myHttpClient; - private HttpResponse myHttpResponse; - @BeforeClass - public static void beforeClass() { - myCtx = new FhirContext(); - } + private HttpResponse myHttpResponse; @Before public void before() { @@ -65,37 +69,52 @@ public class GenericClientTest { myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); } - @Test - public void testCreateWithTagNonFluent() throws Exception { - - Patient p1 = new Patient(); - p1.addIdentifier("foo:bar", "12345"); - p1.addName().addFamily("Smith").addGiven("John"); - TagList list = new TagList(); - list.addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); - ResourceMetadataKeyEnum.TAG_LIST.put(p1, list); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - MethodOutcome outcome = client.create(p1); - assertEquals("44", outcome.getId().getIdPart()); - assertEquals("22", outcome.getId().getVersionIdPart()); - - assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); - assertEquals("POST", capt.getValue().getMethod()); - Header catH = capt.getValue().getFirstHeader("Category"); - assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH); - assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue()); + 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; } + private String getResourceResult() { + //@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 + return msg; + } + + @Test public void testCreateWithTag() throws Exception { @@ -140,7 +159,621 @@ public class GenericClientTest { } + @Test + public void testCreateWithTagNonFluent() throws Exception { + + Patient p1 = new Patient(); + p1.addIdentifier("foo:bar", "12345"); + p1.addName().addFamily("Smith").addGiven("John"); + TagList list = new TagList(); + list.addTag("http://hl7.org/fhir/tag", "urn:happytag", "This is a happy resource"); + ResourceMetadataKeyEnum.TAG_LIST.put(p1, list); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + MethodOutcome outcome = client.create(p1); + assertEquals("44", outcome.getId().getIdPart()); + assertEquals("22", outcome.getId().getVersionIdPart()); + + assertEquals("http://example.com/fhir/Patient", capt.getValue().getURI().toString()); + assertEquals("POST", capt.getValue().getMethod()); + Header catH = capt.getValue().getFirstHeader("Category"); + assertNotNull(Arrays.asList(capt.getValue().getAllHeaders()).toString(), catH); + assertEquals("urn:happytag; label=\"This is a happy resource\"; scheme=\"http://hl7.org/fhir/tag\"", catH.getValue()); + } + + @Test + public void testDelete() throws Exception { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().addLocation().setValue("testDelete01"); + String ooStr = myCtx.newXmlParser().encodeResourceToString(oo); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); + when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ooStr), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + OperationOutcome outcome = client.delete().resourceById("Patient", "123").execute(); + + assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); + assertEquals("DELETE", capt.getValue().getMethod()); + assertEquals("testDelete01",outcome.getIssueFirstRep().getLocationFirstRep().getValue()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8"))); + outcome = client.delete().resourceById(new IdDt("Location", "123","456")).prettyPrint().encodedJson().execute(); + + assertEquals("http://example.com/fhir/Location/123?_format=json&_pretty=true", capt.getAllValues().get(1).getURI().toString()); + assertEquals("DELETE", capt.getValue().getMethod()); + assertEquals(null,outcome); + + } + + @Test + public void testGetTags() throws Exception { + + TagList tagList = new TagList(); + tagList.add(new Tag("CCC", "AAA", "BBB")); + String msg = myCtx.newXmlParser().encodeTagListToString(tagList); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + TagList response = client.getTags() + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/_tags", capt.getValue().getURI().toString()); + assertEquals(1, response.size()); + assertEquals("CCC", response.get(0).getScheme()); + + // Now for patient + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + //@formatter:off + response = client.getTags().forResource(Patient.class) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient/_tags", capt.getValue().getURI().toString()); + assertEquals(1, response.size()); + assertEquals("CCC", response.get(0).getScheme()); + + } + + + @Test + public void testRead() throws Exception { + + String msg = getResourceResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + Header[] headers = new Header[2]; + headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"); + headers[1] = new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"); + when(myHttpResponse.getAllHeaders()).thenReturn(headers); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Patient response = client.read(Patient.class, new IdDt("Patient/1234")); + //@formatter:on + + assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal")); + + assertEquals("http://foo.com/Patient/123/_history/2333", response.getId().getValue()); + + InstantDt lm = (InstantDt) response.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); + lm.setTimeZoneZulu(true); + assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchAllResources() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forAllResources() + .where(Patient.NAME.matches().value("james")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/?name=james", capt.getValue().getURI().toString()); + + } + @SuppressWarnings("unused") + @Test + public void testSearchByComposite() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); + + //@formatter:off + Bundle response = client.search() + .forResource("Observation") + .where(Observation.NAME_VALUE_DATE + .withLeft(Observation.NAME.exactly().code("FOO$BAR")) + .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01")) + ) + .execute(); + //@formatter:on + + assertEquals("http://foo/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01","UTF-8"), capt.getValue().getURI().toString()); + + } + + + @SuppressWarnings("unused") + @Test + public void testSearchByDate() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .encodedJson() + .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) + .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .include(Patient.INCLUDE_MANAGINGORGANIZATION) + .sort().ascending(Patient.BIRTHDATE) + .sort().descending(Patient.NAME) + .limitTo(123) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByNumberExact() throws Exception { + + String msg = new FhirContext().newXmlParser().encodeBundleToString(new Bundle()); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Observation.class) + .where(Observation.VALUE_QUANTITY.greaterThan().number(123).andUnits("foo", "bar")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Observation?value-quantity=%3E123%7Cfoo%7Cbar", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByQuantity() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .where(Encounter.LENGTH.exactly().number(123)) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?length=123", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByReferenceProperty() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + //@formatter:off + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + Bundle response = client.search() + .forResource(Patient.class) + .where(Patient.PROVIDER.hasChainedProperty(Organization.NAME.matches().value("ORG0"))) + .execute(); + + assertEquals("http://example.com/fhir/Patient?provider.name=ORG0", capt.getValue().getURI().toString()); + //@formatter:on + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByReferenceSimple() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.PROVIDER.hasId("123")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?provider=123", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByString() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + //@formatter:off + response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().values("AAA", "BBB", "C,C")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?name=" + URLEncoder.encode("AAA,BBB,C\\,C","UTF-8"), capt.getAllValues().get(1).getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByStringExact() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matchesExactly().value("james")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?name%3Aexact=james", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByToken() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + //@formatter:off + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().code("ZZZ")) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?identifier=ZZZ", capt.getAllValues().get(1).getURI().toString()); + + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + //@formatter:off + response = client.search() + .forResource("Patient") + .where(Patient.IDENTIFIER.exactly().identifiers(new ca.uhn.fhir.model.dstu.composite.IdentifierDt("A", "B"), new ca.uhn.fhir.model.dstu.composite.IdentifierDt("C", "D"))) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient?identifier=" + URLEncoder.encode("A|B,C|D", "UTF-8"), capt.getAllValues().get(2).getURI().toString()); + + } + + + @SuppressWarnings("unused") + @Test + public void testSearchUsingPost() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .usingStyle(SearchStyleEnum.POST) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); + + HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); + UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); + String string = IOUtils.toString(ent.getContent()); + ourLog.info(string); + assertEquals("name=james", string); + } + + + @SuppressWarnings("unused") + @Test + public void testSearchAutomaticallyUsesPost() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + String longValue = StringUtils.leftPad("", 20000, 'B'); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value(longValue)) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient/_search", capt.getValue().getURI().toString()); + + HttpEntityEnclosingRequestBase enc = (HttpEntityEnclosingRequestBase) capt.getValue(); + UrlEncodedFormEntity ent = (UrlEncodedFormEntity) enc.getEntity(); + String string = IOUtils.toString(ent.getContent()); + ourLog.info(string); + assertEquals("name="+longValue, string); + } + + + @SuppressWarnings("unused") + @Test + public void testSearchUsingGetSearch() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.NAME.matches().value("james")) + .usingStyle(SearchStyleEnum.GET_WITH_SEARCH) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir/Patient/_search?name=james", capt.getValue().getURI().toString()); + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithInternalServerError() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "INTERNAL ERRORS")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.search().forResource(Patient.class).execute(); + fail(); + } catch (InternalErrorException e) { + assertEquals(e.getMessage(), "HTTP 500 INTERNAL ERRORS: Server Issues!"); + assertEquals(e.getResponseBody(), "Server Issues!"); + } + + } + + @SuppressWarnings("unused") + @Test + public void testSearchWithNonFhirResponse() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + try { + client.search().forResource(Patient.class).execute(); + fail(); + } catch (NonFhirResponseException e) { + assertThat(e.getMessage(), StringContains.containsString("Server Issues!")); + } + + } + + @Test + public void testTransaction() throws Exception { + String bundleStr = IOUtils.toString(getClass().getResourceAsStream("/bundle.json")); + Bundle bundle = myCtx.newJsonParser().parseBundle(bundleStr); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(bundleStr), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.transaction() + .withBundle(bundle) + .execute(); + //@formatter:on + + assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); + assertEquals(bundle.getEntries().get(0).getId(), response.getEntries().get(0).getId()); + } + + + + @Test + public void testTransactionJson() throws Exception { + String bundleStr = IOUtils.toString(getClass().getResourceAsStream("/bundle.json")); + Bundle bundle = myCtx.newJsonParser().parseBundle(bundleStr); + + ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(bundleStr), Charset.forName("UTF-8"))); + + IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.transaction() + .withBundle(bundle) + .encodedJson() + .execute(); + //@formatter:on + + HttpEntityEnclosingRequestBase value = (HttpEntityEnclosingRequestBase) capt.getValue(); + + Header ct = value.getEntity().getContentType(); + assertNotNull(ct); + assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue()); + + assertEquals("http://example.com/fhir?_format=json", value.getURI().toString()); + assertThat(IOUtils.toString(value.getEntity().getContent()), StringContains.containsString("\"resourceType\"")); + assertEquals(bundle.getEntries().get(0).getId(), response.getEntries().get(0).getId()); + } + + @Test public void testUpdate() throws Exception { @@ -202,525 +835,10 @@ public class GenericClientTest { assertEquals(4, capt.getAllValues().size()); } - - @Test - public void testDelete() throws Exception { - OperationOutcome oo = new OperationOutcome(); - oo.addIssue().addLocation().setValue("testDelete01"); - String ooStr = myCtx.newXmlParser().encodeResourceToString(oo); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK")); - when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") }); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(ooStr), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - OperationOutcome outcome = client.delete().resourceById("Patient", "123").execute(); - - assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString()); - assertEquals("DELETE", capt.getValue().getMethod()); - assertEquals("testDelete01",outcome.getIssueFirstRep().getLocationFirstRep().getValue()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8"))); - outcome = client.delete().resourceById(new IdDt("Location", "123","456")).prettyPrint().encodedJson().execute(); - - assertEquals("http://example.com/fhir/Location/123?_format=json&_pretty=true", capt.getAllValues().get(1).getURI().toString()); - assertEquals("DELETE", capt.getValue().getMethod()); - assertEquals(null,outcome); - - } - - - private String getPatientFeedWithOneResult() { - //@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\">" - + "<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; - } - - private String getResourceResult() { - //@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 - return msg; - } - - - @SuppressWarnings("unused") - @Test - public void testSearchByString() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matches().value("james")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchAllResources() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forAllResources() - .where(Patient.NAME.matches().value("james")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/?name=james", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByStringExact() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource("Patient") - .where(Patient.NAME.matchesExactly().value("james")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?name%3Aexact=james", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByNumberExact() throws Exception { - - String msg = new FhirContext().newXmlParser().encodeBundleToString(new Bundle()); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource(Observation.class) - .where(Observation.VALUE_QUANTITY.greaterThan().number(123).andUnits("foo", "bar")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Observation?value-quantity=%3E123%7Cfoo%7Cbar", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByQuantity() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource(Patient.class) - .where(Encounter.LENGTH.exactly().number(123)) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?length=123", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByToken() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Fexample.com%2Ffhir%7CZZZ", capt.getValue().getURI().toString()); - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - //@formatter:off - response = client.search() - .forResource("Patient") - .where(Patient.IDENTIFIER.exactly().code("ZZZ")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?identifier=ZZZ", capt.getAllValues().get(1).getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByComposite() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://foo"); - - //@formatter:off - Bundle response = client.search() - .forResource("Observation") - .where(Observation.NAME_VALUE_DATE - .withLeft(Observation.NAME.exactly().code("FOO$BAR")) - .withRight(Observation.VALUE_DATE.exactly().day("2001-01-01")) - ) - .execute(); - //@formatter:on - - assertEquals("http://foo/Observation?" + Observation.SP_NAME_VALUE_DATE + "=" + URLEncoder.encode("FOO\\$BAR$2001-01-01","UTF-8"), capt.getValue().getURI().toString()); - - } - - @Test - public void testGetTags() throws Exception { - - TagList tagList = new TagList(); - tagList.add(new Tag("CCC", "AAA", "BBB")); - String msg = myCtx.newXmlParser().encodeTagListToString(tagList); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - TagList response = client.getTags() - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/_tags", capt.getValue().getURI().toString()); - assertEquals(1, response.size()); - assertEquals("CCC", response.get(0).getScheme()); - - // Now for patient - - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - //@formatter:off - response = client.getTags().forResource(Patient.class) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient/_tags", capt.getValue().getURI().toString()); - assertEquals(1, response.size()); - assertEquals("CCC", response.get(0).getScheme()); - - } - - @Test - public void testTransaction() throws Exception { - String bundleStr = IOUtils.toString(getClass().getResourceAsStream("/bundle.json")); - Bundle bundle = myCtx.newJsonParser().parseBundle(bundleStr); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(bundleStr), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.transaction() - .withBundle(bundle) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir", capt.getValue().getURI().toString()); - assertEquals(bundle.getEntries().get(0).getId(), response.getEntries().get(0).getId()); - } - - - @Test - public void testTransactionJson() throws Exception { - String bundleStr = IOUtils.toString(getClass().getResourceAsStream("/bundle.json")); - Bundle bundle = myCtx.newJsonParser().parseBundle(bundleStr); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_JSON + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(bundleStr), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.transaction() - .withBundle(bundle) - .encodedJson() - .execute(); - //@formatter:on - - HttpEntityEnclosingRequestBase value = (HttpEntityEnclosingRequestBase) capt.getValue(); - - Header ct = value.getEntity().getContentType(); - assertNotNull(ct); - assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue()); - - assertEquals("http://example.com/fhir?_format=json", value.getURI().toString()); - assertThat(IOUtils.toString(value.getEntity().getContent()), StringContains.containsString("\"resourceType\"")); - assertEquals(bundle.getEntries().get(0).getId(), response.getEntries().get(0).getId()); - } - - @SuppressWarnings("unused") - @Test - public void testSearchByReferenceSimple() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource("Patient") - .where(Patient.PROVIDER.hasId("123")) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?provider=123", capt.getValue().getURI().toString()); - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByReferenceProperty() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - //@formatter:off - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - Bundle response = client.search() - .forResource(Patient.class) - .where(Patient.PROVIDER.hasChainedProperty(Organization.NAME.matches().value("ORG0"))) - .execute(); - - assertEquals("http://example.com/fhir/Patient?provider.name=ORG0", capt.getValue().getURI().toString()); - //@formatter:on - - } - - @SuppressWarnings("unused") - @Test - public void testSearchByDate() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle response = client.search() - .forResource(Patient.class) - .encodedJson() - .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) - .and(Patient.BIRTHDATE.after().day("2011-01-01")) - .include(Patient.INCLUDE_MANAGINGORGANIZATION) - .sort().ascending(Patient.BIRTHDATE) - .sort().descending(Patient.NAME) - .limitTo(123) - .execute(); - //@formatter:on - - assertEquals("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_count=123&_format=json", capt.getValue().getURI().toString()); - - } - - - - @Test - public void testRead() throws Exception { - - String msg = getResourceResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); - Header[] headers = new Header[2]; - headers[0] = new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"); - headers[1] = new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"); - when(myHttpResponse.getAllHeaders()).thenReturn(headers); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Patient response = client.read(Patient.class, new IdDt("Patient/1234")); - //@formatter:on - - assertThat(response.getNameFirstRep().getFamilyAsSingleString(), StringContains.containsString("Cardinal")); - - assertEquals("http://foo.com/Patient/123/_history/2333", response.getId().getValue()); - - InstantDt lm = (InstantDt) response.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); - lm.setTimeZoneZulu(true); - assertEquals("1995-11-15T04:58:08.000Z", lm.getValueAsString()); - - } - - - @SuppressWarnings("unused") - @Test - public void testSearchWithInternalServerError() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 500, "INTERNAL ERRORS")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - try { - client.search().forResource(Patient.class).execute(); - fail(); - } catch (InternalErrorException e) { - assertEquals(e.getMessage(), "HTTP 500 INTERNAL ERRORS: Server Issues!"); - assertEquals(e.getResponseBody(), "Server Issues!"); - } - - } - - @SuppressWarnings("unused") - @Test - public void testSearchWithNonFhirResponse() throws Exception { - - String msg = getPatientFeedWithOneResult(); - - ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("Server Issues!"), Charset.forName("UTF-8"))); - - IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir"); - - try { - client.search().forResource(Patient.class).execute(); - fail(); - } catch (NonFhirResponseException e) { - assertThat(e.getMessage(), StringContains.containsString("Server Issues!")); - } + @BeforeClass + public static void beforeClass() { + myCtx = new FhirContext(); } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java index 5b1841ecc7c..2cc688c9519 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.List; @@ -8,10 +8,14 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.message.BasicNameValuePair; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -22,11 +26,16 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.composite.CodingDt; +import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.testutil.RandomServerPortProvider; /** @@ -35,24 +44,9 @@ import ca.uhn.fhir.testutil.RandomServerPortProvider; public class SearchTest { private static CloseableHttpClient ourClient; + private static FhirContext ourCtx = new FhirContext(); private static int ourPort; private static Server ourServer; - private static FhirContext ourCtx = new FhirContext(); - - @Test - public void testSearchById() throws Exception { - HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); - HttpResponse status = ourClient.execute(httpGet); - String responseContent = IOUtils.toString(status.getEntity().getContent()); - IOUtils.closeQuietly(status.getEntity().getContent()); - assertEquals(200, status.getStatusLine().getStatusCode()); - Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); - assertEquals(1, bundle.getEntries().size()); - - Patient p = bundle.getResources(Patient.class).get(0); - assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); - assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue()); - } @Test public void testOmitEmptyOptionalParam() throws Exception { @@ -63,13 +57,69 @@ public class SearchTest { assertEquals(200, status.getStatusLine().getStatusCode()); Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); assertEquals(1, bundle.getEntries().size()); - + Patient p = bundle.getResources(Patient.class).get(0); assertEquals(null, p.getNameFirstRep().getFamilyFirstRep().getValue()); } + @Test + public void testSearchById() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=aaa"); + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); + assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue()); + } + @Test + public void testSearchByPost() throws Exception { + HttpPost filePost = new HttpPost("http://localhost:" + ourPort + "/Patient/_search"); + + // add parameters to the post method + List <NameValuePair> parameters = new ArrayList <NameValuePair>(); + parameters.add(new BasicNameValuePair("_id", "aaa")); + + UrlEncodedFormEntity sendentity = new UrlEncodedFormEntity(parameters, "UTF-8"); + filePost.setEntity(sendentity); + + HttpResponse status = ourClient.execute(filePost); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + Patient p = bundle.getResources(Patient.class).get(0); + assertEquals("idaaa", p.getNameFirstRep().getFamilyAsSingleString()); + assertEquals("IDAAA (identifier123)", bundle.getEntries().get(0).getTitle().getValue()); + } + + @Test + public void testSearchGetWithUnderscoreSearch() throws Exception { + HttpGet httpGet = new HttpGet("http://localhost:" + ourPort+"/Observation/_search?subject%3APatient=100&name=3141-9%2C8302-2%2C8287-5%2C39156-5"); + + HttpResponse status = ourClient.execute(httpGet); + String responseContent = IOUtils.toString(status.getEntity().getContent()); + IOUtils.closeQuietly(status.getEntity().getContent()); + assertEquals(200, status.getStatusLine().getStatusCode()); + Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); + assertEquals(1, bundle.getEntries().size()); + + Observation p = bundle.getResources(Observation.class).get(0); + assertEquals("Patient/100", p.getSubject().getReference().toString()); + assertEquals(4, p.getName().getCoding().size()); + assertEquals("3141-9", p.getName().getCoding().get(0).getCode().getValue()); + assertEquals("8302-2", p.getName().getCoding().get(1).getCode().getValue()); + + } + @AfterClass public static void afterClass() throws Exception { ourServer.stop(); @@ -85,8 +135,8 @@ public class SearchTest { ServletHandler proxyHandler = new ServletHandler(); RestfulServer servlet = new RestfulServer(); servlet.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); - - servlet.setResourceProviders(patientProvider); + + servlet.setResourceProviders(patientProvider, new DummyObservationResourceProvider()); ServletHolder servletHolder = new ServletHolder(servlet); proxyHandler.addServletWithMapping(servletHolder, "/*"); ourServer.setHandler(proxyHandler); @@ -99,6 +149,28 @@ public class SearchTest { } + public static class DummyObservationResourceProvider implements IResourceProvider{ + + @Override + public Class<? extends IResource> getResourceType() { + return Observation.class; + } + + @Search + public Observation search(@RequiredParam(name="subject") ReferenceParam theSubject, @RequiredParam(name="name") TokenOrListParam theName) { + Observation o = new Observation(); + o.setId("1"); + + o.getSubject().setReference(theSubject.getResourceType() + "/" + theSubject.getIdPart()); + for (CodingDt next : theName.getListAsCodings()) { + o.getName().getCoding().add(next); + } + + return o; + } + + } + /** * Created by dsotnikov on 2/25/2014. */ @@ -111,8 +183,8 @@ public class SearchTest { Patient patient = new Patient(); patient.setId("1"); patient.addIdentifier("system", "identifier123"); - if (theParam!=null) { - patient.addName().addFamily("id"+theParam.getValue()); + if (theParam != null) { + patient.addName().addFamily("id" + theParam.getValue()); } retVal.add(patient); return retVal; diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerExtraParametersTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerExtraParametersTest.java index b39a2923bb7..0997900c4d0 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerExtraParametersTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ServerExtraParametersTest.java @@ -1,13 +1,17 @@ package ca.uhn.fhir.rest.server; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.hamcrest.core.StringContains; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -19,7 +23,12 @@ 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.annotation.ServerBase; +import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.api.IBasicClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.testutil.RandomServerPortProvider; public class ServerExtraParametersTest { @@ -58,6 +67,28 @@ public class ServerExtraParametersTest { assertEquals("http://localhost:" + myPort, patientProvider.getServerBase()); } + @Test + public void testNonRepeatableParam() throws Exception { + MyServerBaseProvider patientProvider = new MyServerBaseProvider(); + myServlet.setResourceProviders(patientProvider); + + myServer.start(); + + FhirContext ctx = new FhirContext(); + IGenericClient client = ctx.newRestfulGenericClient("http://localhost:" + myPort + "/"); + client.registerInterceptor(new LoggingInterceptor(true)); + + try { + client.search().forResource("Patient").where(new StringClientParam("singleParam").matches().values(Arrays.asList("AA", "BB"))).execute(); + fail(); + } catch (InvalidRequestException e) { + assertThat( + e.getMessage(), + StringContains + .containsString("HTTP 400 Bad Request: Multiple values detected for non-repeatable parameter 'singleParam'. This server is not configured to allow multiple (AND/OR) values for this param.")); + } + } + @After public void after() throws Exception { myServer.stop(); @@ -75,6 +106,13 @@ public class ServerExtraParametersTest { return Patient.class; } + @Search + public List<Patient> searchSingleParam(@RequiredParam(name = "singleParam") StringParam theFooParam) { + Patient retVal = new Patient(); + retVal.setId("1"); + return Collections.singletonList(retVal); + } + @Search public List<Patient> searchForPatients(@RequiredParam(name = "fooParam") StringDt theFooParam, @ServerBase String theServerBase) { myServerBase = theServerBase; diff --git a/restful-server-example/.settings/org.eclipse.wst.common.component b/restful-server-example/.settings/org.eclipse.wst.common.component index fc0c66be7d5..263e87f0f0d 100644 --- a/restful-server-example/.settings/org.eclipse.wst.common.component +++ b/restful-server-example/.settings/org.eclipse.wst.common.component @@ -3,7 +3,7 @@ <wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/> <wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/> <wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/> - <dependent-module archiveName="hapi-fhir-base-0.5.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> + <dependent-module archiveName="hapi-fhir-base-0.6-SNAPSHOT.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/hapi-fhir-base/hapi-fhir-base"> <dependency-type>uses</dependency-type> </dependent-module> <dependent-module deploy-path="/" handle="module:/overlay/prj/hapi-fhir-testpage-overlay?includes=**/**&excludes=META-INF/MANIFEST.MF">