mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-09 14:33:32 +00:00
Work on history suport for fluent client
This commit is contained in:
parent
27e126254c
commit
fc4fb07562
@ -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.Organization;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.method.SearchStyleEnum;
|
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) {
|
public static void main(String[] args) {
|
||||||
fluentSearch();
|
fluentSearch();
|
||||||
}
|
}
|
||||||
|
151
hapi-fhir-android/dependency-reduced-pom.xml
Normal file
151
hapi-fhir-android/dependency-reduced-pom.xml
Normal 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>
|
||||||
|
|
@ -28,6 +28,10 @@
|
|||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpcore</artifactId>
|
<artifactId>httpcore</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -63,17 +67,21 @@
|
|||||||
<!-- <dependency> <groupId>org.codehaus.woodstox</groupId> <artifactId>stax2-api</artifactId>
|
<!-- <dependency> <groupId>org.codehaus.woodstox</groupId> <artifactId>stax2-api</artifactId>
|
||||||
<version>3.1.4</version> </dependency> -->
|
<version>3.1.4</version> </dependency> -->
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-codec</groupId>
|
|
||||||
<artifactId>commons-codec</artifactId>
|
|
||||||
<version>${commons_codec_version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>${commons_io_version}</version>
|
<version>${commons_io_version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Testing -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -102,7 +110,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<classpathDependencyScopeExclude>compile+runtime</classpathDependencyScopeExclude>
|
<classpathDependencyScopeExclude>compile+runtime</classpathDependencyScopeExclude>
|
||||||
<additionalClasspathElements>
|
<additionalClasspathElements>
|
||||||
<additionalClasspathElement>target/hapi-fhir-android-0.9-SNAPSHOT-shaded.jar</additionalClasspathElement>
|
<additionalClasspathElement>${project.build.directory}/hapi-fhir-android-${project.version}.jar</additionalClasspathElement>
|
||||||
</additionalClasspathElements>
|
</additionalClasspathElements>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
@ -125,7 +133,6 @@
|
|||||||
<goal>shade</goal>
|
<goal>shade</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
|
||||||
<createDependencyReducedPom>true</createDependencyReducedPom>
|
<createDependencyReducedPom>true</createDependencyReducedPom>
|
||||||
<!--<minimizeJar>true</minimizeJar>-->
|
<!--<minimizeJar>true</minimizeJar>-->
|
||||||
<artifactSet>
|
<artifactSet>
|
||||||
@ -158,13 +165,6 @@
|
|||||||
<filters>
|
<filters>
|
||||||
<filter>
|
<filter>
|
||||||
<artifact>ca.uhn.hapi.fhir:hapi-fhir-base</artifact>
|
<artifact>ca.uhn.hapi.fhir:hapi-fhir-base</artifact>
|
||||||
<!--
|
|
||||||
<includes>
|
|
||||||
<include>**/*.class</include>
|
|
||||||
<include>**/*.properties</include>
|
|
||||||
<include>**/*.html</include>
|
|
||||||
</includes>
|
|
||||||
-->
|
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>ca/uhn/fhir/model/dstu/valueset/**</exclude>
|
<exclude>ca/uhn/fhir/model/dstu/valueset/**</exclude>
|
||||||
<exclude>**/*.java</exclude>
|
<exclude>**/*.java</exclude>
|
||||||
@ -177,34 +177,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</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>
|
</project>
|
||||||
|
@ -57,9 +57,10 @@ public class BuiltJarIT {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testJarForDuplicates() throws Exception {
|
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()) {
|
if (files.isEmpty()) {
|
||||||
throw new Exception("No files matching target/*-shaded.jar");
|
throw new Exception("No files matching " + wildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
|
@ -25,6 +25,7 @@ import org.apache.commons.codec.binary.Base64;
|
|||||||
import ca.uhn.fhir.model.api.BasePrimitive;
|
import ca.uhn.fhir.model.api.BasePrimitive;
|
||||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||||
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
||||||
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
|
|
||||||
@DatatypeDef(name = "base64Binary")
|
@DatatypeDef(name = "base64Binary")
|
||||||
public class Base64BinaryDt extends BasePrimitive<byte[]> {
|
public class Base64BinaryDt extends BasePrimitive<byte[]> {
|
||||||
@ -46,12 +47,12 @@ public class Base64BinaryDt extends BasePrimitive<byte[]> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] parse(String theValue) {
|
protected byte[] parse(String theValue) {
|
||||||
return Base64.decodeBase64(theValue);
|
return Base64.decodeBase64(theValue.getBytes(Constants.CHARSET_UTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String encode(byte[] theValue) {
|
protected String encode(byte[] theValue) {
|
||||||
return Base64.encodeBase64String(theValue);
|
return new String(Base64.encodeBase64(theValue), Constants.CHARSET_UTF8);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,13 @@ package ca.uhn.fhir.rest.client;
|
|||||||
* #L%
|
* #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.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
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.HttpClient;
|
||||||
import org.apache.http.client.methods.HttpRequestBase;
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
import org.hl7.fhir.instance.model.IBaseResource;
|
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.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
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.base.resource.BaseOperationOutcome;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.model.primitive.UriDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
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.IGetPage;
|
||||||
import ca.uhn.fhir.rest.gclient.IGetPageTyped;
|
import ca.uhn.fhir.rest.gclient.IGetPageTyped;
|
||||||
import ca.uhn.fhir.rest.gclient.IGetTags;
|
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.IOperation;
|
||||||
import ca.uhn.fhir.rest.gclient.IParam;
|
import ca.uhn.fhir.rest.gclient.IParam;
|
||||||
import ca.uhn.fhir.rest.gclient.IQuery;
|
import ca.uhn.fhir.rest.gclient.IQuery;
|
||||||
@ -111,12 +118,13 @@ import ca.uhn.fhir.util.ICallable;
|
|||||||
public class GenericClient extends BaseClient implements IGenericClient {
|
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_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_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 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 static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
|
||||||
|
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
|
|
||||||
private HttpRequestBase myLastRequest;
|
private HttpRequestBase myLastRequest;
|
||||||
private boolean myLogRequestAndResponse;
|
private boolean myLogRequestAndResponse;
|
||||||
|
|
||||||
@ -170,6 +178,19 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return new DeleteInternal();
|
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) {
|
// public IResource read(UriDt url) {
|
||||||
// return read(inferResourceClass(url), url);
|
// return read(inferResourceClass(url), url);
|
||||||
// }
|
// }
|
||||||
@ -184,21 +205,51 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
|
public MethodOutcome delete(Class<? extends IResource> theType, String theId) {
|
||||||
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType)));
|
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()) {
|
if (isKeepResponses()) {
|
||||||
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
|
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
|
||||||
}
|
}
|
||||||
|
|
||||||
final String resourceName = myContext.getResourceDefinition(theType).getName();
|
if (theIfVersionMatches != null) {
|
||||||
OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName);
|
invocation.addHeader(Constants.HEADER_IF_NONE_MATCH, '"' + theIfVersionMatches + '"');
|
||||||
MethodOutcome resp = invokeClient(myContext, binding, invocation, myLogRequestAndResponse);
|
}
|
||||||
return resp;
|
|
||||||
}
|
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() {
|
public HttpRequestBase getLastRequest() {
|
||||||
@ -217,10 +268,15 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return new GetTagsInternal();
|
return new GetTagsInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IHistory history() {
|
||||||
|
return new HistoryInternal();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) {
|
public <T extends IResource> Bundle history(final Class<T> theType, IdDt theIdDt, DateTimeDt theSince, Integer theLimit) {
|
||||||
String resourceName = theType != null ? toResourceName(theType) : null;
|
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);
|
HttpGetClientInvocation invocation = HistoryMethodBinding.createHistoryInvocation(resourceName, id, theSince, theLimit);
|
||||||
if (isKeepResponses()) {
|
if (isKeepResponses()) {
|
||||||
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
|
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);
|
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() {
|
public boolean isLogRequestAndResponse() {
|
||||||
return myLogRequestAndResponse;
|
return myLogRequestAndResponse;
|
||||||
}
|
}
|
||||||
@ -246,10 +338,16 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return new LoadPageInternal();
|
return new LoadPageInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
@Override
|
||||||
// public <T extends IBaseResource> T read(final Class<T> theType, IdDt theId) {
|
public IOperation operation() {
|
||||||
// return doReadOrVRead(theType, theId, false, null, null);
|
// TODO Auto-generated method stub
|
||||||
// }
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IRead read() {
|
||||||
|
return new ReadInternal();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends IBaseResource> T read(Class<T> theType, String theId) {
|
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);
|
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!
|
* 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);
|
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 */
|
/* also deprecated in interface */
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -517,6 +541,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return (T) this;
|
return (T) this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected EncodingEnum getParamEncoding() {
|
||||||
|
return myParamEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
|
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
|
||||||
// if (myParamEncoding != null) {
|
// if (myParamEncoding != null) {
|
||||||
// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
|
// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
|
||||||
@ -534,10 +562,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected EncodingEnum getParamEncoding() {
|
|
||||||
return myParamEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IResource parseResourceBody(String theResourceBody) {
|
protected IResource parseResourceBody(String theResourceBody) {
|
||||||
EncodingEnum encoding = null;
|
EncodingEnum encoding = null;
|
||||||
for (int i = 0; i < theResourceBody.length() && encoding == null; i++) {
|
for (int i = 0; i < theResourceBody.length() && encoding == null; i++) {
|
||||||
@ -574,7 +598,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
|
||||||
if (respType == null) {
|
if (respType == null) {
|
||||||
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
|
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 class CreateInternal extends BaseClientExecutable<ICreateTyped, MethodOutcome> implements ICreate, ICreateTyped, ICreateWithQuery, ICreateWithQueryTyped {
|
||||||
|
|
||||||
|
private CriterionList myCriterionList;
|
||||||
private String myId;
|
private String myId;
|
||||||
private IResource myResource;
|
private IResource myResource;
|
||||||
private String myResourceBody;
|
private String myResourceBody;
|
||||||
private String mySearchUrl;
|
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
|
@Override
|
||||||
public MethodOutcome execute() {
|
public MethodOutcome execute() {
|
||||||
@ -637,6 +680,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ICreateWithQueryTyped where(ICriterion<?> theCriterion) {
|
||||||
|
myCriterionList.add((ICriterionInternal) theCriterion);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CreateInternal withId(IdDt theId) {
|
public CreateInternal withId(IdDt theId) {
|
||||||
myId = theId.getIdPart();
|
myId = theId.getIdPart();
|
||||||
@ -649,38 +698,40 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
}
|
||||||
public ICreateTyped conditionalByUrl(String theSearchUrl) {
|
|
||||||
mySearchUrl = theSearchUrl;
|
private static class CriterionList extends ArrayList<ICriterionInternal> {
|
||||||
return this;
|
|
||||||
|
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 Map<String, List<String>> toParamList() {
|
||||||
public ICreateWithQuery conditional() {
|
LinkedHashMap<String, List<String>> retVal = new LinkedHashMap<String, List<String>>();
|
||||||
myCriterionList = new CriterionList();
|
populateParamList(retVal);
|
||||||
return this;
|
return retVal;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ICreateWithQueryTyped where(ICriterion<?> theCriterion) {
|
|
||||||
myCriterionList.add((ICriterionInternal) theCriterion);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ICreateWithQueryTyped and(ICriterion<?> theCriterion) {
|
|
||||||
myCriterionList.add((ICriterionInternal) theCriterion);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, BaseOperationOutcome> implements IDelete, IDeleteTyped, IDeleteWithQuery, IDeleteWithQueryTyped {
|
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 CriterionList myCriterionList;
|
||||||
|
private IdDt myId;
|
||||||
|
private String myResourceType;
|
||||||
|
private String mySearchUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IDeleteWithQueryTyped and(ICriterion<?> theCriterion) {
|
||||||
|
myCriterionList.add((ICriterionInternal) theCriterion);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaseOperationOutcome execute() {
|
public BaseOperationOutcome execute() {
|
||||||
@ -734,13 +785,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) {
|
|
||||||
Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null");
|
|
||||||
mySearchUrl = theSearchUrl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IDeleteWithQuery resourceConditionalByType(String theResourceType) {
|
public IDeleteWithQuery resourceConditionalByType(String theResourceType) {
|
||||||
Validate.notBlank(theResourceType, "theResourceType can not be blank/null");
|
Validate.notBlank(theResourceType, "theResourceType can not be blank/null");
|
||||||
@ -753,38 +797,19 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IDeleteWithQueryTyped where(ICriterion<?> theCriterion) {
|
public IDeleteTyped resourceConditionalByUrl(String theSearchUrl) {
|
||||||
myCriterionList.add((ICriterionInternal) theCriterion);
|
Validate.notBlank(theSearchUrl, "theSearchUrl can not be blank/null");
|
||||||
|
mySearchUrl = theSearchUrl;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IDeleteWithQueryTyped and(ICriterion<?> theCriterion) {
|
public IDeleteWithQueryTyped where(ICriterion<?> theCriterion) {
|
||||||
myCriterionList.add((ICriterionInternal) theCriterion);
|
myCriterionList.add((ICriterionInternal) theCriterion);
|
||||||
return this;
|
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 class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> implements IGetPageTyped {
|
||||||
|
|
||||||
private String myUrl;
|
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 class GetTagsInternal extends BaseClientExecutable<IGetTags, TagList> implements IGetTags {
|
||||||
|
|
||||||
private String myId;
|
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 {
|
private final class LoadPageInternal implements IGetPage {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1020,7 +1013,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {
|
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {
|
||||||
|
|
||||||
@Override
|
@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);
|
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
|
||||||
if (respType == null) {
|
if (respType == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -1048,7 +1042,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
|
||||||
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
|
if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) {
|
||||||
response.setCreated(true);
|
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 final class ResourceListResponseHandler implements IClientResponseHandler<List<IResource>> {
|
||||||
|
|
||||||
private Class<? extends IResource> myType;
|
private Class<? extends IResource> myType;
|
||||||
@ -1067,7 +1183,8 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@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)) {
|
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
|
||||||
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
|
Class<? extends IBaseResource> bundleType = myContext.getResourceDefinition("Bundle").getImplementingClass();
|
||||||
ResourceResponseHandler<IBaseResource> handler = new ResourceResponseHandler<IBaseResource>((Class<IBaseResource>) bundleType, null);
|
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> {
|
private final class TagListResponseHandler implements IClientResponseHandler<TagList> {
|
||||||
|
|
||||||
@Override
|
@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);
|
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
|
||||||
if (respType == null) {
|
if (respType == null) {
|
||||||
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
|
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 class UpdateInternal extends BaseClientExecutable<IUpdateExecutable, MethodOutcome> implements IUpdate, IUpdateTyped, IUpdateExecutable, IUpdateWithQuery, IUpdateWithQueryTyped {
|
||||||
|
|
||||||
|
private CriterionList myCriterionList;
|
||||||
private IdDt myId;
|
private IdDt myId;
|
||||||
private IResource myResource;
|
private IResource myResource;
|
||||||
private String myResourceBody;
|
private String myResourceBody;
|
||||||
private String mySearchUrl;
|
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
|
@Override
|
||||||
public MethodOutcome execute() {
|
public MethodOutcome execute() {
|
||||||
@ -1391,6 +1527,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IUpdateWithQueryTyped where(ICriterion<?> theCriterion) {
|
||||||
|
myCriterionList.add((ICriterionInternal) theCriterion);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IUpdateExecutable withId(IdDt theId) {
|
public IUpdateExecutable withId(IdDt theId) {
|
||||||
if (theId == null) {
|
if (theId == null) {
|
||||||
@ -1415,36 +1557,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||||||
return this;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,10 +34,13 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||||||
import ca.uhn.fhir.model.primitive.UriDt;
|
import ca.uhn.fhir.model.primitive.UriDt;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
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.ICreate;
|
||||||
|
import ca.uhn.fhir.rest.gclient.ICreateTyped;
|
||||||
import ca.uhn.fhir.rest.gclient.IDelete;
|
import ca.uhn.fhir.rest.gclient.IDelete;
|
||||||
import ca.uhn.fhir.rest.gclient.IGetPage;
|
import ca.uhn.fhir.rest.gclient.IGetPage;
|
||||||
import ca.uhn.fhir.rest.gclient.IGetTags;
|
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.IOperation;
|
||||||
import ca.uhn.fhir.rest.gclient.IRead;
|
import ca.uhn.fhir.rest.gclient.IRead;
|
||||||
import ca.uhn.fhir.rest.gclient.ITransaction;
|
import ca.uhn.fhir.rest.gclient.ITransaction;
|
||||||
@ -101,6 +104,11 @@ public interface IGenericClient {
|
|||||||
*/
|
*/
|
||||||
IGetTags getTags();
|
IGetTags getTags();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of the "history" method
|
||||||
|
*/
|
||||||
|
IHistory history();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the "history instance" method.
|
* 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
|
* server may return less even if more are available, but should not return more according to the FHIR
|
||||||
* specification.
|
* specification.
|
||||||
* @return A bundle containing returned resources
|
* @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);
|
<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
|
* server may return less even if more are available, but should not return more according to the FHIR
|
||||||
* specification.
|
* specification.
|
||||||
* @return A bundle containing returned resources
|
* @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);
|
<T extends IResource> Bundle history(Class<T> theType, String theId, DateTimeDt theSince, Integer theLimit);
|
||||||
|
|
||||||
@ -147,11 +157,6 @@ public interface IGenericClient {
|
|||||||
*/
|
*/
|
||||||
IGetPage loadPage();
|
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
|
// * 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
|
// * 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);
|
// <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.
|
* 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);
|
<T extends IBaseResource> T vread(Class<T> theType, String theId, String theVersionId);
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of the FHIR "extended operations" action
|
|
||||||
*/
|
|
||||||
IOperation operation();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package ca.uhn.fhir.rest.gclient;
|
||||||
|
|
||||||
|
public interface IHistory extends IBaseOn<IHistoryUntyped> {
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package ca.uhn.fhir.rest.gclient;
|
package ca.uhn.fhir.rest.gclient;
|
||||||
|
|
||||||
public interface IOperationOn {
|
public interface IOperationOn extends IBaseOn<IOperationTyped> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package ca.uhn.fhir.rest.gclient;
|
||||||
|
|
||||||
|
public interface IOperationTyped {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -190,7 +190,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding<Void>
|
|||||||
HttpServletResponse response = theRequest.getServletResponse();
|
HttpServletResponse response = theRequest.getServletResponse();
|
||||||
response.setContentType(Constants.CT_TEXT);
|
response.setContentType(Constants.CT_TEXT);
|
||||||
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
||||||
response.setCharacterEncoding(Constants.CHARSET_UTF_8);
|
response.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
|
||||||
|
|
||||||
theServer.addHeadersToResponse(response);
|
theServer.addHeadersToResponse(response);
|
||||||
|
|
||||||
|
@ -20,6 +20,10 @@ package ca.uhn.fhir.rest.method;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -207,7 +211,14 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
|
|||||||
|
|
||||||
if (myResource != null && BaseBinary.class.isAssignableFrom(myResource.getClass())) {
|
if (myResource != null && BaseBinary.class.isAssignableFrom(myResource.getClass())) {
|
||||||
BaseBinary binary = (BaseBinary) myResource;
|
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);
|
HttpRequestBase retVal = createRequest(url, entity);
|
||||||
addMatchHeaders(retVal, url);
|
addMatchHeaders(retVal, url);
|
||||||
return retVal;
|
return retVal;
|
||||||
@ -275,7 +286,14 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
|
|||||||
contents = parser.encodeResourceToString(myResource);
|
contents = parser.encodeResourceToString(myResource);
|
||||||
contentType = encoding.getResourceContentType();
|
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);
|
HttpRequestBase retVal = createRequest(url, entity);
|
||||||
|
@ -173,7 +173,7 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
|
|||||||
HttpServletResponse response = theRequest.getServletResponse();
|
HttpServletResponse response = theRequest.getServletResponse();
|
||||||
response.setContentType(responseEncoding.getResourceContentType());
|
response.setContentType(responseEncoding.getResourceContentType());
|
||||||
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
response.setStatus(Constants.STATUS_HTTP_200_OK);
|
||||||
response.setCharacterEncoding(Constants.CHARSET_UTF_8);
|
response.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
|
||||||
|
|
||||||
theServer.addHeadersToResponse(response);
|
theServer.addHeadersToResponse(response);
|
||||||
|
|
||||||
|
@ -20,7 +20,8 @@ package ca.uhn.fhir.rest.method;
|
|||||||
* #L%
|
* #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.Method;
|
||||||
import java.lang.reflect.Modifier;
|
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.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
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.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
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) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
@ -133,13 +135,13 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||||||
return BundleTypeEnum.HISTORY;
|
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();
|
StringBuilder b = new StringBuilder();
|
||||||
if (theResourceName != null) {
|
if (theResourceName != null) {
|
||||||
b.append(theResourceName);
|
b.append(theResourceName);
|
||||||
if (theId != null && !theId.isEmpty()) {
|
if (isNotBlank(theId)) {
|
||||||
b.append('/');
|
b.append('/');
|
||||||
b.append(theId.getValue());
|
b.append(theId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (b.length() > 0) {
|
if (b.length() > 0) {
|
||||||
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.server;
|
|||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@ -28,7 +29,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
public class Constants {
|
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_ATOM_XML = "application/atom+xml";
|
||||||
public static final String CT_FHIR_JSON = "application/json+fhir";
|
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_NEXT = "next";
|
||||||
public static final String LINK_LAST = "last";
|
public static final String LINK_LAST = "last";
|
||||||
public static final String LINK_FHIR_BASE = "fhir-base";
|
public static final String LINK_FHIR_BASE = "fhir-base";
|
||||||
|
public static final Charset CHARSET_UTF8;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();
|
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();
|
||||||
@ -142,6 +144,8 @@ public class Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
FORMAT_VAL_TO_ENCODING = Collections.unmodifiableMap(valToEncoding);
|
FORMAT_VAL_TO_ENCODING = Collections.unmodifiableMap(valToEncoding);
|
||||||
|
|
||||||
|
CHARSET_UTF8 = Charset.forName(CHARSETNAME_UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ public class RestfulServerUtils {
|
|||||||
} else {
|
} else {
|
||||||
theHttpResponse.setContentType(responseEncoding.getResourceContentType());
|
theHttpResponse.setContentType(responseEncoding.getResourceContentType());
|
||||||
}
|
}
|
||||||
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
|
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
|
||||||
|
|
||||||
theServer.addHeadersToResponse(theHttpResponse);
|
theServer.addHeadersToResponse(theHttpResponse);
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ public class RestfulServerUtils {
|
|||||||
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
|
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
|
||||||
}
|
}
|
||||||
|
|
||||||
theHttpResponse.setCharacterEncoding(Constants.CHARSET_UTF_8);
|
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
|
||||||
|
|
||||||
theServer.addHeadersToResponse(theHttpResponse);
|
theServer.addHeadersToResponse(theHttpResponse);
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
|
<attribute name="org.eclipse.jst.component.nondependency" value=""/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</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>
|
<attributes>
|
||||||
<attribute name="owner.project.facets" value="java"/>
|
<attribute name="owner.project.facets" value="java"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<faceted-project>
|
<faceted-project>
|
||||||
<installed facet="jst.utility" version="1.0"/>
|
<installed facet="jst.utility" version="1.0"/>
|
||||||
<installed facet="java" version="1.6"/>
|
<installed facet="java" version="1.7"/>
|
||||||
</faceted-project>
|
</faceted-project>
|
||||||
|
@ -5,6 +5,7 @@ import static org.junit.Assert.*;
|
|||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
@ -30,6 +31,8 @@ import org.junit.BeforeClass;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
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.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
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")
|
@SuppressWarnings("unused")
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByNumberExact() throws Exception {
|
public void testSearchByNumberExact() throws Exception {
|
||||||
@ -1069,7 +1127,7 @@ public class GenericClientTest {
|
|||||||
|
|
||||||
HttpEntityEnclosingRequestBase value = (HttpEntityEnclosingRequestBase) capt.getValue();
|
HttpEntityEnclosingRequestBase value = (HttpEntityEnclosingRequestBase) capt.getValue();
|
||||||
|
|
||||||
Header ct = value.getEntity().getContentType();
|
Header ct = value.getFirstHeader(Constants.HEADER_CONTENT_TYPE);
|
||||||
assertNotNull(ct);
|
assertNotNull(ct);
|
||||||
assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue());
|
assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue());
|
||||||
|
|
||||||
|
1
hapi-fhir-structures-dstu2/.gitignore
vendored
1
hapi-fhir-structures-dstu2/.gitignore
vendored
@ -10,3 +10,4 @@
|
|||||||
/target/
|
/target/
|
||||||
/target/
|
/target/
|
||||||
/target/
|
/target/
|
||||||
|
/target/
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package ca.uhn.fhir.rest.client;
|
package ca.uhn.fhir.rest.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.mockito.Mockito.*;
|
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.IOException;
|
||||||
import java.io.Reader;
|
import java.io.InputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
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.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
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.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.server.Constants;
|
import ca.uhn.fhir.rest.server.Constants;
|
||||||
@ -58,6 +59,82 @@ public class GenericClientTestDstu2 {
|
|||||||
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
|
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
|
@Test
|
||||||
public void testSearchByString() throws Exception {
|
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\"}]}}]}";
|
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\"}]}}]}";
|
||||||
|
2
pom.xml
2
pom.xml
@ -761,7 +761,7 @@
|
|||||||
<module>restful-server-example-test</module>
|
<module>restful-server-example-test</module>
|
||||||
<module>hapi-fhir-testpage-overlay</module>
|
<module>hapi-fhir-testpage-overlay</module>
|
||||||
<module>hapi-fhir-jpaserver-uhnfhirtest</module>
|
<module>hapi-fhir-jpaserver-uhnfhirtest</module>
|
||||||
<!--<module>hapi-fhir-android</module>-->
|
<module>hapi-fhir-android</module>
|
||||||
<module>hapi-fhir-dist</module>
|
<module>hapi-fhir-dist</module>
|
||||||
</modules>
|
</modules>
|
||||||
</profile>
|
</profile>
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Type - Search/Query">
|
<subsection name="Search/Query - Type">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Searching for resources is probably the most common initial scenario for
|
Searching for resources is probably the most common initial scenario for
|
||||||
@ -186,7 +186,7 @@
|
|||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Type - Create">
|
<subsection name="Create - Type">
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to perform a create
|
The following example shows how to perform a create
|
||||||
operation using the generic client:
|
operation using the generic client:
|
||||||
@ -211,7 +211,7 @@
|
|||||||
</macro>
|
</macro>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Instance - Read / VRead">
|
<subsection name="Read/VRead - Instance">
|
||||||
<p>
|
<p>
|
||||||
Given a resource name and ID, it is simple to retrieve
|
Given a resource name and ID, it is simple to retrieve
|
||||||
the latest version of that resource (a 'read')
|
the latest version of that resource (a 'read')
|
||||||
@ -249,7 +249,7 @@
|
|||||||
|
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Instance - Delete">
|
<subsection name="Delete - Instance">
|
||||||
<p>
|
<p>
|
||||||
The following example shows how to perform a delete
|
The following example shows how to perform a delete
|
||||||
operation using the generic client:
|
operation using the generic client:
|
||||||
@ -275,7 +275,7 @@
|
|||||||
</macro>
|
</macro>
|
||||||
</subsection>
|
</subsection>
|
||||||
|
|
||||||
<subsection name="Instance - Update">
|
<subsection name="Update - Instance">
|
||||||
<p>
|
<p>
|
||||||
Updating a resource is similar to creating one, except that
|
Updating a resource is similar to creating one, except that
|
||||||
an ID must be supplied since you are updating a previously
|
an ID must be supplied since you are updating a previously
|
||||||
@ -315,7 +315,54 @@
|
|||||||
|
|
||||||
</subsection>
|
</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>
|
<p>
|
||||||
To retrieve the server's conformance statement, simply call the <code>conformance()</code>
|
To retrieve the server's conformance statement, simply call the <code>conformance()</code>
|
||||||
method as shown below.
|
method as shown below.
|
||||||
@ -326,18 +373,7 @@
|
|||||||
value="examples/src/main/java/example/GenericClientExample.java" />
|
value="examples/src/main/java/example/GenericClientExample.java" />
|
||||||
</macro>
|
</macro>
|
||||||
</subsection>
|
</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>
|
||||||
|
|
||||||
<section name="The Annotation-Driven Client">
|
<section name="The Annotation-Driven Client">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user