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.server.IResourceProvider;
import javax.annotation.Nonnull;
@SuppressWarnings("null")
// START SNIPPET: provider
public class PagingPatientProvider implements IResourceProvider {
@ -43,7 +45,8 @@ public class PagingPatientProvider implements IResourceProvider {
return matchingResourceIds.size();
}
@Override
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
int end = Math.max(theToIndex, matchingResourceIds.size() - 1);
List<Long> idsToReturn = matchingResourceIds.subList(theFromIndex, end);

View File

@ -1765,14 +1765,30 @@ public class GenericJaxRsClientDstu3Test {
@Test
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.addName().setFamily("PAT_FAMILY");
req.addEntry().setResource(patient);
patient.setId("C01");
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.getCode().setText("OBS_TEXT");
req.addEntry().setResource(observation);
String reqString = ourCtx.newJsonParser().encodeResourceToString(req);
observation.setId("C02");
observation.setStatus(Observation.ObservationStatus.FINAL);
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();
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.TransactionTemplate;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.TypedQuery;
@ -222,6 +223,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
return new InstantDt(mySearchEntity.getCreated());
}
@Nonnull
@Override
public List<IBaseResource> getResources(final int theFromIndex, final int theToIndex) {
ensureDependenciesInjected();

View File

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

View File

@ -99,6 +99,7 @@ public class TestUtil {
if (!isTransient) {
boolean hasColumn = nextField.getAnnotation(Column.class) != null;
boolean hasJoinColumn = nextField.getAnnotation(JoinColumn.class) != null;
boolean hasEmbeddedId = nextField.getAnnotation(EmbeddedId.class) != null;
OneToMany oneToMany = nextField.getAnnotation(OneToMany.class);
OneToOne oneToOne = nextField.getAnnotation(OneToOne.class);
boolean isOtherSideOfOneToManyMapping = oneToMany != null && isNotBlank(oneToMany.mappedBy());
@ -107,7 +108,8 @@ public class TestUtil {
hasColumn ||
hasJoinColumn ||
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.ResourceTable;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
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.r4.model.*;
import org.junit.AfterClass;
@ -11,6 +17,9 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*;
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
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.IPrimitiveType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Date;
import java.util.List;
@ -97,7 +99,7 @@ public interface IBundleProvider {
* additional 20 resources which matched a client's _include specification.
* <p>
* 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
* previous page, then the indexes should be ignored and the
* whole page returned.
@ -107,6 +109,7 @@ public interface IBundleProvider {
* @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>.
*/
@Nonnull
List<IBaseResource> getResources(int theFromIndex, int theToIndex);
/**
@ -126,6 +129,7 @@ public interface IBundleProvider {
* the search, and not to the individual page.
* </p>
*/
@Nullable
String getUuid();
/**
@ -144,6 +148,19 @@ public interface IBundleProvider {
* _include's or OperationOutcome). May return {@literal null} if the total size is not
* known or would be too expensive to calculate.
*/
@Nullable
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.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import java.util.List;
/**
@ -84,6 +85,7 @@ public class BundleProviderWithNamedPages extends SimpleBundleProvider {
return this;
}
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
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.util.CoverageIgnore;
import javax.annotation.Nonnull;
/**
* Utility methods for working with {@link IBundleProvider}
*/
@ -46,6 +48,7 @@ public class BundleProviders {
public static IBundleProvider newEmptyList() {
final InstantDt published = InstantDt.withCurrentTime();
return new IBundleProvider() {
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
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.IPrimitiveType;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -78,6 +79,7 @@ public class SimpleBundleProvider implements IBundleProvider {
myPublished = thePublished;
}
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
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();
}
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int 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 javax.annotation.Nonnull;
public class SearchDstu2Test {
private static CloseableHttpClient ourClient;
@ -556,6 +558,7 @@ public class SearchDstu2Test {
return ourReturnPublished;
}
@Nonnull
@Override
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
throw new IllegalStateException();

View File

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

View File

@ -239,6 +239,11 @@
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.
</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 version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">