Implement sorting on _id and clean up create/update semantics for DSTU2
This commit is contained in:
parent
b4911788a3
commit
a2e562de64
|
@ -22,18 +22,29 @@ package ca.uhn.fhir.rest.gclient;
|
|||
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
|
||||
public interface ICreateTyped extends IClientExecutable<ICreateTyped, MethodOutcome> {
|
||||
|
||||
/**
|
||||
* If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
|
||||
* need to invoke this method, so that the server will assign the ID itself.
|
||||
*
|
||||
* <p>
|
||||
* Note that creating a resource by ID is no longer supported as of FHIR DSTU2. You should use the {@link IGenericClient#update()} operation
|
||||
* to create-by-ID in DSTU2.
|
||||
* </p>
|
||||
*/
|
||||
ICreateTyped withId(String theId);
|
||||
|
||||
/**
|
||||
* If you want the explicitly state an ID for your created resource, put that ID here. You generally do not
|
||||
* need to invoke this method, so that the server will assign the ID itself.
|
||||
*
|
||||
* <p>
|
||||
* Note that creating a resource by ID is no longer supported as of FHIR DSTU2. You should use the {@link IGenericClient#update()} operation
|
||||
* to create-by-ID in DSTU2.
|
||||
* </p>
|
||||
*/
|
||||
ICreateTyped withId(IdDt theId);
|
||||
|
||||
|
|
|
@ -35,5 +35,6 @@ ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionMissingUrl=Unable to perform {0
|
|||
ca.uhn.fhir.jpa.dao.BaseFhirSystemDao.transactionInvalidUrl=Unable to perform {0}, URL provided is invalid: {1}
|
||||
|
||||
ca.uhn.fhir.jpa.dao.FhirResourceDao.duplicateCreateForcedId=Can not create entity with ID[{0}], a resource with this ID already exists
|
||||
ca.uhn.fhir.jpa.dao.FhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create entity with ID[{0}], this server does not allow clients to assign numeric IDs
|
||||
ca.uhn.fhir.jpa.dao.FhirResourceDao.failedToCreateWithClientAssignedNumericId=Can not create resource with ID[{0}], no resource with this ID exists and clients may only assign IDs which begin with a non-numeric character on this server
|
||||
ca.uhn.fhir.jpa.dao.FhirResourceDao.failedToCreateWithClientAssignedId=Can not create resource with ID[{0}], ID must not be supplied on a create (POST) operation
|
||||
ca.uhn.fhir.jpa.dao.FhirResourceDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed.
|
||||
|
|
|
@ -65,6 +65,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
|||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
|
@ -690,16 +691,24 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
|
||||
@Override
|
||||
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
|
||||
StopWatch w = new StopWatch();
|
||||
ResourceTable entity = new ResourceTable();
|
||||
entity.setResourceType(toResourceName(theResource));
|
||||
|
||||
if (isNotBlank(theResource.getId().getIdPart())) {
|
||||
if (theResource.getId().isIdPartValidLong()) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart()));
|
||||
if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) {
|
||||
if (theResource.getId().isIdPartValidLong()) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart()));
|
||||
}
|
||||
} else {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getId().getIdPart()));
|
||||
}
|
||||
}
|
||||
|
||||
return doCreate(theResource, theIfNoneExist, thePerformIndexing);
|
||||
}
|
||||
|
||||
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing) {
|
||||
StopWatch w = new StopWatch();
|
||||
ResourceTable entity = new ResourceTable();
|
||||
entity.setResourceType(toResourceName(theResource));
|
||||
|
||||
if (isNotBlank(theIfNoneExist)) {
|
||||
Set<Long> match = processMatchUrl(theIfNoneExist, myResourceType);
|
||||
if (match.size() > 1) {
|
||||
|
@ -867,6 +876,20 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
return;
|
||||
}
|
||||
|
||||
if ("_id".equals(theSort.getParamName())) {
|
||||
From<?, ?> forcedIdJoin = theFrom.join("myForcedId", JoinType.LEFT);
|
||||
if (theSort.getOrder() == null || theSort.getOrder() == SortOrderEnum.ASC) {
|
||||
theOrders.add(theBuilder.asc(forcedIdJoin.get("myForcedId")));
|
||||
theOrders.add(theBuilder.asc(theFrom.get("myId")));
|
||||
}else {
|
||||
theOrders.add(theBuilder.desc(forcedIdJoin.get("myForcedId")));
|
||||
theOrders.add(theBuilder.desc(theFrom.get("myId")));
|
||||
}
|
||||
|
||||
createSort(theBuilder, theFrom, theSort.getChain(), theOrders, null);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(myResourceType);
|
||||
RuntimeSearchParam param = resourceDef.getSearchParam(theSort.getParamName());
|
||||
if (param == null) {
|
||||
|
@ -1632,7 +1655,14 @@ public class FhirResourceDao<T extends IResource> extends BaseFhirDao implements
|
|||
if (resourceId == null || isBlank(resourceId.getIdPart())) {
|
||||
throw new InvalidRequestException("Can not update a resource with no ID");
|
||||
}
|
||||
entity = readEntityLatestVersion(resourceId);
|
||||
try {
|
||||
entity = readEntityLatestVersion(resourceId);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
if (Character.isDigit(theResource.getId().getIdPart().charAt(0))) {
|
||||
throw new InvalidRequestException(getContext().getLocalizer().getMessage(FhirResourceDao.class, "failedToCreateWithClientAssignedNumericId", theResource.getId().getIdPart()));
|
||||
}
|
||||
return doCreate(theResource, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceId.hasVersionIdPart() && resourceId.getVersionIdPartAsLong().longValue() != entity.getVersion()) {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.hamcrest.core.StringContains;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Device;
|
||||
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
|
||||
import ca.uhn.fhir.model.dstu.resource.Encounter;
|
||||
import ca.uhn.fhir.model.dstu.resource.Location;
|
||||
import ca.uhn.fhir.model.dstu.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
||||
public class FhirResourceDaoDstu1Test {
|
||||
|
||||
private static ClassPathXmlApplicationContext ourCtx;
|
||||
private static IFhirResourceDao<Device> ourDeviceDao;
|
||||
private static IFhirResourceDao<DiagnosticReport> ourDiagnosticReportDao;
|
||||
private static IFhirResourceDao<Encounter> ourEncounterDao;
|
||||
private static FhirContext ourFhirCtx;
|
||||
private static IFhirResourceDao<Location> ourLocationDao;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu1Test.class);
|
||||
private static IFhirResourceDao<Observation> ourObservationDao;
|
||||
private static IFhirResourceDao<Organization> ourOrganizationDao;
|
||||
private static IFhirResourceDao<Patient> ourPatientDao;
|
||||
|
||||
@Test
|
||||
public void testCreateDuplicateIdFails() {
|
||||
String methodName = "testCreateDuplocateIdFailsText";
|
||||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.setId("Patient/" + methodName);
|
||||
IdDt id = ourPatientDao.create(p).getId();
|
||||
ourLog.info("Created patient, got it: {}", id);
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.addName().addFamily("Hello");
|
||||
p.setId("Patient/" + methodName);
|
||||
try {
|
||||
ourPatientDao.create(p);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Can not create entity with ID[" + methodName + "], a resource with this ID already exists"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNumericIdFails() {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("testCreateNumericIdFails");
|
||||
p.addName().addFamily("Hello");
|
||||
p.setId("Patient/123");
|
||||
try {
|
||||
ourPatientDao.create(p);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("clients may only assign IDs which begin with a non-numeric"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithInvalidReferenceFailsGracefully() {
|
||||
Patient patient = new Patient();
|
||||
patient.addName().addFamily("testSearchResourceLinkWithChainWithMultipleTypes01");
|
||||
patient.setManagingOrganization(new ResourceReferenceDt("Patient/99999999"));
|
||||
try {
|
||||
ourPatientDao.create(patient);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), StringContains.containsString("99999 not found"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRejectsIdWhichPointsToForcedId() throws InterruptedException {
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsIdWhichPointsToForcedId01");
|
||||
p1.addName().addFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId01");
|
||||
p1.setId("ABABA");
|
||||
IdDt p1id = ourPatientDao.create(p1).getId();
|
||||
assertEquals("ABABA", p1id.getIdPart());
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("urn:system").setValue("testUpdateRejectsIdWhichPointsToForcedId02");
|
||||
p2.addName().addFamily("Tester").addGiven("testUpdateRejectsIdWhichPointsToForcedId02");
|
||||
IdDt p2id = ourPatientDao.create(p2).getId();
|
||||
long p1longId = p2id.getIdPartAsLong() - 1;
|
||||
|
||||
try {
|
||||
ourPatientDao.read(new IdDt("Patient/" + p1longId));
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
p1.setId(new IdDt("Patient/" + p1longId));
|
||||
ourPatientDao.update(p1);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertThat(e.getMessage(), containsString("clients may only assign IDs which begin with a non-numeric"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
ourCtx.close();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
ourCtx = new ClassPathXmlApplicationContext("hapi-fhir-server-resourceproviders-dstu1.xml", "fhir-jpabase-spring-test-config.xml");
|
||||
ourPatientDao = ourCtx.getBean("myPatientDaoDstu1", IFhirResourceDao.class);
|
||||
ourObservationDao = ourCtx.getBean("myObservationDaoDstu1", IFhirResourceDao.class);
|
||||
ourDiagnosticReportDao = ourCtx.getBean("myDiagnosticReportDaoDstu1", IFhirResourceDao.class);
|
||||
ourDeviceDao = ourCtx.getBean("myDeviceDaoDstu1", IFhirResourceDao.class);
|
||||
ourOrganizationDao = ourCtx.getBean("myOrganizationDaoDstu1", IFhirResourceDao.class);
|
||||
ourLocationDao = ourCtx.getBean("myLocationDaoDstu1", IFhirResourceDao.class);
|
||||
ourEncounterDao = ourCtx.getBean("myEncounterDaoDstu1", IFhirResourceDao.class);
|
||||
ourFhirCtx = ourCtx.getBean(FhirContext.class);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -49,7 +49,7 @@ public class FhirSystemDaoDstu2Test {
|
|||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.setId("Patient/" + methodName);
|
||||
IdDt id = ourPatientDao.create(p).getId();
|
||||
IdDt id = ourPatientDao.update(p).getId();
|
||||
ourLog.info("Created patient, got it: {}", id);
|
||||
|
||||
p = new Patient();
|
||||
|
@ -91,7 +91,7 @@ public class FhirSystemDaoDstu2Test {
|
|||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.setId("Patient/" + methodName);
|
||||
IdDt idv1 = ourPatientDao.create(p).getId();
|
||||
IdDt idv1 = ourPatientDao.update(p).getId();
|
||||
ourLog.info("Created patient, got id: {}", idv1);
|
||||
|
||||
p = new Patient();
|
||||
|
@ -301,7 +301,7 @@ public class FhirSystemDaoDstu2Test {
|
|||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p2.setId("Patient/" + methodName);
|
||||
IdDt id2 = ourPatientDao.create(p2).getId();
|
||||
IdDt id2 = ourPatientDao.update(p2).getId();
|
||||
ourLog.info("Created patient, got it: {}", id2);
|
||||
|
||||
Bundle request = new Bundle();
|
||||
|
@ -356,7 +356,7 @@ public class FhirSystemDaoDstu2Test {
|
|||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.setId("Patient/" + methodName);
|
||||
IdDt id = ourPatientDao.create(p).getId();
|
||||
IdDt id = ourPatientDao.update(p).getId();
|
||||
ourLog.info("Created patient, got it: {}", id);
|
||||
|
||||
Bundle request = new Bundle();
|
||||
|
@ -516,7 +516,7 @@ public class FhirSystemDaoDstu2Test {
|
|||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName);
|
||||
p.setId("Patient/" + methodName);
|
||||
IdDt id = ourPatientDao.create(p).getId();
|
||||
IdDt id = ourPatientDao.update(p).getId();
|
||||
ourLog.info("Created patient, got it: {}", id);
|
||||
|
||||
p = new Patient();
|
||||
|
|
|
@ -415,36 +415,6 @@ public class ResourceProviderDstu2Test {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateWithClientSuppliedId() {
|
||||
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system:rpdstu2", "testCreateWithId01");
|
||||
|
||||
Patient p1 = new Patient();
|
||||
p1.addIdentifier().setSystem("urn:system:rpdstu2").setValue("testCreateWithId01");
|
||||
IdDt p1Id = ourClient.create().resource(p1).withId("testCreateWithIdRpDstu2").execute().getId();
|
||||
|
||||
assertThat(p1Id.getValue(), containsString("Patient/testCreateWithIdRpDstu2/_history"));
|
||||
|
||||
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system:rpdstu2", "testCreateWithId01")).encodedJson().prettyPrint().execute();
|
||||
assertEquals(1, actual.size());
|
||||
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getResource().getId().getIdPart());
|
||||
|
||||
/*
|
||||
* ensure that trying to create the same ID again fails appropriately
|
||||
*/
|
||||
try {
|
||||
ourClient.create().resource(p1).withId("testCreateWithIdRpDstu2").execute().getId();
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
Bundle history = ourClient.history(null, (String) null, null, null);
|
||||
|
||||
assertEquals("Expected[" + p1Id.getIdPart() + "] but was " + history.getEntries().get(0).getResource().getId(), p1Id.getIdPart(), history.getEntries().get(0).getResource().getId().getIdPart());
|
||||
assertNotNull(history.getEntries().get(0).getResource());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepChaining() {
|
||||
delete("Location", Location.SP_NAME, "testDeepChainingL1");
|
||||
|
|
Loading…
Reference in New Issue