Work on history suport for fluent client

This commit is contained in:
James Agnew 2015-03-03 18:31:01 -05:00
parent 27e126254c
commit fc4fb07562
26 changed files with 975 additions and 410 deletions

View File

@ -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();
}

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>0.9-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hapi-fhir-android</artifactId>
<name>HAPI FHIR - Android</name>
<build>
<plugins>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven_failsafe_plugin_version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<classpathDependencyScopeExclude>compile+runtime</classpathDependencyScopeExclude>
<additionalClasspathElements>
<additionalClasspathElement>${project.build.directory}/hapi-fhir-android-${project.version}.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<artifactSet>
<includes>
<include>commons-codec:commons-codec</include>
<include>commons-io:commons-io</include>
<include>ca.uhn.hapi.fhir:hapi-fhir-base</include>
<include>ca.uhn.hapi.fhir:hapi-fhir-structures-dstu</include>
<include>ca.uhn.hapi.fhir:hapi-fhir-structures-dstu2</include>
<include>org.glassfish:javax.json</include>
<include>org.codehaus.woodstox:woodstox-core-asl</include>
<include>javax.xml.stream:stax-api</include>
<include>org.codehaus.woodstox:stax2-api</include>
<include>org.slf4j:slf4j*</include>
<include>org.apache.commons:*</include>
<include>org.apache.httpcomponents:*</include>
<include>org.glassfish:javax.json</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>javax.xml.stream</pattern>
<shadedPattern>ca.uhn.fhir.repackage.javax.xml.stream</shadedPattern>
</relocation>
<relocation>
<pattern>javax.json</pattern>
<shadedPattern>ca.uhn.fhir.repackage.javax.json</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>ca.uhn.hapi.fhir:hapi-fhir-base</artifact>
<excludes>
<exclude>ca/uhn/fhir/model/dstu/valueset/**</exclude>
<exclude>**/*.java</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>DIST</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven_assembly_plugin_version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>false</attach>
<descriptors>
<descriptor>${project.basedir}/src/assembly/hapi-fhir-all.xml</descriptor>
<descriptor>${project.basedir}/src/assembly/hapi-fhir-jpaserver-example.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
<version>2.7.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-commons</artifactId>
<version>4.3.5</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -28,6 +28,10 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</exclusion>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -63,17 +67,21 @@
<!-- <dependency> <groupId>org.codehaus.woodstox</groupId> <artifactId>stax2-api</artifactId>
<version>3.1.4</version> </dependency> -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons_codec_version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons_io_version}</version>
</dependency>
<!--
Android does not come with the Servlet API bundled, and MethodUtil requires it
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet_api_version}</version>
<scope>compile</scope>
</dependency>
<!-- Testing -->
<dependency>
@ -102,7 +110,7 @@
<configuration>
<classpathDependencyScopeExclude>compile+runtime</classpathDependencyScopeExclude>
<additionalClasspathElements>
<additionalClasspathElement>target/hapi-fhir-android-0.9-SNAPSHOT-shaded.jar</additionalClasspathElement>
<additionalClasspathElement>${project.build.directory}/hapi-fhir-android-${project.version}.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
<executions>
@ -125,7 +133,6 @@
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<!--<minimizeJar>true</minimizeJar>-->
<artifactSet>
@ -158,13 +165,6 @@
<filters>
<filter>
<artifact>ca.uhn.hapi.fhir:hapi-fhir-base</artifact>
<!--
<includes>
<include>**/*.class</include>
<include>**/*.properties</include>
<include>**/*.html</include>
</includes>
-->
<excludes>
<exclude>ca/uhn/fhir/model/dstu/valueset/**</exclude>
<exclude>**/*.java</exclude>
@ -177,34 +177,4 @@
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>DIST</id>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>${maven_assembly_plugin_version}</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>false</attach>
<descriptors>
<descriptor>${project.basedir}/src/assembly/hapi-fhir-all.xml</descriptor>
<descriptor>${project.basedir}/src/assembly/hapi-fhir-jpaserver-example.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -57,9 +57,10 @@ public class BuiltJarIT {
*/
@Test
public void testJarForDuplicates() throws Exception {
Collection<File> files = FileUtils.listFiles(new File("target"), new WildcardFileFilter("*-shaded.jar"), null);
String wildcard = "hapi-fhir-android-*.jar";
Collection<File> 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) {

View File

@ -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<byte[]> {
@ -46,12 +47,12 @@ public class Base64BinaryDt extends BasePrimitive<byte[]> {
@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);
}
}

View File

@ -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<? extends IResource> 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<? extends IResource> theType, IdDt theId) {
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType)));
public MethodOutcome delete(Class<? extends IResource> theType, String theId) {
return delete(theType, new IdDt(theId));
}
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IdDt theId, boolean theVRead, ICallable<T> 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<T> binding = new ResourceResponseHandler<T>(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<? extends IResource> 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 <T extends IResource> Bundle history(final Class<T> 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<? extends IBaseResource> 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 extends IBaseResource> T read(final Class<T> 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 extends IBaseResource> T read(final Class<T> 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 extends IBaseResource> T read(Class<T> theType, String theId) {
@ -316,37 +414,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return search(inferResourceClass(theUrl), theUrl);
}
private Class<? extends IBaseResource> 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 extends IBaseResource> T doReadOrVRead(final Class<T> theType, IdDt theId, boolean theVRead, ICallable<T> 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<T> binding = new ResourceResponseHandler<T>(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> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> 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<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<ICreateTyped, MethodOutcome> 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<ICriterionInternal> {
private static final long serialVersionUID = 1L;
public void populateParamList(Map<String, List<String>> 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<String, List<String>> toParamList() {
LinkedHashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
populateParamList(retVal);
return retVal;
}
}
private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, BaseOperationOutcome> 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<ICriterionInternal> {
private static final long serialVersionUID = 1L;
public void populateParamList(Map<String, List<String>> theParams) {
for (ICriterionInternal next : this) {
String parameterName = next.getParameterName();
String parameterValue = next.getParameterValue();
addParam(theParams, parameterName, parameterValue);
}
}
public Map<String, List<String>> toParamList() {
LinkedHashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
populateParamList(retVal);
return retVal;
}
}
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> 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 <T extends IBaseResource> IReadTyped<T> resource(Class<T> 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<IBaseResource> 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<IGetTags, TagList> 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<? extends IBaseBundle> myReturnType;
private InstantDt mySince;
private Class<? extends IBaseResource> 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<? extends IBaseResource> 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<BaseOperationOutcome> {
@Override
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public BaseOperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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 <T extends IBaseResource> IReadTyped<T> resource(Class<T> 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<IBaseResource> 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<List<IResource>> {
private Class<? extends IResource> myType;
@ -1067,7 +1183,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("unchecked")
@Override
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType, null);
@ -1278,7 +1395,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class TagListResponseHandler implements IClientResponseHandler<TagList> {
@Override
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> 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<IUpdateExecutable, MethodOutcome> 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;
}
}

View File

@ -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
*/
<T extends IResource> Bundle history(Class<T> 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
*/
<T extends IResource> Bundle history(Class<T> 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 extends IBaseResource> T read(Class<T> 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 extends IBaseResource> T vread(Class<T> theType, String theId, String theVersionId);
/**
* Implementation of the FHIR "extended operations" action
*/
IOperation operation();
}

View File

@ -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<T> {
/**
* 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<? extends IBaseResource> theResourceType);
/**
* Perform the operation across all versions of a specific resource (by ID and type) on the server.
* Note that <code>theId</code> must be populated with both a resource type and a resource ID at
* a minimum.
*
* @throws IllegalArgumentException If <code>theId</code> does not contain at least a resource type and ID
*/
T ofInstance(IdDt theId);
}

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.rest.gclient;
public interface IHistory extends IBaseOn<IHistoryUntyped> {
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.rest.gclient;
import java.util.Date;
import ca.uhn.fhir.model.primitive.InstantDt;
public interface IHistoryTyped<T> extends IClientExecutable<IHistoryTyped<T>, T> {
/**
* Request that the server return only resource versions that were created at or after the given time (inclusive)
*/
IHistoryTyped<T> since(Date theCutoff);
/**
* Request that the server return only resource versions that were created at or after the given time (inclusive)
*/
IHistoryTyped<T> since(InstantDt theCutoff);
/**
* Request that the server return only up to <code>theCount</code> number of resources
*/
IHistoryTyped<T> count(Integer theCount);
}

View File

@ -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<Bundle> andReturnDstu1Bundle();
/**
* Request that the method return a Bundle resource (such as <code>ca.uhn.fhir.model.dstu2.resource.Bundle</code>).
* Use this method if you are accessing a DSTU2+ server.
*/
<T extends IBaseBundle> IHistoryTyped<T> andReturnBundle(Class<T> theType);
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.rest.gclient;
public interface IOperationOn {
public interface IOperationOn extends IBaseOn<IOperationTyped> {

View File

@ -0,0 +1,7 @@
package ca.uhn.fhir.rest.gclient;
public interface IOperationTyped {
}

View File

@ -190,7 +190,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
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);

View File

@ -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);

View File

@ -173,7 +173,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
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);

View File

@ -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) {

View File

@ -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<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();
@ -142,6 +144,8 @@ public class Constants {
}
FORMAT_VAL_TO_ENCODING = Collections.unmodifiableMap(valToEncoding);
CHARSET_UTF8 = Charset.forName(CHARSETNAME_UTF_8);
}
}

View File

@ -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);

View File

@ -27,7 +27,7 @@
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="owner.project.facets" value="java"/>
</attributes>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<installed facet="jst.utility" version="1.0"/>
<installed facet="java" version="1.6"/>
<installed facet="java" version="1.7"/>
</faceted-project>

View File

@ -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<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()).thenAnswer(new Answer<InputStream>() {
@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());

View File

@ -10,3 +10,4 @@
/target/
/target/
/target/
/target/

View File

@ -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 = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
"<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" +
"<entry>\n" +
"<resource>"
+ "<Patient>"
+ "<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>"
+ "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />"
+ "</Patient>"
+ "</resource>\n"
+ " </entry>\n"
+ "</feed>";
//@formatter:on
return msg;
}
@Test
public void testHistory() throws Exception {
final 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()).thenAnswer(new Answer<InputStream>() {
@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\"}]}}]}";

View File

@ -761,7 +761,7 @@
<module>restful-server-example-test</module>
<module>hapi-fhir-testpage-overlay</module>
<module>hapi-fhir-jpaserver-uhnfhirtest</module>
<!--<module>hapi-fhir-android</module>-->
<module>hapi-fhir-android</module>
<module>hapi-fhir-dist</module>
</modules>
</profile>

View File

@ -74,7 +74,7 @@
</p>
</subsection>
<subsection name="Type - Search/Query">
<subsection name="Search/Query - Type">
<p>
Searching for resources is probably the most common initial scenario for
@ -186,7 +186,7 @@
</subsection>
<subsection name="Type - Create">
<subsection name="Create - Type">
<p>
The following example shows how to perform a create
operation using the generic client:
@ -211,7 +211,7 @@
</macro>
</subsection>
<subsection name="Instance - Read / VRead">
<subsection name="Read/VRead - Instance">
<p>
Given a resource name and ID, it is simple to retrieve
the latest version of that resource (a 'read')
@ -249,7 +249,7 @@
</subsection>
<subsection name="Instance - Delete">
<subsection name="Delete - Instance">
<p>
The following example shows how to perform a delete
operation using the generic client:
@ -275,7 +275,7 @@
</macro>
</subsection>
<subsection name="Instance - Update">
<subsection name="Update - Instance">
<p>
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 @@
</subsection>
<subsection name="Server - Conformance">
<subsection name="History - Server/Type/Instance">
<p>
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 <code>history()</code>
method.
</p>
<macro name="snippet">
<param name="id" value="historyDstu1" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<p>
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 <code>Bundle</code>,
but the DSTU2 bundle is found in the <code>.resources.</code> package.
</p>
<macro name="snippet">
<param name="id" value="historyDstu2" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<p>
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.
</p>
<macro name="snippet">
<param name="id" value="historyFeatures" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Transaction - Server">
<p>
The following example shows how to execute a transaction using the generic client:
</p>
<macro name="snippet">
<param name="id" value="transaction" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Conformance - Server">
<p>
To retrieve the server's conformance statement, simply call the <code>conformance()</code>
method as shown below.
@ -326,18 +373,7 @@
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Server - Transaction">
<p>
The following example shows how to execute a transaction using the generic client:
</p>
<macro name="snippet">
<param name="id" value="transaction" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
</section>
<section name="The Annotation-Driven Client">