Skip to content

Commit

Permalink
moved questionnaire canonical reference check, code cleanup, more tests
Browse files Browse the repository at this point in the history
ReferenceExtractorImpl now also extracts canonical references.

Moved the QuestionnaireResponse.qestionnaire canonical reference check
from QuestionnaireResponseAuthorizationRule to the general reference
check infrastructure, enabling creation of Questionnaire and
QuestionnaireResponse resources in the same transaction bundle
regardless of order.

Currently only Task.instantiatesCanonical and
QuestionnaireResponse.qestionnaire canonical references are enforced.
  • Loading branch information
hhund committed Oct 8, 2024
1 parent 2650244 commit 0094c2a
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.BackboneElement;
import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.CareTeam;
import org.hl7.fhir.r4.model.ClaimResponse;
Expand Down Expand Up @@ -80,18 +81,25 @@ public class ReferenceExtractorImpl implements ReferenceExtractor
private Function<Reference, ResourceReference> toResourceReferenceFromReference(String referenceLocation,
Class<? extends Resource>... referenceTypes)
{
return ref -> new ResourceReference(referenceLocation, ref, referenceTypes);
return reference -> new ResourceReference(referenceLocation, reference, referenceTypes);
}

private Function<RelatedArtifact, ResourceReference> toResourceReferenceFromRelatedArtifact(
String relatedArtifactLocation)
{
return rel -> new ResourceReference(relatedArtifactLocation, rel);
return relatedArtifact -> new ResourceReference(relatedArtifactLocation, relatedArtifact);
}

private Function<Attachment, ResourceReference> toResourceReferenceFromAttachment(String attachmentLocation)
{
return rel -> new ResourceReference(attachmentLocation, rel);
return attachment -> new ResourceReference(attachmentLocation, attachment);
}

@SafeVarargs
private Function<CanonicalType, ResourceReference> toResourceReferenceFromCanonical(String canonicalLocation,
Class<? extends Resource>... referenceTypes)
{
return canonical -> new ResourceReference(canonicalLocation, canonical, referenceTypes);
}

@SafeVarargs
Expand Down Expand Up @@ -260,6 +268,50 @@ private <E extends BackboneElement> Stream<ResourceReference> getReferences(E ba
.map(toResourceReferenceFromReference(referenceLocation, referenceTypes)) : Stream.empty();
}

@SafeVarargs
private <R extends Resource> Stream<ResourceReference> getCanonical(R resource, Predicate<R> hasCanonical,
Function<R, CanonicalType> getCanonical, String canonicalLocation,
Class<? extends Resource>... canonicalTypes)
{
return hasCanonical.test(resource) ? Stream.of(getCanonical.apply(resource))
.map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
}

@SafeVarargs
private <R extends Resource> Stream<ResourceReference> getCanonicals(R resource, Predicate<R> hasCanonical,
Function<R, List<CanonicalType>> getCanonicals, String canonicalLocation,
Class<? extends Resource>... canonicalTypes)
{
return hasCanonical.test(resource) ? Stream.of(getCanonicals.apply(resource)).flatMap(List::stream)
.map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
}

@SafeVarargs
private <E extends BackboneElement> Stream<ResourceReference> getCanonical(E backboneElement,
Predicate<E> hasCanonical, Function<E, CanonicalType> getCanonical, String canonicalLocation,
Class<? extends Resource>... canonicalTypes)
{
return hasCanonical.test(backboneElement) ? Stream.of(getCanonical.apply(backboneElement))
.map(toResourceReferenceFromCanonical(canonicalLocation, canonicalTypes)) : Stream.empty();
}

@SafeVarargs
private <R extends Resource, E extends BackboneElement> Stream<ResourceReference> getBackboneElementsCanonical(
R resource, Predicate<R> hasBackboneElements, Function<R, List<E>> getBackboneElements,
Predicate<E> hasCanonical, Function<E, CanonicalType> getCanonical, String canonicalLocation,
Class<? extends Resource>... canonicalTypes)
{
if (hasBackboneElements.test(resource))
{
List<E> backboneElements = getBackboneElements.apply(resource);
return backboneElements.stream()
.map(e -> getCanonical(e, hasCanonical, getCanonical, canonicalLocation, canonicalTypes))
.flatMap(Function.identity());
}
else
return Stream.empty();
}

private Stream<ResourceReference> getExtensionReferences(DomainResource resource)
{
var extensions = resource.getExtension().stream().filter(e -> e.getValue() instanceof Reference)
Expand Down Expand Up @@ -447,9 +499,14 @@ public Stream<ResourceReference> getReferences(CodeSystem resource)
if (resource == null)
return Stream.empty();

var supplements = getCanonical(resource, CodeSystem::hasSupplementsElement, CodeSystem::getSupplementsElement,
"CodeSystem.supplements", CodeSystem.class);
var valueSet = getCanonical(resource, CodeSystem::hasValueSetElement, CodeSystem::getValueSetElement,
"CodeSystem.valueSet", ValueSet.class);

var extensionReferences = getExtensionReferences(resource);

return extensionReferences;
return concat(valueSet, supplements, extensionReferences);
}

@Override
Expand Down Expand Up @@ -588,14 +645,16 @@ public Stream<ResourceReference> getReferences(Measure resource)
if (resource == null)
return Stream.empty();

var library = getCanonicals(resource, Measure::hasLibrary, Measure::getLibrary, "Measure.library",
Library.class);
var subject = getReference(resource, Measure::hasSubjectReference, Measure::getSubjectReference,
"Measure.subject", Group.class);
var relatedArtifacts = getRelatedArtifacts(resource, Measure::hasRelatedArtifact, Measure::getRelatedArtifact,
"Measure.relatedArtifact");

var extensionReferences = getExtensionReferences(resource);

return concat(subject, relatedArtifacts, extensionReferences);
return concat(library, subject, relatedArtifacts, extensionReferences);
}

@Override
Expand All @@ -604,6 +663,8 @@ public Stream<ResourceReference> getReferences(MeasureReport resource)
if (resource == null)
return Stream.empty();

var measure = getCanonical(resource, MeasureReport::hasMeasureElement, MeasureReport::getMeasureElement,
"MeasureReport.measure", Measure.class);
var subject = getReference(resource, MeasureReport::hasSubject, MeasureReport::getSubject,
"MeasureReport.subject", Patient.class, Practitioner.class, PractitionerRole.class, Location.class,
Device.class, RelatedPerson.class, Group.class);
Expand All @@ -627,7 +688,8 @@ public Stream<ResourceReference> getReferences(MeasureReport resource)

var extensionReferences = getExtensionReferences(resource);

return concat(subject, reporter, subjectResults1, subjectResults2, evaluatedResource, extensionReferences);
return concat(measure, subject, reporter, subjectResults1, subjectResults2, evaluatedResource,
extensionReferences);
}

@Override
Expand Down Expand Up @@ -785,20 +847,24 @@ public Stream<ResourceReference> getReferences(Questionnaire resource)
if (resource == null)
return Stream.empty();

var derivedFrom = getCanonicals(resource, Questionnaire::hasDerivedFrom, Questionnaire::getDerivedFrom,
"Questionnaire.derivedFrom", Questionnaire.class);
var enableWhen = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasEnableWhen,
Questionnaire.QuestionnaireItemComponent::getEnableWhen,
Questionnaire.QuestionnaireItemEnableWhenComponent::hasAnswerReference,
Questionnaire.QuestionnaireItemEnableWhenComponent::getAnswerReference,
"Questionnaire.item.enableWhen.answerReference");

var answerOption = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasAnswerOption,
Questionnaire.QuestionnaireItemComponent::getAnswerOption,
Questionnaire.QuestionnaireItemAnswerOptionComponent::hasValueReference,
Questionnaire.QuestionnaireItemAnswerOptionComponent::getValueReference,
"Questionnaire.item.answerOption.valueReference");

var answerValueSet = getBackboneElementsCanonical(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasAnswerValueSetElement,
Questionnaire.QuestionnaireItemComponent::getAnswerValueSetElement, "Questionnaire.item.answerValueSet",
ValueSet.class);
var initial = getBackboneElements2Reference(resource, Questionnaire::hasItem, Questionnaire::getItem,
Questionnaire.QuestionnaireItemComponent::hasInitial,
Questionnaire.QuestionnaireItemComponent::getInitial,
Expand All @@ -808,7 +874,7 @@ public Stream<ResourceReference> getReferences(Questionnaire resource)

var extensionReferences = getExtensionReferences(resource);

return concat(enableWhen, answerOption, initial, extensionReferences);
return concat(derivedFrom, enableWhen, answerOption, answerValueSet, initial, extensionReferences);
}

@Override
Expand All @@ -820,26 +886,24 @@ public Stream<ResourceReference> getReferences(QuestionnaireResponse resource)
var author = getReference(resource, QuestionnaireResponse::hasAuthor, QuestionnaireResponse::getAuthor,
"QuestionnaireResponse.author", Device.class, Organization.class, Patient.class, Practitioner.class,
PractitionerRole.class, RelatedPerson.class);

var basedOn = getReferences(resource, QuestionnaireResponse::hasBasedOn, QuestionnaireResponse::getBasedOn,
"QuestionnaireResponse.basedOn", CarePlan.class, ServiceRequest.class);

var encounter = getReference(resource, QuestionnaireResponse::hasEncounter, QuestionnaireResponse::getEncounter,
"QuestionnaireResponse.encounter", Encounter.class);

var partOf = getReferences(resource, QuestionnaireResponse::hasPartOf, QuestionnaireResponse::getPartOf,
"QuestionnaireResponse.partOf", Observation.class, Procedure.class);

var questionnaire = getCanonical(resource, QuestionnaireResponse::hasQuestionnaireElement,
QuestionnaireResponse::getQuestionnaireElement, "QuestionnaireResponse.questionnaire",
Questionnaire.class);
var source = getReference(resource, QuestionnaireResponse::hasSource, QuestionnaireResponse::getSource,
"QuestionnaireResponse.source", Patient.class, Practitioner.class, PractitionerRole.class,
RelatedPerson.class);

var subject = getReference(resource, QuestionnaireResponse::hasSubject, QuestionnaireResponse::getSubject,
"QuestionnaireResponse.subject");

var extensionReferences = getExtensionReferences(resource);

return concat(author, basedOn, encounter, partOf, source, subject, extensionReferences);
return concat(author, basedOn, encounter, partOf, questionnaire, source, subject, extensionReferences);
}

@Override
Expand Down Expand Up @@ -876,9 +940,13 @@ public Stream<ResourceReference> getReferences(StructureDefinition resource)
if (resource == null)
return Stream.empty();

var baseDefinition = getCanonical(resource, StructureDefinition::hasBaseDefinitionElement,
StructureDefinition::getBaseDefinitionElement, "StructureDefinition.baseDefinition",
StructureDefinition.class);

var extensionReferences = getExtensionReferences(resource);

return extensionReferences;
return concat(baseDefinition, extensionReferences);
}

@Override
Expand All @@ -899,23 +967,25 @@ public Stream<ResourceReference> getReferences(Task resource)
return Stream.empty();

var basedOns = getReferences(resource, Task::hasBasedOn, Task::getBasedOn, "Task.basedOn");
var partOfs = getReferences(resource, Task::hasPartOf, Task::getPartOf, "Task.partOf", Task.class);
var focus = getReference(resource, Task::hasFocus, Task::getFocus, "Task.focus");
var forRef = getReference(resource, Task::hasFor, Task::getFor, "Task.for");
var encounter = getReference(resource, Task::hasEncounter, Task::getEncounter, "Task.encounter",
Encounter.class);
var requester = getReference(resource, Task::hasRequester, Task::getRequester, "Task.requester", Device.class,
Organization.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class);
var focus = getReference(resource, Task::hasFocus, Task::getFocus, "Task.focus");
var forRef = getReference(resource, Task::hasFor, Task::getFor, "Task.for");
var instantiatesCanonical = getCanonical(resource, Task::hasInstantiatesCanonicalElement,
Task::getInstantiatesCanonicalElement, "Task.instantiatesCanonical", ActivityDefinition.class);
var insurance = getReferences(resource, Task::hasInsurance, Task::getInsurance, "Task.insurance",
Coverage.class, ClaimResponse.class);
var location = getReference(resource, Task::hasLocation, Task::getLocation, "Task.location", Location.class);
var owner = getReference(resource, Task::hasOwner, Task::getOwner, "Task.owner", Practitioner.class,
PractitionerRole.class, Organization.class, CareTeam.class, HealthcareService.class, Patient.class,
Device.class, RelatedPerson.class);
var location = getReference(resource, Task::hasLocation, Task::getLocation, "Task.location", Location.class);
var partOfs = getReferences(resource, Task::hasPartOf, Task::getPartOf, "Task.partOf", Task.class);
var reasonReference = getReference(resource, Task::hasReasonReference, Task::getReasonReference,
"Task.reasonReference");
var insurance = getReferences(resource, Task::hasInsurance, Task::getInsurance, "Task.insurance",
Coverage.class, ClaimResponse.class);
var relevanteHistories = getReferences(resource, Task::hasRelevantHistory, Task::getRelevantHistory,
"Task.relevantHistory", Provenance.class);
var requester = getReference(resource, Task::hasRequester, Task::getRequester, "Task.requester", Device.class,
Organization.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class);
var restrictionRecipiets = getBackboneElementReferences(resource, Task::hasRestriction, Task::getRestriction,
Task.TaskRestrictionComponent::hasRecipient, Task.TaskRestrictionComponent::getRecipient,
"Task.restriction.recipient", Patient.class, Practitioner.class, PractitionerRole.class,
Expand All @@ -925,8 +995,8 @@ public Stream<ResourceReference> getReferences(Task resource)
var outputReferences = getOutputReferences(resource);
var extensionReferences = getExtensionReferences(resource);

return concat(basedOns, partOfs, focus, forRef, encounter, requester, owner, location, reasonReference,
insurance, relevanteHistories, restrictionRecipiets, inputReferences, outputReferences,
return concat(basedOns, encounter, focus, forRef, instantiatesCanonical, insurance, location, owner, partOfs,
reasonReference, relevanteHistories, requester, restrictionRecipiets, inputReferences, outputReferences,
extensionReferences);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dev.dsf.fhir.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
Expand All @@ -11,6 +10,7 @@
import java.util.regex.Pattern;

import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.RelatedArtifact;
Expand Down Expand Up @@ -143,42 +143,54 @@ public enum ReferenceType
/**
* unknown url in Attachment
*/
ATTACHMENT_UNKNOWN_URL
ATTACHMENT_UNKNOWN_URL,
/**
* canoncial reference
*/
CANONICAL
}

private final String location;
private final Reference reference;
private final RelatedArtifact relatedArtifact;
private final Attachment attachment;
private final CanonicalType canonical;
private final List<Class<? extends Resource>> referenceTypes = new ArrayList<>();

@SafeVarargs
public ResourceReference(String location, Reference reference, Class<? extends Resource>... referenceTypes)
{
this(location, reference, null, null, Arrays.asList(referenceTypes));
this(location, reference, null, null, null, List.of(referenceTypes));
}

public ResourceReference(String location, RelatedArtifact relatedArtifact)
{
this(location, null, relatedArtifact, null, Collections.emptyList());
this(location, null, relatedArtifact, null, null, Collections.emptyList());
}

public ResourceReference(String location, Attachment attachment)
{
this(location, null, null, attachment, Collections.emptyList());
this(location, null, null, attachment, null, Collections.emptyList());
}

@SafeVarargs
public ResourceReference(String location, CanonicalType canonical, Class<? extends Resource>... referenceTypes)
{
this(location, null, null, null, canonical, List.of(referenceTypes));
}

private ResourceReference(String location, Reference reference, RelatedArtifact relatedArtifact,
Attachment attachment, Collection<Class<? extends Resource>> referenceTypes)
Attachment attachment, CanonicalType canonical, Collection<Class<? extends Resource>> referenceTypes)
{
this.location = location;

if (reference == null && relatedArtifact == null && attachment == null)
throw new IllegalArgumentException("Either reference, relatedArtifact or attachment expected");
if (reference == null && relatedArtifact == null && attachment == null && canonical == null)
throw new IllegalArgumentException("Either reference, relatedArtifact, attachment or canonical expected");

this.reference = reference;
this.relatedArtifact = relatedArtifact;
this.attachment = attachment;
this.canonical = canonical;

if (referenceTypes != null)
this.referenceTypes.addAll(referenceTypes);
Expand Down Expand Up @@ -214,6 +226,16 @@ public Attachment getAttachment()
return attachment;
}

public boolean hasCanonical()
{
return canonical != null;
}

public CanonicalType getCanonical()
{
return canonical;
}

public String getValue()
{
if (hasReference())
Expand All @@ -222,8 +244,10 @@ else if (hasRelatedArtifact())
return relatedArtifact.getUrl();
else if (hasAttachment())
return attachment.getUrl();
else if (hasCanonical())
return canonical.getValue();
else
throw new IllegalArgumentException("reference, related artefact or attachment not set");
throw new IllegalArgumentException("reference, related artefact, attachment or canonical not set");
}

public List<Class<? extends Resource>> getReferenceTypes()
Expand Down Expand Up @@ -336,8 +360,15 @@ else if (reference.hasType() && reference.hasIdentifier() && reference.getIdenti

return ReferenceType.UNKNOWN;
}
else if (canonical != null)
{
if (canonical.hasValue())
return ReferenceType.CANONICAL;
else
return ReferenceType.UNKNOWN;
}
else
throw new IllegalStateException("Either reference or relatedArtifact expected");
throw new IllegalStateException("Either reference, related artefact, attachment or canonical expected");
}

public String getLocation()
Expand Down
Loading

0 comments on commit 0094c2a

Please sign in to comment.