Merge branch 'master' of github.com:jamesagnew/hapi-fhir

This commit is contained in:
jamesagnew 2019-07-04 09:01:35 -04:00
commit e762ac9d10
10 changed files with 104 additions and 24 deletions

View File

@ -36,6 +36,7 @@ public class ResourceGoneException extends BaseServerResponseException {
public static final int STATUS_CODE = Constants.STATUS_HTTP_410_GONE; public static final int STATUS_CODE = Constants.STATUS_HTTP_410_GONE;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private IIdType myResourceId;
/** /**
* Constructor which creates an error message based on a given resource ID * 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) { public ResourceGoneException(IIdType theResourceId) {
super(STATUS_CODE, "Resource " + (theResourceId != null ? theResourceId.getValue() : "") + " is gone/deleted"); super(STATUS_CODE, "Resource " + (theResourceId != null ? theResourceId.getValue() : "") + " is gone/deleted");
myResourceId = theResourceId;
} }
/** /**
@ -53,6 +55,7 @@ public class ResourceGoneException extends BaseServerResponseException {
@Deprecated @Deprecated
public ResourceGoneException(Class<? extends IBaseResource> theClass, BaseIdentifierDt thePatientId) { public ResourceGoneException(Class<? extends IBaseResource> theClass, BaseIdentifierDt thePatientId) {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is gone/deleted"); 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<? extends IBaseResource> theClass, IIdType theResourceId) { public ResourceGoneException(Class<? extends IBaseResource> theClass, IIdType theResourceId) {
super(STATUS_CODE, "Resource of type " + theClass.getSimpleName() + " with ID " + theResourceId + " is gone/deleted"); 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); super(STATUS_CODE, theMessage);
} }
public IIdType getResourceId() {
return myResourceId;
}
public void setResourceId(IIdType theResourceId) {
myResourceId = theResourceId;
}
} }

View File

@ -54,6 +54,7 @@ import ca.uhn.fhir.util.*;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*; import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.InstantType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -863,7 +864,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
throw new ResourceNotFoundException("No resource found with PID " + thePid); throw new ResourceNotFoundException("No resource found with PID " + thePid);
} }
if (entity.get().getDeleted() != null) { 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); T retVal = toResource(myResourceType, entity.get(), null, false);
@ -872,6 +873,16 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return retVal; 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 @Override
public T read(IIdType theId) { public T read(IIdType theId) {
@ -902,7 +913,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
if (theDeletedOk == false) { if (theDeletedOk == false) {
if (entity.getDeleted() != null) { if (entity.getDeleted() != null) {
throw new ResourceGoneException("Resource was deleted at " + new InstantType(entity.getDeleted()).getValueAsString()); throw newResourceGoneException(entity);
} }
} }
@ -1204,7 +1215,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
// Note that this will only fire if someone actually goes to use the // Note that this will only fire if someone actually goes to use the
// resource in a response (it's their responsibility to call // resource in a response (it's their responsibility to call
// outcome.fireResourceViewCallback()) // outcome.fireResourceViewCallback())
outcome.registerResourceViewCallback(()->{ outcome.registerResourceViewCallback(() -> {
if (outcome.getResource() != null) { if (outcome.getResource() != null) {
SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource()); SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource());
HookParams params = new HookParams() HookParams params = new HookParams()

View File

@ -15,7 +15,7 @@ import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener; import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOptions;
import ca.uhn.fhir.jpa.util.JpaConstants; import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.jpa.util.LoggingRule; import ca.uhn.fhir.test.utilities.LoggingRule;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
@ -25,7 +25,6 @@ import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
@ -56,7 +55,6 @@ import java.util.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ca.uhn.fhir.util.TestUtil.randomizeLocale; import static ca.uhn.fhir.util.TestUtil.randomizeLocale;

View File

@ -375,6 +375,34 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
} catch (ResourceGoneException e) { } catch (ResourceGoneException e) {
// good // 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<String> locationHeader = captureInterceptor.getLastResponse().getHeaders(Constants.HEADER_LOCATION);
assertEquals(1, locationHeader.size());
assertThat(locationHeader.get(0), containsString(id.getValue() + "/_history/2"));
} }
@Test @Test

View File

@ -171,7 +171,7 @@ public class PointcutLatch implements IAnonymousInterceptor, IPointcutLatch {
super(getName() + ": " + message + " called with values: " + hookParamsToString(theArgs)); super(getName() + ": " + message + " called with values: " + hookParamsToString(theArgs));
} }
PointcutLatchException(String message) { public PointcutLatchException(String message) {
super(getName() + ": " + message); super(getName() + ": " + message);
} }
} }

View File

@ -1046,6 +1046,20 @@ public class RestfulServer extends HttpServlet implements IRestfulServer<Servlet
exception = DEFAULT_EXCEPTION_HANDLER.preProcessOutgoingException(requestDetails, e, theRequest); exception = DEFAULT_EXCEPTION_HANDLER.preProcessOutgoingException(requestDetails, e, theRequest);
} }
/*
* If it's a 410 Gone, we want to include a location header inthe response
* if we can, since that can include the resource version which is nice
* for the user.
*/
if (exception instanceof ResourceGoneException) {
IIdType resourceId = ((ResourceGoneException) exception).getResourceId();
if (resourceId != null && resourceId.hasResourceType() && resourceId.hasIdPart()) {
String baseUrl = myServerAddressStrategy.determineServerBase(theRequest.getServletContext(), theRequest);
resourceId = resourceId.withServerBase(baseUrl, resourceId.getResourceType());
requestDetails.getResponse().addHeader(Constants.HEADER_LOCATION, resourceId.getValue());
}
}
/* /*
* Next, interceptors get a shot at handling the exception * Next, interceptors get a shot at handling the exception
*/ */

View File

@ -42,6 +42,10 @@
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package ca.uhn.fhir.jpa.util; package ca.uhn.fhir.test.utilities;
import org.junit.rules.TestRule; import org.junit.rules.TestRule;
import org.junit.runner.Description; import org.junit.runner.Description;
@ -40,19 +40,22 @@ public class LoggingRule implements TestRule {
* @param description A description of the test implemented in <code>statement</code>. * @param description A description of the test implemented in <code>statement</code>.
* @return The modified statement. * @return The modified statement.
*/ */
@Override
public Statement apply(final Statement statement, final Description description) { public Statement apply(final Statement statement, final Description description) {
return new Statement() { return new Statement() {
@Override @Override
public void evaluate() throws Throwable { public void evaluate() throws Throwable {
final Logger logger = LoggerFactory.getLogger(description.getTestClass()); final Logger logger = LoggerFactory.getLogger(description.getTestClass());
logger.info(MessageFormat.format("Starting test case [{0}]", description.getDisplayName())); logger.info(MessageFormat.format("Starting test case [{0}]", description.getDisplayName()));
boolean success = false;
try { try {
statement.evaluate(); statement.evaluate();
success = true;
} catch (final Throwable e) { } catch (final Throwable e) {
logger.error(MessageFormat.format("Exception thrown in test case [{0}]: {1}", description.getDisplayName(), e.toString()), e); logger.error(MessageFormat.format("Exception thrown in test case [{0}]: {1}", description.getDisplayName(), e.toString()), e);
throw e; throw e;
} finally { } finally {
logger.info(MessageFormat.format("Finished test case [{0}]", description.getDisplayName())); logger.info(MessageFormat.format("Finished test case [{0}] (success={1})", description.getDisplayName(), success));
} }
} }
}; };

View File

@ -1492,6 +1492,11 @@
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.0</version> <version>3.1.0</version>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.1</version>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jxr-plugin</artifactId> <artifactId>maven-jxr-plugin</artifactId>

View File

@ -220,6 +220,11 @@
request and add it to the response headers. Clients may supply the transaction request and add it to the response headers. Clients may supply the transaction
header via the <![CDATA[<code>X-Request-ID</code>]]> header. header via the <![CDATA[<code>X-Request-ID</code>]]> header.
</action> </action>
<action type="add">
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.
</action>
</release> </release>
<release version="3.8.0" date="2019-05-30" description="Hippo"> <release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix"> <action type="fix">