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 880cd96e91e..eb9f0600d97 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 @@ -179,7 +179,7 @@ public abstract class BaseHapiFhirResourceDao extends B } @Override - public ResourceTable delete(IIdType theId, List deleteConflicts, RequestDetails theRequestDetails) { + public DaoMethodOutcome delete(IIdType theId, List deleteConflicts, RequestDetails theRequestDetails) { if (theId == null || !theId.hasIdPart()) { throw new InvalidRequestException("Can not perform delete, no ID provided"); } @@ -188,6 +188,8 @@ public abstract class BaseHapiFhirResourceDao extends B throw new ResourceVersionConflictException("Trying to delete " + theId + " but this is not the current version"); } + StopWatch w = new StopWatch(); + T resourceToDelete = toResource(myResourceType, entity, false); validateOkToDelete(deleteConflicts, entity); @@ -202,6 +204,16 @@ public abstract class BaseHapiFhirResourceDao extends B Date updateTime = new Date(); ResourceTable savedEntity = updateEntity(null, entity, updateTime, updateTime); + resourceToDelete.setId(entity.getIdDt()); + +// /* +// * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), +// * we'll manually increase the version. This is important because we want the updated version number +// * to be reflected in the resource shared with interceptors +// */ +// if (!thePerformIndexing) { +// incremenetId(resourceToDelete); +// } // Notify JPA interceptors if (theRequestDetails != null) { @@ -214,7 +226,16 @@ public abstract class BaseHapiFhirResourceDao extends B } } - return savedEntity; + DaoMethodOutcome outcome = toMethodOutcome(savedEntity, resourceToDelete).setCreated(true); + + IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); + String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, w.getMillis()); + String severity = "information"; + String code = "informational"; + OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); + outcome.setOperationOutcome(oo); + + return outcome; } @Override @@ -222,24 +243,22 @@ public abstract class BaseHapiFhirResourceDao extends B List deleteConflicts = new ArrayList(); StopWatch w = new StopWatch(); - ResourceTable savedEntity = delete(theId, deleteConflicts, theRequestDetails); + DaoMethodOutcome retVal = delete(theId, deleteConflicts, theRequestDetails); validateDeleteConflictsEmptyOrThrowException(deleteConflicts); - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulDeletes", 1, w.getMillis()); - String severity = "information"; - String code = "informational"; - OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); - ourLog.info("Processed delete on {} in {}ms", theId.getValue(), w.getMillisAndRestart()); - DaoMethodOutcome retVal = toMethodOutcome(savedEntity, null); - retVal.setOperationOutcome(oo); return retVal; } + /** + * This method gets called by {@link #deleteByUrl(String, List, RequestDetails)} as well as by + * transaction processors + */ @Override - public List deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequestDetails) { + public DeleteMethodOutcome deleteByUrl(String theUrl, List deleteConflicts, RequestDetails theRequestDetails) { + StopWatch w = new StopWatch(); + Set resource = processMatchUrl(theUrl, myResourceType); if (resource.size() > 1) { if (myDaoConfig.isAllowMultipleDelete() == false) { @@ -247,10 +266,10 @@ public abstract class BaseHapiFhirResourceDao extends B } } - List retVal = new ArrayList(); + List deletedResources = new ArrayList(); for (Long pid : resource) { ResourceTable entity = myEntityManager.find(ResourceTable.class, pid); - retVal.add(entity); + deletedResources.add(entity); validateOkToDelete(deleteConflicts, entity); @@ -274,18 +293,6 @@ public abstract class BaseHapiFhirResourceDao extends B } - return retVal; - } - - @Override - public DaoMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) { - StopWatch w = new StopWatch(); - List deleteConflicts = new ArrayList(); - - List deletedResources = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); - - validateDeleteConflictsEmptyOrThrowException(deleteConflicts); - IBaseOperationOutcome oo; if (deletedResources.isEmpty()) { oo = OperationOutcomeUtil.newInstance(getContext()); @@ -302,10 +309,23 @@ public abstract class BaseHapiFhirResourceDao extends B } ourLog.info("Processed delete on {} (matched {} resource(s)) in {}ms", new Object[] { theUrl, deletedResources.size(), w.getMillis() }); - DaoMethodOutcome retVal = new DaoMethodOutcome(); + + DeleteMethodOutcome retVal = new DeleteMethodOutcome(); + retVal.setDeletedEntities(deletedResources); retVal.setOperationOutcome(oo); return retVal; } + + @Override + public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) { + List deleteConflicts = new ArrayList(); + + DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequestDetails); + + validateDeleteConflictsEmptyOrThrowException(deleteConflicts); + + return outcome; + } @PostConstruct public void detectSearchDaoDisabled() { @@ -362,6 +382,16 @@ public abstract class BaseHapiFhirResourceDao extends B updateEntity(theResource, entity, null, thePerformIndexing, thePerformIndexing, theUpdateTime); theResource.setId(entity.getIdDt()); + + /* + * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), + * we'll manually increase the version. This is important because we want the updated version number + * to be reflected in the resource shared with interceptors + */ + if (!thePerformIndexing) { + incremenetId(theResource); + } + // Notify JPA interceptors if (theRequestDetails != null) { ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getContext(), theResource); @@ -374,6 +404,9 @@ public abstract class BaseHapiFhirResourceDao extends B } DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true); + if (!thePerformIndexing) { + outcome.setId(theResource.getIdElement()); + } String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); outcome.setOperationOutcome(createInfoOperationOutcome(msg)); @@ -382,6 +415,12 @@ public abstract class BaseHapiFhirResourceDao extends B return outcome; } + private void incremenetId(T theResource) { + String newVersion = Long.toString(theResource.getIdElement().getVersionIdPartAsLong() + 1); + IIdType newId = theResource.getIdElement().withVersion(newVersion); + theResource.setId(newId); + } + private void doMetaAdd(MT theMetaAdd, BaseHasResource entity) { List tags = toTagList(theMetaAdd); @@ -1138,6 +1177,15 @@ public abstract class BaseHapiFhirResourceDao extends B // Perform update ResourceTable savedEntity = updateEntity(theResource, entity, null, thePerformIndexing, thePerformIndexing, new Date()); + /* + * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), + * we'll manually increase the version. This is important because we want the updated version number + * to be reflected in the resource shared with interceptors + */ + if (!thePerformIndexing) { + incremenetId(theResource); + } + // Notify interceptors if (theRequestDetails != null) { theRequestDetails.getRequestOperationCallback().resourceUpdated(theResource); @@ -1150,6 +1198,10 @@ public abstract class BaseHapiFhirResourceDao extends B DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false); + if (!thePerformIndexing) { + outcome.setId(theResource.getIdElement()); + } + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); outcome.setOperationOutcome(createInfoOperationOutcome(msg)); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java new file mode 100644 index 00000000000..2d61f33eec0 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DeleteMethodOutcome.java @@ -0,0 +1,25 @@ +package ca.uhn.fhir.jpa.dao; + +import java.util.List; + +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.rest.api.MethodOutcome; + +/** + * This class is a replacement for {@link DaoMethodOutcome} for delete operations, + * as they can perform their operation over multiple resources + */ +public class DeleteMethodOutcome extends MethodOutcome { + + private List myDeletedEntities; + + public List getDeletedEntities() { + return myDeletedEntities; + } + + public DeleteMethodOutcome setDeletedEntities(List theDeletedEntities) { + myDeletedEntities = theDeletedEntities; + return this; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index 2af6fbc3f04..7ad8a92cc4d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -406,14 +406,16 @@ public class FhirSystemDaoDstu2 extends BaseHapiFhirSystemDao { ca.uhn.fhir.jpa.dao.IFhirResourceDao dao = toDao(parts, verb.getCode(), url); int status = Constants.STATUS_HTTP_204_NO_CONTENT; if (parts.getResourceId() != null) { - ResourceTable deleted = dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()), deleteConflicts, theRequestDetails); - if (deleted != null) { - deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless()); + DaoMethodOutcome outcome = dao.delete(new IdDt(parts.getResourceType(), parts.getResourceId()), deleteConflicts, theRequestDetails); + if (outcome.getEntity() != null) { + deletedResources.add(outcome.getId().toUnqualifiedVersionless()); + entriesToProcess.put(nextRespEntry, outcome.getEntity()); } } else { - List allDeleted = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails); + DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails); + List allDeleted = deleteOutcome.getDeletedEntities(); for (ResourceTable deleted : allDeleted) { - deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless()); + deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless()); } if (allDeleted.isEmpty()) { status = Constants.STATUS_HTTP_404_NOT_FOUND; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index 5c64e197ff2..1fbcf8d53eb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -39,7 +39,6 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -84,7 +83,7 @@ public interface IFhirResourceDao extends IDao { * in the provided list * @param theRequestDetails TODO */ - ResourceTable delete(IIdType theResource, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); + DaoMethodOutcome delete(IIdType theResource, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); /** * This method throws an exception if there are delete conflicts @@ -95,12 +94,12 @@ public interface IFhirResourceDao extends IDao { * This method does not throw an exception if there are delete conflicts, but populates them * in the provided list */ - List deleteByUrl(String theUrl, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); + DeleteMethodOutcome deleteByUrl(String theUrl, List theDeleteConflictsListToPopulate, RequestDetails theRequestDetails); /** * This method throws an exception if there are delete conflicts */ - DaoMethodOutcome deleteByUrl(String theString, RequestDetails theRequestDetails); + DeleteMethodOutcome deleteByUrl(String theString, RequestDetails theRequestDetails); TagList getAllResourceTags(RequestDetails theRequestDetails); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java index 51b3caa17f1..94c51d69ee1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirSystemDaoDstu3.java @@ -50,6 +50,7 @@ import com.google.common.collect.ArrayListMultimap; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jpa.dao.BaseHapiFhirSystemDao; import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.entity.TagDefinition; @@ -369,6 +370,12 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } } +// } else { +// +// if (isNotBlank(nextReqEntry.getRequest().getUrl())) { +// nextResourceId = new IdType(nextReqEntry.getRequest().getUrl()); +// } + } HTTPVerb verb = nextReqEntry.getRequest().getMethodElement().getValue(); @@ -385,7 +392,9 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { DaoMethodOutcome outcome; String matchUrl = nextReqEntry.getRequest().getIfNoneExist(); outcome = resourceDao.create(res, matchUrl, false, theRequestDetails); - handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); + if (nextResourceId != null) { + handleTransactionCreateOrUpdateOutcome(idSubstitutions, idToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res); + } entriesToProcess.put(nextRespEntry, outcome.getEntity()); if (outcome.getCreated() == false) { nonUpdatedEntities.add(outcome.getEntity()); @@ -402,22 +411,27 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { if (parts.getResourceId() != null) { IdType deleteId = new IdType(parts.getResourceType(), parts.getResourceId()); if (!deletedResources.contains(deleteId.getValueAsString())) { - ResourceTable deleted = dao.delete(deleteId, deleteConflicts, theRequestDetails); - if (deleted != null) { + DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequestDetails); + if (outcome.getEntity() != null) { deletedResources.add(deleteId.getValueAsString()); + entriesToProcess.put(nextRespEntry, outcome.getEntity()); } } } else { - List allDeleted = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails); + DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(parts.getResourceType() + '?' + parts.getParams(), deleteConflicts, theRequestDetails); + List allDeleted = deleteOutcome.getDeletedEntities(); for (ResourceTable deleted : allDeleted) { deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString()); } if (allDeleted.isEmpty()) { status = Constants.STATUS_HTTP_204_NO_CONTENT; } + + nextRespEntry.getResponse().setOutcome((Resource) deleteOutcome.getOperationOutcome()); } nextRespEntry.getResponse().setStatus(toStatusString(status)); + break; } case PUT: { @@ -630,7 +644,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao { } private static boolean isPlaceholder(IdType theId) { - if (theId.getValue() != null) { + if (theId != null && theId.getValue() != null) { if (theId.getValue().startsWith("urn:oid:") || theId.getValue().startsWith("urn:uuid:")) { return true; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java index 50b43323743..fab33bd1fc7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/BaseJpaTest.java @@ -67,12 +67,15 @@ public abstract class BaseJpaTest { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaTest.class); protected ServletRequestDetails mySrd; + protected ArrayList myServerInterceptorList; + protected IRequestOperationCallback myRequestOperationCallback = mock(IRequestOperationCallback.class); @Before public void beforeCreateSrd() { mySrd = mock(ServletRequestDetails.class, Mockito.RETURNS_DEEP_STUBS); - when(mySrd.getRequestOperationCallback()).thenReturn(mock(IRequestOperationCallback.class)); - when(mySrd.getServer().getInterceptors()).thenReturn(new ArrayList()); + when(mySrd.getRequestOperationCallback()).thenReturn(myRequestOperationCallback); + myServerInterceptorList = new ArrayList(); + when(mySrd.getServer().getInterceptors()).thenReturn(myServerInterceptorList); when(mySrd.getUserData()).thenReturn(new HashMap()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2InterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2InterceptorTest.java index 7b95b83db4b..c53bf566f4d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2InterceptorTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/FhirResourceDaoDstu2InterceptorTest.java @@ -1,58 +1,65 @@ package ca.uhn.fhir.jpa.dao.dstu2; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; import ca.uhn.fhir.jpa.interceptor.JpaServerInterceptorAdapter; +import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum; +import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu2InterceptorTest extends BaseJpaDstu2Test { - + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2InterceptorTest.class); private IJpaServerInterceptor myJpaInterceptor; private JpaServerInterceptorAdapter myJpaInterceptorAdapter = new JpaServerInterceptorAdapter(); - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @After public void after() { myDaoConfig.getInterceptors().remove(myJpaInterceptor); myDaoConfig.getInterceptors().remove(myJpaInterceptorAdapter); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); } - + @Before public void before() { myJpaInterceptor = mock(IJpaServerInterceptor.class); myDaoConfig.getInterceptors().add(myJpaInterceptor); myDaoConfig.getInterceptors().add(myJpaInterceptorAdapter); } - - /* - * ***************************************************** - * Note that non JPA specific operations get tested in individual - * operation test methods too - * ***************************************************** - */ - + + @Test public void testJpaCreate() { Patient p = new Patient(); @@ -86,7 +93,7 @@ public class FhirResourceDaoDstu2InterceptorTest extends BaseJpaDstu2Test { verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); } - + @Test public void testJpaDelete() { Patient p = new Patient(); @@ -106,6 +113,12 @@ public class FhirResourceDaoDstu2InterceptorTest extends BaseJpaDstu2Test { } + /* + * ***************************************************** + * Note that non JPA specific operations get tested in individual + * operation test methods too + * ***************************************************** + */ @Test public void testJpaUpdate() { @@ -162,4 +175,247 @@ public class FhirResourceDaoDstu2InterceptorTest extends BaseJpaDstu2Test { } + @Test + public void testRequestOperationCreate() { + IServerOperationInterceptor interceptor = mock(IServerOperationInterceptor.class); + myServerInterceptorList.add(interceptor); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + IIdType id = myPatientDao.create(p, mySrd).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + + @Test + public void testRequestOperationDelete() { + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + IIdType newId = myPatientDao.delete(new IdDt("Patient/" + id), mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + + + @Test + public void testRequestOperationDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + DeleteMethodOutcome outcome = myPatientDao.deleteByUrl("Patient?name=PATIENT", mySrd); + String oo = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); + ourLog.info(oo); + assertThat(oo, containsString("deleted 2 resource(s)")); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionCreate() { + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleTypeEnum.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(HTTPVerbEnum.POST); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdDt newId = new IdDt(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(1L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDelete() { + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleTypeEnum.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerbEnum.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdDt newId = new IdDt(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().addFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleTypeEnum.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient?name=PATIENT") + .setMethod(HTTPVerbEnum.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionUpdate() { + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.setId(new IdDt("Patient/" + id)); + p.addName().addFamily("PATIENT2"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleTypeEnum.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerbEnum.PUT); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdDt newId = new IdDt(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationUpdate() { + Patient p = new Patient(); + p.addName().addFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class)); + + p = new Patient(); + p.setId(new IdDt("Patient/" + id)); + p.addName().addFamily("PATIENT2"); + IIdType newId = myPatientDao.update(p, mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InterceptorTest.java index af0721caa8e..4f6a276792d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InterceptorTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InterceptorTest.java @@ -1,78 +1,87 @@ package ca.uhn.fhir.jpa.dao.dstu3; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import java.util.Arrays; + +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Bundle.BundleType; +import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.DeleteMethodOutcome; import ca.uhn.fhir.jpa.entity.ResourceTable; import ca.uhn.fhir.jpa.interceptor.IJpaServerInterceptor; import ca.uhn.fhir.jpa.interceptor.JpaServerInterceptorAdapter; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; +import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.TestUtil; public class FhirResourceDaoDstu3InterceptorTest extends BaseJpaDstu3Test { - + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3InterceptorTest.class); private IJpaServerInterceptor myJpaInterceptor; private JpaServerInterceptorAdapter myJpaInterceptorAdapter = new JpaServerInterceptorAdapter(); - - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @After public void after() { myDaoConfig.getInterceptors().remove(myJpaInterceptor); myDaoConfig.getInterceptors().remove(myJpaInterceptorAdapter); + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); } - + @Before public void before() { myJpaInterceptor = mock(IJpaServerInterceptor.class); myDaoConfig.getInterceptors().add(myJpaInterceptor); myDaoConfig.getInterceptors().add(myJpaInterceptorAdapter); } - - /* - * ***************************************************** - * Note that non JPA specific operations get tested in individual - * operation test methods too - * ***************************************************** - */ - + @Test public void testJpaCreate() { Patient p = new Patient(); p.addName().setFamily("PATIENT"); Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); - + ArgumentCaptor detailsCapt; ArgumentCaptor tableCapt; - + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); tableCapt = ArgumentCaptor.forClass(ResourceTable.class); verify(myJpaInterceptor, times(1)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); assertNotNull(tableCapt.getValue().getId()); assertEquals(id, tableCapt.getValue().getId()); - + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); tableCapt = ArgumentCaptor.forClass(ResourceTable.class); verify(myJpaInterceptor, times(0)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); - + /* * Not do a conditional create */ @@ -88,6 +97,13 @@ public class FhirResourceDaoDstu3InterceptorTest extends BaseJpaDstu3Test { } + /* + * ***************************************************** + * Note that non JPA specific operations get tested in individual + * operation test methods too + * ***************************************************** + */ + @Test public void testJpaDelete() { Patient p = new Patient(); @@ -95,19 +111,18 @@ public class FhirResourceDaoDstu3InterceptorTest extends BaseJpaDstu3Test { Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); myPatientDao.delete(new IdType("Patient", id), mySrd); - + ArgumentCaptor detailsCapt; ArgumentCaptor tableCapt; - + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); tableCapt = ArgumentCaptor.forClass(ResourceTable.class); verify(myJpaInterceptor, times(1)).resourceDeleted(detailsCapt.capture(), tableCapt.capture()); assertNotNull(tableCapt.getValue().getId()); assertEquals(id, tableCapt.getValue().getId()); - + } - - + @Test public void testJpaUpdate() { Patient p = new Patient(); @@ -122,13 +137,13 @@ public class FhirResourceDaoDstu3InterceptorTest extends BaseJpaDstu3Test { ArgumentCaptor detailsCapt; ArgumentCaptor tableCapt; - + detailsCapt = ArgumentCaptor.forClass(ActionRequestDetails.class); tableCapt = ArgumentCaptor.forClass(ResourceTable.class); verify(myJpaInterceptor, times(1)).resourceUpdated(detailsCapt.capture(), tableCapt.capture()); assertNotNull(tableCapt.getValue().getId()); assertEquals(id, tableCapt.getValue().getId()); - + /* * Now do a conditional update */ @@ -160,7 +175,251 @@ public class FhirResourceDaoDstu3InterceptorTest extends BaseJpaDstu3Test { verify(myJpaInterceptor, times(2)).resourceCreated(detailsCapt.capture(), tableCapt.capture()); assertEquals(id2, tableCapt.getAllValues().get(3).getId()); + } + + @Test + public void testRequestOperationCreate() { + IServerOperationInterceptor interceptor = mock(IServerOperationInterceptor.class); + myServerInterceptorList.add(interceptor); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + IIdType id = myPatientDao.create(p, mySrd).getId(); + assertEquals(1L, id.getVersionIdPartAsLong().longValue()); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationDelete() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + IIdType newId = myPatientDao.delete(new IdType("Patient/" + id), mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + DeleteMethodOutcome outcome = myPatientDao.deleteByUrl("Patient?name=PATIENT", mySrd); + String oo = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()); + ourLog.info(oo); + assertThat(oo, containsString("deleted 2 resource(s)")); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionCreate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/1", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceCreated(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient") + .setMethod(HTTPVerb.POST); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(1L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDelete() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerb.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionDeleteMulti() { + myDaoConfig.setAllowMultipleDelete(true); + + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.addName().setFamily("PATIENT"); + Long id2 = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + Long id = res.getIdElement().getIdPartAsLong(); + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceDeleted(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .getRequest() + .setUrl("Patient?name=PATIENT") + .setMethod(HTTPVerb.DELETE); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + String oo = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(resp); + ourLog.info(oo); + assertThat(oo, containsString("deleted 2 resource(s)")); + + verify(myRequestOperationCallback, times(2)).resourceDeleted(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(2)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationTransactionUpdate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + p = new Patient(); + p.setId(new IdType("Patient/" + id)); + p.addName().setFamily("PATIENT2"); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class)); + + Bundle xactBundle = new Bundle(); + xactBundle.setType(BundleType.TRANSACTION); + xactBundle + .addEntry() + .setResource(p) + .getRequest() + .setUrl("Patient/" + id) + .setMethod(HTTPVerb.PUT); + Bundle resp = mySystemDao.transaction(mySrd, xactBundle); + + IdType newId = new IdType(resp.getEntry().get(0).getResponse().getLocation()); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @Test + public void testRequestOperationUpdate() { + Patient p = new Patient(); + p.addName().setFamily("PATIENT"); + final Long id = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); + + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock theInvocation) throws Throwable { + IBaseResource res = (IBaseResource) theInvocation.getArguments()[0]; + assertEquals("Patient/" + id + "/_history/2", res.getIdElement().getValue()); + return null; + }}).when(myRequestOperationCallback).resourceUpdated(any(IBaseResource.class)); + + p = new Patient(); + p.setId(new IdType("Patient/" + id)); + p.addName().setFamily("PATIENT2"); + IIdType newId = myPatientDao.update(p, mySrd).getId(); + assertEquals(2L, newId.getVersionIdPartAsLong().longValue()); + + verify(myRequestOperationCallback, times(1)).resourceUpdated(any(IBaseResource.class)); + verify(myRequestOperationCallback, times(1)).resourceCreated(any(IBaseResource.class)); + verifyNoMoreInteractions(myRequestOperationCallback); + } + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1921ee5e13c..36e62161940 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -162,6 +162,10 @@ occur under certain circumstances when the response contained contained resources + + JPA server interceptor methods for create/update/delete provided + the wrong version ID to the interceptors +