From f1848fb1ad816f1b1b513a1e21d0213bcbfff1a7 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 3 Jul 2019 14:44:12 -0400 Subject: [PATCH] Include version ID in response for deleted resource --- .../exceptions/ResourceGoneException.java | 16 +++++++- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 41 ++++++++++++------- .../provider/r4/ResourceProviderR4Test.java | 28 +++++++++++++ .../uhn/fhir/rest/server/RestfulServer.java | 14 +++++++ src/changes/changes.xml | 5 +++ 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceGoneException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceGoneException.java index 2c96d63a918..28235b61d33 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceGoneException.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/exceptions/ResourceGoneException.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.rest.server.exceptions; * 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. @@ -36,6 +36,7 @@ public class ResourceGoneException extends BaseServerResponseException { public static final int STATUS_CODE = Constants.STATUS_HTTP_410_GONE; private static final long serialVersionUID = 1L; + private IIdType myResourceId; /** * Constructor which creates an error message based on a given resource ID @@ -44,6 +45,7 @@ public class ResourceGoneException extends BaseServerResponseException { */ public ResourceGoneException(IIdType theResourceId) { super(STATUS_CODE, "Resource " + (theResourceId != null ? theResourceId.getValue() : "") + " is gone/deleted"); + myResourceId = theResourceId; } /** @@ -53,6 +55,7 @@ public class ResourceGoneException extends BaseServerResponseException { @Deprecated public ResourceGoneException(Class theClass, BaseIdentifierDt thePatientId) { super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is gone/deleted"); + myResourceId = null; } /** @@ -63,6 +66,7 @@ public class ResourceGoneException extends BaseServerResponseException { */ public ResourceGoneException(Class theClass, IIdType theResourceId) { super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + theResourceId + " is gone/deleted"); + myResourceId = theResourceId; } /** @@ -84,4 +88,12 @@ public class ResourceGoneException extends BaseServerResponseException { super(STATUS_CODE, theMessage); } + public IIdType getResourceId() { + return myResourceId; + } + + public void setResourceId(IIdType theResourceId) { + myResourceId = theResourceId; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 5dd7558c4c3..3172a0d5626 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * 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. @@ -54,6 +54,7 @@ import ca.uhn.fhir.util.*; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.r4.model.InstantType; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -863,7 +864,7 @@ public abstract class BaseHapiFhirResourceDao extends B throw new ResourceNotFoundException("No resource found with PID " + thePid); } if (entity.get().getDeleted() != null) { - throw new ResourceGoneException("Resource was deleted at " + new InstantType(entity.get().getDeleted()).getValueAsString()); + throw newResourceGoneException(entity.get()); } T retVal = toResource(myResourceType, entity.get(), null, false); @@ -872,6 +873,16 @@ public abstract class BaseHapiFhirResourceDao extends B return retVal; } + @NotNull + private ResourceGoneException newResourceGoneException(BaseHasResource theResourceEntity) { + StringBuilder b = new StringBuilder(); + b.append("Resource was deleted at "); + b.append(new InstantType(theResourceEntity.getDeleted()).getValueAsString()); + ResourceGoneException retVal = new ResourceGoneException(b.toString()); + retVal.setResourceId(theResourceEntity.getIdDt()); + return retVal; + } + @Override public T read(IIdType theId) { @@ -902,22 +913,22 @@ public abstract class BaseHapiFhirResourceDao extends B if (theDeletedOk == false) { if (entity.getDeleted() != null) { - throw new ResourceGoneException("Resource was deleted at " + new InstantType(entity.getDeleted()).getValueAsString()); + throw newResourceGoneException(entity); } } // Interceptor broadcast: STORAGE_PREACCESS_RESOURCES { - SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(retVal); - HookParams params = new HookParams() - .add(IPreResourceAccessDetails.class, accessDetails) - .add(RequestDetails.class, theRequest) - .addIfMatchesType(ServletRequestDetails.class, theRequest); - JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); - if (accessDetails.isDontReturnResourceAtIndex(0)) { - throw new ResourceNotFoundException(theId); + SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(retVal); + HookParams params = new HookParams() + .add(IPreResourceAccessDetails.class, accessDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + JpaInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); + if (accessDetails.isDontReturnResourceAtIndex(0)) { + throw new ResourceNotFoundException(theId); + } } - } // Interceptor broadcast: STORAGE_PRESHOW_RESOURCES { @@ -1183,7 +1194,7 @@ public abstract class BaseHapiFhirResourceDao extends B outcome.setId(id); if (theEntity.getDeleted() == null) { - outcome.setResource(theResource); + outcome.setResource(theResource); } outcome.setEntity(theEntity); @@ -1204,7 +1215,7 @@ public abstract class BaseHapiFhirResourceDao extends B // Note that this will only fire if someone actually goes to use the // resource in a response (it's their responsibility to call // outcome.fireResourceViewCallback()) - outcome.registerResourceViewCallback(()->{ + outcome.registerResourceViewCallback(() -> { if (outcome.getResource() != null) { SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource()); HookParams params = new HookParams() diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 45f9b569e6a..1650ded8d84 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -375,6 +375,34 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } catch (ResourceGoneException e) { // good } + + } + + @Test + public void testResourceGoneIncludesVersion() { + + Patient p = new Patient(); + p.addName().setFamily("FAM").addGiven("GIV"); + IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); + + ourClient + .delete() + .resourceById(id) + .execute(); + + CapturingInterceptor captureInterceptor = new CapturingInterceptor(); + ourClient.registerInterceptor(captureInterceptor); + + try { + ourClient.read().resource("Patient").withId(id.toUnqualifiedVersionless()).execute(); + fail(); + } catch (ResourceGoneException e) { + // good + } + + List locationHeader = captureInterceptor.getLastResponse().getHeaders(Constants.HEADER_LOCATION); + assertEquals(1, locationHeader.size()); + assertThat(locationHeader.get(0), containsString(id.getValue() + "/_history/2")); } @Test diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index 5b4063f1dd2..4c4999e06ea 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -1046,6 +1046,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServerX-Request-ID]]> header. + + When attempting to read a resource that is deleted, a Location header is now + returned that includes the resource ID and the version ID for the deleted + resource. +