diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/InputParameterResolver.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/InputParameterResolver.java index 10d2a8ad52..5d40e74e17 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/InputParameterResolver.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/common/InputParameterResolver.java @@ -34,6 +34,7 @@ * This class provides functionality to resolve parameters passed into an operation as CQL Resource parameters * for evaluation. e.g. "%subject" */ +@SuppressWarnings("UnstableApiUsage") public class InputParameterResolver implements IInputParameterResolver { private static final Logger logger = LoggerFactory.getLogger(InputParameterResolver.class); @@ -62,7 +63,7 @@ public InputParameterResolver( } protected final IRepository resolveRepository(IRepository serverRepository, IBaseBundle data) { - return data == null + return data == null || BundleHelper.getEntry(data).isEmpty() ? serverRepository : new FederatedRepository( serverRepository, new InMemoryFhirRepository(serverRepository.fhirContext(), data)); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyProcessor.java index ea63ee8bfd..5e58bd2e05 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyProcessor.java @@ -2,6 +2,7 @@ import static org.opencds.cqf.fhir.cr.common.ExtensionBuilders.buildReferenceExt; import static org.opencds.cqf.fhir.cr.common.ExtensionBuilders.pertainToGoalExtension; +import static org.opencds.cqf.fhir.cr.questionnaire.Helpers.getQuestionnaireFromContained; import static org.opencds.cqf.fhir.utility.BundleHelper.addEntry; import static org.opencds.cqf.fhir.utility.BundleHelper.getEntry; import static org.opencds.cqf.fhir.utility.BundleHelper.getEntryResources; @@ -15,6 +16,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.fhir.cr.common.ExtensionProcessor; @@ -24,6 +26,7 @@ import org.opencds.cqf.fhir.cr.questionnaireresponse.QuestionnaireResponseProcessor; import org.opencds.cqf.fhir.utility.Constants; import org.opencds.cqf.fhir.utility.Ids; +import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter; import org.opencds.cqf.fhir.utility.monad.Eithers; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -116,63 +119,94 @@ public IBaseBundle applyR5(ApplyRequest request) { } protected void initApply(ApplyRequest request) { + var questionnaireResponses = request.getQuestionnaireResponses(); var url = request.getPlanDefinitionAdapter().getUrl(); // If the PlanDefinition has no URL we will not generate a Questionnaire // We will also add a warning to the result informing the user if (url != null) { var questionnaireUrl = url.replace("/PlanDefinition/", "/Questionnaire/"); - List entryResources = - request.getData() == null ? List.of() : getEntryResources(request.getData()); - var questionnaire = entryResources.stream() - .filter(r -> r.fhirType().equals("Questionnaire")) - .map(q -> request.getAdapterFactory().createQuestionnaire(q)) - .filter(q -> q.getUrl().equals(questionnaireUrl)) + // In the case of an adaptive Questionnaire it should be contained within the QuestionnaireResponse + // We are assuming a single QuestionnaireResponse in this instance + var questionnaireResponse = questionnaireResponses.stream() + .filter(r -> r.hasQuestionnaire() && r.getQuestionnaire().contains("#")) .findFirst() - .orElse(request.getAdapterFactory() - .createQuestionnaire(generateProcessor.generate( - request.getPlanDefinition().getIdElement().getIdPart()))); - questionnaire.setUrl(questionnaireUrl); + .orElse(null); + var containedQuestionnaire = getQuestionnaireFromContained(questionnaireResponse); + var questionnaire = containedQuestionnaire == null + ? null + : request.getAdapterFactory().createQuestionnaire(containedQuestionnaire); + // Otherwise if we have any Questionnaire in the data Bundle + // with a url that matches the PlanDefinition we will use it + if (questionnaire == null) { + questionnaire = getEntryResources(request.getData()).stream() + .filter(r -> r.fhirType().equals("Questionnaire")) + .map(q -> request.getAdapterFactory().createQuestionnaire(q)) + .filter(q -> q.getUrl().equals(questionnaireUrl)) + .findFirst() + .orElse(null); + } + // If we still don't have a Questionnaire we will generate one and give it the correct url + if (questionnaire == null) { + questionnaire = request.getAdapterFactory() + .createQuestionnaire(generateProcessor.generate( + request.getPlanDefinition().getIdElement().getIdPart())); + questionnaire.setUrl(questionnaireUrl); + } + // Update the version var version = request.getPlanDefinitionAdapter().getVersion(); if (version != null) { var formatter = new SimpleDateFormat("yyyy-MM-dd-hh.mm.ss"); questionnaire.setVersion(version.concat( "-%s-%s".formatted(request.getSubjectId().getIdPart(), formatter.format(new Date())))); } + // If we don't have a questionnaireResponse check for one in the data bundle + if (questionnaireResponse == null) { + var canonical = questionnaire.getCanonical(); + questionnaireResponse = questionnaireResponses.stream() + .filter(IQuestionnaireResponseAdapter::hasQuestionnaire) + .filter(r -> r.getQuestionnaire().equals(canonical)) + .findFirst() + .orElse(null); + } request.setQuestionnaire(questionnaire); + request.setQuestionnaireResponse(questionnaireResponse); request.addCqlLibraryExtension(); } else { request.logException("PlanDefinition %s is missing a canonical url." .formatted(request.getPlanDefinition().getIdElement().getValue())); } - extractQuestionnaireResponse(request); + extractQuestionnaireResponse(request, questionnaireResponses); } - protected void extractQuestionnaireResponse(ApplyRequest request) { - if (request.getData() != null) { - getEntryResources(request.getData()).stream() - .filter(r -> r.fhirType().equals("QuestionnaireResponse")) - .map(qr -> request.getAdapterFactory().createQuestionnaireResponse(qr)) - .forEach(questionnaireResponse -> { - try { - var extractBundle = extractProcessor.extract( - Eithers.forRight(questionnaireResponse.get()), - Eithers.forRight(request.getQuestionnaire()), - request.getParameters(), - request.getData(), - request.getLibraryEngine()); - for (var entry : getEntry(extractBundle)) { - addEntry(request.getData(), entry); - // Not adding extracted resources back into the response to reduce size of payload - // $extract can be called on the QuestionnaireResponse if these are desired - // addEntry(request.getExtractedResources(), getEntryResource(request.getFhirVersion(), - // entry)) - } - } catch (Exception e) { - request.logException("Error encountered extracting %s: %s" - .formatted(questionnaireResponse.getId().getIdPart(), e.getMessage())); - } - }); - } + protected void extractQuestionnaireResponse(ApplyRequest request, List responses) { + var questionnaireUrl = request.getQuestionnaireAdapter() != null + ? request.getQuestionnaireAdapter().getUrl() + : null; + responses.forEach(questionnaireResponse -> { + try { + var questionnaire = StringUtils.isNotBlank(questionnaireUrl) + && questionnaireResponse.hasQuestionnaire() + && questionnaireResponse.getQuestionnaire().equals(questionnaireUrl) + ? request.getQuestionnaire() + : null; + var extractBundle = extractProcessor.extract( + Eithers.forRight(questionnaireResponse.get()), + questionnaire == null ? null : Eithers.forRight(questionnaire), + request.getParameters(), + request.getData(), + request.getLibraryEngine()); + for (var entry : getEntry(extractBundle)) { + addEntry(request.getData(), entry); + // Not adding extracted resources back into the response to reduce size of payload + // $extract can be called on the QuestionnaireResponse if these are desired + // addEntry(request.getExtractedResources(), getEntryResource(request.getFhirVersion(), + // entry)) + } + } catch (Exception e) { + request.logException("Error encountered extracting %s: %s" + .formatted(questionnaireResponse.getId().getIdPart(), e.getMessage())); + } + }); } public IBaseResource applyPlanDefinition(ApplyRequest request) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyRequest.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyRequest.java index bc9e50a8cb..a7e2e24c15 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyRequest.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ApplyRequest.java @@ -2,6 +2,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static org.opencds.cqf.fhir.cr.common.IInputParameterResolver.createResolver; +import static org.opencds.cqf.fhir.utility.BundleHelper.getEntryResources; import static org.opencds.cqf.fhir.utility.BundleHelper.newBundle; import static org.opencds.cqf.fhir.utility.Constants.APPLY_PARAMETER_ACTIVITY_DEFINITION; import static org.opencds.cqf.fhir.utility.Constants.APPLY_PARAMETER_DATA; @@ -44,6 +45,7 @@ import org.opencds.cqf.fhir.utility.adapter.IPlanDefinitionAdapter; import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireAdapter; import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireItemComponentAdapter; +import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter; public class ApplyRequest implements ICpgRequest { private static final String ACTIVITY_DEFINITION = "ActivityDefinition"; @@ -68,6 +70,7 @@ public class ApplyRequest implements ICpgRequest { private final Collection extractedResources; private IBaseOperationOutcome operationOutcome; private IQuestionnaireAdapter questionnaireAdapter; + private IQuestionnaireResponseAdapter questionnaireResponseAdapter; private Boolean containResources; private Set questionnaireDefinitions; // actionId is used to ensure all actions have an Id so they can be mapped @@ -105,10 +108,10 @@ public ApplyRequest( this.setting = setting; this.settingContext = settingContext; this.parameters = parameters; + if (data == null) { + data = newBundle(fhirVersion); + } if (prefetchData != null && !prefetchData.isEmpty()) { - if (data == null) { - data = newBundle(fhirVersion); - } resolvePrefetchData(data, prefetchData); } this.data = data; @@ -149,6 +152,7 @@ public ApplyRequest copy(IBaseResource planDefinition) { modelResolver, inputParameterResolver) .setQuestionnaire(getQuestionnaireAdapter()) + .setQuestionnaireResponse(getQuestionnaireResponseAdapter()) .setContainResources(containResources); } @@ -232,7 +236,15 @@ public PopulateRequest toPopulateRequest() { } }); return new PopulateRequest( - questionnaireAdapter.get(), subjectId, context, null, data, libraryEngine, modelResolver); + questionnaireAdapter.get(), + // Not yet ready to pass QR to $populate + // getQuestionnaireResponse(), + subjectId, + context, + null, + data, + libraryEngine, + modelResolver); } public IBaseResource getPlanDefinition() { @@ -373,6 +385,28 @@ public ApplyRequest setQuestionnaire(IQuestionnaireAdapter questionnaire) { return this; } + public ApplyRequest setQuestionnaireResponse(IQuestionnaireResponseAdapter questionnaireResponse) { + questionnaireResponseAdapter = questionnaireResponse; + return this; + } + + public IBaseResource getQuestionnaireResponse() { + return questionnaireResponseAdapter == null ? null : questionnaireResponseAdapter.get(); + } + + public IQuestionnaireResponseAdapter getQuestionnaireResponseAdapter() { + return questionnaireResponseAdapter; + } + + public List getQuestionnaireResponses() { + return data == null + ? new ArrayList<>() + : getEntryResources(data).stream() + .filter(r -> r.fhirType().equals("QuestionnaireResponse")) + .map(qr -> getAdapterFactory().createQuestionnaireResponse(qr)) + .toList(); + } + public ApplyRequest setData(IBaseBundle bundle) { data = bundle; return this; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaire/Helpers.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaire/Helpers.java index 540cbc8ce5..5994ef035d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaire/Helpers.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaire/Helpers.java @@ -2,8 +2,11 @@ import ca.uhn.fhir.context.FhirVersionEnum; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter; public class Helpers { + public static final String QUESTIONNAIRE = "Questionnaire"; private static final String CHOICE = "choice"; private static final String QUESTION = "question"; private static final String GROUP = "group"; @@ -50,4 +53,20 @@ public static String getSliceName(String elementId) { } return sliceName; } + + public static IBaseResource getQuestionnaireFromContained(IQuestionnaireResponseAdapter questionnaireResponse) { + return questionnaireResponse == null || !questionnaireResponse.hasQuestionnaire() + ? null + : questionnaireResponse.getContained().stream() + .filter(r -> r.fhirType().equals(QUESTIONNAIRE) + && questionnaireResponse + .getQuestionnaire() + .equals(getContainedId(r.getIdElement().getIdPart()))) + .findFirst() + .orElse(null); + } + + private static String getContainedId(String id) { + return id.startsWith("#") ? id : "#" + id; + } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/QuestionnaireResponseProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/QuestionnaireResponseProcessor.java index 3745d434c6..4a2807036b 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/QuestionnaireResponseProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/QuestionnaireResponseProcessor.java @@ -1,6 +1,8 @@ package org.opencds.cqf.fhir.cr.questionnaireresponse; import static java.util.Objects.requireNonNull; +import static org.opencds.cqf.fhir.cr.questionnaire.Helpers.QUESTIONNAIRE; +import static org.opencds.cqf.fhir.cr.questionnaire.Helpers.getQuestionnaireFromContained; import static org.opencds.cqf.fhir.utility.repository.Repositories.proxy; import ca.uhn.fhir.context.FhirContext; @@ -12,7 +14,6 @@ import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.CrSettings; @@ -22,6 +23,7 @@ import org.opencds.cqf.fhir.cr.questionnaireresponse.extract.ExtractRequest; import org.opencds.cqf.fhir.cr.questionnaireresponse.extract.IExtractProcessor; import org.opencds.cqf.fhir.utility.SearchHelper; +import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; import org.opencds.cqf.fhir.utility.model.FhirModelResolverCache; import org.opencds.cqf.fhir.utility.monad.Either; import org.slf4j.Logger; @@ -30,11 +32,11 @@ @SuppressWarnings("UnstableApiUsage") public class QuestionnaireResponseProcessor { protected static final Logger logger = LoggerFactory.getLogger(QuestionnaireResponseProcessor.class); - protected static final String QUESTIONNAIRE = "Questionnaire"; protected final ResourceResolver questionnaireResponseResolver; protected final ResourceResolver questionnaireResolver; protected final ModelResolver modelResolver; protected final FhirVersionEnum fhirVersion; + protected final IAdapterFactory adapterFactory; protected IRepository repository; protected CrSettings crSettings; protected IExtractProcessor extractProcessor; @@ -55,6 +57,7 @@ public QuestionnaireResponseProcessor( this.questionnaireResolver = new ResourceResolver(QUESTIONNAIRE, this.repository); this.fhirVersion = this.repository.fhirContext().getVersion().getVersion(); modelResolver = FhirModelResolverCache.resolverForVersion(fhirVersion); + adapterFactory = IAdapterFactory.forFhirContext(this.repository.fhirContext()); if (operationProcessors != null && !operationProcessors.isEmpty()) { operationProcessors.forEach(p -> { if (p instanceof IExtractProcessor extract) { @@ -72,30 +75,22 @@ protected R resolveQuestionnaireResponse(Either IBaseResource resolveQuestionnaire( IBaseResource questionnaireResponse, Either questionnaireId) { if (questionnaireId != null) { return questionnaireResolver.resolve(questionnaireId); } else { try { - IPrimitiveType canonical; - if (questionnaireResponse.getStructureFhirVersionEnum().equals(FhirVersionEnum.DSTU3)) { - var pathResult = modelResolver.resolvePath(questionnaireResponse, "questionnaire"); - canonical = pathResult == null ? null : ((IBaseReference) pathResult).getReferenceElement(); - } else { - canonical = - (IPrimitiveType) modelResolver.resolvePath(questionnaireResponse, "questionnaire"); - } - if (canonical == null) { + var responseAdapter = adapterFactory.createQuestionnaireResponse(questionnaireResponse); + if (!responseAdapter.hasQuestionnaire()) { return null; } IBaseResource questionnaire = null; - questionnaire = getQuestionnaireFromContained(questionnaireResponse, canonical, questionnaire); + questionnaire = getQuestionnaireFromContained(responseAdapter); if (questionnaire == null) { questionnaire = SearchHelper.searchRepositoryByCanonical( repository, - canonical, + responseAdapter.getQuestionnaireCanonical(), repository .fhirContext() .getResourceDefinition(QUESTIONNAIRE) @@ -109,26 +104,6 @@ protected IBaseResource resolveQuestionnaire( } } - @SuppressWarnings("unchecked") - private IBaseResource getQuestionnaireFromContained( - IBaseResource questionnaireResponse, IPrimitiveType canonical, IBaseResource questionnaire) { - var contained = (List) modelResolver.resolvePath(questionnaireResponse, "contained"); - if (contained != null && !contained.isEmpty()) { - questionnaire = contained.stream() - .filter(r -> r.fhirType().equals(QUESTIONNAIRE) - && canonical - .getValueAsString() - .equals(getContainedId(r.getIdElement().getIdPart()))) - .findFirst() - .orElse(null); - } - return questionnaire; - } - - private String getContainedId(String id) { - return id.startsWith("#") ? id : "#" + id; - } - public IBaseBundle extract(Either resource) { return extract(resource, null, null, null, true); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/extract/ExtractProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/extract/ExtractProcessor.java index 155ee9bd13..81f69bfdc6 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/extract/ExtractProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/questionnaireresponse/extract/ExtractProcessor.java @@ -89,7 +89,10 @@ protected void processGroupItem( .noneMatch(e -> e.getUrl().equals(Constants.SDC_QUESTIONNAIRE_RESPONSE_IS_SUBJECT))) { var childPair = new ItemPair( request.getQuestionnaireItem( - childResponseItem, item.getItem().getItem()), + childResponseItem, + item.getItem() == null + ? null + : item.getItem().getItem()), childResponseItem); if (childResponseItem.hasItem()) { processGroupItem(request, childPair, questionnaireCodeMap, resources, groupSubject); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ProcessActionTests.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ProcessActionTests.java index 4dbddedcb4..189ba39f74 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ProcessActionTests.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/plandefinition/apply/ProcessActionTests.java @@ -141,7 +141,7 @@ void testConditionResultNull() { FhirVersionEnum.R4, libraryEngine, null, null, inputParameterResolver); doReturn(null) .when(libraryEngine) - .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null)); + .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), any(), any(), eq(null)); var actionAdapter = (IPlanDefinitionActionAdapter) IAdapterFactory.createAdapterForBase(FhirVersionEnum.R4, action); var result = fixture.meetsConditions(request, actionAdapter); @@ -158,7 +158,7 @@ void testConditionResultNotBoolean() { FhirVersionEnum.R4, libraryEngine, null, null, inputParameterResolver); doReturn(List.of(new StringType("Test"))) .when(libraryEngine) - .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null)); + .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), any(), any(), eq(null)); var actionAdapter = (IPlanDefinitionActionAdapter) IAdapterFactory.createAdapterForBase(FhirVersionEnum.R4, action); var result = fixture.meetsConditions(request, actionAdapter); @@ -195,7 +195,7 @@ void testProcessChildActionsApplicabilityBehavior() { var metConditions = new ArrayList(); doReturn(List.of(new BooleanType(true))) .when(libraryEngine) - .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), eq(null), any(), eq(null)); + .resolveExpression(eq(RequestHelpers.PATIENT_ID), any(), eq(null), any(), any(), any(), eq(null)); fixture.processChildActions(request, requestOrchestration, metConditions, actionAdapter, requestAction); assertEquals(3, requestAction.getAction().size()); diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/eras/input/resources/GraphDefinition-eras-postop.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/eras/input/resources/GraphDefinition-eras-postop.json index 0283d42632..b6b3f17e84 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/eras/input/resources/GraphDefinition-eras-postop.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/shared/r4/eras/input/resources/GraphDefinition-eras-postop.json @@ -24,8 +24,11 @@ "type": "GraphDefinition", "extension": [ { - "url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-relatedsummarydefinition", - "valueCanonical": "http://example.org/cdosdh/demo/GraphDefinition/hgb-pathway-event" + "url": "http://hl7.org/fhir/uv/cpg/StructureDefinition/artifact-relatedArtifact", + "valueRelatedArtifact": { + "type": "depends-on", + "resource": "http://example.org/cdosdh/demo/GraphDefinition/hgb-pathway-event" + } } ] }, diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IQuestionnaireResponseAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IQuestionnaireResponseAdapter.java index 3b958d8b60..5d6a0c68ba 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IQuestionnaireResponseAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/IQuestionnaireResponseAdapter.java @@ -2,7 +2,9 @@ import java.util.Date; import java.util.List; +import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; /** * This interface exposes common functionality across all FHIR Questionnaire versions. @@ -11,10 +13,18 @@ public interface IQuestionnaireResponseAdapter extends IResourceAdapter { IQuestionnaireResponseAdapter setId(String id); - IQuestionnaireResponseAdapter setQuestionnaire(String canonical); + boolean hasQuestionnaire(); String getQuestionnaire(); + IPrimitiveType getQuestionnaireCanonical(); + + IQuestionnaireResponseAdapter setQuestionnaire(String canonical); + + boolean hasSubject(); + + IIdType getSubject(); + IQuestionnaireResponseAdapter setSubject(IIdType subject); IQuestionnaireResponseAdapter setAuthored(Date date); @@ -23,8 +33,32 @@ public interface IQuestionnaireResponseAdapter extends IResourceAdapter { boolean hasItem(); + default boolean hasItem(String linkId) { + return !getItem(linkId).isEmpty(); + } + List getItem(); + default List getItem(String linkId) { + return getItemsWithLinkId(getItem(), linkId); + } + + default List getItemsWithLinkId( + List items, String linkId) { + var matchingItems = + items.stream().filter(i -> linkId.equals(i.getLinkId())).collect(Collectors.toList()); + items.forEach(i -> { + if (i.hasItem()) { + matchingItems.addAll(getItemsWithLinkId( + i.getItem().stream() + .map(IQuestionnaireResponseItemComponentAdapter.class::cast) + .toList(), + linkId)); + } + }); + return matchingItems; + } + void setItem(List items); void addItem(IQuestionnaireResponseItemComponentAdapter item); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapter.java index 3d0d653419..671fd5a685 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapter.java @@ -7,8 +7,10 @@ import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus; import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.instance.model.api.IDomainResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.adapter.IAdapter; import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseAdapter; import org.opencds.cqf.fhir.utility.adapter.IQuestionnaireResponseItemComponentAdapter; @@ -42,17 +44,37 @@ public IQuestionnaireResponseAdapter setId(String id) { return this; } + @Override + public boolean hasQuestionnaire() { + return get().hasQuestionnaire(); + } + @Override public String getQuestionnaire() { return get().getQuestionnaire().getReference(); } + @Override + public StringType getQuestionnaireCanonical() { + return get().hasQuestionnaire() ? get().getQuestionnaire().getReferenceElement_() : null; + } + @Override public IQuestionnaireResponseAdapter setQuestionnaire(String canonical) { get().setQuestionnaire(new Reference(canonical)); return this; } + @Override + public boolean hasSubject() { + return get().hasSubject(); + } + + @Override + public IIdType getSubject() { + return Ids.newId(fhirVersion(), get().getSubject().getReference()); + } + @Override public IQuestionnaireResponseAdapter setSubject(IIdType subject) { get().setSubject(new Reference(subject)); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapter.java index 14fd209fc3..e93b5da17d 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapter.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IDomainResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r4.model.QuestionnaireResponse.QuestionnaireResponseStatus; @@ -42,17 +43,37 @@ public IQuestionnaireResponseAdapter setId(String id) { return this; } + @Override + public boolean hasQuestionnaire() { + return get().hasQuestionnaire(); + } + @Override public String getQuestionnaire() { return get().getQuestionnaire(); } + @Override + public CanonicalType getQuestionnaireCanonical() { + return get().hasQuestionnaire() ? get().getQuestionnaireElement() : null; + } + @Override public IQuestionnaireResponseAdapter setQuestionnaire(String canonical) { get().setQuestionnaire(canonical); return this; } + @Override + public boolean hasSubject() { + return get().hasSubject(); + } + + @Override + public IIdType getSubject() { + return get().getSubject().getReferenceElement(); + } + @Override public IQuestionnaireResponseAdapter setSubject(IIdType subject) { get().setSubject(new Reference(subject)); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapter.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapter.java index eb163cce88..afbf8c0187 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapter.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapter.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IDomainResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.QuestionnaireResponse; import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseItemComponent; import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseStatus; @@ -47,17 +48,37 @@ public IQuestionnaireResponseAdapter setId(String id) { return this; } + @Override + public boolean hasQuestionnaire() { + return get().hasQuestionnaire(); + } + @Override public String getQuestionnaire() { return get().getQuestionnaire(); } + @Override + public CanonicalType getQuestionnaireCanonical() { + return get().hasQuestionnaire() ? get().getQuestionnaireElement() : null; + } + @Override public IQuestionnaireResponseAdapter setQuestionnaire(String canonical) { get().setQuestionnaire(canonical); return this; } + @Override + public boolean hasSubject() { + return get().hasSubject(); + } + + @Override + public IIdType getSubject() { + return get().getSubject().getReferenceElement(); + } + @Override public IQuestionnaireResponseAdapter setSubject(IIdType subject) { get().setSubject(new Reference(subject)); diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapterTest.java index cfb61d1c14..1e9d49ef2a 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/dstu3/QuestionnaireResponseAdapterTest.java @@ -35,8 +35,15 @@ void test() { assertNotNull(adapter.getModelResolver()); assertNotNull(adapter.getAdapterFactory()); adapter.setId("test"); - adapter.setQuestionnaire("test.com/Questionnaire/test"); - adapter.setSubject(new IdType("test1")); + var canonical = "test.com/Questionnaire/test"; + adapter.setQuestionnaire(canonical); + assertTrue(adapter.hasQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaireCanonical().getValueAsString()); + var id = new IdType("test1"); + adapter.setSubject(id); + assertTrue(adapter.hasSubject()); + assertEquals(id, adapter.getSubject()); adapter.setAuthored(new Date()); adapter.setStatus("in-progress"); } @@ -46,6 +53,9 @@ void testItem() { var questionnaireResponse = new QuestionnaireResponse(); var item1 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent().setLinkId("1")); + var item1_1 = adapterFactory.createQuestionnaireResponseItem( + new QuestionnaireResponseItemComponent().setLinkId("1.1")); + item1.addItem(item1_1); var item2 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent().setLinkId("2")); var item3 = @@ -56,6 +66,7 @@ void testItem() { questionnaireResponse.addItem((QuestionnaireResponseItemComponent) item2.get()); var adapter = adapterFactory.createQuestionnaireResponse(questionnaireResponse); assertTrue(adapter.hasItem()); + assertTrue(adapter.hasItem("1")); assertEquals(2, adapter.getItem().size()); adapter.addItem(item3); assertEquals(3, adapter.getItem().size()); @@ -63,5 +74,7 @@ void testItem() { assertEquals(1, adapter.getItem().size()); adapter.addItems(List.of(item2, item3, item4)); assertEquals(4, adapter.getItem().size()); + assertEquals(1, adapter.getItem("1.1").size()); + assertEquals(item1_1.get(), adapter.getItem("1.1").get(0).get()); } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapterTest.java index 0893f24f01..895e0931bc 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r4/QuestionnaireResponseAdapterTest.java @@ -35,8 +35,15 @@ void test() { assertNotNull(adapter.getModelResolver()); assertNotNull(adapter.getAdapterFactory()); adapter.setId("test"); - adapter.setQuestionnaire("test.com/Questionnaire/test"); - adapter.setSubject(new IdType("test1")); + var canonical = "test.com/Questionnaire/test"; + adapter.setQuestionnaire(canonical); + assertTrue(adapter.hasQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaireCanonical().getValueAsString()); + var id = new IdType("test1"); + adapter.setSubject(id); + assertTrue(adapter.hasSubject()); + assertEquals(id.getValue(), adapter.getSubject().getValue()); adapter.setAuthored(new Date()); adapter.setStatus("in-progress"); } @@ -46,6 +53,9 @@ void testItem() { var questionnaireResponse = new QuestionnaireResponse(); var item1 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent().setLinkId("1")); + var item1_1 = adapterFactory.createQuestionnaireResponseItem( + new QuestionnaireResponseItemComponent().setLinkId("1.1")); + item1.addItem(item1_1); var item2 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent().setLinkId("2")); var item3 = @@ -56,6 +66,7 @@ void testItem() { questionnaireResponse.addItem((QuestionnaireResponseItemComponent) item2.get()); var adapter = adapterFactory.createQuestionnaireResponse(questionnaireResponse); assertTrue(adapter.hasItem()); + assertTrue(adapter.hasItem("1")); assertEquals(2, adapter.getItem().size()); adapter.addItem(item3); assertEquals(3, adapter.getItem().size()); @@ -63,5 +74,7 @@ void testItem() { assertEquals(1, adapter.getItem().size()); adapter.addItems(List.of(item2, item3, item4)); assertEquals(4, adapter.getItem().size()); + assertEquals(1, adapter.getItem("1.1").size()); + assertEquals(item1_1.get(), adapter.getItem("1.1").get(0).get()); } } diff --git a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapterTest.java b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapterTest.java index 266f564898..dc2216a96d 100644 --- a/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapterTest.java +++ b/cqf-fhir-utility/src/test/java/org/opencds/cqf/fhir/utility/adapter/r5/QuestionnaireResponseAdapterTest.java @@ -35,8 +35,15 @@ void test() { assertNotNull(adapter.getModelResolver()); assertNotNull(adapter.getAdapterFactory()); adapter.setId("test"); - adapter.setQuestionnaire("test.com/Questionnaire/test"); - adapter.setSubject(new IdType("test1")); + var canonical = "test.com/Questionnaire/test"; + adapter.setQuestionnaire(canonical); + assertTrue(adapter.hasQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaire()); + assertEquals(canonical, adapter.getQuestionnaireCanonical().getValueAsString()); + var id = new IdType("test1"); + adapter.setSubject(id); + assertTrue(adapter.hasSubject()); + assertEquals(id.getValue(), adapter.getSubject().getValue()); adapter.setAuthored(new Date()); adapter.setStatus("in-progress"); } @@ -45,6 +52,9 @@ void test() { void testItem() { var questionnaireResponse = new QuestionnaireResponse(); var item1 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent("1")); + var item1_1 = adapterFactory.createQuestionnaireResponseItem( + new QuestionnaireResponseItemComponent().setLinkId("1.1")); + item1.addItem(item1_1); var item2 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent("2")); var item3 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent("3")); var item4 = adapterFactory.createQuestionnaireResponseItem(new QuestionnaireResponseItemComponent("4")); @@ -52,6 +62,7 @@ void testItem() { questionnaireResponse.addItem((QuestionnaireResponseItemComponent) item2.get()); var adapter = adapterFactory.createQuestionnaireResponse(questionnaireResponse); assertTrue(adapter.hasItem()); + assertTrue(adapter.hasItem("1")); assertEquals(2, adapter.getItem().size()); adapter.addItem(item3); assertEquals(3, adapter.getItem().size()); @@ -59,5 +70,7 @@ void testItem() { assertEquals(1, adapter.getItem().size()); adapter.addItems(List.of(item2, item3, item4)); assertEquals(4, adapter.getItem().size()); + assertEquals(1, adapter.getItem("1.1").size()); + assertEquals(item1_1.get(), adapter.getItem("1.1").get(0).get()); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7fb81cde9..fb86954ffe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,6 @@ hapi = "8.8.0" cql = "4.4.0" junit = "5.14.3" slf4j = "2.0.17" -spring-security = "6.4.13" spring-boot = "3.4.11" picocli = "4.7.7" jmh = "1.37" @@ -64,7 +63,6 @@ jakarta-servlet-api = { module = "jakarta.servlet:jakarta.servlet-api" } # Spring spring-context = { module = "org.springframework:spring-context" } spring-test = { module = "org.springframework:spring-test" } -spring-security-core = { module = "org.springframework.security:spring-security-core", version.ref = "spring-security" } # Logging slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }