Add implementation to validate conditional URLs post-transaction
This commit is contained in:
parent
94f157a4ac
commit
59c8765e6a
|
@ -44,7 +44,9 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
|
||||
import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
@ -64,6 +66,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.NotModifiedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PayloadTooLargeException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.method.BaseResourceReturningMethodBinding;
|
||||
|
@ -157,6 +160,8 @@ public abstract class BaseTransactionProcessor {
|
|||
private ModelConfig myModelConfig;
|
||||
@Autowired
|
||||
private InMemoryResourceMatcher myInMemoryResourceMatcher;
|
||||
@Autowired
|
||||
private SearchParamMatcher mySearchParamMatcher;
|
||||
|
||||
private TaskExecutor myExecutor ;
|
||||
|
||||
|
@ -852,6 +857,7 @@ public abstract class BaseTransactionProcessor {
|
|||
Map<IBase, IIdType> entriesToProcess = new IdentityHashMap<>();
|
||||
Set<IIdType> nonUpdatedEntities = new HashSet<>();
|
||||
Set<IBasePersistedResource> updatedEntities = new HashSet<>();
|
||||
Map<String, IIdType> conditionalUrlToIdMap = new HashMap<>();
|
||||
List<IBaseResource> updatedResources = new ArrayList<>();
|
||||
Map<String, Class<? extends IBaseResource>> conditionalRequestUrls = new HashMap<>();
|
||||
|
||||
|
@ -891,6 +897,7 @@ public abstract class BaseTransactionProcessor {
|
|||
String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
|
||||
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||
outcome = resourceDao.create(res, matchUrl, false, theTransactionDetails, theRequest);
|
||||
conditionalUrlToIdMap.put(matchUrl, outcome.getId());
|
||||
res.setId(outcome.getId());
|
||||
if (nextResourceId != null) {
|
||||
handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest);
|
||||
|
@ -925,6 +932,7 @@ public abstract class BaseTransactionProcessor {
|
|||
String matchUrl = parts.getResourceType() + '?' + parts.getParams();
|
||||
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequest);
|
||||
conditionalUrlToIdMap.put(matchUrl, deleteOutcome.getId());
|
||||
List<ResourceTable> allDeleted = deleteOutcome.getDeletedEntities();
|
||||
for (ResourceTable deleted : allDeleted) {
|
||||
deletedResources.add(deleted.getIdDt().toUnqualifiedVersionless().getValueAsString());
|
||||
|
@ -967,6 +975,7 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||
outcome = resourceDao.update(res, matchUrl, false, false, theRequest, theTransactionDetails);
|
||||
conditionalUrlToIdMap.put(matchUrl, outcome.getId());
|
||||
if (Boolean.TRUE.equals(outcome.getCreated())) {
|
||||
conditionalRequestUrls.put(matchUrl, res.getClass());
|
||||
}
|
||||
|
@ -1025,6 +1034,7 @@ public abstract class BaseTransactionProcessor {
|
|||
IFhirResourceDao<? extends IBaseResource> dao = toDao(parts, verb, url);
|
||||
IIdType patchId = myContext.getVersion().newIdType().setValue(parts.getResourceId());
|
||||
DaoMethodOutcome outcome = dao.patch(patchId, matchUrl, patchType, patchBody, patchBodyParameters, theRequest);
|
||||
conditionalUrlToIdMap.put(matchUrl, outcome.getId());
|
||||
updatedEntities.add(outcome.getEntity());
|
||||
if (outcome.getResource() != null) {
|
||||
updatedResources.add(outcome.getResource());
|
||||
|
@ -1081,6 +1091,11 @@ public abstract class BaseTransactionProcessor {
|
|||
if (!myDaoConfig.isMassIngestionMode()) {
|
||||
validateNoDuplicates(theRequest, theActionName, conditionalRequestUrls, theIdToPersistedOutcome.values());
|
||||
}
|
||||
|
||||
if (!myDaoConfig.isMassIngestionMode()) {
|
||||
validateAllInsertsMatchTheirConditionalUrls(theIdToPersistedOutcome, conditionalUrlToIdMap, theRequest);
|
||||
}
|
||||
|
||||
theTransactionStopWatch.endCurrentTask();
|
||||
|
||||
for (IIdType next : theAllIds) {
|
||||
|
@ -1119,6 +1134,32 @@ public abstract class BaseTransactionProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After transaction processing and resolution of indexes and references, we want to validate that the resources that were stored _actually_
|
||||
* match the conditional URLs that they were brought in on.
|
||||
* @param theIdToPersistedOutcome
|
||||
* @param conditionalUrlToIdMap
|
||||
*/
|
||||
private void validateAllInsertsMatchTheirConditionalUrls(Map<IIdType, DaoMethodOutcome> theIdToPersistedOutcome, Map<String, IIdType> conditionalUrlToIdMap, RequestDetails theRequest) {
|
||||
conditionalUrlToIdMap.entrySet().stream()
|
||||
.forEach(entry -> {
|
||||
String matchUrl = entry.getKey();
|
||||
IIdType value = entry.getValue();
|
||||
DaoMethodOutcome daoMethodOutcome = theIdToPersistedOutcome.get(value);
|
||||
if (daoMethodOutcome != null && daoMethodOutcome.getResource() != null) {
|
||||
InMemoryMatchResult match = mySearchParamMatcher.match(matchUrl, daoMethodOutcome.getResource(), theRequest);
|
||||
if (ourLog.isDebugEnabled()) {
|
||||
ourLog.debug("Checking conditional URL [{}] against resource with ID [{}]: Supported?:[{}], Matched?:[{}]", matchUrl, value, match.supported(), match.matched());
|
||||
}
|
||||
if (match.supported()) {
|
||||
if (!match.matched()) {
|
||||
throw new PreconditionFailedException("Invalid conditional URL \"" + matchUrl + "\". The given resource is not matched by this URL.");
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any delete conflicts.
|
||||
* @param theDeleteConflicts - set of delete conflicts
|
||||
|
@ -1409,7 +1450,7 @@ public abstract class BaseTransactionProcessor {
|
|||
thePersistedOutcomes
|
||||
.stream()
|
||||
.filter(t -> !t.isNop())
|
||||
.filter(t -> t.getEntity() instanceof ResourceTable)
|
||||
.filter(t -> t.getEntity() instanceof ResourceTable)//N.B. GGG: This validation never occurs for mongo, as nothing is a ResourceTable.
|
||||
.filter(t -> t.getEntity().getDeleted() == null)
|
||||
.filter(t -> t.getResource() != null)
|
||||
.forEach(t -> resourceToIndexedParams.put(t.getResource(), new ResourceIndexedSearchParams((ResourceTable) t.getEntity())));
|
||||
|
|
|
@ -98,7 +98,9 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.emptyString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.matchesPattern;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
@ -1207,12 +1209,14 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testConditionalUrlWhichDoesNotMatcHResource() {
|
||||
public void testConditionalUrlWhichDoesNotMatchResource() {
|
||||
Bundle transactionBundle = new Bundle().setType(BundleType.TRANSACTION);
|
||||
|
||||
String storedIdentifierValue = "woop";
|
||||
String conditionalUrlIdentifierValue = "zoop";
|
||||
// Patient
|
||||
HumanName patientName = new HumanName().setFamily("TEST_LAST_NAME").addGiven("TEST_FIRST_NAME");
|
||||
Identifier patientIdentifier = new Identifier().setSystem("http://example.com/mrns").setValue("U1234567890");
|
||||
Identifier patientIdentifier = new Identifier().setSystem("http://example.com/mrns").setValue(storedIdentifierValue);
|
||||
Patient patient = new Patient()
|
||||
.setName(List.of(patientName))
|
||||
.setIdentifier(List.of(patientIdentifier));
|
||||
|
@ -1224,14 +1228,14 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
.setResource(patient)
|
||||
.getRequest()
|
||||
.setMethod(Bundle.HTTPVerb.PUT)
|
||||
.setUrl("/Patient?identifier=" + patientIdentifier.getSystem() + "|" + "zoop");
|
||||
.setUrl("/Patient?identifier=" + patientIdentifier.getSystem() + "|" + conditionalUrlIdentifierValue);
|
||||
|
||||
ourLog.info("Patient TEMP UUID: {}", patient.getId());
|
||||
String s = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(transactionBundle);
|
||||
System.out.println(s);
|
||||
Bundle outcome= mySystemDao.transaction(null, transactionBundle);
|
||||
String patientLocation = outcome.getEntry().get(0).getResponse().getLocation();
|
||||
assertThat(patientLocation, matchesPattern("Patient/[a-z0-9-]+/_history/1"));
|
||||
try {
|
||||
mySystemDao.transaction(null, transactionBundle);
|
||||
fail();
|
||||
} catch (PreconditionFailedException e) {
|
||||
assertThat(e.getMessage(), is(equalTo("Invalid conditional URL \"Patient?identifier=http://example.com/mrns|" + conditionalUrlIdentifierValue +"\". The given resource is not matched by this URL.")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1267,6 +1271,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTransactionUpdateTwoResourcesWithSameId() {
|
||||
Bundle request = new Bundle();
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="info">
|
||||
<logger name="ca.uhn.fhir.jpa.dao" additivity="false" level="debug">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</logger>
|
||||
|
||||
|
|
|
@ -156,6 +156,7 @@ public class InMemoryResourceMatcher {
|
|||
String resourceName = theResourceDefinition.getName();
|
||||
RuntimeSearchParam paramDef = mySearchParamRegistry.getActiveSearchParam(resourceName, theParamName);
|
||||
InMemoryMatchResult checkUnsupportedResult = checkUnsupportedPrefixes(theParamName, paramDef, theAndOrParams);
|
||||
|
||||
if (!checkUnsupportedResult.supported()) {
|
||||
return checkUnsupportedResult;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue