From fc4fb07562a7e8ddffeeb138187e97f79243dbc9 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 3 Mar 2015 18:31:01 -0500 Subject: [PATCH] Work on history suport for fluent client --- .../java/example/GenericClientExample.java | 38 + hapi-fhir-android/dependency-reduced-pom.xml | 151 ++++ hapi-fhir-android/pom.xml | 58 +- .../java/ca/uhn/fhir/android/BuiltJarIT.java | 5 +- .../fhir/model/primitive/Base64BinaryDt.java | 5 +- .../uhn/fhir/rest/client/GenericClient.java | 734 ++++++++++-------- .../uhn/fhir/rest/client/IGenericClient.java | 30 +- .../ca/uhn/fhir/rest/gclient/IBaseOn.java | 28 + .../ca/uhn/fhir/rest/gclient/IHistory.java | 5 + .../uhn/fhir/rest/gclient/IHistoryTyped.java | 25 + .../fhir/rest/gclient/IHistoryUntyped.java | 21 + .../uhn/fhir/rest/gclient/IOperationOn.java | 2 +- .../fhir/rest/gclient/IOperationTyped.java | 7 + .../BaseAddOrDeleteTagsMethodBinding.java | 2 +- .../BaseHttpClientInvocationWithContents.java | 22 +- .../rest/method/GetTagsMethodBinding.java | 2 +- .../rest/method/HistoryMethodBinding.java | 14 +- .../ca/uhn/fhir/rest/server/Constants.java | 6 +- .../fhir/rest/server/RestfulServerUtils.java | 4 +- hapi-fhir-jpaserver-base/.classpath | 2 +- ....eclipse.wst.common.project.facet.core.xml | 2 +- .../fhir/rest/client/GenericClientTest.java | 60 +- hapi-fhir-structures-dstu2/.gitignore | 1 + .../rest/client/GenericClientTestDstu2.java | 87 ++- pom.xml | 2 +- src/site/xdoc/doc_rest_client.xml | 72 +- 26 files changed, 975 insertions(+), 410 deletions(-) create mode 100644 hapi-fhir-android/dependency-reduced-pom.xml create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistory.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryUntyped.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationTyped.java diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java index 29f62a843df..14e904ae0bc 100644 --- a/examples/src/main/java/example/GenericClientExample.java +++ b/examples/src/main/java/example/GenericClientExample.java @@ -14,6 +14,7 @@ import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.Organization; import ca.uhn.fhir.model.dstu2.resource.Patient; 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.IGenericClient; import ca.uhn.fhir.rest.method.SearchStyleEnum; @@ -320,6 +321,43 @@ public class GenericClientExample { } + @SuppressWarnings("unused") + public static void history() { + IGenericClient client = FhirContext.forDstu2().newRestfulGenericClient(""); + { + Bundle response; + // START SNIPPET: historyDstu1 + response = client + .history() + .ofServer() + .andReturnDstu1Bundle() + .execute(); + // END SNIPPET: historyDstu1 + } + { + ca.uhn.fhir.model.dstu2.resource.Bundle response; + // START SNIPPET: historyDstu2 + response = client + .history() + .ofServer() + .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + // END SNIPPET: historyDstu2 + } + { + ca.uhn.fhir.model.dstu2.resource.Bundle response; + // START SNIPPET: historyFeatures + response = client + .history() + .ofServer() + .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .since(new InstantDt("2012-01-01T12:22:32.038Z")) + .count(100) + .execute(); + // END SNIPPET: historyFeatures + } + } + public static void main(String[] args) { fluentSearch(); } diff --git a/hapi-fhir-android/dependency-reduced-pom.xml b/hapi-fhir-android/dependency-reduced-pom.xml new file mode 100644 index 00000000000..0246aa284a5 --- /dev/null +++ b/hapi-fhir-android/dependency-reduced-pom.xml @@ -0,0 +1,151 @@ + + + + hapi-fhir + ca.uhn.hapi.fhir + 0.9-SNAPSHOT + + 4.0.0 + hapi-fhir-android + HAPI FHIR - Android + + + + maven-deploy-plugin + + true + + + + maven-failsafe-plugin + ${maven_failsafe_plugin_version} + + + + integration-test + verify + + + + + compile+runtime + + ${project.build.directory}/hapi-fhir-android-${project.version}.jar + + + + + maven-shade-plugin + 2.3 + + + package + + shade + + + true + + + commons-codec:commons-codec + commons-io:commons-io + ca.uhn.hapi.fhir:hapi-fhir-base + ca.uhn.hapi.fhir:hapi-fhir-structures-dstu + ca.uhn.hapi.fhir:hapi-fhir-structures-dstu2 + org.glassfish:javax.json + org.codehaus.woodstox:woodstox-core-asl + javax.xml.stream:stax-api + org.codehaus.woodstox:stax2-api + org.slf4j:slf4j* + org.apache.commons:* + org.apache.httpcomponents:* + org.glassfish:javax.json + + + + + javax.xml.stream + ca.uhn.fhir.repackage.javax.xml.stream + + + javax.json + ca.uhn.fhir.repackage.javax.json + + + + + ca.uhn.hapi.fhir:hapi-fhir-base + + ca/uhn/fhir/model/dstu/valueset/** + **/*.java + + + + + + + + + + + + DIST + + + + maven-assembly-plugin + ${maven_assembly_plugin_version} + + + package + + single + + + false + + ${project.basedir}/src/assembly/hapi-fhir-all.xml + ${project.basedir}/src/assembly/hapi-fhir-jpaserver-example.xml + + + + + + + + + + + + com.phloc + phloc-schematron + 2.7.1 + compile + + + com.phloc + phloc-commons + 4.3.5 + compile + + + javax.servlet + javax.servlet-api + 3.1.0 + compile + + + junit + junit + 4.12 + test + + + hamcrest-core + org.hamcrest + + + + + + diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index d3c73a1c71f..ce409b3b9da 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -28,6 +28,10 @@ org.apache.httpcomponents httpcore + + commons-codec + commons-codec + @@ -63,17 +67,21 @@ - - commons-codec - commons-codec - ${commons_codec_version} - commons-io commons-io ${commons_io_version} + + + javax.servlet + javax.servlet-api + ${servlet_api_version} + compile + @@ -102,7 +110,7 @@ compile+runtime - target/hapi-fhir-android-0.9-SNAPSHOT-shaded.jar + ${project.build.directory}/hapi-fhir-android-${project.version}.jar @@ -125,7 +133,6 @@ shade - true true @@ -158,13 +165,6 @@ ca.uhn.hapi.fhir:hapi-fhir-base - ca/uhn/fhir/model/dstu/valueset/** **/*.java @@ -177,34 +177,4 @@ - - - - DIST - - - - maven-assembly-plugin - ${maven_assembly_plugin_version} - - - package - - single - - - false - - ${project.basedir}/src/assembly/hapi-fhir-all.xml - ${project.basedir}/src/assembly/hapi-fhir-jpaserver-example.xml - - - - - - - - - - diff --git a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java index 496e1ebcc59..51d4e19625c 100644 --- a/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java +++ b/hapi-fhir-android/src/test/java/ca/uhn/fhir/android/BuiltJarIT.java @@ -57,9 +57,10 @@ public class BuiltJarIT { */ @Test public void testJarForDuplicates() throws Exception { - Collection files = FileUtils.listFiles(new File("target"), new WildcardFileFilter("*-shaded.jar"), null); + String wildcard = "hapi-fhir-android-*.jar"; + Collection files = FileUtils.listFiles(new File("target"), new WildcardFileFilter(wildcard), null); if (files.isEmpty()) { - throw new Exception("No files matching target/*-shaded.jar"); + throw new Exception("No files matching " + wildcard); } for (File file : files) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/Base64BinaryDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/Base64BinaryDt.java index d65b6d218f0..6b9924d6f39 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/Base64BinaryDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/Base64BinaryDt.java @@ -25,6 +25,7 @@ import org.apache.commons.codec.binary.Base64; import ca.uhn.fhir.model.api.BasePrimitive; import ca.uhn.fhir.model.api.annotation.DatatypeDef; import ca.uhn.fhir.model.api.annotation.SimpleSetter; +import ca.uhn.fhir.rest.server.Constants; @DatatypeDef(name = "base64Binary") public class Base64BinaryDt extends BasePrimitive { @@ -46,12 +47,12 @@ public class Base64BinaryDt extends BasePrimitive { @Override protected byte[] parse(String theValue) { - return Base64.decodeBase64(theValue); + return Base64.decodeBase64(theValue.getBytes(Constants.CHARSET_UTF8)); } @Override protected String encode(byte[] theValue) { - return Base64.encodeBase64String(theValue); + return new String(Base64.encodeBase64(theValue), Constants.CHARSET_UTF8); } } 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 772adc52f46..b6ed2c1aab0 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 @@ -20,11 +20,13 @@ package ca.uhn.fhir.rest.client; * #L% */ -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -36,6 +38,7 @@ import org.apache.commons.lang3.Validate; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpRequestBase; import org.hl7.fhir.instance.model.IBaseResource; +import org.hl7.fhir.instance.model.api.IBaseBundle; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -49,6 +52,7 @@ import ca.uhn.fhir.model.base.resource.BaseConformance; import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.IParser; @@ -68,6 +72,9 @@ import ca.uhn.fhir.rest.gclient.IDeleteWithQueryTyped; import ca.uhn.fhir.rest.gclient.IGetPage; import ca.uhn.fhir.rest.gclient.IGetPageTyped; import ca.uhn.fhir.rest.gclient.IGetTags; +import ca.uhn.fhir.rest.gclient.IHistory; +import ca.uhn.fhir.rest.gclient.IHistoryTyped; +import ca.uhn.fhir.rest.gclient.IHistoryUntyped; import ca.uhn.fhir.rest.gclient.IOperation; import ca.uhn.fhir.rest.gclient.IParam; import ca.uhn.fhir.rest.gclient.IQuery; @@ -111,12 +118,13 @@ import ca.uhn.fhir.util.ICallable; public class GenericClient extends BaseClient implements IGenericClient { private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri"; + private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead"; private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread"; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); private FhirContext myContext; + private HttpRequestBase myLastRequest; private boolean myLogRequestAndResponse; @@ -170,6 +178,19 @@ public class GenericClient extends BaseClient implements IGenericClient { return new DeleteInternal(); } + @Override + public MethodOutcome delete(final Class theType, IdDt theId) { + HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType))); + if (isKeepResponses()) { + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + } + + final String resourceName = myContext.getResourceDefinition(theType).getName(); + OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); + MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); + return resp; + } + // public IResource read(UriDt url) { // return read(inferResourceClass(url), url); // } @@ -184,21 +205,51 @@ public class GenericClient extends BaseClient implements IGenericClient { // } @Override - public MethodOutcome delete(final Class theType, IdDt theId) { - HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType))); + public MethodOutcome delete(Class theType, String theId) { + return delete(theType, new IdDt(theId)); + } + + private T doReadOrVRead(final Class theType, IdDt theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches) { + String resName = toResourceName(theType); + IdDt id = theId; + if (!id.hasBaseUrl()) { + id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart()); + } + + HttpGetClientInvocation invocation; + if (id.hasBaseUrl()) { + if (theVRead) { + invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id); + } else { + invocation = ReadMethodBinding.createAbsoluteReadInvocation(id); + } + } else { + if (theVRead) { + invocation = ReadMethodBinding.createVReadInvocation(id, resName); + } else { + invocation = ReadMethodBinding.createReadInvocation(id, resName); + } + } if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); } - final String resourceName = myContext.getResourceDefinition(theType).getName(); - OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); - MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse); - return resp; - } + if (theIfVersionMatches != null) { + invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); + } + + ResourceResponseHandler binding = new ResourceResponseHandler(theType, id); + + if (theNotModifiedHandler == null) { + return invokeClient(myContext, binding, invocation, myLogRequestAndResponse); + } else { + try { + return invokeClient(myContext, binding, invocation, myLogRequestAndResponse); + } catch (NotModifiedException e) { + return theNotModifiedHandler.call(); + } + } - @Override - public MethodOutcome delete(Class theType, String theId) { - return delete(theType, new IdDt(theId)); } public HttpRequestBase getLastRequest() { @@ -217,10 +268,15 @@ public class GenericClient extends BaseClient implements IGenericClient { return new GetTagsInternal(); } + @Override + public IHistory history() { + return new HistoryInternal(); + } + @Override public Bundle history(final Class theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) { String resourceName = theType != null ? toResourceName(theType) : null; - IdDt id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt : null; + String id = theIdDt != null && theIdDt.isEmpty() == false ? theIdDt.getValue() : null; HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit); if (isKeepResponses()) { myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); @@ -237,6 +293,42 @@ public class GenericClient extends BaseClient implements IGenericClient { return history(theType, new IdDt(theId), theSince, theLimit); } + private Class inferResourceClass(UriDt theUrl) { + String urlString = theUrl.getValueAsString(); + int i = urlString.indexOf('?'); + + if (i >= 0) { + urlString = urlString.substring(0, i); + } + + i = urlString.indexOf("://"); + + if (i >= 0) { + urlString = urlString.substring(i + 3); + } + + String[] pcs = urlString.split("\\/"); + + for (i = pcs.length - 1; i >= 0; i--) { + String s = pcs[i].trim(); + + if (!s.isEmpty()) { + RuntimeResourceDefinition def = myContext.getResourceDefinition(s); + if (def != null) { + return def.getImplementingClass(); + } + } + } + + throw new RuntimeException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString())); + + } + + // @Override + // public T read(final Class theType, IdDt theId) { + // return doReadOrVRead(theType, theId, false, null, null); + // } + public boolean isLogRequestAndResponse() { return myLogRequestAndResponse; } @@ -246,10 +338,16 @@ public class GenericClient extends BaseClient implements IGenericClient { return new LoadPageInternal(); } - // @Override - // public T read(final Class theType, IdDt theId) { - // return doReadOrVRead(theType, theId, false, null, null); - // } + @Override + public IOperation operation() { + // TODO Auto-generated method stub + return null; + } + + @Override + public IRead read() { + return new ReadInternal(); + } @Override public T read(Class theType, String theId) { @@ -316,37 +414,6 @@ public class GenericClient extends BaseClient implements IGenericClient { return search(inferResourceClass(theUrl), theUrl); } - private Class inferResourceClass(UriDt theUrl) { - String urlString = theUrl.getValueAsString(); - int i = urlString.indexOf('?'); - - if (i >= 0) { - urlString = urlString.substring(0, i); - } - - i = urlString.indexOf("://"); - - if (i >= 0) { - urlString = urlString.substring(i + 3); - } - - String[] pcs = urlString.split("\\/"); - - for (i = pcs.length - 1; i >= 0; i--) { - String s = pcs[i].trim(); - - if (!s.isEmpty()) { - RuntimeResourceDefinition def = myContext.getResourceDefinition(s); - if (def != null) { - return def.getImplementingClass(); - } - } - } - - throw new RuntimeException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theUrl.getValueAsString())); - - } - /** * For now, this is a part of the internal API of HAPI - Use with caution as this method may change! */ @@ -428,49 +495,6 @@ public class GenericClient extends BaseClient implements IGenericClient { return doReadOrVRead(theType, theId, true, null, null); } - private T doReadOrVRead(final Class theType, IdDt theId, boolean theVRead, ICallable theNotModifiedHandler, String theIfVersionMatches) { - String resName = toResourceName(theType); - IdDt id = theId; - if (!id.hasBaseUrl()) { - id = new IdDt(resName, id.getIdPart(), id.getVersionIdPart()); - } - - HttpGetClientInvocation invocation; - if (id.hasBaseUrl()) { - if (theVRead) { - invocation = ReadMethodBinding.createAbsoluteVReadInvocation(id); - } else { - invocation = ReadMethodBinding.createAbsoluteReadInvocation(id); - } - } else { - if (theVRead) { - invocation = ReadMethodBinding.createVReadInvocation(id, resName); - } else { - invocation = ReadMethodBinding.createReadInvocation(id, resName); - } - } - if (isKeepResponses()) { - myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); - } - - if (theIfVersionMatches != null) { - invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"'); - } - - ResourceResponseHandler binding = new ResourceResponseHandler(theType, id); - - if (theNotModifiedHandler == null) { - return invokeClient(myContext, binding, invocation, myLogRequestAndResponse); - } else { - try { - return invokeClient(myContext, binding, invocation, myLogRequestAndResponse); - } catch (NotModifiedException e) { - return theNotModifiedHandler.call(); - } - } - - } - /* also deprecated in interface */ @Deprecated @Override @@ -517,6 +541,10 @@ public class GenericClient extends BaseClient implements IGenericClient { return (T) this; } + protected EncodingEnum getParamEncoding() { + return myParamEncoding; + } + protected Z invoke(Map> theParams, IClientResponseHandler theHandler, BaseHttpClientInvocation theInvocation) { // if (myParamEncoding != null) { // theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType())); @@ -534,10 +562,6 @@ public class GenericClient extends BaseClient implements IGenericClient { return resp; } - protected EncodingEnum getParamEncoding() { - return myParamEncoding; - } - protected IResource parseResourceBody(String theResourceBody) { EncodingEnum encoding = null; for (int i = 0; i < theResourceBody.length() && encoding == null; i++) { @@ -574,7 +598,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); @@ -586,11 +611,29 @@ public class GenericClient extends BaseClient implements IGenericClient { private class CreateInternal extends BaseClientExecutable implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped { + private CriterionList myCriterionList; private String myId; private IResource myResource; private String myResourceBody; private String mySearchUrl; - private CriterionList myCriterionList; + + @Override + public ICreateWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public ICreateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public ICreateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } @Override public MethodOutcome execute() { @@ -637,6 +680,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public ICreateWithQueryTyped where(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + @Override public CreateInternal withId(IdDt theId) { myId = theId.getIdPart(); @@ -649,38 +698,40 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } - @Override - public ICreateTyped conditionalByUrl(String theSearchUrl) { - mySearchUrl = theSearchUrl; - return this; + } + + private static class CriterionList extends ArrayList { + + private static final long serialVersionUID = 1L; + + public void populateParamList(Map> theParams) { + for (ICriterionInternal next : this) { + String parameterName = next.getParameterName(); + String parameterValue = next.getParameterValue(); + addParam(theParams, parameterName, parameterValue); + } } - @Override - public ICreateWithQuery conditional() { - myCriterionList = new CriterionList(); - return this; - } - - @Override - public ICreateWithQueryTyped where(ICriterion theCriterion) { - myCriterionList.add((ICriterionInternal) theCriterion); - return this; - } - - @Override - public ICreateWithQueryTyped and(ICriterion theCriterion) { - myCriterionList.add((ICriterionInternal) theCriterion); - return this; + public Map> toParamList() { + LinkedHashMap> retVal = new LinkedHashMap>(); + populateParamList(retVal); + return retVal; } } private class DeleteInternal extends BaseClientExecutable implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped { - private IdDt myId; - private String mySearchUrl; - private String myResourceType; private CriterionList myCriterionList; + private IdDt myId; + private String myResourceType; + private String mySearchUrl; + + @Override + public IDeleteWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } @Override public BaseOperationOutcome execute() { @@ -734,13 +785,6 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } - @Override - public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { - Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null"); - mySearchUrl = theSearchUrl; - return this; - } - @Override public IDeleteWithQuery resourceConditionalByType(String theResourceType) { Validate.notBlank(theResourceType, "theResourceType can not be blank/null"); @@ -753,38 +797,19 @@ public class GenericClient extends BaseClient implements IGenericClient { } @Override - public IDeleteWithQueryTyped where(ICriterion theCriterion) { - myCriterionList.add((ICriterionInternal) theCriterion); + public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) { + Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null"); + mySearchUrl = theSearchUrl; return this; } @Override - public IDeleteWithQueryTyped and(ICriterion theCriterion) { + public IDeleteWithQueryTyped where(ICriterion theCriterion) { myCriterionList.add((ICriterionInternal) theCriterion); return this; } } - private static class CriterionList extends ArrayList { - - private static final long serialVersionUID = 1L; - - public void populateParamList(Map> theParams) { - for (ICriterionInternal next : this) { - String parameterName = next.getParameterName(); - String parameterValue = next.getParameterValue(); - addParam(theParams, parameterName, parameterValue); - } - } - - public Map> toParamList() { - LinkedHashMap> retVal = new LinkedHashMap>(); - populateParamList(retVal); - return retVal; - } - - } - private class GetPageInternal extends BaseClientExecutable implements IGetPageTyped { private String myUrl; @@ -806,132 +831,6 @@ public class GenericClient extends BaseClient implements IGenericClient { } - @Override - public IRead read() { - return new ReadInternal(); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { - private RuntimeResourceDefinition myType; - private IdDt myId; - private ICallable myNotModifiedHandler; - private String myIfVersionMatches; - - @Override - public IReadTyped resource(Class theResourceType) { - Validate.notNull(theResourceType, "theResourceType must not be null"); - myType = myContext.getResourceDefinition(theResourceType); - if (myType == null) { - throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); - } - return this; - } - - @Override - public IReadTyped resource(String theResourceAsText) { - Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); - myType = myContext.getResourceDefinition(theResourceAsText); - if (myType == null) { - throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); - } - return this; - } - - @Override - public IReadExecutable withId(String theId) { - Validate.notBlank(theId, "The ID can not be blank"); - myId = new IdDt(myType.getName(), theId); - return this; - } - - @Override - public IReadExecutable withIdAndVersion(String theId, String theVersion) { - Validate.notBlank(theId, "The ID can not be blank"); - myId = new IdDt(myType.getName(), theId, theVersion); - return this; - } - - @Override - public IReadExecutable withId(IdDt theId) { - Validate.notNull(theId, "The ID can not be null"); - Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); - myId = theId.toUnqualified(); - return this; - } - - @Override - public IReadExecutable withUrl(String theUrl) { - myId = new IdDt(theUrl); - processUrl(); - return this; - } - - private void processUrl() { - String resourceType = myId.getResourceType(); - if (isBlank(resourceType)) { - throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); - } - myType = myContext.getResourceDefinition(resourceType); - if (myType == null) { - throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); - } - } - - @Override - public Object execute() { - if (myId.hasVersionIdPart()) { - return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches); - } else { - return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches); - } - } - - @Override - public IReadExecutable withUrl(IdDt theUrl) { - Validate.notNull(theUrl, "theUrl can not be null"); - myId = theUrl; - processUrl(); - return this; - } - - @Override - public IReadIfNoneMatch ifVersionMatches(String theVersion) { - myIfVersionMatches = theVersion; - return new IReadIfNoneMatch() { - - @Override - public IReadExecutable returnResource(final IBaseResource theInstance) { - myNotModifiedHandler = new ICallable() { - @Override - public Object call() { - return theInstance; - } - }; - return ReadInternal.this; - } - - @Override - public IReadExecutable returnNull() { - myNotModifiedHandler = new ICallable() { - @Override - public Object call() { - return null; - } - }; - return ReadInternal.this; - } - - @Override - public IReadExecutable throwNotModifiedException() { - myNotModifiedHandler = null; - return ReadInternal.this; - } - }; - } - - } - private class GetTagsInternal extends BaseClientExecutable implements IGetTags { private String myId; @@ -998,6 +897,100 @@ public class GenericClient extends BaseClient implements IGenericClient { } + @SuppressWarnings("rawtypes") + public class HistoryInternal extends BaseClientExecutable implements IHistory, IHistoryUntyped, IHistoryTyped { + + private Integer myCount; + private IdDt myId; + private Class myReturnType; + private InstantDt mySince; + private Class myType; + + @SuppressWarnings("unchecked") + @Override + public IHistoryTyped andReturnBundle(Class theType) { + myReturnType = theType; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public IHistoryTyped andReturnDstu1Bundle() { + return this; + } + + @Override + public IHistoryTyped count(Integer theCount) { + myCount = theCount; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Object execute() { + String resourceName; + String id; + if (myType != null) { + resourceName = myContext.getResourceDefinition(myType).getName(); + id = null; + } else if (myId != null) { + resourceName = myId.getResourceType(); + id = myId.getIdPart(); + } else { + resourceName = null; + id = null; + } + + HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, mySince, myCount); + + IClientResponseHandler handler; + if (myReturnType != null) { + handler = new ResourceResponseHandler(myReturnType, null); + } else { + handler = new BundleResponseHandler(null); + } + + return invoke(null, handler, invocation); + } + + @Override + public IHistoryUntyped ofInstance(IdDt theId) { + if (theId.hasResourceType() == false) { + throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue()); + } + myId = theId; + return this; + } + + @Override + public IHistoryUntyped ofServer() { + return this; + } + + @Override + public IHistoryUntyped ofType(Class theResourceType) { + myType = theResourceType; + return this; + } + + @Override + public IHistoryTyped since(Date theCutoff) { + if (theCutoff != null) { + mySince = new InstantDt(theCutoff); + } else { + mySince = null; + } + return this; + } + + @Override + public IHistoryTyped since(InstantDt theCutoff) { + mySince = theCutoff; + return this; + } + + } + private final class LoadPageInternal implements IGetPage { @Override @@ -1020,7 +1013,8 @@ public class GenericClient extends BaseClient implements IGenericClient { private final class OperationOutcomeResponseHandler implements IClientResponseHandler { @Override - public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { + public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, + BaseServerResponseException { EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); if (respType == null) { return null; @@ -1048,7 +1042,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); if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) { response.setCreated(true); @@ -1057,6 +1052,127 @@ public class GenericClient extends BaseClient implements IGenericClient { } } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private class ReadInternal extends BaseClientExecutable implements IRead, IReadTyped, IReadExecutable { + private IdDt myId; + private String myIfVersionMatches; + private ICallable myNotModifiedHandler; + private RuntimeResourceDefinition myType; + + @Override + public Object execute() { + if (myId.hasVersionIdPart()) { + return doReadOrVRead(myType.getImplementingClass(), myId, true, myNotModifiedHandler, myIfVersionMatches); + } else { + return doReadOrVRead(myType.getImplementingClass(), myId, false, myNotModifiedHandler, myIfVersionMatches); + } + } + + @Override + public IReadIfNoneMatch ifVersionMatches(String theVersion) { + myIfVersionMatches = theVersion; + return new IReadIfNoneMatch() { + + @Override + public IReadExecutable returnNull() { + myNotModifiedHandler = new ICallable() { + @Override + public Object call() { + return null; + } + }; + return ReadInternal.this; + } + + @Override + public IReadExecutable returnResource(final IBaseResource theInstance) { + myNotModifiedHandler = new ICallable() { + @Override + public Object call() { + return theInstance; + } + }; + return ReadInternal.this; + } + + @Override + public IReadExecutable throwNotModifiedException() { + myNotModifiedHandler = null; + return ReadInternal.this; + } + }; + } + + private void processUrl() { + String resourceType = myId.getResourceType(); + if (isBlank(resourceType)) { + throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_INCOMPLETE_URI_FOR_READ, myId)); + } + myType = myContext.getResourceDefinition(resourceType); + if (myType == null) { + throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, myId)); + } + } + + @Override + public IReadTyped resource(Class theResourceType) { + Validate.notNull(theResourceType, "theResourceType must not be null"); + myType = myContext.getResourceDefinition(theResourceType); + if (myType == null) { + throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceType)); + } + return this; + } + + @Override + public IReadTyped resource(String theResourceAsText) { + Validate.notBlank(theResourceAsText, "You must supply a value for theResourceAsText"); + myType = myContext.getResourceDefinition(theResourceAsText); + if (myType == null) { + throw new IllegalArgumentException(myContext.getLocalizer().getMessage(I18N_CANNOT_DETEMINE_RESOURCE_TYPE, theResourceAsText)); + } + return this; + } + + @Override + public IReadExecutable withId(IdDt theId) { + Validate.notNull(theId, "The ID can not be null"); + Validate.notBlank(theId.getIdPart(), "The ID can not be blank"); + myId = theId.toUnqualified(); + return this; + } + + @Override + public IReadExecutable withId(String theId) { + Validate.notBlank(theId, "The ID can not be blank"); + myId = new IdDt(myType.getName(), theId); + return this; + } + + @Override + public IReadExecutable withIdAndVersion(String theId, String theVersion) { + Validate.notBlank(theId, "The ID can not be blank"); + myId = new IdDt(myType.getName(), theId, theVersion); + return this; + } + + @Override + public IReadExecutable withUrl(IdDt theUrl) { + Validate.notNull(theUrl, "theUrl can not be null"); + myId = theUrl; + processUrl(); + return this; + } + + @Override + public IReadExecutable withUrl(String theUrl) { + myId = new IdDt(theUrl); + processUrl(); + return this; + } + + } + private final class ResourceListResponseHandler implements IClientResponseHandler> { private Class myType; @@ -1067,7 +1183,8 @@ public class GenericClient extends BaseClient implements IGenericClient { @SuppressWarnings("unchecked") @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 { if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { Class bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass(); ResourceResponseHandler handler = new ResourceResponseHandler((Class) bundleType, null); @@ -1278,7 +1395,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); @@ -1335,11 +1453,29 @@ public class GenericClient extends BaseClient implements IGenericClient { private class UpdateInternal extends BaseClientExecutable implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped { + private CriterionList myCriterionList; private IdDt myId; private IResource myResource; private String myResourceBody; private String mySearchUrl; - private CriterionList myCriterionList; + + @Override + public IUpdateWithQueryTyped and(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IUpdateWithQuery conditional() { + myCriterionList = new CriterionList(); + return this; + } + + @Override + public IUpdateTyped conditionalByUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } @Override public MethodOutcome execute() { @@ -1391,6 +1527,12 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + @Override + public IUpdateWithQueryTyped where(ICriterion theCriterion) { + myCriterionList.add((ICriterionInternal) theCriterion); + return this; + } + @Override public IUpdateExecutable withId(IdDt theId) { if (theId == null) { @@ -1415,36 +1557,6 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } - @Override - public IUpdateTyped conditionalByUrl(String theSearchUrl) { - mySearchUrl = theSearchUrl; - return this; - } - - @Override - public IUpdateWithQuery conditional() { - myCriterionList = new CriterionList(); - return this; - } - - @Override - public IUpdateWithQueryTyped where(ICriterion theCriterion) { - myCriterionList.add((ICriterionInternal) theCriterion); - return this; - } - - @Override - public IUpdateWithQueryTyped and(ICriterion theCriterion) { - myCriterionList.add((ICriterionInternal) theCriterion); - return this; - } - - } - - @Override - public IOperation operation() { - // TODO Auto-generated method stub - return null; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java index 0479387a149..cb34846e28b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java @@ -34,10 +34,13 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IRestfulClient; +import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.ICreate; +import ca.uhn.fhir.rest.gclient.ICreateTyped; import ca.uhn.fhir.rest.gclient.IDelete; import ca.uhn.fhir.rest.gclient.IGetPage; import ca.uhn.fhir.rest.gclient.IGetTags; +import ca.uhn.fhir.rest.gclient.IHistory; import ca.uhn.fhir.rest.gclient.IOperation; import ca.uhn.fhir.rest.gclient.IRead; import ca.uhn.fhir.rest.gclient.ITransaction; @@ -101,6 +104,11 @@ public interface IGenericClient { */ IGetTags getTags(); + /** + * Implementation of the "history" method + */ + IHistory history(); + /** * Implementation of the "history instance" method. * @@ -117,6 +125,7 @@ public interface IGenericClient { * server may return less even if more are available, but should not return more according to the FHIR * specification. * @return A bundle containing returned resources + * @deprecated As of 0.9, use the fluent {@link #history()} method instead */ Bundle history(Class theType, IdDt theId, DateTimeDt theSince, Integer theLimit); @@ -136,6 +145,7 @@ public interface IGenericClient { * server may return less even if more are available, but should not return more according to the FHIR * specification. * @return A bundle containing returned resources + * @deprecated As of 0.9, use the fluent {@link #history()} method instead */ Bundle history(Class theType, String theId, DateTimeDt theSince, Integer theLimit); @@ -147,11 +157,6 @@ public interface IGenericClient { */ IGetPage loadPage(); - /** - * Fluent method for "read" and "vread" methods. - */ - IRead read(); - // /** // * Implementation of the "instance read" method. This method will only ever do a "read" for the latest version of a // * given resource instance, even if the ID passed in contains a version. If you wish to request a specific version @@ -170,6 +175,16 @@ public interface IGenericClient { // */ // T read(Class theType, IdDt theId); + /** + * Implementation of the FHIR "extended operations" action + */ + IOperation operation(); + + /** + * Fluent method for "read" and "vread" methods. + */ + IRead read(); + /** * Implementation of the "instance read" method. * @@ -343,9 +358,4 @@ public interface IGenericClient { */ T vread(Class theType, String theId, String theVersionId); - /** - * Implementation of the FHIR "extended operations" action - */ - IOperation operation(); - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java new file mode 100644 index 00000000000..6341c0227f3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IBaseOn.java @@ -0,0 +1,28 @@ +package ca.uhn.fhir.rest.gclient; + +import org.hl7.fhir.instance.model.IBaseResource; + +import ca.uhn.fhir.model.primitive.IdDt; + +public interface IBaseOn { + + /** + * Perform the operation across all versions of all resources of all types on the server + */ + T ofServer(); + + /** + * Perform the operation across all versions of all resources of the given type on the server + */ + T ofType(Class theResourceType); + + /** + * Perform the operation across all versions of a specific resource (by ID and type) on the server. + * Note that theId must be populated with both a resource type and a resource ID at + * a minimum. + * + * @throws IllegalArgumentException If theId does not contain at least a resource type and ID + */ + T ofInstance(IdDt theId); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistory.java new file mode 100644 index 00000000000..3a3dde7e140 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistory.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IHistory extends IBaseOn { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java new file mode 100644 index 00000000000..e2c790bb87d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryTyped.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.rest.gclient; + +import java.util.Date; + +import ca.uhn.fhir.model.primitive.InstantDt; + +public interface IHistoryTyped extends IClientExecutable, T> { + + /** + * Request that the server return only resource versions that were created at or after the given time (inclusive) + */ + IHistoryTyped since(Date theCutoff); + + /** + * Request that the server return only resource versions that were created at or after the given time (inclusive) + */ + IHistoryTyped since(InstantDt theCutoff); + + /** + * Request that the server return only up to theCount number of resources + */ + IHistoryTyped count(Integer theCount); + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryUntyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryUntyped.java new file mode 100644 index 00000000000..53b3b5dbdb9 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IHistoryUntyped.java @@ -0,0 +1,21 @@ +package ca.uhn.fhir.rest.gclient; + +import org.hl7.fhir.instance.model.api.IBaseBundle; + +import ca.uhn.fhir.model.api.Bundle; + +public interface IHistoryUntyped { + + /** + * Request that the method return a DSTU1 style bundle object. This method should only + * be called if you are accessing a DSTU1 server. + */ + IHistoryTyped andReturnDstu1Bundle(); + + /** + * Request that the method return a Bundle resource (such as ca.uhn.fhir.model.dstu2.resource.Bundle). + * Use this method if you are accessing a DSTU2+ server. + */ + IHistoryTyped andReturnBundle(Class theType); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationOn.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationOn.java index 79dfb2fd9f0..8be7145a07d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationOn.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationOn.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.rest.gclient; -public interface IOperationOn { +public interface IOperationOn extends IBaseOn { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationTyped.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationTyped.java new file mode 100644 index 00000000000..cdfb83bac89 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationTyped.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.rest.gclient; + +public interface IOperationTyped { + + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java index c2cb2ef97d8..2baec5d23cb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java @@ -190,7 +190,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding HttpServletResponse response = theRequest.getServletResponse(); response.setContentType(Constants.CT_TEXT); response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_UTF_8); + response.setCharacterEncoding(Constants.CHARSETNAME_UTF_8); theServer.addHeadersToResponse(response); 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 65ebdbf3e01..3d8d527f8c8 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseHttpClientInvocationWithContents.java @@ -20,6 +20,10 @@ package ca.uhn.fhir.rest.method; * #L% */ +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; @@ -207,7 +211,14 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca if (myResource != null && BaseBinary.class.isAssignableFrom(myResource.getClass())) { BaseBinary binary = (BaseBinary) myResource; - ByteArrayEntity entity = new ByteArrayEntity(binary.getContent(), ContentType.parse(binary.getContentType())); + + /* + * Note: Be careful about changing which constructor we use for ByteArrayEntity, + * as Android's version of HTTPClient doesn't support the newer ones for + * whatever reason. + */ + ByteArrayEntity entity = new ByteArrayEntity(binary.getContent()); + entity.setContentType(binary.getContentType()); HttpRequestBase retVal = createRequest(url, entity); addMatchHeaders(retVal, url); return retVal; @@ -275,7 +286,14 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca contents = parser.encodeResourceToString(myResource); contentType = encoding.getResourceContentType(); } - entity = new StringEntity(contents, ContentType.create(contentType, Constants.CHARSET_UTF_8)); + + /* + * We aren't using a StringEntity here because the constructors supported by + * Android aren't available in non-Android, and vice versa. Since we add the + * content type header manually, it makes no difference which one + * we use anyhow. + */ + entity = new ByteArrayEntity(contents.getBytes(Constants.CHARSET_UTF8)); } HttpRequestBase retVal = createRequest(url, entity); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index b19db9b2e19..4056433f40c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java @@ -173,7 +173,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding { HttpServletResponse response = theRequest.getServletResponse(); response.setContentType(responseEncoding.getResourceContentType()); response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_UTF_8); + response.setCharacterEncoding(Constants.CHARSETNAME_UTF_8); theServer.addHeadersToResponse(response); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index ccf2e630e90..13ad5d3a5a6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -20,7 +20,8 @@ package ca.uhn.fhir.rest.method; * #L% */ -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -34,7 +35,7 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; -import ca.uhn.fhir.model.primitive.DateTimeDt; +import ca.uhn.fhir.model.primitive.BaseDateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.valueset.BundleTypeEnum; @@ -116,7 +117,8 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } } - HttpGetClientInvocation retVal = createHistoryInvocation(resourceName, id, null, null); + String historyId = id != null ? id.getIdPart() : null; + HttpGetClientInvocation retVal = createHistoryInvocation(resourceName, historyId, null, null); if (theArgs != null) { for (int idx = 0; idx < theArgs.length; idx++) { @@ -133,13 +135,13 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { return BundleTypeEnum.HISTORY; } - public static HttpGetClientInvocation createHistoryInvocation(String theResourceName, IdDt theId, DateTimeDt theSince, Integer theLimit) { + public static HttpGetClientInvocation createHistoryInvocation(String theResourceName, String theId, BaseDateTimeDt theSince, Integer theLimit) { StringBuilder b = new StringBuilder(); if (theResourceName != null) { b.append(theResourceName); - if (theId != null && !theId.isEmpty()) { + if (isNotBlank(theId)) { b.append('/'); - b.append(theId.getValue()); + b.append(theId); } } if (b.length() > 0) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 4296edc4a88..be99c1feae2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.server; * #L% */ +import java.nio.charset.Charset; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -28,7 +29,7 @@ import java.util.Set; public class Constants { - public static final String CHARSET_UTF_8 = "UTF-8"; + public static final String CHARSETNAME_UTF_8 = "UTF-8"; public static final String CT_ATOM_XML = "application/atom+xml"; public static final String CT_FHIR_JSON = "application/json+fhir"; @@ -119,6 +120,7 @@ public class Constants { public static final String LINK_NEXT = "next"; public static final String LINK_LAST = "last"; public static final String LINK_FHIR_BASE = "fhir-base"; + public static final Charset CHARSET_UTF8; static { Map valToEncoding = new HashMap(); @@ -142,6 +144,8 @@ public class Constants { } FORMAT_VAL_TO_ENCODING = Collections.unmodifiableMap(valToEncoding); + + CHARSET_UTF8 = Charset.forName(CHARSETNAME_UTF_8); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 597546fb148..516c7700348 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -104,7 +104,7 @@ public class RestfulServerUtils { } else { theHttpResponse.setContentType(responseEncoding.getResourceContentType()); } - theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8); + theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8); theServer.addHeadersToResponse(theHttpResponse); @@ -340,7 +340,7 @@ public class RestfulServerUtils { theHttpResponse.setContentType(responseEncoding.getBundleContentType()); } - theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8); + theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8); theServer.addHeadersToResponse(theHttpResponse); diff --git a/hapi-fhir-jpaserver-base/.classpath b/hapi-fhir-jpaserver-base/.classpath index 9ebe1a6b158..f9148a1a7ac 100644 --- a/hapi-fhir-jpaserver-base/.classpath +++ b/hapi-fhir-jpaserver-base/.classpath @@ -27,7 +27,7 @@ - + diff --git a/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml b/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml index 5c9bd7532ab..4f92af543f1 100644 --- a/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml +++ b/hapi-fhir-jpaserver-base/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -1,5 +1,5 @@ - + diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index e828e14875d..39bdf198d93 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.io.IOException; +import java.io.InputStream; import java.io.StringReader; import java.net.URLEncoder; import java.nio.charset.Charset; @@ -30,6 +31,8 @@ import org.junit.BeforeClass; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; @@ -639,6 +642,61 @@ public class GenericClientTest { } + @Test + public void testHistory() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor 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()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + }}); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + Bundle response; + + //@formatter:off + response = client + .history() + .ofServer() + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + + //@formatter:off + response = client + .history() + .ofType(Patient.class) + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + + //@formatter:off + response = client + .history() + .ofInstance(new IdDt("Patient", "123")) + .andReturnDstu1Bundle() + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.size()); + idx++; + } + + @SuppressWarnings("unused") @Test public void testSearchByNumberExact() throws Exception { @@ -1069,7 +1127,7 @@ public class GenericClientTest { HttpEntityEnclosingRequestBase value = (HttpEntityEnclosingRequestBase) capt.getValue(); - Header ct = value.getEntity().getContentType(); + Header ct = value.getFirstHeader(Constants.HEADER_CONTENT_TYPE); assertNotNull(ct); assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue()); diff --git a/hapi-fhir-structures-dstu2/.gitignore b/hapi-fhir-structures-dstu2/.gitignore index 3f6aa3cd273..69d045b5132 100644 --- a/hapi-fhir-structures-dstu2/.gitignore +++ b/hapi-fhir-structures-dstu2/.gitignore @@ -10,3 +10,4 @@ /target/ /target/ /target/ +/target/ diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java index 676f7d8b741..06590c5ac78 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/client/GenericClientTestDstu2.java @@ -1,11 +1,13 @@ package ca.uhn.fhir.rest.client; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.IOException; -import java.io.Reader; +import java.io.InputStream; import java.io.StringReader; import java.nio.charset.Charset; import java.util.ArrayList; @@ -34,7 +36,6 @@ import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.server.Constants; @@ -58,6 +59,82 @@ public class GenericClientTestDstu2 { myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); } + private String getPatientFeedWithOneResult() { + //@formatter:off + String msg = "\n" + + "d039f91a-cc3c-4013-988e-af4d8d0614bd\n" + + "\n" + + "" + + "" + + "
John Cardinal: 444333333
" + + "" + + "" + + "" + + "" + + "
" + + "
" + + "
\n" + + "
\n" + + ""; + //@formatter:on + return msg; + } + + @Test + public void testHistory() throws Exception { + + final String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor 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()).thenAnswer(new Answer() { + @Override + public InputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")); + }}); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + int idx = 0; + ca.uhn.fhir.model.dstu2.resource.Bundle response; + + //@formatter:off + response = client + .history() + .ofServer() + .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + + //@formatter:off + response = client + .history() + .ofType(Patient.class) + .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + + //@formatter:off + response = client + .history() + .ofInstance(new IdDt("Patient", "123")) + .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + //@formatter:on + assertEquals("http://example.com/fhir/Patient/123/_history", capt.getAllValues().get(idx).getURI().toString()); + assertEquals(1, response.getEntry().size()); + idx++; + } + + @Test public void testSearchByString() throws Exception { String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}"; diff --git a/pom.xml b/pom.xml index 2bf5f85cdd3..6e596d7739c 100644 --- a/pom.xml +++ b/pom.xml @@ -761,7 +761,7 @@ restful-server-example-test hapi-fhir-testpage-overlay hapi-fhir-jpaserver-uhnfhirtest - + hapi-fhir-android hapi-fhir-dist diff --git a/src/site/xdoc/doc_rest_client.xml b/src/site/xdoc/doc_rest_client.xml index 3d79cd3b349..e7a2160cc09 100644 --- a/src/site/xdoc/doc_rest_client.xml +++ b/src/site/xdoc/doc_rest_client.xml @@ -74,7 +74,7 @@

- +

Searching for resources is probably the most common initial scenario for @@ -186,7 +186,7 @@ - +

The following example shows how to perform a create operation using the generic client: @@ -211,7 +211,7 @@ - +

Given a resource name and ID, it is simple to retrieve the latest version of that resource (a 'read') @@ -249,7 +249,7 @@ - +

The following example shows how to perform a delete operation using the generic client: @@ -275,7 +275,7 @@ - +

Updating a resource is similar to creating one, except that an ID must be supplied since you are updating a previously @@ -315,7 +315,54 @@ - + +

+ To retrieve the version history of all resources, or all resources of a given type, or + of a specific instance of a resource, you call the history() + method. +

+ + + + + +

+ If you are using a DSTU2 compliant server, you should instead use the + Bundle resource which is found in the DSTU2 structures JAR, as shown + in the syntax below. Note that in both cases, the class name is Bundle, + but the DSTU2 bundle is found in the .resources. package. +

+ + + + + +

+ You can also optionally request that only resource versions + later than a given date, and/or only up to a given count (number) + of resource versions be returned. +

+ + + + +
+ + +

+ The following example shows how to execute a transaction using the generic client: +

+ + + + +
+ +

To retrieve the server's conformance statement, simply call the conformance() method as shown below. @@ -326,18 +373,7 @@ value="examples/src/main/java/example/GenericClientExample.java" /> - - -

- The following example shows how to execute a transaction using the generic client: -

- - - - -
- +