Add conditional deletes and updates
This commit is contained in:
parent
819dc67d71
commit
c2a6e78e67
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.model.dev;
|
||||
package ca.uhn.fhir.model.dstu2;
|
||||
|
||||
import java.util.Properties;
|
||||
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue