Add conditional deletes and updates

This commit is contained in:
James Agnew 2015-02-23 18:27:13 -05:00
parent 819dc67d71
commit c2a6e78e67
15 changed files with 533 additions and 61 deletions

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.model.base.resource.BaseConformance;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
@ -68,6 +69,30 @@ public class GenericClientExample {
System.out.println("Got ID: " + id.getValue());
// END SNIPPET: create
}
{
Patient patient = new Patient();
// START SNIPPET: createConditional
// One form
MethodOutcome outcome = client.create()
.resource(patient)
.conditionalByUrl("Patient?identifier=system%7C00001")
.execute();
// Another form
MethodOutcome outcome2 = client.create()
.resource(patient)
.conditional()
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
.execute();
// This will return true if the server responded with an HTTP 201 created,
// otherwise it will return null.
Boolean created = outcome.getCreated();
// The ID of the created, or the pre-existing resource
IdDt id = outcome.getId();
// END SNIPPET: createConditional
}
{
// START SNIPPET: update
Patient patient = new Patient();
@ -95,6 +120,21 @@ public class GenericClientExample {
System.out.println("Got ID: " + id.getValue());
// END SNIPPET: update
}
{
Patient patient = new Patient();
// START SNIPPET: updateConditional
client.update()
.resource(patient)
.conditionalByUrl("Patient?identifier=system%7C00001")
.execute();
client.update()
.resource(patient)
.conditional()
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
.execute();
// END SNIPPET: updateConditional
}
{
// START SNIPPET: etagupdate
// First, let's retrive the latest version of a resource
@ -132,16 +172,27 @@ public class GenericClientExample {
}
{
// START SNIPPET: delete
// Retrieve the server's conformance statement and print its
// description
BaseOperationOutcome outcome = client.delete().resourceById(new IdDt("Patient", "1234")).execute();
BaseOperationOutcome resp = client.delete().resourceById(new IdDt("Patient", "1234")).execute();
// outcome may be null if the server didn't return one
if (outcome != null) {
if (resp != null) {
OperationOutcome outcome = (OperationOutcome) resp;
System.out.println(outcome.getIssueFirstRep().getDetailsElement().getValue());
}
// END SNIPPET: delete
}
{
// START SNIPPET: deleteConditional
client.delete()
.resourceConditionalByUrl("Patient?identifier=system%7C00001")
.execute();
client.delete()
.resourceConditionalByType("Patient")
.where(Patient.IDENTIFIER.exactly().systemAndIdentifier("system", "00001"))
.execute();
// END SNIPPET: deleteConditional
}
{
// START SNIPPET: search
Bundle response = client.search()

View File

@ -34,6 +34,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.DeleteTags;
@ -324,6 +325,23 @@ public void deletePatient(@IdParam IdDt theId) {
//END SNIPPET: delete
//START SNIPPET: deleteConditional
@Read()
public void deletePatientConditional(@IdParam IdDt theId, @ConditionalOperationParam String theConditionalUrl) {
// Only one of theId or theConditionalUrl will have a value depending
// on whether the URL receieved was a logical ID, or a conditional
// search string
if (theId != null) {
// do a normal delete
} else {
// do a conditional delete
}
// otherwise, delete was successful
return; // can also return MethodOutcome
}
//END SNIPPET: deleteConditional
//START SNIPPET: history
@History()
public List<Patient> getPatientHistory(@IdParam IdDt theId) {
@ -681,6 +699,25 @@ public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient);
//END SNIPPET: createClient
//START SNIPPET: updateConditional
@Update
public MethodOutcome updatePatientConditional(
@ResourceParam Patient thePatient,
@IdParam IdDt theId,
@ConditionalOperationParam String theConditional) {
// Only one of theId or theConditional will have a value and the other will be null,
// depending on the URL passed into the server.
if (theConditional != null) {
// Do a conditional update. theConditional will have a value like "Patient?identifier=system%7C00001"
} else {
// Do a normal update. theId will have the identity of the resource to update
}
return new MethodOutcome(); // populate this
}
//END SNIPPET: updateConditional
//START SNIPPET: update
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {

View File

@ -0,0 +1,32 @@
package ca.uhn.fhir.rest.annotation;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface ConditionalOperationParam {
// just a marker
}

View File

@ -131,6 +131,10 @@ public class MethodOutcome {
return myVersionId;
}
/**
* This will be set to {@link Boolean#TRUE} for instance of MethodOutcome which are
* returned to client instances, if the server has responded with an HTTP 201 Created.
*/
public Boolean getCreated() {
return myCreated;
}

View File

@ -273,11 +273,16 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
BufferedReader requestReader = theRequest.getServletRequest().getReader();
Class<? extends IBaseResource> wantedResourceType = requestContainsResourceType();
IResource retVal;
if (wantedResourceType != null) {
return (IResource) parser.parseResource(wantedResourceType, requestReader);
retVal = (IResource) parser.parseResource(wantedResourceType, requestReader);
} else {
return parser.parseResource(requestReader);
retVal = parser.parseResource(requestReader);
}
retVal.setId(theRequest.getId());
return retVal;
}
protected abstract Set<RequestType> provideAllowableRequestTypes();

View File

@ -0,0 +1,57 @@
package ca.uhn.fhir.rest.method;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2015 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class ConditionalParamBinder implements IParameter {
ConditionalParamBinder() {
super();
}
@Override
public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
throw new UnsupportedOperationException();
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
if (theRequest.getId() != null && theRequest.getId().hasIdPart()) {
return null;
}
int questionMarkIndex = theRequest.getCompleteUrl().indexOf('?');
return theRequest.getResourceName() + theRequest.getCompleteUrl().substring(questionMarkIndex);
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// nothing
}
}

View File

@ -47,6 +47,7 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
@ -289,6 +290,10 @@ public class MethodUtil {
return MethodUtil.findParamAnnotationIndex(theMethod, TagListParam.class);
}
public static Integer findConditionalOperationParameterIndex(Method theMethod) {
return MethodUtil.findParamAnnotationIndex(theMethod, ConditionalOperationParam.class);
}
@SuppressWarnings("deprecation")
public static Integer findVersionIdParameterIndex(Method theMethod) {
return MethodUtil.findParamAnnotationIndex(theMethod, VersionIdParam.class);
@ -390,6 +395,8 @@ public class MethodUtil {
param = new SortParameter();
} else if (nextAnnotation instanceof TransactionParam) {
param = new TransactionParamBinder(theContext);
} else if (nextAnnotation instanceof ConditionalOperationParam) {
param = new ConditionalParamBinder();
} else {
continue;
}

View File

@ -20,19 +20,17 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
@ -43,16 +41,11 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;
private Integer myVersionIdParameterIndex;
public UpdateMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) {
super(theMethod, theContext, Update.class, theProvider);
myIdParameterIndex = MethodUtil.findIdParameterIndex(theMethod);
if (myIdParameterIndex == null) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation");
}
myVersionIdParameterIndex = MethodUtil.findVersionIdParameterIndex(theMethod);
}
@Override
@ -92,7 +85,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
if (theRequest.getId() != null && theRequest.getId().hasVersionIdPart() == false) {
if (id != null && id.hasVersionIdPart()) {
theRequest.setId(id);
theRequest.getId().setValue(id.getValue());
}
}
@ -104,9 +97,8 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
}
}
if (myIdParameterIndex != null) {
theParams[myIdParameterIndex] = theRequest.getId();
if (myVersionIdParameterIndex != null) {
theParams[myVersionIdParameterIndex] = theRequest.getId();
}
}
@ -117,12 +109,6 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP
throw new NullPointerException("ID can not be null");
}
if (myVersionIdParameterIndex != null) {
IdDt versionIdDt = (IdDt) theArgs[myVersionIdParameterIndex];
if (idDt.hasVersionIdPart() == false) {
idDt = idDt.withVersion(versionIdDt.getIdPart());
}
}
FhirContext context = getContext();
HttpPutClientInvocation retVal = MethodUtil.createUpdateInvocation(theResource, null, idDt, context);

View File

@ -119,6 +119,32 @@ public class GenericClientTest {
return msg;
}
@Test
public void testCreatePopulatesIsCreated() throws Exception {
Patient p1 = new Patient();
p1.addIdentifier("foo:bar", "12345");
p1.addName().addFamily("Smith").addGiven("John");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
MethodOutcome resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute();
assertTrue(resp.getCreated());
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
resp = client.create().resource(ourCtx.newXmlParser().encodeResourceToString(p1)).execute();
assertNull(resp.getCreated());
}
@Test
public void testCreateWithStringAutoDetectsEncoding() throws Exception {

View File

@ -0,0 +1,125 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.PortUtil;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class DeleteConditionalTest {
private static CloseableHttpClient ourClient;
private static String ourLastConditionalUrl;
private static int ourPort;
private static Server ourServer;
private static IdDt ourLastIdParam;
@Before
public void before() {
ourLastConditionalUrl = null;
ourLastIdParam = null;
}
@Test
public void testUpdateWithConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpDelete httpPost = new HttpDelete("http://localhost:" + ourPort + "/Patient?identifier=system%7C001");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(204, status.getStatusLine().getStatusCode());
assertNull(ourLastIdParam);
assertEquals("Patient?identifier=system%7C001", ourLastConditionalUrl);
}
@Test
public void testUpdateWithoutConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpDelete httpPost = new HttpDelete("http://localhost:" + ourPort + "/Patient/2");
HttpResponse status = ourClient.execute(httpPost);
assertEquals(204, status.getStatusLine().getStatusCode());
assertEquals("Patient/2", ourLastIdParam.toUnqualified().getValue());
assertNull(ourLastConditionalUrl);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
ourServer = new Server(ourPort);
PatientProvider patientProvider = new PatientProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
public static class PatientProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Delete()
public MethodOutcome updatePatient(@ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
ourLastConditionalUrl = theConditional;
ourLastIdParam = theIdParam;
return new MethodOutcome(new IdDt("Patient/001/_history/002"));
}
}
}

View File

@ -31,6 +31,7 @@ import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.ConditionalOperationParam;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
@ -42,17 +43,30 @@ import ca.uhn.fhir.util.PortUtil;
*/
public class UpdateConditionalTest {
private static CloseableHttpClient ourClient;
private static String ourLastConditionalUrl;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UpdateConditionalTest.class);
private static int ourPort;
private static Server ourServer;
private static IdDt ourLastId;
private static IdDt ourLastIdParam;
@Before
public void before() {
ourLastId = null;
ourLastConditionalUrl = null;
ourLastIdParam = null;
}
@Test
public void testUpdate() throws Exception {
public void testUpdateWithConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/001");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient?identifier=system%7C001");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
@ -62,22 +76,48 @@ public class UpdateConditionalTest {
ourLog.info("Response was:\n{}", responseContent);
OperationOutcome oo = new FhirContext().newXmlParser().parseResource(OperationOutcome.class, responseContent);
assertEquals("OODETAILS", oo.getIssueFirstRep().getDetails());
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue());
assertNull(ourLastId.getValue());
assertNull(ourLastIdParam);
assertEquals("Patient?identifier=system%7C001", ourLastConditionalUrl);
}
@Test
public void testUpdateWithoutConditionalUrl() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
HttpPut httpPost = new HttpPut("http://localhost:" + ourPort + "/Patient/2");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue());
assertEquals("Patient/2", ourLastId.toUnqualified().getValue());
assertEquals("Patient/2", ourLastIdParam.toUnqualified().getValue());
assertNull(ourLastConditionalUrl);
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = PortUtil.findFreePort();
@ -100,14 +140,6 @@ public class UpdateConditionalTest {
}
private static String ourLastConditionalUrl;
@Before
public void before() {
ourLastConditionalUrl=null;
}
public static class PatientProvider implements IResourceProvider {
@Override
@ -117,15 +149,11 @@ public class UpdateConditionalTest {
@Update()
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient) {
IdDt id = theId.withVersion(thePatient.getIdentifierFirstRep().getValue());
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDetails("OODETAILS");
if (theId.getValueAsString().contains("CREATE")) {
return new MethodOutcome(id,oo, true);
}
return new MethodOutcome(id,oo);
public MethodOutcome updatePatient(@ResourceParam Patient thePatient, @ConditionalOperationParam String theConditional, @IdParam IdDt theIdParam) {
ourLastConditionalUrl = theConditional;
ourLastId = thePatient.getId();
ourLastIdParam = theIdParam;
return new MethodOutcome(new IdDt("Patient/001/_history/002"));
}
}

View File

@ -43,6 +43,25 @@
*/
.well {
padding: 5px;
}
.nav-list {
padding-right: 0px;
font-size: 12px;
}
.nav-list LI {
line-height: 15px;
}
/*
.nav-list .divider {
display: none;
}
*/
body {
padding-top: 40px;
padding-bottom: 10px;
@ -126,6 +145,7 @@ h4 {
font-size: 1.2em;
padding: 0px;
margin-bottom: 0px;
margin-top: 20px;
}
li.expanded ul {

View File

@ -12,9 +12,6 @@
<!-- The body of the document contains a number of sections -->
<section name="Creating a RESTful Client">
<macro name="toc">
</macro>
<p>
HAPI provides a built-in mechanism for connecting to FHIR RESTful
servers.
@ -151,13 +148,23 @@
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Search - Using HTTP POST or GET with _search</h4>
<h4>Search - Using HTTP POST</h4>
<p>
The FHIR specification allows the use of an HTTP POST to transmit a search to a server instead of using
an HTTP GET. With this style of search, the search parameters are included in the request body instead
of the request URL, which can be useful if you need to transmit a search with a large number
of parameters.
</p>
<p>
The FHIR specification allows several styles of search (HTTP POST, a GET with _search at the end of the URL, etc.)
The <code>usingStyle()</code> method controls which style to use. By default, GET style is used
unless the client detects that the request would result in a very long URL (over 8000 chars) in which
case the client automatically switches to POST.
</p>
<p>
An alternate form of the search URL (using a URL ending with <code>_search</code>) was also
supported in FHIR DSTU1. This form is no longer valid in FHIR DSTU2, but HAPI retains support
for using this form in order to interoperate with servers which use it.
</p>
<macro name="snippet">
<param name="id" value="searchPost" />
<param name="file"
@ -189,6 +196,19 @@
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Conditional Creates</h4>
<p>
FHIR also specifies a type of update called "conditional create", where
a set of search parameters are provided and a new resource is only
created if no existing resource matches those parameters. See the
FHIR specification for more information on conditional creation.
</p>
<macro name="snippet">
<param name="id" value="updateConditional" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Instance - Read / VRead">
@ -239,6 +259,20 @@
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Conditional Deletes</h4>
<p>
Conditional deletions are also possible, which is a form where
instead of deleting a resource using its logical ID, you specify
a set of search criteria and a single resource is deleted if
it matches that criteria. Note that this is not a mechanism
for bulk deletion; see the FHIR specification for information
on conditional deletes and how they are used.
</p>
<macro name="snippet">
<param name="id" value="deleteConditional" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Instance - Update">
@ -257,6 +291,21 @@
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>Conditional Updates</h4>
<p>
FHIR also specifies a type of update called "conditional updates", where
insetad of using the logical ID of a resource to update, a set of
search parameters is provided. If a single resource matches that set of
parameters, that resource is updated. See the FHIR specification for
information on how conditional updates work.
</p>
<macro name="snippet">
<param name="id" value="updateConditional" />
<param name="file"
value="examples/src/main/java/example/GenericClientExample.java" />
</macro>
<h4>ETags and Resource Contention</h4>
<p>
<b>See also</b> the page on
<a href="./doc_rest_etag.html#client_update">ETag Support</a>

View File

@ -9,6 +9,7 @@
<body>
<section name="Implementing Resource Provider Operations">
<!--
<p>
Jump To...
</p>
@ -44,15 +45,17 @@
implementations, but client methods will follow the same patterns.
</p>
<a name="operations" />
-->
</section>
<section name="Operations">
<a name="operations" />
<p>
The following table lists the operations supported by
HAPI FHIR RESTful Servers and Clients.
</p>
<!--
<table>
<thead>
<tr style="font-weight: bold; font-size: 1.2em;">
@ -180,6 +183,7 @@
</tbody>
</table>
-->
<a name="instance_read" />
</section>
@ -279,10 +283,10 @@
annotation. This parameter contains the resource instance to be created.
</p>
<p>
In addition, the method must have a parameter annotated with the
In addition, the method may optionally have a parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, and optionally may have a parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/VersionIdParam.html">@VersionIdParam</a>
annotation, or they may obtain the ID of the resource being updated from
the resource itself. Either way, this ID comes from the URL passed in.
</p>
<p>
Update methods must return an object of type
@ -316,6 +320,27 @@
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<h4>Conditional Updates</h4>
<p>
If you wish to suport conditional updates, you can add a parameter
tagged with a
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ConditionalOperationParam.html">@ConditionalOperationParam</a>
annotation. If the request URL contains search parameters instead of a
resource ID, then this parameter will be populated.
</p>
<macro name="snippet">
<param name="id" value="updateConditional" />
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method (this would be invoked using an HTTP PUT,
with the resource in the PUT body):
<br />
<code>http://fhir.example.com/Patient?identifier=system%7C00001</code>
</p>
<a name="instance_delete" />
</section>
@ -373,6 +398,26 @@
<code>http://fhir.example.com/Patient/111</code>
</p>
<h4>Conditional Deletes</h4>
<p>
The FHIR specification also allows "conditional deletes". A conditional
delete uses a search style URL instead of a read style URL, and
deletes a single resource if it matches the given search parameters.
The following example shows how to
</p>
<macro name="snippet">
<param name="id" value="deleteConditional" />
<param name="file" value="examples/src/main/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to perform a conditional delete (HTTP DELETE):
<br />
<code>http://fhir.example.com/Patient?identifier=system%7C0001</code>
</p>
<a name="type_create" />
</section>