Add an isEmpty() method to IBundleProvider

This commit is contained in:
James Agnew 2019-07-05 16:14:28 -04:00
parent dff2fdd3cf
commit 1c7c83cd8e
15 changed files with 160 additions and 9 deletions

View File

@ -13,6 +13,8 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import javax.annotation.Nonnull;
@SuppressWarnings("null") @SuppressWarnings("null")
// START SNIPPET: provider // START SNIPPET: provider
public class PagingPatientProvider implements IResourceProvider { public class PagingPatientProvider implements IResourceProvider {
@ -43,7 +45,8 @@ public class PagingPatientProvider implements IResourceProvider {
return matchingResourceIds.size(); return matchingResourceIds.size();
} }
@Override @Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
int end = Math.max(theToIndex, matchingResourceIds.size() - 1); int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end); List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);

View File

@ -1765,14 +1765,30 @@ public class GenericJaxRsClientDstu3Test {
@Test @Test
public void testTransactionWithString() { public void testTransactionWithString() {
org.hl7.fhir.dstu3.model.Bundle req = new org.hl7.fhir.dstu3.model.Bundle(); Bundle req = new Bundle();
req.setType(BundleType.TRANSACTION);
Patient patient = new Patient(); Patient patient = new Patient();
patient.addName().setFamily("PAT_FAMILY"); patient.setId("C01");
req.addEntry().setResource(patient); patient.addName().setFamily("Smith").addGiven("John");
req.addEntry()
.setFullUrl("Patient/C01")
.setResource(patient).getRequest().setMethod(HTTPVerb.PUT).setUrl("Patient/C01");
Observation observation = new Observation(); Observation observation = new Observation();
observation.getCode().setText("OBS_TEXT"); observation.setId("C02");
req.addEntry().setResource(observation); observation.setStatus(Observation.ObservationStatus.FINAL);
String reqString = ourCtx.newJsonParser().encodeResourceToString(req); observation.setEffective(new DateTimeType("2019-02-21T13:35:00-05:00"));
observation.getSubject().setReference("Patient/C01");
observation.getCode().addCoding().setSystem("http://loinc.org").setCode("3141-9").setDisplay("Body Weight Measured");
observation.setValue(new Quantity(null, 190, "http://unitsofmeaure.org", "{lb_av}", "{lb_av}"));
req.addEntry()
.setFullUrl("Observation/C02")
.setResource(observation).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation/C02");
String reqString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(req);
ourLog.info(reqString);
reqString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(req);
ourLog.info(reqString);
org.hl7.fhir.dstu3.model.Bundle resp = new org.hl7.fhir.dstu3.model.Bundle(); org.hl7.fhir.dstu3.model.Bundle resp = new org.hl7.fhir.dstu3.model.Bundle();
resp.addEntry().getResponse().setLocation("Patient/1/_history/1"); resp.addEntry().getResponse().setLocation("Patient/1/_history/1");

View File

@ -44,6 +44,7 @@ import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.NoResultException; import javax.persistence.NoResultException;
import javax.persistence.TypedQuery; import javax.persistence.TypedQuery;
@ -222,6 +223,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
return new InstantDt(mySearchEntity.getCreated()); return new InstantDt(mySearchEntity.getCreated());
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) { public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
ensureDependenciesInjected(); ensureDependenciesInjected();

View File

@ -36,6 +36,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -56,6 +57,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
myTxManager = theTxManager; myTxManager = theTxManager;
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch); SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);

View File

@ -99,6 +99,7 @@ public class TestUtil {
if (!isTransient) { if (!isTransient) {
boolean hasColumn = nextField.getAnnotation(Column.class) != null; boolean hasColumn = nextField.getAnnotation(Column.class) != null;
boolean hasJoinColumn = nextField.getAnnotation(JoinColumn.class) != null; boolean hasJoinColumn = nextField.getAnnotation(JoinColumn.class) != null;
boolean hasEmbeddedId = nextField.getAnnotation(EmbeddedId.class) != null;
OneToMany oneToMany = nextField.getAnnotation(OneToMany.class); OneToMany oneToMany = nextField.getAnnotation(OneToMany.class);
OneToOne oneToOne = nextField.getAnnotation(OneToOne.class); OneToOne oneToOne = nextField.getAnnotation(OneToOne.class);
boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy()); boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy());
@ -107,7 +108,8 @@ public class TestUtil {
hasColumn || hasColumn ||
hasJoinColumn || hasJoinColumn ||
isOtherSideOfOneToManyMapping || isOtherSideOfOneToManyMapping ||
isOtherSideOfOneToOneMapping, "Non-transient has no @Column or @JoinColumn: " + nextField); isOtherSideOfOneToOneMapping ||
hasEmbeddedId, "Non-transient has no @Column or @JoinColumn or @EmbeddedId: " + nextField);
} }

View File

@ -2,8 +2,14 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
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.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -11,6 +17,9 @@ import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test { public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
@ -60,6 +69,88 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
} }
@Test
public void testDeleteCircularReferenceInTransaction() throws IOException {
// Create two resources with a circular reference
Organization org1 = new Organization();
org1.setId(IdType.newRandomUuid());
Organization org2 = new Organization();
org2.setId(IdType.newRandomUuid());
org1.getPartOf().setReference(org2.getId());
org2.getPartOf().setReference(org1.getId());
// Upload them in a transaction
Bundle createTransaction = new Bundle();
createTransaction.setType(Bundle.BundleType.TRANSACTION);
createTransaction
.addEntry()
.setResource(org1)
.setFullUrl(org1.getId())
.getRequest()
.setMethod(Bundle.HTTPVerb.POST)
.setUrl("Organization");
createTransaction
.addEntry()
.setResource(org2)
.setFullUrl(org2.getId())
.getRequest()
.setMethod(Bundle.HTTPVerb.POST)
.setUrl("Organization");
Bundle createResponse = mySystemDao.transaction(mySrd, createTransaction);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createResponse));
IdType orgId1 = new IdType(createResponse.getEntry().get(0).getResponse().getLocation()).toUnqualifiedVersionless();
IdType orgId2 = new IdType(createResponse.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
// Nope, can't delete 'em!
try {
myOrganizationDao.delete(orgId1);
fail();
} catch (ResourceVersionConflictException e) {
// good
}
try {
myOrganizationDao.delete(orgId2);
fail();
} catch (ResourceVersionConflictException e) {
// good
}
// Now in a transaction
Bundle deleteTransaction = new Bundle();
deleteTransaction.setType(Bundle.BundleType.TRANSACTION);
deleteTransaction.addEntry()
.getRequest()
.setMethod(Bundle.HTTPVerb.DELETE)
.setUrl(orgId1.getValue());
deleteTransaction.addEntry()
.getRequest()
.setMethod(Bundle.HTTPVerb.DELETE)
.setUrl(orgId2.getValue());
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(deleteTransaction));
mySystemDao.transaction(mySrd, deleteTransaction);
// Make sure they were deleted
try {
myOrganizationDao.read(orgId1);
fail();
} catch (ResourceGoneException e) {
// good
}
try {
myOrganizationDao.read(orgId2);
fail();
} catch (ResourceGoneException e) {
// good
}
}
@Test @Test
public void testResourceIsConsideredDeletedIfOnlyResourceTableEntryIsDeleted() { public void testResourceIsConsideredDeletedIfOnlyResourceTableEntryIsDeleted() {

View File

@ -3,6 +3,8 @@ package ca.uhn.fhir.rest.api.server;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -97,7 +99,7 @@ public interface IBundleProvider {
* additional 20 resources which matched a client's _include specification. * additional 20 resources which matched a client's _include specification.
* <p> * <p>
* Note that if this bundle provider was loaded using a * Note that if this bundle provider was loaded using a
* page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(String, String)} * page ID (i.e. via {@link ca.uhn.fhir.rest.server.IPagingProvider#retrieveResultList(RequestDetails, String, String)}
* because {@link #getNextPageId()} provided a value on the * because {@link #getNextPageId()} provided a value on the
* previous page, then the indexes should be ignored and the * previous page, then the indexes should be ignored and the
* whole page returned. * whole page returned.
@ -107,6 +109,7 @@ public interface IBundleProvider {
* @param theToIndex The high index (exclusive) to return * @param theToIndex The high index (exclusive) to return
* @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>. * @return A list of resources. The size of this list must be at least <code>theToIndex - theFromIndex</code>.
*/ */
@Nonnull
List<IBaseResource> getResources(int theFromIndex, int theToIndex); List<IBaseResource> getResources(int theFromIndex, int theToIndex);
/** /**
@ -126,6 +129,7 @@ public interface IBundleProvider {
* the search, and not to the individual page. * the search, and not to the individual page.
* </p> * </p>
*/ */
@Nullable
String getUuid(); String getUuid();
/** /**
@ -144,6 +148,19 @@ public interface IBundleProvider {
* _include's or OperationOutcome). May return {@literal null} if the total size is not * _include's or OperationOutcome). May return {@literal null} if the total size is not
* known or would be too expensive to calculate. * known or would be too expensive to calculate.
*/ */
@Nullable
Integer size(); Integer size();
/**
* This method returns <code>true</code> if the bundle provider knows that at least
* one result exists.
*/
default boolean isEmpty() {
Integer size = size();
if (size != null) {
return size > 0;
}
return getResources(0, 1).isEmpty();
}
} }

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.server;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
/** /**
@ -84,6 +85,7 @@ public class BundleProviderWithNamedPages extends SimpleBundleProvider {
return this; return this;
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
return (List<IBaseResource>) getList(); // indexes are ignored for this provider type return (List<IBaseResource>) getList(); // indexes are ignored for this provider type

View File

@ -29,6 +29,8 @@ import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
import javax.annotation.Nonnull;
/** /**
* Utility methods for working with {@link IBundleProvider} * Utility methods for working with {@link IBundleProvider}
*/ */
@ -46,6 +48,7 @@ public class BundleProviders {
public static IBundleProvider newEmptyList() { public static IBundleProvider newEmptyList() {
final InstantDt published = InstantDt.withCurrentTime(); final InstantDt published = InstantDt.withCurrentTime();
return new IBundleProvider() { return new IBundleProvider() {
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
return Collections.emptyList(); return Collections.emptyList();

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -78,6 +79,7 @@ public class SimpleBundleProvider implements IBundleProvider {
myPublished = thePublished; myPublished = thePublished;
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
return (List<IBaseResource>) myList.subList(theFromIndex, Math.min(theToIndex, myList.size())); return (List<IBaseResource>) myList.subList(theFromIndex, Math.min(theToIndex, myList.size()));

View File

@ -168,6 +168,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
return resources.getPublished(); return resources.getPublished();
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex); List<IBaseResource> retVal = resources.getResources(theFromIndex, theToIndex);

View File

@ -51,6 +51,8 @@ import static org.junit.Assert.*;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
import javax.annotation.Nonnull;
public class SearchDstu2Test { public class SearchDstu2Test {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
@ -556,6 +558,7 @@ public class SearchDstu2Test {
return ourReturnPublished; return ourReturnPublished;
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
throw new IllegalStateException(); throw new IllegalStateException();

View File

@ -23,6 +23,7 @@ import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -120,6 +121,7 @@ public class SearchHl7OrgDstu2Test {
return ourReturnPublished; return ourReturnPublished;
} }
@Nonnull
@Override @Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
throw new IllegalStateException(); throw new IllegalStateException();

View File

@ -239,6 +239,11 @@
been corrected. Note that at this time, we do not index canonical references been corrected. Note that at this time, we do not index canonical references
at all (as we were previously doing it incorrectly). This will be improved soon. at all (as we were previously doing it incorrectly). This will be improved soon.
</action> </action>
<action type="add">
IBundleProvider now has an isEmpty() method that can be used to check whether any
results exist. A default implementation has been provided, so this is not
a breaking change.
</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">