Support inline match URL references, per Simone's requast for the next

connectathon
This commit is contained in:
James Agnew 2016-02-23 13:12:30 -08:00
parent 1f875c1ca6
commit 1ba0ae3960
10 changed files with 733 additions and 519 deletions

View File

@ -43,6 +43,9 @@ ca.uhn.fhir.validation.ValidationResult.noIssuesDetected=No issues detected duri
# JPA Messages # JPA Messages
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request. ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request.
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlInvalidResourceType=Invalid match URL "{0}" - Unknown resource type: "{1}"
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlNoMatches=Invalid match URL "{0}" - No resources match this search
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.invalidMatchUrlMultipleMatches=Invalid match URL "{0}" - Multiple resources match this search
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationWithMultipleMatchFailure=Failed to {0} resource with match URL "{1}" because this search matched {2} resources
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedNoId=Failed to {0} resource in transaction because no ID was provided
ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1} ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.transactionOperationFailedUnknownId=Failed to {0} resource in transaction because no resource could be found with ID {1}

View File

@ -34,6 +34,32 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class DaoConfig { public class DaoConfig {
// ***
// update setter javadoc if default changes
// ***
private boolean myAllowInlineMatchUrlReferences = false;
/**
* @see #setAllowInlineMatchUrlReferences(boolean)
*/
public boolean isAllowInlineMatchUrlReferences() {
return myAllowInlineMatchUrlReferences;
}
/**
* Should references containing match URLs be resolved and replaced in create and update operations. For
* example, if this property is set to true and a resource is created containing a reference
* to "Patient?identifier=12345", this is reference match URL will be resolved and replaced according
* to the usual match URL rules.
* <p>
* Default is false for now, as this is an experimental feature.
* </p>
* @since 1.5
*/
public void setAllowInlineMatchUrlReferences(boolean theAllowInlineMatchUrlReferences) {
myAllowInlineMatchUrlReferences = theAllowInlineMatchUrlReferences;
}
private boolean myAllowMultipleDelete; private boolean myAllowMultipleDelete;
private int myHardSearchLimit = 1000; private int myHardSearchLimit = 1000;
private int myHardTagListLimit = 1000; private int myHardTagListLimit = 1000;

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.jpa.dao.data;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import ca.uhn.fhir.jpa.entity.ForcedId;
public interface IForcedIdDao extends JpaRepository<ForcedId, Long> {
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
public ForcedId findByResourcePid(@Param("resource_pid") Long theResourcePid);
}

View File

@ -462,7 +462,7 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
FhirTerser terser = getContext().newTerser(); FhirTerser terser = getContext().newTerser();
for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) { for (DaoMethodOutcome nextOutcome : idToPersistedOutcome.values()) {
IBaseResource nextResource = (IBaseResource) nextOutcome.getResource(); IBaseResource nextResource = nextOutcome.getResource();
if (nextResource == null) { if (nextResource == null) {
continue; continue;
} }

View File

@ -35,7 +35,8 @@ import javax.persistence.UniqueConstraint;
//@formatter:off //@formatter:off
@Entity() @Entity()
@Table(name = "HFJ_FORCED_ID", uniqueConstraints = { @Table(name = "HFJ_FORCED_ID", uniqueConstraints = {
@UniqueConstraint(name = "IDX_FORCEDID", columnNames = {"FORCED_ID"}) @UniqueConstraint(name = "IDX_FORCEDID", columnNames = {"FORCED_ID"}),
@UniqueConstraint(name = "IDX_FORCEDID_RESID", columnNames = {"RESOURCE_PID"})
}) })
@NamedQueries(value = { @NamedQueries(value = {
@NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID") @NamedQuery(name = "Q_GET_FORCED_ID", query = "SELECT f FROM ForcedId f WHERE myForcedId = :ID")

View File

@ -17,10 +17,8 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -44,8 +42,10 @@ import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@ -113,7 +113,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
// Try making the resource unparseable // Try making the resource unparseable
TransactionTemplate template = new TransactionTemplate(myTxManager); TransactionTemplate template = new TransactionTemplate(myTxManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW); template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
template.execute(new TransactionCallback<ResourceTable>() { template.execute(new TransactionCallback<ResourceTable>() {
@Override @Override
public ResourceTable doInTransaction(TransactionStatus theStatus) { public ResourceTable doInTransaction(TransactionStatus theStatus) {
@ -300,12 +300,106 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1"));
assertEquals("1", respEntry.getResponse().getEtag()); assertEquals("1", respEntry.getResponse().getEtag());
o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement())); o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()));
assertEquals(id.toVersionless().getValue(), o.getSubject().getReference()); assertEquals(id.toVersionless().getValue(), o.getSubject().getReference());
assertEquals("1", o.getIdElement().getVersionIdPart()); assertEquals("1", o.getIdElement().getVersionIdPart());
} }
@After
public void after() {
myDaoConfig.setAllowInlineMatchUrlReferences(false);
}
@Test
public void testTransactionCreateInlineMatchUrlWithOneMatch() {
String methodName = "testTransactionCreateInlineMatchUrlWithOneMatch";
Bundle request = new Bundle();
myDaoConfig.setAllowInlineMatchUrlReferences(true);
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
p.setId("Patient/" + methodName);
IIdType id = myPatientDao.update(p).getId();
ourLog.info("Created patient, got it: {}", id);
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
Bundle resp = mySystemDao.transaction(myRequestDetails, request);
assertEquals(1, resp.getEntry().size());
BundleEntryComponent respEntry = resp.getEntry().get(0);
assertEquals(Constants.STATUS_HTTP_201_CREATED + " Created", respEntry.getResponse().getStatus());
assertThat(respEntry.getResponse().getLocation(), containsString("Observation/"));
assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1"));
assertEquals("1", respEntry.getResponse().getEtag());
o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()));
assertEquals(id.toVersionless().getValue(), o.getSubject().getReference());
assertEquals("1", o.getIdElement().getVersionIdPart());
}
@Test
public void testTransactionCreateInlineMatchUrlWithNoMatches() {
String methodName = "testTransactionCreateInlineMatchUrlWithNoMatches";
Bundle request = new Bundle();
myDaoConfig.setAllowInlineMatchUrlReferences(true);
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
myPatientDao.create(p).getId();
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
myPatientDao.create(p).getId();
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
try {
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithNoMatches\" - Multiple resources match this search", e.getMessage());
}
}
@Test
public void testTransactionCreateInlineMatchUrlWithTwoMatches() {
String methodName = "testTransactionCreateInlineMatchUrlWithTwoMatches";
Bundle request = new Bundle();
myDaoConfig.setAllowInlineMatchUrlReferences(true);
Patient p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
myPatientDao.create(p).getId();
p = new Patient();
p.addIdentifier().setSystem("urn:system").setValue(methodName);
myPatientDao.create(p).getId();
Observation o = new Observation();
o.getCode().setText("Some Observation");
o.getSubject().setReference("Patient?identifier=urn%3Asystem%7C" + methodName);
request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST);
try {
mySystemDao.transaction(myRequestDetails, request);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid match URL \"Patient?identifier=urn%3Asystem%7CtestTransactionCreateInlineMatchUrlWithTwoMatches\" - Multiple resources match this search", e.getMessage());
}
}
@Test @Test
public void testTransactionCreateMatchUrlWithTwoMatch() { public void testTransactionCreateMatchUrlWithTwoMatch() {
String methodName = "testTransactionCreateMatchUrlWithTwoMatch"; String methodName = "testTransactionCreateMatchUrlWithTwoMatch";
@ -374,7 +468,7 @@ public class FhirSystemDaoDstu3Test extends BaseJpaDstu3SystemTest {
assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1"));
assertEquals("1", respEntry.getResponse().getEtag()); assertEquals("1", respEntry.getResponse().getEtag());
o = (Observation) myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement())); o = myObservationDao.read(new IdType(respEntry.getResponse().getLocationElement()));
assertEquals(new IdType(patientId).toUnqualifiedVersionless().getValue(), o.getSubject().getReference()); assertEquals(new IdType(patientId).toUnqualifiedVersionless().getValue(), o.getSubject().getReference());
} }

View File

@ -50,6 +50,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowMultipleDelete(true); retVal.setAllowMultipleDelete(true);
retVal.setAllowInlineMatchUrlReferences(true);
return retVal; return retVal;
} }

View File

@ -46,6 +46,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowMultipleDelete(true); retVal.setAllowMultipleDelete(true);
retVal.setAllowInlineMatchUrlReferences(true);
return retVal; return retVal;
} }

View File

@ -59,6 +59,13 @@
<action type="add"> <action type="add">
JPA server now supports :above and :below qualifiers on URI search params JPA server now supports :above and :below qualifiers on URI search params
</action> </action>
<action type="add">
Add optional support (disabled by default for now) to JPA server to support
inline references containing search URLs. These URLs will be resolved when
a resource is being created/updated and replaced with the single matching
resource. This is being used as a part of the May 2016 Connectathon for
a testing scenario.
</action>
</release> </release>
<release version="1.4" date="2016-02-04"> <release version="1.4" date="2016-02-04">
<action type="add"> <action type="add">