From 57c1b58d4ff4b759bfd5b4b1cfdf8f6d37a28334 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 13 May 2020 21:21:12 -0600 Subject: [PATCH 001/198] Fixed bug in topic argument --- .../cqf/r4/providers/MeasureOperationsProvider.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 7ba89dce1..c5c49ba86 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -50,6 +51,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -223,8 +225,11 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "topic") String topic, @RequiredParam(name = "patient") String patientRef) { - List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap().add("topic", - new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))).getResources(0, 1000); + SearchParameterMap theParams = new SearchParameterMap(); + IQueryParameterType parameter = new TokenParam(topic); + theParams.add("topic", parameter); + List measures = this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); + Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); From 79fccff1436398c5d79ea39d1785320df5a37997 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 13 May 2020 21:29:31 -0600 Subject: [PATCH 002/198] Create a container for the gic sandbox --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 89cf7ff3e..370dcea3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,13 @@ before_deploy: # NOTE: tests were already run as part of the script phase deploy: + # deploy gic as a snapshot + - provider: script + script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:gic && docker push contentgroup/cqf-ruler:gic" + cleanup: false + skip_cleanup: true # This is the current correct option, but is soon to be deprecated by the above. + on: + branch: davinci_gic # deploy develop as a snapshot - provider: script script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:develop && docker push contentgroup/cqf-ruler:develop" From f2c1198e1e35171443ac9a36ccb0ce22ac00d48f Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 13 May 2020 22:22:54 -0600 Subject: [PATCH 003/198] Fixed indentation on .yml --- .travis.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 370dcea3e..5886737d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,13 +28,13 @@ before_deploy: # NOTE: tests were already run as part of the script phase deploy: - # deploy gic as a snapshot - - provider: script - script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:gic && docker push contentgroup/cqf-ruler:gic" - cleanup: false - skip_cleanup: true # This is the current correct option, but is soon to be deprecated by the above. - on: - branch: davinci_gic + # deploy gic as a snapshot + - provider: script + script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:gic && docker push contentgroup/cqf-ruler:gic" + cleanup: false + skip_cleanup: true # This is the current correct option, but is soon to be deprecated by the above. + on: + branch: davinci_gic # deploy develop as a snapshot - provider: script script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:develop && docker push contentgroup/cqf-ruler:develop" From da3163a2afa5347aac3a956f8c411f6b1445d2a7 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 10:31:01 -0600 Subject: [PATCH 004/198] Update the patient parameter to subject to match the spec --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index c5c49ba86..5a79bd472 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -224,7 +224,7 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "topic") String topic, - @RequiredParam(name = "patient") String patientRef) { + @RequiredParam(name = "subject") String subject) { SearchParameterMap theParams = new SearchParameterMap(); IQueryParameterType parameter = new TokenParam(topic); theParams.add("topic", parameter); @@ -239,7 +239,7 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS CodeableConcept typeCode = new CodeableConcept() .addCoding(new Coding().setSystem("http://loinc.org").setCode("57024-2")); composition.setStatus(Composition.CompositionStatus.FINAL).setType(typeCode) - .setSubject(new Reference(patientRef.startsWith("Patient/") ? patientRef : "Patient/" + patientRef)) + .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) .setTitle(topic + " Care Gap Report"); List reports = new ArrayList<>(); @@ -265,7 +265,7 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS seed.setup(measure, periodStart, periodEnd, null, null, null, null); MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only - report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); + report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), subject); if (report.hasGroup() && measure.hasScoring()) { int numerator = 0; From d7ec0a72aaab1e45b8f66b4a3a1a5c6490b2afd0 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 13:57:38 -0600 Subject: [PATCH 005/198] Made topic optional --- .../r4/providers/MeasureOperationsProvider.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 5a79bd472..d1d033be4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -223,11 +223,14 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, - @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "topic") String topic, - @RequiredParam(name = "subject") String subject) { - SearchParameterMap theParams = new SearchParameterMap(); - IQueryParameterType parameter = new TokenParam(topic); - theParams.add("topic", parameter); + @RequiredParam(name = "periodEnd") String periodEnd, + @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { + SearchParameterMap theParams = new SearchParameterMap(); + if (topic != null && !topic.equals("")) { + var topicParam = new TokenParam(topic); + theParams.add("topic", topicParam); + } + List measures = this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); Bundle careGapReport = new Bundle(); @@ -240,7 +243,7 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS .addCoding(new Coding().setSystem("http://loinc.org").setCode("57024-2")); composition.setStatus(Composition.CompositionStatus.FINAL).setType(typeCode) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) - .setTitle(topic + " Care Gap Report"); + .setTitle((topic != null && !topic.equals("") ? topic : "") + " Care Gap Report"); List reports = new ArrayList<>(); MeasureReport report = new MeasureReport(); From 17a0837a3ed0a0630d0e2eb39f9a4eaaa1983be5 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 18:00:59 -0600 Subject: [PATCH 006/198] Fixed bug with Measure reference instead of MeasureReport reference --- .../providers/MeasureOperationsProvider.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index d1d033be4..7e67123dc 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import javax.servlet.http.HttpServletRequest; @@ -225,11 +226,21 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { - SearchParameterMap theParams = new SearchParameterMap(); + SearchParameterMap theParams = new SearchParameterMap(); + + if (subject == null || subject.equals("")) { + throw new IllegalArgumentException("Subject isrequired."); + } + + // if (theId != null) { + // var measureParam = new StringParam(theId.getIdPart()); + // theParams.add("_id", measureParam); + // } + if (topic != null && !topic.equals("")) { var topicParam = new TokenParam(topic); theParams.add("topic", topicParam); - } + } List measures = this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); @@ -246,13 +257,13 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS .setTitle((topic != null && !topic.equals("") ? topic : "") + " Care Gap Report"); List reports = new ArrayList<>(); - MeasureReport report = new MeasureReport(); + MeasureReport report = null; + for (IBaseResource resource : measures) { + + Measure measure = (Measure) resource; Composition.SectionComponent section = new Composition.SectionComponent(); - Measure measure = (Measure) resource; - section.addEntry( - new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); if (measure.hasTitle()) { section.setTitle(measure.getTitle()); } @@ -269,6 +280,9 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), subject); + report.setId(UUID.randomUUID().toString()); + section.addEntry( + new Reference("MeasureReport/" + report.getId())); if (report.hasGroup() && measure.hasScoring()) { int numerator = 0; From b4bc76fac01a573552b0865fe8363a3f56bd2cc9 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 19:43:13 -0600 Subject: [PATCH 007/198] Fixed various spec conformance issues. --- .../providers/MeasureOperationsProvider.java | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 7e67123dc..61c98ceda 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,6 +1,7 @@ package org.opencds.cqf.r4.providers; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -247,12 +248,8 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); - Composition composition = new Composition(); - // TODO - this is a placeholder code for now ... replace with preferred code - // once identified - CodeableConcept typeCode = new CodeableConcept() - .addCoding(new Coding().setSystem("http://loinc.org").setCode("57024-2")); - composition.setStatus(Composition.CompositionStatus.FINAL).setType(typeCode) + Composition composition = new Composition(); + composition.setStatus(Composition.CompositionStatus.FINAL) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) .setTitle((topic != null && !topic.equals("") ? topic : "") + " Care Gap Report"); @@ -261,19 +258,13 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS for (IBaseResource resource : measures) { - Measure measure = (Measure) resource; + Measure measure = (Measure) resource; Composition.SectionComponent section = new Composition.SectionComponent(); if (measure.hasTitle()) { section.setTitle(measure.getTitle()); } - CodeableConcept improvementNotation = new CodeableConcept().addCoding(new Coding().setCode("increase").setSystem("http://terminology.hl7.org/CodeSystem/measure-improvement-notation")); // defaulting to "increase" - if (measure.hasImprovementNotation()) { - improvementNotation = measure.getImprovementNotation(); - section.setText(new Narrative().setStatus(Narrative.NarrativeStatus.GENERATED) - .setDiv(new XhtmlNode().setValue(improvementNotation.getCodingFirstRep().getCode()))); - } - + LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); seed.setup(measure, periodStart, periodEnd, null, null, null, null); @@ -281,8 +272,11 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS // TODO - this is configured for patient-level evaluation only report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), subject); report.setId(UUID.randomUUID().toString()); - section.addEntry( - new Reference("MeasureReport/" + report.getId())); + report.setDate(new Date()); + report.setImprovementNotation(measure.getImprovementNotation()); + section.setFocus(new Reference("MeasureReport/" + report.getId())); + //TODO: DetectedIssue + //section.addEntry(new Reference("MeasureReport/" + report.getId())); if (report.hasGroup() && measure.hasScoring()) { int numerator = 0; @@ -321,12 +315,12 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS // TODO - this is super hacky ... change once improvementNotation is specified // as a code - if (improvementNotation.getCodingFirstRep().getCode().toLowerCase().equals("increase")) { + if (measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase().equals("increase")) { if (proportion < 1.0) { composition.addSection(section); reports.add(report); } - } else if (improvementNotation.getCodingFirstRep().getCode().toLowerCase().equals("decrease")) { + } else if (measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase().equals("decrease")) { if (proportion > 0.0) { composition.addSection(section); reports.add(report); From 035622246be30b7dc1720161bcedd99a08d25adf Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 21:19:02 -0600 Subject: [PATCH 008/198] Added organization --- .../providers/MeasureOperationsProvider.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 61c98ceda..2db767587 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -10,7 +10,6 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Composition; import org.hl7.fhir.r4.model.Extension; @@ -18,7 +17,9 @@ import org.hl7.fhir.r4.model.ListResource; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; +import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.Narrative; +import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.RelatedArtifact; @@ -42,6 +43,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -227,12 +229,17 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { - SearchParameterMap theParams = new SearchParameterMap(); - + if (subject == null || subject.equals("")) { - throw new IllegalArgumentException("Subject isrequired."); + throw new IllegalArgumentException("Subject is required."); } + //TODO: this is an org hack. Need to figure out what the right thing is. + IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); + var org = orgDao.search(new SearchParameterMap()).getResources(0, 1); + + SearchParameterMap theParams = new SearchParameterMap(); + // if (theId != null) { // var measureParam = new StringParam(theId.getIdPart()); // theParams.add("_id", measureParam); @@ -251,7 +258,7 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS Composition composition = new Composition(); composition.setStatus(Composition.CompositionStatus.FINAL) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) - .setTitle((topic != null && !topic.equals("") ? topic : "") + " Care Gap Report"); + .setTitle("Care Gap Report"); List reports = new ArrayList<>(); MeasureReport report = null; @@ -274,6 +281,9 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS report.setId(UUID.randomUUID().toString()); report.setDate(new Date()); report.setImprovementNotation(measure.getImprovementNotation()); + //TODO: this is an org hack + report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); + report.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/indv-measurereport-deqm")); section.setFocus(new Reference("MeasureReport/" + report.getId())); //TODO: DetectedIssue //section.addEntry(new Reference("MeasureReport/" + report.getId())); From c18328be38209a1ee1ba0523176b635a1f5339bd Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 21:41:08 -0600 Subject: [PATCH 009/198] Added note --- .../org/opencds/cqf/r4/providers/MeasureOperationsProvider.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 2db767587..ef3189519 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -230,6 +230,8 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { + //TODO: topic should be many + if (subject == null || subject.equals("")) { throw new IllegalArgumentException("Subject is required."); } From c891149737179be7f9855d85b316789d3e3ca3bc Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 14 May 2020 22:32:16 -0600 Subject: [PATCH 010/198] Updated evaluation to use existing evaluation --- .../cqf/r4/providers/MeasureOperationsProvider.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index ef3189519..2629f22d0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -274,12 +274,10 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS section.setTitle(measure.getTitle()); } - LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); - seed.setup(measure, periodStart, periodEnd, null, null, null, null); - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only - report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), subject); + report = evaluateMeasure(measure.getIdElement(), periodStart, periodEnd, null, null, subject, null, + null, null, null, null, null); + report.setId(UUID.randomUUID().toString()); report.setDate(new Date()); report.setImprovementNotation(measure.getImprovementNotation()); From 6f71084b1cc041f22dfb0dd6e97230a107503a8c Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 15 May 2020 08:26:28 -0600 Subject: [PATCH 011/198] Branch to add $extract operation for extracting observations from a QuestionnaireResponse --- .../cqf/common/config/HapiProperties.java | 8 ++ .../r4/providers/QuestionnaireProvider.java | 136 ++++++++++++++++++ .../opencds/cqf/r4/servlet/BaseServlet.java | 25 +--- r4/src/main/resources/hapi.properties | 5 +- 4 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 44ca4578d..65c980ca7 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -345,4 +345,12 @@ public static Long getReuseCachedSearchResultsMillis() { String value = HapiProperties.getProperty(REUSE_CACHED_SEARCH_RESULTS_MILLIS, "-1"); return Long.valueOf(value); } + + public static String getObservationEndpoint() { + return HapiProperties.getProperty("observation.endpoint"); + } + public static String getObservationUserName(){return HapiProperties.getProperty("observation.username");}; + public static String getObservationPassword(){return HapiProperties.getProperty("observation.password");}; + + } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java new file mode 100644 index 000000000..293c52503 --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -0,0 +1,136 @@ +package org.opencds.cqf.r4.providers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.util.BundleUtil; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.QuestionnaireResponse; +import org.opencds.cqf.common.config.HapiProperties; +import org.opencds.cqf.utilities.BundleUtils; + +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; + +public class QuestionnaireProvider { + + private FhirContext fhirContext; + public QuestionnaireProvider(FhirContext fhirContext){ + this.fhirContext = fhirContext; + } + + @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) + public Observation extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { + Observation newObs = new Observation(); + newObs.setId("123456"); + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return newObs; + } + + private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ + Bundle newBundle = new Bundle(); + + QuestionnaireResponse.QuestionnaireResponseItemComponent item = questionnaireResponse.getItem().get(0); + String answer = item.getAnswer().get(0).getValueStringType().getValue(); + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + // obs.setCode(); +// obs.setValue() = +/* + Bundle bundleToPost = new Bundle(); + for (StringOrListParam params : valuesets.getValuesAsQueryTokens()) { + for (StringParam valuesetId : params.getValuesAsQueryTokens()) { + bundleToPost.addEntry() + .setRequest(new Bundle.BundleEntryRequestComponent().setMethod(Bundle.HTTPVerb.PUT) + .setUrl("ValueSet/" + valuesetId.getValue())) + .setResource(resolveValueSet(client, valuesetId.getValue())); + } + } + + */ + + Identifier bundleId = new Identifier(); + bundleId.setValue(questionnaireResponse.getId()); + newBundle.setType(Bundle.BundleType.TRANSACTION); + newBundle.setIdentifier(bundleId); + + Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); + berc.setMethod(Bundle.HTTPVerb.PUT); + berc.setUrl("Observation/65austin"); + + Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); + bec.setResource(obs); + bec.setRequest(berc); + + newBundle.addEntry(bec); + return newBundle; + } + + private Bundle sendObservationBundle(Bundle observationsBundle){ + String url = HapiProperties.getObservationEndpoint(); + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); +Bundle testBundle = new Bundle(); +IParser iParser = fhirContext.newJsonParser(); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; +/* + Bundle result = new Bundle(); + try { + result = client.operation().onServer() + .named("$process-reference-list") + .withParameter(Parameters.class, "reference-list", referenceList) + .encodedJson() + .returnResourceType(Bundle.class) + .execute(); + }catch(Exception ex){ + ex.printStackTrace(); + } +*/ + } + + /** + * How do we store the QuestionnaireResponse Id?? + * + * var obxs = []; + * for(var i=0, iLen=values.length; i Date: Fri, 15 May 2020 08:46:32 -0600 Subject: [PATCH 012/198] Fixed divide by zero error --- .../providers/MeasureOperationsProvider.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 2629f22d0..a1bbbfd9f 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -230,7 +230,7 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { - //TODO: topic should be many + //TODO: topic should allow many if (subject == null || subject.equals("")) { throw new IllegalArgumentException("Subject is required."); @@ -314,28 +314,35 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS } } + //TODO: implement this per the spec + //Holding off on implementiation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) double proportion = 0.0; if (measure.getScoring().hasCoding() && denominator != 0) { for (Coding coding : measure.getScoring().getCoding()) { if (coding.hasCode() && coding.getCode().equals("proportion")) { - proportion = numerator / denominator; + if (denominator != 0.0 ) { + proportion = numerator / denominator; + } } } } // TODO - this is super hacky ... change once improvementNotation is specified // as a code - if (measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase().equals("increase")) { - if (proportion < 1.0) { - composition.addSection(section); - reports.add(report); - } - } else if (measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase().equals("decrease")) { - if (proportion > 0.0) { + String improvementNotation = measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); + if ( + ((improvementNotation.equals("increase")) && (proportion < 1.0)) + || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { + //WIP + // DetectedIssue detectedIssue = new DetectedIssue(); + // section.addEntry( + // new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); + if (measure.hasTitle()) { + // add DetectedIssue added to section.entry composition.addSection(section); reports.add(report); - } - } + + } // TODO - add other types of improvement notation cases } From c154241b6cb9a54688b06604b982cba8e3a969ac Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Fri, 15 May 2020 09:17:39 -0600 Subject: [PATCH 013/198] No idea how the prior commit got that way. That was not the intent. Fixed. --- .../cqf/r4/providers/MeasureOperationsProvider.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index a1bbbfd9f..3b71aaca6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -333,12 +333,13 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS if ( ((improvementNotation.equals("increase")) && (proportion < 1.0)) || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { - //WIP - // DetectedIssue detectedIssue = new DetectedIssue(); - // section.addEntry( - // new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); - if (measure.hasTitle()) { + //WIP // add DetectedIssue added to section.entry + // DetectedIssue detectedIssue = new DetectedIssue(); + // section.addEntry( + // new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); + + composition.addSection(section); reports.add(report); From c7bbefd8231585c258ab79b4052c0bdd97380dd2 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Fri, 15 May 2020 09:56:42 -0600 Subject: [PATCH 014/198] Added DetectedIssue --- .../providers/MeasureOperationsProvider.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 3b71aaca6..3ed1720b6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -10,8 +10,10 @@ import javax.servlet.http.HttpServletRequest; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.DetectedIssue; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.ListResource; @@ -25,6 +27,8 @@ import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueEvidenceComponent; +import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueStatus; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; @@ -263,8 +267,9 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS .setTitle("Care Gap Report"); List reports = new ArrayList<>(); + List detectedIssues = new ArrayList(); MeasureReport report = null; - + for (IBaseResource resource : measures) { Measure measure = (Measure) resource; @@ -329,20 +334,26 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS // TODO - this is super hacky ... change once improvementNotation is specified // as a code - String improvementNotation = measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); + String improvementNotation = measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); if ( ((improvementNotation.equals("increase")) && (proportion < 1.0)) || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { //WIP // add DetectedIssue added to section.entry - // DetectedIssue detectedIssue = new DetectedIssue(); - // section.addEntry( - // new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); - + DetectedIssue detectedIssue = new DetectedIssue(); + detectedIssue.setId(UUID.randomUUID().toString()); + detectedIssue.setStatus(DetectedIssueStatus.FINAL); + detectedIssue.getEvidence().add(new DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); + CodeableConcept code = new CodeableConcept() + .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); + detectedIssue.setCode(code); + + section.addEntry( + new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); + composition.addSection(section); - composition.addSection(section); - reports.add(report); - + detectedIssues.add(detectedIssue); + reports.add(report); } // TODO - add other types of improvement notation cases @@ -355,6 +366,10 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); } + for (DetectedIssue detectedIssue: detectedIssues) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); + } + return careGapReport; } From 8b7d68c1376d59235164128e3a3275d05dee4920 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Fri, 15 May 2020 10:22:46 -0600 Subject: [PATCH 015/198] Added patient to DetectedIssue --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 3ed1720b6..d066b72ad 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -338,11 +338,11 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS if ( ((improvementNotation.equals("increase")) && (proportion < 1.0)) || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { - //WIP - // add DetectedIssue added to section.entry + DetectedIssue detectedIssue = new DetectedIssue(); detectedIssue.setId(UUID.randomUUID().toString()); detectedIssue.setStatus(DetectedIssueStatus.FINAL); + detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); detectedIssue.getEvidence().add(new DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); CodeableConcept code = new CodeableConcept() .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); From 7a24dbf6be91f4d5b4f7f129ca927e9022b7a151 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 15 May 2020 11:57:13 -0600 Subject: [PATCH 016/198] added functionality to observation --- .../r4/providers/QuestionnaireProvider.java | 71 +++++++------------ 1 file changed, 25 insertions(+), 46 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 293c52503..c6b25dec5 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -6,13 +6,13 @@ import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.util.BundleUtil; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Observation; -import org.hl7.fhir.r4.model.QuestionnaireResponse; +import com.github.dnault.xmlpatch.repackaged.org.jaxen.util.SingletonList; +import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.utilities.BundleUtils; +import java.util.List; + import static org.opencds.cqf.common.helpers.ClientHelper.getClient; public class QuestionnaireProvider { @@ -23,51 +23,45 @@ public QuestionnaireProvider(FhirContext fhirContext){ } @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) - public Observation extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - Observation newObs = new Observation(); - newObs.setId("123456"); + public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return newObs; + return returnBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ Bundle newBundle = new Bundle(); - QuestionnaireResponse.QuestionnaireResponseItemComponent item = questionnaireResponse.getItem().get(0); - String answer = item.getAnswer().get(0).getValueStringType().getValue(); - Observation obs = new Observation(); - obs.setStatus(Observation.ObservationStatus.FINAL); - // obs.setCode(); -// obs.setValue() = -/* - Bundle bundleToPost = new Bundle(); - for (StringOrListParam params : valuesets.getValuesAsQueryTokens()) { - for (StringParam valuesetId : params.getValuesAsQueryTokens()) { - bundleToPost.addEntry() - .setRequest(new Bundle.BundleEntryRequestComponent().setMethod(Bundle.HTTPVerb.PUT) - .setUrl("ValueSet/" + valuesetId.getValue())) - .setResource(resolveValueSet(client, valuesetId.getValue())); - } - } - - */ - Identifier bundleId = new Identifier(); bundleId.setValue(questionnaireResponse.getId()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); + questionnaireResponse.getItem().stream().forEach(item ->{ + newBundle.addEntry(extractItem(item, questionnaireResponse.getQuestionnaire())); + }); + return newBundle; + } + + //TODO - ids need work; add encounter; what more?? + private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, String questionnaire){ + // obs.setCode(); +// obs.setValue() = + + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(item.getLinkId()); +// obs.setDerivedFrom(new SingletonList("QuestionnaireResponse." + questionnaire + "." + item.getLinkId())); + obs.setValue(new StringType(item.getText() + "::" + item.getAnswer().get(0).getValueStringType().getValue())); + Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); - berc.setUrl("Observation/65austin"); + berc.setUrl("Observation/" + item.getId() + "." + item.getLinkId()); Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); bec.setResource(obs); bec.setRequest(berc); - - newBundle.addEntry(bec); - return newBundle; + return bec; } private Bundle sendObservationBundle(Bundle observationsBundle){ @@ -76,25 +70,10 @@ private Bundle sendObservationBundle(Bundle observationsBundle){ String password = HapiProperties.getObservationPassword(); IGenericClient client = getClient(fhirContext, url, user, password); -Bundle testBundle = new Bundle(); -IParser iParser = fhirContext.newJsonParser(); Bundle outcomeBundle = client.transaction() .withBundle(observationsBundle) .execute(); return outcomeBundle; -/* - Bundle result = new Bundle(); - try { - result = client.operation().onServer() - .named("$process-reference-list") - .withParameter(Parameters.class, "reference-list", referenceList) - .encodedJson() - .returnResourceType(Bundle.class) - .execute(); - }catch(Exception ex){ - ex.printStackTrace(); - } -*/ } /** From bb8474ada2dc5f2241b6c7049ad76c62d43a30b1 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 20 May 2020 08:49:59 -0600 Subject: [PATCH 017/198] Adding info to observation. Some temporary --- .../cqf/r4/providers/QuestionnaireProvider.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index c6b25dec5..b32fc0116 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -7,10 +7,12 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.util.BundleUtil; import com.github.dnault.xmlpatch.repackaged.org.jaxen.util.SingletonList; +import org.hl7.fhir.DateTime; import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.utilities.BundleUtils; +import java.util.Date; import java.util.List; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -38,21 +40,23 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon newBundle.setIdentifier(bundleId); questionnaireResponse.getItem().stream().forEach(item ->{ - newBundle.addEntry(extractItem(item, questionnaireResponse.getQuestionnaire())); + newBundle.addEntry(extractItem(item, questionnaireResponse)); }); return newBundle; } - //TODO - ids need work; add encounter; what more?? - private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, String questionnaire){ + //TODO - ids need work; add encounter; subject/patient; effectiveDateTime; value + // TODO - using value for questionnaire question - NOT THE RIGHT PLACE!! + private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, QuestionnaireResponse questionnaireResponse){ // obs.setCode(); // obs.setValue() = Observation obs = new Observation(); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setId(item.getLinkId()); -// obs.setDerivedFrom(new SingletonList("QuestionnaireResponse." + questionnaire + "." + item.getLinkId())); +// obs.setDerivedFrom(new SingletonList(questionnaireResponse.get); //create a reference to the QuestionnaireResponse ???? obs.setValue(new StringType(item.getText() + "::" + item.getAnswer().get(0).getValueStringType().getValue())); + obs.setEffective(new DateTimeType( questionnaireResponse.getMeta().getLastUpdated())); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); From 5564195052dc20be0048598dc8748beb38d8bd6c Mon Sep 17 00:00:00 2001 From: Brenin Date: Mon, 1 Jun 2020 21:11:21 -0600 Subject: [PATCH 018/198] LibraryHelper.loadLibraries now uses the primary library of a measure to load related libraries. --- .../opencds/cqf/dstu3/helpers/LibraryHelper.java | 3 ++- pom.xml | 2 +- .../org/opencds/cqf/r4/helpers/LibraryHelper.java | 14 +++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 1cc73f067..6d809c5b3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -67,7 +67,8 @@ public static List loadLibraries(Meas ); } - for (RelatedArtifact artifact : measure.getRelatedArtifact()) { + org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryById(libraries.get(0).getIdentifier().getId()); + for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) { org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); libraries.add( diff --git a/pom.xml b/pom.xml index bcbdb45aa..3313d8bf0 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 0 1 - 14.4 + 14.5 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 75fd356f3..6b6f5e3f4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -43,16 +43,14 @@ public static List loadLibraries(Meas List libraries = new ArrayList(); // load libraries - // load libraries - for (CanonicalType ref : measure.getLibrary()) { - // if library is contained in measure, load it into server + for (CanonicalType ref : measure.getLibrary()) { + // if library is contained in measure, load it into server String id = CanonicalHelper.getId(ref); if (id.startsWith("#")) { id = id.substring(1); for (Resource resource : measure.getContained()) { if (resource instanceof org.hl7.fhir.r4.model.Library - && resource.getIdElement().getIdPart().equals(id)) - { + && resource.getIdElement().getIdPart().equals(id)) { libraryResourceProvider.update((org.hl7.fhir.r4.model.Library) resource); } } @@ -60,12 +58,14 @@ public static List loadLibraries(Meas // We just loaded it into the server so we can access it by Id org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(id); + libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) ); } - for (RelatedArtifact artifact : measure.getRelatedArtifact()) { + org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryById(libraries.get(0).getIdentifier().getId()); + for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.hasResource()) { org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource()); libraries.add( From 72f67a9cdc2157f2b7e93da953ac9080e7bead96 Mon Sep 17 00:00:00 2001 From: Brenin Date: Wed, 3 Jun 2020 22:47:21 -0600 Subject: [PATCH 019/198] Use correct resolve function to ensure primary library is loaded. --- .../main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java | 3 ++- pom.xml | 2 +- r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 6d809c5b3..9339d0339 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -67,7 +67,8 @@ public static List loadLibraries(Meas ); } - org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryById(libraries.get(0).getIdentifier().getId()); + VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); + org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) { org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); diff --git a/pom.xml b/pom.xml index 3313d8bf0..65fe92dfc 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 0 1 - 14.5 + 14.6 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 6b6f5e3f4..042d837e9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -64,7 +64,8 @@ public static List loadLibraries(Meas ); } - org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryById(libraries.get(0).getIdentifier().getId()); + VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); + org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.hasResource()) { org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource()); From 8a183036b3e22152d7a8359d5cd2f480a6ce9f99 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Feb 2020 13:00:05 -0700 Subject: [PATCH 020/198] branch to utilize noAuth cql-engine --- .../cqf/common/helpers/ClientHelper.java | 51 ++++++++++++++++++ .../cqf/common/helpers/FhirContextHelper.java | 16 ++++++ .../Dstu3ApelonFhirTerminologyProvider.java | 6 +-- .../R4ApelonFhirTerminologyProvider.java | 6 +-- .../cqf/dstu3/evaluation/ProviderFactory.java | 8 +-- jpaserver_derby_files_r4.7z | Bin 0 -> 194866 bytes .../cqf/r4/evaluation/ProviderFactory.java | 10 ++-- 7 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java create mode 100644 common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java create mode 100644 jpaserver_derby_files_r4.7z diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java new file mode 100644 index 000000000..bb71de29d --- /dev/null +++ b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java @@ -0,0 +1,51 @@ +package org.opencds.cqf.common.helpers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +@Component +public class ClientHelper { + + @PostConstruct + public void init() { + ClientHelper.environment = this.environmentTemp; + } + + @Autowired + public Environment environmentTemp; + + public static Environment environment; + + /* TODO - depending on future needs: + 1. add OAuth + 2. change if to switch to accommodate additional FHIR versions + */ + private static IGenericClient getRestClient(String whichFHIRVersion, String url){ + FhirContext fhirContext = whichFHIRVersion.equalsIgnoreCase("dstu3")? FhirContextHelper.getFhirContextDstu3() : FhirContextHelper.getFhirContextR4(); + fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + return fhirContext.newRestfulGenericClient(url); + } + + // Overload in case you need to specify a specific version of the context + public static IGenericClient getClient(String whichFHIRVersion, String url, String user, String password) + { + IGenericClient client = getRestClient(whichFHIRVersion, url); + registerAuth(client, user, password); + + return client; + } + + private static void registerAuth(IGenericClient client, String userId, String password) { + if (userId != null) { + BasicAuthInterceptor authInterceptor = new BasicAuthInterceptor(userId, password); + client.registerInterceptor(authInterceptor); + } + } +} diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java new file mode 100644 index 000000000..a86a62c9e --- /dev/null +++ b/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java @@ -0,0 +1,16 @@ +package org.opencds.cqf.common.helpers; + +import ca.uhn.fhir.context.FhirContext; + +public class FhirContextHelper { + static FhirContext fhirContextDstu3 = FhirContext.forDstu3(); + static FhirContext fhirContextR4 = FhirContext.forR4(); + + public static FhirContext getFhirContextDstu3(){ + return fhirContextDstu3; + } + + public static FhirContext getFhirContextR4(){ + return fhirContextR4; + } +} diff --git a/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java index f4528ba93..5759e6a23 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java @@ -5,13 +5,13 @@ import java.util.Map; import java.util.HashMap; +import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; import org.opencds.cqf.cql.runtime.Code; import org.opencds.cqf.cql.terminology.ValueSetInfo; import org.opencds.cqf.cql.terminology.fhir.Dstu3FhirTerminologyProvider; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -24,8 +24,8 @@ public Dstu3ApelonFhirTerminologyProvider() { super(); } - public Dstu3ApelonFhirTerminologyProvider(FhirContext fhirContext) { - super(fhirContext); + public Dstu3ApelonFhirTerminologyProvider(IGenericClient fhirClient) { + super(fhirClient); } @Override diff --git a/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java index cbc7938f5..56fedc374 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java @@ -5,13 +5,13 @@ import java.util.Map; import java.util.HashMap; +import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.opencds.cqf.cql.runtime.Code; import org.opencds.cqf.cql.terminology.ValueSetInfo; import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @@ -24,8 +24,8 @@ public R4ApelonFhirTerminologyProvider() { super(); } - public R4ApelonFhirTerminologyProvider(FhirContext fhirContext) { - super(fhirContext); + public R4ApelonFhirTerminologyProvider(IGenericClient fhirClient) { + super(fhirClient) ; } @Override diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index 8c0a17dd6..c6b792716 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -1,5 +1,7 @@ package org.opencds.cqf.dstu3.evaluation; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.opencds.cqf.common.helpers.ClientHelper; import org.opencds.cqf.cql.data.CompositeDataProvider; import org.opencds.cqf.cql.data.DataProvider; import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; @@ -54,12 +56,12 @@ public DataProvider createDataProvider(String model, String version, Terminology } public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { + IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); if (url != null && url.contains("apelon.com")) { - return new Dstu3ApelonFhirTerminologyProvider(this.fhirContext) - .withBasicAuth(user, pass).setEndpoint(url, false); + return new Dstu3ApelonFhirTerminologyProvider(client); } else if (url != null && !url.isEmpty()) { - return new Dstu3FhirTerminologyProvider(this.fhirContext).withBasicAuth(user, pass).setEndpoint(url, false); + return new Dstu3FhirTerminologyProvider(client); } else return this.defaultTerminologyProvider; } diff --git a/jpaserver_derby_files_r4.7z b/jpaserver_derby_files_r4.7z new file mode 100644 index 0000000000000000000000000000000000000000..1011de7fde84134e1feed383f3655f680e263900 GIT binary patch literal 194866 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 pUs|g@!CO-Deq5*rA*I!$|uIaB4wRXnAUCA|Dc+ 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%0Au=~|CFWrlX$9!Sf9 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 literal 0 HcmV?d00001 diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index a30f16186..09cb125b8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -1,10 +1,14 @@ package org.opencds.cqf.r4.evaluation; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.opencds.cqf.common.helpers.ClientHelper; +import org.opencds.cqf.common.providers.Dstu3ApelonFhirTerminologyProvider; import org.opencds.cqf.cql.data.CompositeDataProvider; import org.opencds.cqf.cql.data.DataProvider; import org.opencds.cqf.cql.model.R4FhirModelResolver; import org.opencds.cqf.cql.searchparam.SearchParameterResolver; import org.opencds.cqf.cql.terminology.TerminologyProvider; +import org.opencds.cqf.cql.terminology.fhir.Dstu3FhirTerminologyProvider; import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; @@ -53,12 +57,12 @@ public DataProvider createDataProvider(String model, String version, Terminology } public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { + IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); if (url != null && url.contains("apelon.com")) { - return new R4ApelonFhirTerminologyProvider(this.fhirContext) - .withBasicAuth(user, pass).setEndpoint(url, false); + return new R4ApelonFhirTerminologyProvider(client); } else if (url != null && !url.isEmpty()) { - return new R4FhirTerminologyProvider(this.fhirContext).withBasicAuth(user, pass).setEndpoint(url, false); + return new R4FhirTerminologyProvider(client); } else return this.defaultTerminologyProvider; } From 0691de0772142c1fce03f59d147a60e8b5be9f2f Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 2 Mar 2020 13:44:07 -0700 Subject: [PATCH 021/198] fixes so tests will run properly --- .../cqf/dstu3/evaluation/ProviderFactory.java | 16 +++++++++------- .../cqf/r4/evaluation/ProviderFactory.java | 14 +++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index c6b792716..73f6b71c1 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import org.opencds.cqf.common.helpers.ClientHelper; +import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; import org.opencds.cqf.cql.data.CompositeDataProvider; import org.opencds.cqf.cql.data.DataProvider; import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; @@ -15,6 +16,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; // This class is a relatively dumb factory for data providers. It supports only // creating JPA providers for FHIR and only basic auth for terminology @@ -56,13 +58,13 @@ public DataProvider createDataProvider(String model, String version, Terminology } public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { - IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); - if (url != null && url.contains("apelon.com")) { - return new Dstu3ApelonFhirTerminologyProvider(client); - } - else if (url != null && !url.isEmpty()) { + if(url != null && !url.isEmpty()){ + IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); + if (url.contains("apelon.com")) { + return new Dstu3ApelonFhirTerminologyProvider(client); + } return new Dstu3FhirTerminologyProvider(client); - } else - return this.defaultTerminologyProvider; + } + return this.defaultTerminologyProvider; } } \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index 09cb125b8..109351f39 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -57,13 +57,13 @@ public DataProvider createDataProvider(String model, String version, Terminology } public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { - IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); - if (url != null && url.contains("apelon.com")) { - return new R4ApelonFhirTerminologyProvider(client); - } - else if (url != null && !url.isEmpty()) { + if(url != null && !url.isEmpty()){ + IGenericClient client = ClientHelper.getClient("r4", url, user, pass); + if (url.contains("apelon.com")) { + return new R4ApelonFhirTerminologyProvider(client); + } return new R4FhirTerminologyProvider(client); - } else - return this.defaultTerminologyProvider; + } + return this.defaultTerminologyProvider; } } \ No newline at end of file From 02f0f2adc9ef32cf57c08ce9efa1fa3cffeb8163 Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 3 Mar 2020 12:22:15 -0700 Subject: [PATCH 022/198] changes to match Pull Request requests --- .../cqf/common/helpers/ClientHelper.java | 18 +++--------------- .../cqf/common/helpers/FhirContextHelper.java | 16 ---------------- .../cqf/dstu3/evaluation/ProviderFactory.java | 2 +- .../cqf/r4/evaluation/ProviderFactory.java | 2 +- 4 files changed, 5 insertions(+), 33 deletions(-) delete mode 100644 common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java index bb71de29d..81d925d12 100644 --- a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java +++ b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java @@ -10,33 +10,21 @@ import javax.annotation.PostConstruct; -@Component public class ClientHelper { - @PostConstruct - public void init() { - ClientHelper.environment = this.environmentTemp; - } - - @Autowired - public Environment environmentTemp; - - public static Environment environment; - /* TODO - depending on future needs: 1. add OAuth 2. change if to switch to accommodate additional FHIR versions */ - private static IGenericClient getRestClient(String whichFHIRVersion, String url){ - FhirContext fhirContext = whichFHIRVersion.equalsIgnoreCase("dstu3")? FhirContextHelper.getFhirContextDstu3() : FhirContextHelper.getFhirContextR4(); + private static IGenericClient getRestClient(FhirContext fhirContext, String url){ fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); return fhirContext.newRestfulGenericClient(url); } // Overload in case you need to specify a specific version of the context - public static IGenericClient getClient(String whichFHIRVersion, String url, String user, String password) + public static IGenericClient getClient(FhirContext fhirContext, String url, String user, String password) { - IGenericClient client = getRestClient(whichFHIRVersion, url); + IGenericClient client = getRestClient(fhirContext, url); registerAuth(client, user, password); return client; diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java deleted file mode 100644 index a86a62c9e..000000000 --- a/common/src/main/java/org/opencds/cqf/common/helpers/FhirContextHelper.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.opencds.cqf.common.helpers; - -import ca.uhn.fhir.context.FhirContext; - -public class FhirContextHelper { - static FhirContext fhirContextDstu3 = FhirContext.forDstu3(); - static FhirContext fhirContextR4 = FhirContext.forR4(); - - public static FhirContext getFhirContextDstu3(){ - return fhirContextDstu3; - } - - public static FhirContext getFhirContextR4(){ - return fhirContextR4; - } -} diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index 73f6b71c1..0310f9ad6 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -59,7 +59,7 @@ public DataProvider createDataProvider(String model, String version, Terminology public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { if(url != null && !url.isEmpty()){ - IGenericClient client = ClientHelper.getClient("dstu3", url, user, pass); + IGenericClient client = ClientHelper.getClient(FhirContext.forDstu3(), url, user, pass); if (url.contains("apelon.com")) { return new Dstu3ApelonFhirTerminologyProvider(client); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index 109351f39..870a27acf 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -58,7 +58,7 @@ public DataProvider createDataProvider(String model, String version, Terminology public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { if(url != null && !url.isEmpty()){ - IGenericClient client = ClientHelper.getClient("r4", url, user, pass); + IGenericClient client = ClientHelper.getClient(FhirContext.forR4(), url, user, pass); if (url.contains("apelon.com")) { return new R4ApelonFhirTerminologyProvider(client); } From 0a994a9992cb1f12c7b181c838e9b2a48aa04f30 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 26 Mar 2020 13:52:58 -0600 Subject: [PATCH 023/198] Remove derby files --- .gitignore | 1 + jpaserver_derby_files_r4.7z | Bin 194866 -> 0 bytes 2 files changed, 1 insertion(+) delete mode 100644 jpaserver_derby_files_r4.7z diff --git a/.gitignore b/.gitignore index b0abaae28..83c5121d9 100644 --- a/.gitignore +++ b/.gitignore @@ -156,6 +156,7 @@ org.eclipse.m2e.core.prefs LogMessages.html *.zip +*.7z core.* heapdump.* diff --git a/jpaserver_derby_files_r4.7z b/jpaserver_derby_files_r4.7z deleted file mode 100644 index 1011de7fde84134e1feed383f3655f680e263900..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 194866 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 pUs|g@!CO-Deq5*rA*I!$|uIaB4wRXnAUCA|Dc+ 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%0Au=~|CFWrlX$9!Sf9 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 From 41664e2ec5e9c779a1afda357a4cc983b2cda9fd Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 26 Mar 2020 15:49:34 -0600 Subject: [PATCH 024/198] Rev to 0.1.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 65fe92dfc..728edafe4 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 0 1 - 14.6 + 15 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT From 94f1fe4e04baf77fd760cc9fc282ceac68c6acc2 Mon Sep 17 00:00:00 2001 From: Chris Schuler Date: Fri, 27 Mar 2020 11:31:26 -0600 Subject: [PATCH 025/198] Fixed incorrect parameter name in submit-data operation --- .../org/opencds/cqf/r4/providers/MeasureOperationsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 7ba89dce1..5ef69d175 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -411,7 +411,7 @@ public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId, @Operation(name = "$submit-data", idempotent = true, type = Measure.class) public Resource submitData(RequestDetails details, @IdParam IdType theId, - @OperationParam(name = "measure-report", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, + @OperationParam(name = "measurereport", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, @OperationParam(name = "resource") List resources) { Bundle transactionBundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); From 8e342d649c1ee88c3c9b4bffb0b972ba71d21fb1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 29 Apr 2020 13:34:33 -0600 Subject: [PATCH 026/198] Update to engine 1.4, HAPI 4.2 --- .gitignore | 3 +- .../cqf/common/config/FhirServerConfig.java | 39 ++- .../cqf/common/config/HapiProperties.java | 41 ++- .../evaluation/EvaluationProviderFactory.java | 7 +- .../cqf/common/evaluation/LibraryLoader.java | 32 +- .../cqf/common/helpers/ClientHelper.java | 16 +- .../cqf/common/helpers/TranslatorHelper.java | 29 +- .../cqf/common/helpers/UsingHelper.java | 22 +- ...bledCustomThymeleafNarrativeGenerator.java | 49 ++- .../Dstu3ApelonFhirTerminologyProvider.java | 130 ++++--- .../InMemoryLibraryResourceProvider.java | 18 +- .../providers/LibrarySourceProvider.java | 24 +- .../R4ApelonFhirTerminologyProvider.java | 130 ++++--- .../retrieve/JpaFhirRetrieveProvider.java | 46 ++- cqf-ruler-dstu3/pom.xml | 33 +- cqf-ruler-r4/pom.xml | 33 +- dstu3/pom.xml | 15 +- .../builders/ActivityDefinitionBuilder.java | 14 +- .../cqf/dstu3/builders/AnnotationBuilder.java | 6 +- .../cqf/dstu3/builders/AttachmentBuilder.java | 6 +- .../builders/CarePlanActivityBuilder.java | 10 +- .../CarePlanActivityDetailBuilder.java | 12 +- .../cqf/dstu3/builders/CarePlanBuilder.java | 14 +- .../builders/CodeableConceptBuilder.java | 8 +- .../cqf/dstu3/builders/CodingBuilder.java | 2 +- .../cqf/dstu3/builders/ExtensionBuilder.java | 3 +- .../cqf/dstu3/builders/IdentifierBuilder.java | 2 +- .../cqf/dstu3/builders/JavaDateBuilder.java | 5 +- .../cqf/dstu3/builders/LibraryBuilder.java | 23 +- .../dstu3/builders/MeasureReportBuilder.java | 14 +- .../builders/OperationOutcomeBuilder.java | 20 +- .../cqf/dstu3/builders/PeriodBuilder.java | 6 +- .../dstu3/builders/PractitionerBuilder.java | 2 +- .../builders/ProcedureRequestBuilder.java | 30 +- .../cqf/dstu3/builders/ReferenceBuilder.java | 2 +- .../builders/RelatedArtifactBuilder.java | 2 +- .../builders/RequestGroupActionBuilder.java | 15 +- .../dstu3/builders/RequestGroupBuilder.java | 6 +- .../builders/StructureMapGroupBuilder.java | 6 +- .../dstu3/builders/StructuredMapBuilder.java | 9 +- .../builders/StructuredMapRuleBuilder.java | 14 +- .../StructuredMapRuleSourceBuilder.java | 4 +- .../StructuredMapRuleTargetBuilder.java | 28 +- .../StructuredMapStructureBuilder.java | 24 +- .../cqf/dstu3/builders/ValueSetBuilder.java | 2 +- .../builders/ValueSetComposeBuilder.java | 6 +- .../builders/ValueSetIncludesBuilder.java | 6 +- .../dstu3/config/FhirServerConfigDstu3.java | 37 +- .../dstu3/evaluation/MeasureEvaluation.java | 188 ++++++---- .../evaluation/MeasureEvaluationSeed.java | 55 ++- .../cqf/dstu3/evaluation/ProviderFactory.java | 26 +- .../cqf/dstu3/helpers/FhirMeasureBundler.java | 20 +- .../cqf/dstu3/helpers/LibraryHelper.java | 64 ++-- .../ActivityDefinitionApplyProvider.java | 141 ++++---- .../providers/ApplyCqlOperationProvider.java | 78 +++-- .../providers/CacheValueSetsProvider.java | 80 ++--- .../providers/CodeSystemUpdateProvider.java | 141 ++++---- .../dstu3/providers/CqlExecutionProvider.java | 120 +++---- .../providers/DataRequirementsProvider.java | 227 ++++++------ .../providers/JpaTerminologyProvider.java | 32 +- .../providers/LibraryOperationsProvider.java | 54 ++- .../providers/MeasureOperationsProvider.java | 113 +++--- .../PlanDefinitionApplyProvider.java | 202 +++++------ .../providers/Stu3BundleRetrieveProvider.java | 244 +++++++------ .../cqf/dstu3/servlet/BaseServlet.java | 107 +++--- .../cqf/dstu3/servlet/CdsHooksServlet.java | 125 +++---- pom.xml | 90 ++--- r4/pom.xml | 5 +- .../builders/ActivityDefinitionBuilder.java | 22 +- .../cqf/r4/builders/AnnotationBuilder.java | 6 +- .../cqf/r4/builders/AttachmentBuilder.java | 6 +- .../r4/builders/CarePlanActivityBuilder.java | 10 +- .../CarePlanActivityDetailBuilder.java | 13 +- .../cqf/r4/builders/CarePlanBuilder.java | 15 +- .../r4/builders/CodeableConceptBuilder.java | 8 +- .../cqf/r4/builders/CodingBuilder.java | 2 +- .../cqf/r4/builders/ExtensionBuilder.java | 2 +- .../cqf/r4/builders/IdentifierBuilder.java | 4 +- .../cqf/r4/builders/JavaDateBuilder.java | 5 +- .../cqf/r4/builders/LibraryBuilder.java | 23 +- .../cqf/r4/builders/MeasureReportBuilder.java | 30 +- .../r4/builders/OperationOutcomeBuilder.java | 20 +- .../cqf/r4/builders/PeriodBuilder.java | 6 +- .../cqf/r4/builders/PractitionerBuilder.java | 2 +- .../cqf/r4/builders/ReferenceBuilder.java | 2 +- .../r4/builders/RelatedArtifactBuilder.java | 2 +- .../builders/RequestGroupActionBuilder.java | 15 +- .../cqf/r4/builders/RequestGroupBuilder.java | 6 +- .../r4/builders/ServiceRequestBuilder.java | 23 +- .../r4/builders/StructureMapGroupBuilder.java | 6 +- .../cqf/r4/builders/StructuredMapBuilder.java | 9 +- .../r4/builders/StructuredMapRuleBuilder.java | 14 +- .../StructuredMapRuleSourceBuilder.java | 4 +- .../StructuredMapRuleTargetBuilder.java | 28 +- .../StructuredMapStructureBuilder.java | 24 +- .../cqf/r4/builders/ValueSetBuilder.java | 4 +- .../r4/builders/ValueSetComposeBuilder.java | 6 +- .../r4/builders/ValueSetIncludesBuilder.java | 6 +- .../cqf/r4/config/FhirServerConfigR4.java | 37 +- .../cqf/r4/evaluation/MeasureEvaluation.java | 218 +++++++----- .../r4/evaluation/MeasureEvaluationSeed.java | 55 ++- .../cqf/r4/evaluation/ProviderFactory.java | 27 +- .../cqf/r4/helpers/FhirMeasureBundler.java | 20 +- .../opencds/cqf/r4/helpers/LibraryHelper.java | 52 +-- .../ActivityDefinitionApplyProvider.java | 79 +++-- .../providers/ApplyCqlOperationProvider.java | 77 +++-- .../r4/providers/CacheValueSetsProvider.java | 80 ++--- .../providers/CodeSystemUpdateProvider.java | 143 ++++---- .../r4/providers/CqlExecutionProvider.java | 167 +++++---- .../providers/DataRequirementsProvider.java | 209 +++++------ .../cqf/r4/providers/HQMFProvider.java | 327 +++++++++--------- .../r4/providers/JpaTerminologyProvider.java | 54 +-- .../providers/LibraryOperationsProvider.java | 57 ++- .../providers/MeasureOperationsProvider.java | 134 ++++--- .../PlanDefinitionApplyProvider.java | 208 ++++++----- .../opencds/cqf/r4/servlet/BaseServlet.java | 105 +++--- .../cqf/r4/servlet/CdsHooksServlet.java | 175 ++++------ 117 files changed, 2858 insertions(+), 2806 deletions(-) diff --git a/.gitignore b/.gitignore index 83c5121d9..b1dd749e8 100644 --- a/.gitignore +++ b/.gitignore @@ -162,5 +162,4 @@ core.* heapdump.* Snap.* -.flattened-pom.xml - +.flattened-pom.xml \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index c215aaf4c..e07f77c7c 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -3,7 +3,6 @@ import java.lang.reflect.InvocationTargetException; import java.sql.Driver; -import ca.uhn.fhir.jpa.model.entity.ModelConfig; import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; @@ -11,7 +10,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; @@ -32,10 +31,13 @@ public class FhirServerConfig { public FhirServerConfig() { ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); - ourLog.info("Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); + ourLog.info( + "Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); ourLog.info("Server configured to " + (this.expungeEnabled ? "enable" : "disable") + " expunges"); - ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); - ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); + ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + + " placeholder references"); + ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + + " overriding default search params"); } /** @@ -54,10 +56,11 @@ public DaoConfig daoConfig() { Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); retVal.setFetchSizeDefaultMaximum(maxFetchSize); - ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE? "'unlimited'": maxFetchSize)); + ourLog.info("Server configured to have a maximum fetch size of " + + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); Long reuseCachedSearchResultsMillis = HapiProperties.getReuseCachedSearchResultsMillis(); - retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis ); + retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis); return retVal; @@ -75,16 +78,17 @@ public ModelConfig modelConfig() { } /** - * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a - * directory called "jpaserver_derby_files". + * The following bean configures the database connection. The 'url' property + * value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates + * that the server should save resources in a directory called + * "jpaserver_derby_files". * - * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. + * A URL to a remote database could also be placed here, along with login + * credentials and other properties supported by BasicDataSource. */ @Bean(destroyMethod = "close") - public BasicDataSource dataSource() - throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, - InvocationTargetException, InstantiationException - { + public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InstantiationException { BasicDataSource retVal = new BasicDataSource(); Driver driver = (Driver) Class.forName(HapiProperties.getDataSourceDriver()).getConstructor().newInstance(); retVal.setDriver(driver); @@ -95,9 +99,9 @@ public BasicDataSource dataSource() return retVal; } - /** - * Do some fancy logging to create a nice access log that has details about each incoming request. + * Do some fancy logging to create a nice access log that has details about each + * incoming request. */ public LoggingInterceptor loggingInterceptor() { LoggingInterceptor retVal = new LoggingInterceptor(); @@ -109,7 +113,8 @@ public LoggingInterceptor loggingInterceptor() { } /** - * This interceptor adds some pretty syntax highlighting in responses when a browser is detected + * This interceptor adds some pretty syntax highlighting in responses when a + * browser is detected */ @Bean(autowire = Autowire.BY_TYPE) public ResponseHighlighterInterceptor responseHighlighterInterceptor() { diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index d587176cb..44ca4578d 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -1,14 +1,15 @@ package org.opencds.cqf.common.config; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Properties; + +import com.google.common.annotations.VisibleForTesting; + import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; -import com.google.common.annotations.VisibleForTesting; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.Properties; public class HapiProperties { static final String ALLOW_EXTERNAL_REFERENCES = "allow_external_references"; @@ -59,8 +60,8 @@ public static void forceReload() { } /** - * This is mostly here for unit tests. Use the actual properties file - * to set values + * This is mostly here for unit tests. Use the actual properties file to set + * values */ @VisibleForTesting public static void setProperty(String theKey, String theValue) { @@ -70,7 +71,7 @@ public static void setProperty(String theKey, String theValue) { public static Properties getProperties() { if (properties == null) { // Load the configurable properties file - try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)){ + try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)) { HapiProperties.properties = new Properties(); HapiProperties.properties.load(in); } catch (Exception e) { @@ -78,8 +79,8 @@ public static Properties getProperties() { } Properties overrideProps = loadOverrideProperties(); - if(overrideProps != null) { - properties.putAll(overrideProps); + if (overrideProps != null) { + properties.putAll(overrideProps); } } @@ -87,22 +88,24 @@ public static Properties getProperties() { } /** - * If a configuration file path is explicitly specified via -Dhapi.properties=, the properties there will - * be used to override the entries in the default hapi.properties file (currently under WEB-INF/classes) - * @return properties loaded from the explicitly specified configuraiton file if there is one, or null otherwise. + * If a configuration file path is explicitly specified via + * -Dhapi.properties=, the properties there will be used to override the + * entries in the default hapi.properties file (currently under WEB-INF/classes) + * + * @return properties loaded from the explicitly specified configuraiton file if + * there is one, or null otherwise. */ private static Properties loadOverrideProperties() { String confFile = System.getProperty(HAPI_PROPERTIES + "." + HapiProperties.getProperty(FHIR_VERSION)); if (confFile == null) { confFile = System.getProperty(HAPI_PROPERTIES); } - if(confFile != null) { + if (confFile != null) { try { Properties props = new Properties(); props.load(new FileInputStream(confFile)); return props; - } - catch (Exception e) { + } catch (Exception e) { throw new ConfigurationException("Could not load HAPI properties file: " + confFile, e); } } @@ -213,7 +216,8 @@ public static String getLoggerName() { } public static String getLoggerFormat() { - return HapiProperties.getProperty(LOGGER_FORMAT, "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); + return HapiProperties.getProperty(LOGGER_FORMAT, + "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); } public static String getLoggerErrorFormat() { @@ -237,7 +241,8 @@ public static Integer getDataSourceMaxPoolSize() { } public static String getDataSourceUrl() { - return HapiProperties.getProperty(DATASOURCE_URL, "jdbc:derby:directory:target/jpaserver_derby_files;create=true"); + return HapiProperties.getProperty(DATASOURCE_URL, + "jdbc:derby:directory:target/jpaserver_derby_files;create=true"); } public static String getDataSourceUsername() { diff --git a/common/src/main/java/org/opencds/cqf/common/evaluation/EvaluationProviderFactory.java b/common/src/main/java/org/opencds/cqf/common/evaluation/EvaluationProviderFactory.java index 763e103e7..6292d394b 100644 --- a/common/src/main/java/org/opencds/cqf/common/evaluation/EvaluationProviderFactory.java +++ b/common/src/main/java/org/opencds/cqf/common/evaluation/EvaluationProviderFactory.java @@ -1,7 +1,7 @@ package org.opencds.cqf.common.evaluation; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.terminology.TerminologyProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; // TODO: This interface is a partial duplicate of the provider factory interface // in the cql service layer. We need another round of refactoring to consolidate that. @@ -12,5 +12,6 @@ public interface EvaluationProviderFactory { public DataProvider createDataProvider(String model, String version, TerminologyProvider terminologyProvider); - public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass); + public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, + String pass); } \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java index 7a260d8e6..f21eee88b 100644 --- a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java +++ b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java @@ -1,15 +1,9 @@ package org.opencds.cqf.common.evaluation; -import org.cqframework.cql.cql2elm.CqlTranslator; -import org.cqframework.cql.cql2elm.CqlTranslatorException; -import org.cqframework.cql.cql2elm.LibraryManager; -import org.cqframework.cql.cql2elm.ModelManager; -import org.cqframework.cql.cql2elm.CqlTranslatorException.ErrorSeverity; -import org.cqframework.cql.cql2elm.LibraryBuilder.SignatureLevel; -import org.cqframework.cql.elm.execution.Library; -import org.cqframework.cql.elm.execution.VersionedIdentifier; +import static org.opencds.cqf.common.helpers.TranslatorHelper.errorsToString; +import static org.opencds.cqf.common.helpers.TranslatorHelper.getTranslator; +import static org.opencds.cqf.common.helpers.TranslatorHelper.readLibrary; -import javax.xml.bind.JAXBException; import java.io.ByteArrayInputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -17,9 +11,17 @@ import java.util.HashMap; import java.util.Map; -import static org.opencds.cqf.common.helpers.TranslatorHelper.*; +import javax.xml.bind.JAXBException; + +import org.cqframework.cql.cql2elm.CqlTranslator; +import org.cqframework.cql.cql2elm.CqlTranslatorException; +import org.cqframework.cql.cql2elm.CqlTranslatorOptions; +import org.cqframework.cql.cql2elm.LibraryManager; +import org.cqframework.cql.cql2elm.ModelManager; +import org.cqframework.cql.elm.execution.Library; +import org.cqframework.cql.elm.execution.VersionedIdentifier; -public class LibraryLoader implements org.opencds.cqf.cql.execution.LibraryLoader { +public class LibraryLoader implements org.opencds.cqf.cql.engine.execution.LibraryLoader { private LibraryManager libraryManager; private ModelManager modelManager; @@ -78,12 +80,8 @@ private Library loadLibrary(VersionedIdentifier libraryIdentifier) { .withVersion(libraryIdentifier.getVersion()); ArrayList errors = new ArrayList<>(); - org.hl7.elm.r1.Library translatedLibrary = libraryManager.resolveLibrary(identifier, ErrorSeverity.Error, - SignatureLevel.All, - new CqlTranslator.Options[] { CqlTranslator.Options.EnableAnnotations, - CqlTranslator.Options.EnableLocators, CqlTranslator.Options.DisableListDemotion, - CqlTranslator.Options.DisableListPromotion, CqlTranslator.Options.DisableMethodInvocation }, - errors).getLibrary(); + org.hl7.elm.r1.Library translatedLibrary = libraryManager + .resolveLibrary(identifier, CqlTranslatorOptions.defaultOptions(), errors).getLibrary(); if (CqlTranslatorException.HasErrors(errors)) { throw new IllegalArgumentException(errorsToString(errors)); diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java index 81d925d12..87c417616 100644 --- a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java +++ b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelper.java @@ -4,26 +4,20 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; public class ClientHelper { - /* TODO - depending on future needs: - 1. add OAuth - 2. change if to switch to accommodate additional FHIR versions + /* + * TODO - depending on future needs: 1. add OAuth 2. change if to switch to + * accommodate additional FHIR versions */ - private static IGenericClient getRestClient(FhirContext fhirContext, String url){ + private static IGenericClient getRestClient(FhirContext fhirContext, String url) { fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); return fhirContext.newRestfulGenericClient(url); } // Overload in case you need to specify a specific version of the context - public static IGenericClient getClient(FhirContext fhirContext, String url, String user, String password) - { + public static IGenericClient getClient(FhirContext fhirContext, String url, String user, String password) { IGenericClient client = getRestClient(fhirContext, url); registerAuth(client, user, password); diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/TranslatorHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/TranslatorHelper.java index 6a6116937..53607fa17 100644 --- a/common/src/main/java/org/opencds/cqf/common/helpers/TranslatorHelper.java +++ b/common/src/main/java/org/opencds/cqf/common/helpers/TranslatorHelper.java @@ -14,7 +14,7 @@ import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; import org.cqframework.cql.elm.tracking.TrackBack; -import org.opencds.cqf.cql.execution.CqlLibraryReader; +import org.opencds.cqf.cql.engine.execution.CqlLibraryReader; public class TranslatorHelper { @@ -30,10 +30,12 @@ public static String errorsToString(Iterable exceptions) ArrayList errors = new ArrayList<>(); for (CqlTranslatorException error : exceptions) { TrackBack tb = error.getLocator(); - String lines = tb == null ? "[n/a]" : String.format("%s[%d:%d, %d:%d]", - (tb.getLibrary() != null ? tb.getLibrary().getId() + (tb.getLibrary().getVersion() != null - ? ("-" + tb.getLibrary().getVersion()) : "") : ""), - tb.getStartLine(), tb.getStartChar(), tb.getEndLine(), tb.getEndChar()); + String lines = tb == null ? "[n/a]" + : String.format("%s[%d:%d, %d:%d]", + (tb.getLibrary() != null ? tb.getLibrary().getId() + + (tb.getLibrary().getVersion() != null ? ("-" + tb.getLibrary().getVersion()) : "") + : ""), + tb.getStartLine(), tb.getStartChar(), tb.getEndLine(), tb.getEndChar()); errors.add(lines + error.getMessage()); } @@ -41,10 +43,12 @@ public static String errorsToString(Iterable exceptions) } public static CqlTranslator getTranslator(String cql, LibraryManager libraryManager, ModelManager modelManager) { - return getTranslator(new ByteArrayInputStream(cql.getBytes(StandardCharsets.UTF_8)), libraryManager, modelManager); + return getTranslator(new ByteArrayInputStream(cql.getBytes(StandardCharsets.UTF_8)), libraryManager, + modelManager); } - public static CqlTranslator getTranslator(InputStream cqlStream, LibraryManager libraryManager, ModelManager modelManager) { + public static CqlTranslator getTranslator(InputStream cqlStream, LibraryManager libraryManager, + ModelManager modelManager) { ArrayList options = new ArrayList<>(); options.add(CqlTranslator.Options.EnableAnnotations); options.add(CqlTranslator.Options.EnableLocators); @@ -56,17 +60,20 @@ public static CqlTranslator getTranslator(InputStream cqlStream, LibraryManager translator = CqlTranslator.fromStream(cqlStream, modelManager, libraryManager, options.toArray(new CqlTranslator.Options[options.size()])); } catch (IOException e) { - throw new IllegalArgumentException(String.format("Errors occurred translating library: %s", e.getMessage())); + throw new IllegalArgumentException( + String.format("Errors occurred translating library: %s", e.getMessage())); } - + return translator; } public static Library translateLibrary(String cql, LibraryManager libraryManager, ModelManager modelManager) { - return translateLibrary(new ByteArrayInputStream(cql.getBytes(StandardCharsets.UTF_8)), libraryManager, modelManager); + return translateLibrary(new ByteArrayInputStream(cql.getBytes(StandardCharsets.UTF_8)), libraryManager, + modelManager); } - public static Library translateLibrary(InputStream cqlStream, LibraryManager libraryManager, ModelManager modelManager) { + public static Library translateLibrary(InputStream cqlStream, LibraryManager libraryManager, + ModelManager modelManager) { CqlTranslator translator = getTranslator(cqlStream, libraryManager, modelManager); return readLibrary(new ByteArrayInputStream(translator.toXml().getBytes(StandardCharsets.UTF_8))); } diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java index 9d5125961..ce8377bf2 100644 --- a/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java +++ b/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java @@ -7,31 +7,35 @@ import java.util.Map; import org.apache.commons.lang3.tuple.Triple; -import org.cqframework.cql.elm.execution.UsingDef; import org.cqframework.cql.elm.execution.Library.Usings; +import org.cqframework.cql.elm.execution.UsingDef; public class UsingHelper { - private static Map urlsByModelName = new HashMap () {{ + private static Map urlsByModelName = new HashMap() { + { put("FHIR", "http://hl7.org/fhir"); put("QDM", "urn:healthit-gov:qdm:v5_4"); - }}; + } + }; - // Returns a list of (Model, Version, Url) for the usings in library. The "System" using is excluded. - public static List> getUsingUrlAndVersion(Usings usings) { + // Returns a list of (Model, Version, Url) for the usings in library. The + // "System" using is excluded. + public static List> getUsingUrlAndVersion(Usings usings) { if (usings == null || usings.getDef() == null) { return Collections.emptyList(); } - List> usingDefs = new ArrayList<>(); + List> usingDefs = new ArrayList<>(); for (UsingDef def : usings.getDef()) { - if (def.getLocalIdentifier().equals("System")) continue; + if (def.getLocalIdentifier().equals("System")) + continue; - usingDefs.add(Triple.of(def.getLocalIdentifier(), def.getVersion(), urlsByModelName.get(def.getLocalIdentifier()))); + usingDefs.add(Triple.of(def.getLocalIdentifier(), def.getVersion(), + urlsByModelName.get(def.getLocalIdentifier()))); } return usingDefs; } } - diff --git a/common/src/main/java/org/opencds/cqf/common/narrative/JarEnabledCustomThymeleafNarrativeGenerator.java b/common/src/main/java/org/opencds/cqf/common/narrative/JarEnabledCustomThymeleafNarrativeGenerator.java index d88836bf5..251b80a6d 100644 --- a/common/src/main/java/org/opencds/cqf/common/narrative/JarEnabledCustomThymeleafNarrativeGenerator.java +++ b/common/src/main/java/org/opencds/cqf/common/narrative/JarEnabledCustomThymeleafNarrativeGenerator.java @@ -5,8 +5,8 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -16,10 +16,7 @@ import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; -import java.util.Arrays; - import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative2.NarrativeTemplateManifest; import ca.uhn.fhir.narrative2.ThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -27,23 +24,23 @@ public class JarEnabledCustomThymeleafNarrativeGenerator extends ThymeleafNarrativeGenerator { private List myPropertyFile; - public JarEnabledCustomThymeleafNarrativeGenerator(String... thePropertyFile) { + public JarEnabledCustomThymeleafNarrativeGenerator(String... thePropertyFile) { super(); setPropertyFile(thePropertyFile); - } + } - private boolean myInitialized; + private boolean myInitialized; - @Override + @Override public boolean populateResourceNarrative(FhirContext theFhirContext, IBaseResource theResource) { if (!myInitialized) { initialize(); } super.populateResourceNarrative(theFhirContext, theResource); return false; - } - - private synchronized void initialize() { + } + + private synchronized void initialize() { if (myInitialized) { return; } @@ -57,24 +54,26 @@ private synchronized void initialize() { } myInitialized = true; - } - - public static NarrativeTemplateManifest forManifestFileLocation(Collection thePropertyFilePaths) throws IOException { + } + + public static NarrativeTemplateManifest forManifestFileLocation(Collection thePropertyFilePaths) + throws IOException { List manifestFileContents = new ArrayList<>(thePropertyFilePaths.size()); for (String next : thePropertyFilePaths) { String resource = loadResourceAlsoFromJar(next); manifestFileContents.add(resource); } - return NarrativeTemplateManifest.forManifestFileContents(manifestFileContents); - } - - static String loadResourceAlsoFromJar(String name) throws IOException { + return NarrativeTemplateManifest.forManifestFileContents(manifestFileContents); + } + + static String loadResourceAlsoFromJar(String name) throws IOException { if (name.startsWith("classpath:")) { String cpName = name.substring("classpath:".length()); - try (InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(cpName)) { + try (InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(cpName)) { if (resource == null) { - try (InputStream resource2 =Thread.currentThread().getContextClassLoader().getResourceAsStream("/" + cpName)) { + try (InputStream resource2 = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("/" + cpName)) { if (resource2 == null) { throw new IOException("Can not find '" + cpName + "' on classpath"); } @@ -93,16 +92,16 @@ static String loadResourceAlsoFromJar(String name) throws IOException { } } else if (name.startsWith("jar:")) { URL url = new URL(name); - try (InputStream resource = url.openStream()) { + try (InputStream resource = url.openStream()) { if (resource == null) { throw new IOException("Can not find jar url '" + name + "'"); } return IOUtils.toString(resource, Charsets.UTF_8); } - } - else { - throw new IOException("Invalid resource name: '" + name + "' (must start with classpath: or file: or jar: )"); - } + } else { + throw new IOException( + "Invalid resource name: '" + name + "' (must start with classpath: or file: or jar: )"); + } } public void setPropertyFile(String... thePropertyFile) { diff --git a/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java index 5759e6a23..06179d736 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/Dstu3ApelonFhirTerminologyProvider.java @@ -1,96 +1,86 @@ package org.opencds.cqf.common.providers; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent; -import org.opencds.cqf.cql.runtime.Code; -import org.opencds.cqf.cql.terminology.ValueSetInfo; -import org.opencds.cqf.cql.terminology.fhir.Dstu3FhirTerminologyProvider; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.opencds.cqf.cql.engine.fhir.terminology.Dstu3FhirTerminologyProvider; +import org.opencds.cqf.cql.engine.runtime.Code; +import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; +import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -public class Dstu3ApelonFhirTerminologyProvider extends Dstu3FhirTerminologyProvider - { - private Map> cache = new HashMap<>(); +public class Dstu3ApelonFhirTerminologyProvider extends Dstu3FhirTerminologyProvider { + private Map> cache = new HashMap<>(); + + public Dstu3ApelonFhirTerminologyProvider() { + super(); + } - public Dstu3ApelonFhirTerminologyProvider() { - super(); + public Dstu3ApelonFhirTerminologyProvider(IGenericClient fhirClient) { + super(fhirClient); + } + + @Override + public Iterable expand(ValueSetInfo valueSet) throws ResourceNotFoundException { + String id = valueSet.getId(); + if (this.cache.containsKey(id)) { + return this.cache.get(id); } - public Dstu3ApelonFhirTerminologyProvider(IGenericClient fhirClient) { - super(fhirClient); + String url = this.resolveByIdentifier(valueSet); + + Parameters respParam = this.getFhirClient().operation().onType(ValueSet.class).named("expand") + .withSearchParameter(Parameters.class, "url", new StringParam(url)) + .andSearchParameter("includeDefinition", new StringParam("true")).useHttpGet().execute(); + + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + List codes = new ArrayList<>(); + for (ValueSet.ValueSetExpansionContainsComponent codeInfo : expanded.getExpansion().getContains()) { + Code nextCode = new Code().withCode(codeInfo.getCode()).withSystem(codeInfo.getSystem()) + .withVersion(codeInfo.getVersion()).withDisplay(codeInfo.getDisplay()); + codes.add(nextCode); } - @Override - public Iterable expand(ValueSetInfo valueSet) throws ResourceNotFoundException { - String id = valueSet.getId(); - if (this.cache.containsKey(id)) { - return this.cache.get(id); - } + this.cache.put(id, codes); + return codes; + } - String url = this.resolveByIdentifier(valueSet); - - Parameters respParam = this.getFhirClient() - .operation() - .onType(ValueSet.class) - .named("expand") - .withSearchParameter(Parameters.class, "url", new StringParam(url)) - .andSearchParameter("includeDefinition", new StringParam("true")) - .useHttpGet() - .execute(); - - ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); - List codes = new ArrayList<>(); - for (ValueSet.ValueSetExpansionContainsComponent codeInfo : expanded.getExpansion().getContains()) { - Code nextCode = new Code() - .withCode(codeInfo.getCode()) - .withSystem(codeInfo.getSystem()) - .withVersion(codeInfo.getVersion()) - .withDisplay(codeInfo.getDisplay()); - codes.add(nextCode); - } + public String resolveByIdentifier(ValueSetInfo valueSet) { + String valueSetId = valueSet.getId(); - this.cache.put(id, codes); - return codes; + // Turns out we got a FHIR url. Let's use that. + if (valueSetId.startsWith("http")) { + return valueSetId; } - public String resolveByIdentifier(ValueSetInfo valueSet) { - String valueSetId = valueSet.getId(); + valueSetId = valueSetId.replace("urn:oid:", ""); - // Turns out we got a FHIR url. Let's use that. - if (valueSetId.startsWith("http")) { - return valueSetId; - } - - valueSetId = valueSetId.replace("urn:oid:", ""); - - IQuery bundleQuery = this.getFhirClient() - .search() - .byUrl("ValueSet?identifier=" + valueSetId) - .returnBundle(Bundle.class) - .accept("application/fhir+xml"); - - Bundle searchResults = bundleQuery.execute(); - - if (searchResults.hasEntry()) { - for (BundleEntryComponent bec : searchResults.getEntry()) { - if (bec.hasResource()) { - String id = bec.getResource().getIdElement().getIdPart(); - if (id.equals(valueSetId)) { - return ((ValueSet)bec.getResource()).getUrl(); - } - } + IQuery bundleQuery = this.getFhirClient().search().byUrl("ValueSet?identifier=" + valueSetId) + .returnBundle(Bundle.class).accept("application/fhir+xml"); + + Bundle searchResults = bundleQuery.execute(); + if (searchResults.hasEntry()) { + for (BundleEntryComponent bec : searchResults.getEntry()) { + if (bec.hasResource()) { + String id = bec.getResource().getIdElement().getIdPart(); + if (id.equals(valueSetId)) { + return ((ValueSet) bec.getResource()).getUrl(); + } } - } - return null; + } } - } \ No newline at end of file + + return null; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java index 5fd699f8d..bca9d169f 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java @@ -1,11 +1,11 @@ package org.opencds.cqf.common.providers; +import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.Function; -import java.util.List; import java.util.stream.Collectors; -import java.util.Collection; public class InMemoryLibraryResourceProvider implements LibraryResolutionProvider { @@ -14,9 +14,12 @@ public class InMemoryLibraryResourceProvider implements LibraryReso private Function getName; private Function getVersion; - public InMemoryLibraryResourceProvider() {}; + public InMemoryLibraryResourceProvider() { + }; - public InMemoryLibraryResourceProvider(Collection initialLibraries, Function getId, Function getName, Function getVersion) { + public InMemoryLibraryResourceProvider(Collection initialLibraries, + Function getId, Function getName, + Function getVersion) { this.getId = getId; this.getName = getName; @@ -29,7 +32,7 @@ public InMemoryLibraryResourceProvider(Collection initialLibraries, @Override public LibraryType resolveLibraryById(String libraryId) { - if (this.libraries.containsKey(libraryId)){ + if (this.libraries.containsKey(libraryId)) { return this.libraries.get(libraryId); } @@ -38,7 +41,8 @@ public LibraryType resolveLibraryById(String libraryId) { @Override public LibraryType resolveLibraryByName(String libraryName, String libraryVersion) { - List libraries = this.libraries.values().stream().filter(x -> this.getName.apply(x).equals(libraryName)).collect(Collectors.toList()); + List libraries = this.libraries.values().stream() + .filter(x -> this.getName.apply(x).equals(libraryName)).collect(Collectors.toList()); LibraryType library = LibraryResolutionProvider.selectFromList(libraries, libraryVersion, this.getVersion); if (library == null) { @@ -51,6 +55,6 @@ public LibraryType resolveLibraryByName(String libraryName, String libraryVersio @Override public void update(LibraryType library) { this.libraries.put(this.getId.apply(library), library); - } + } } \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/providers/LibrarySourceProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/LibrarySourceProvider.java index 1deb3cddb..5196d04f2 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/LibrarySourceProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/LibrarySourceProvider.java @@ -1,17 +1,17 @@ package org.opencds.cqf.common.providers; -import org.cqframework.cql.cql2elm.FhirLibrarySourceProvider; -import org.hl7.elm.r1.VersionedIdentifier; -import org.opencds.cqf.common.providers.LibraryResolutionProvider; - import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.function.Function; +import org.cqframework.cql.cql2elm.FhirLibrarySourceProvider; +import org.hl7.elm.r1.VersionedIdentifier; + /** * Created by Christopher on 1/12/2017. */ -public class LibrarySourceProvider implements org.cqframework.cql.cql2elm.LibrarySourceProvider { +public class LibrarySourceProvider + implements org.cqframework.cql.cql2elm.LibrarySourceProvider { private FhirLibrarySourceProvider innerProvider; private LibraryResolutionProvider provider; @@ -19,11 +19,10 @@ public class LibrarySourceProvider implements org.c private Function getContentType; private Function getContent; - public LibrarySourceProvider(LibraryResolutionProvider provider, - Function> getAttachments, - Function getContentType, - Function getContent) { - + public LibrarySourceProvider(LibraryResolutionProvider provider, + Function> getAttachments, + Function getContentType, Function getContent) { + this.innerProvider = new FhirLibrarySourceProvider(); this.provider = provider; @@ -35,14 +34,15 @@ public LibrarySourceProvider(LibraryResolutionProvider provider, @Override public InputStream getLibrarySource(VersionedIdentifier versionedIdentifier) { try { - LibraryType lib = this.provider.resolveLibraryByName(versionedIdentifier.getId(), versionedIdentifier.getVersion()); + LibraryType lib = this.provider.resolveLibraryByName(versionedIdentifier.getId(), + versionedIdentifier.getVersion()); for (AttachmentType attachment : this.getAttachments.apply(lib)) { if (this.getContentType.apply(attachment).equals("text/cql")) { return new ByteArrayInputStream(this.getContent.apply(attachment)); } } + } catch (Exception e) { } - catch(Exception e){} return this.innerProvider.getLibrarySource(versionedIdentifier); } diff --git a/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java index 56fedc374..4acd21a8c 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/R4ApelonFhirTerminologyProvider.java @@ -1,96 +1,86 @@ package org.opencds.cqf.common.providers; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; -import org.opencds.cqf.cql.runtime.Code; -import org.opencds.cqf.cql.terminology.ValueSetInfo; -import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.ValueSet; +import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; +import org.opencds.cqf.cql.engine.runtime.Code; +import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; +import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.gclient.IQuery; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -public class R4ApelonFhirTerminologyProvider extends R4FhirTerminologyProvider - { - private Map> cache = new HashMap<>(); +public class R4ApelonFhirTerminologyProvider extends R4FhirTerminologyProvider { + private Map> cache = new HashMap<>(); + + public R4ApelonFhirTerminologyProvider() { + super(); + } - public R4ApelonFhirTerminologyProvider() { - super(); + public R4ApelonFhirTerminologyProvider(IGenericClient fhirClient) { + super(fhirClient); + } + + @Override + public Iterable expand(ValueSetInfo valueSet) throws ResourceNotFoundException { + String id = valueSet.getId(); + if (this.cache.containsKey(id)) { + return this.cache.get(id); } - public R4ApelonFhirTerminologyProvider(IGenericClient fhirClient) { - super(fhirClient) ; + String url = this.resolveByIdentifier(valueSet); + + Parameters respParam = this.getFhirClient().operation().onType(ValueSet.class).named("expand") + .withSearchParameter(Parameters.class, "url", new StringParam(url)) + .andSearchParameter("includeDefinition", new StringParam("true")).useHttpGet().execute(); + + ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); + List codes = new ArrayList<>(); + for (ValueSet.ValueSetExpansionContainsComponent codeInfo : expanded.getExpansion().getContains()) { + Code nextCode = new Code().withCode(codeInfo.getCode()).withSystem(codeInfo.getSystem()) + .withVersion(codeInfo.getVersion()).withDisplay(codeInfo.getDisplay()); + codes.add(nextCode); } - @Override - public Iterable expand(ValueSetInfo valueSet) throws ResourceNotFoundException { - String id = valueSet.getId(); - if (this.cache.containsKey(id)) { - return this.cache.get(id); - } + this.cache.put(id, codes); + return codes; + } - String url = this.resolveByIdentifier(valueSet); - - Parameters respParam = this.getFhirClient() - .operation() - .onType(ValueSet.class) - .named("expand") - .withSearchParameter(Parameters.class, "url", new StringParam(url)) - .andSearchParameter("includeDefinition", new StringParam("true")) - .useHttpGet() - .execute(); - - ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); - List codes = new ArrayList<>(); - for (ValueSet.ValueSetExpansionContainsComponent codeInfo : expanded.getExpansion().getContains()) { - Code nextCode = new Code() - .withCode(codeInfo.getCode()) - .withSystem(codeInfo.getSystem()) - .withVersion(codeInfo.getVersion()) - .withDisplay(codeInfo.getDisplay()); - codes.add(nextCode); - } + public String resolveByIdentifier(ValueSetInfo valueSet) { + String valueSetId = valueSet.getId(); - this.cache.put(id, codes); - return codes; + // Turns out we got a FHIR url. Let's use that. + if (valueSetId.startsWith("http")) { + return valueSetId; } - public String resolveByIdentifier(ValueSetInfo valueSet) { - String valueSetId = valueSet.getId(); + valueSetId = valueSetId.replace("urn:oid:", ""); - // Turns out we got a FHIR url. Let's use that. - if (valueSetId.startsWith("http")) { - return valueSetId; - } - - valueSetId = valueSetId.replace("urn:oid:", ""); - - IQuery bundleQuery = this.getFhirClient() - .search() - .byUrl("ValueSet?identifier=" + valueSetId) - .returnBundle(Bundle.class) - .accept("application/fhir+xml"); - - Bundle searchResults = bundleQuery.execute(); - - if (searchResults.hasEntry()) { - for (BundleEntryComponent bec : searchResults.getEntry()) { - if (bec.hasResource()) { - String id = bec.getResource().getIdElement().getIdPart(); - if (id.equals(valueSetId)) { - return ((ValueSet)bec.getResource()).getUrl(); - } - } + IQuery bundleQuery = this.getFhirClient().search().byUrl("ValueSet?identifier=" + valueSetId) + .returnBundle(Bundle.class).accept("application/fhir+xml"); + + Bundle searchResults = bundleQuery.execute(); + if (searchResults.hasEntry()) { + for (BundleEntryComponent bec : searchResults.getEntry()) { + if (bec.hasResource()) { + String id = bec.getResource().getIdElement().getIdPart(); + if (id.equals(valueSetId)) { + return ((ValueSet) bec.getResource()).getUrl(); + } } - } - return null; + } } - } \ No newline at end of file + + return null; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index 1f6fa0aa0..a44c01daf 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -1,34 +1,35 @@ package org.opencds.cqf.common.retrieve; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.opencds.cqf.cql.retrieve.*; -import org.opencds.cqf.cql.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.retrieve.SearchParamFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterMap; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; public class JpaFhirRetrieveProvider extends SearchParamFhirRetrieveProvider { - + DaoRegistry registry; - public JpaFhirRetrieveProvider(DaoRegistry registry, SearchParameterResolver searchParameterResolver) { - super (searchParameterResolver); - this.registry = registry; - } + public JpaFhirRetrieveProvider(DaoRegistry registry, SearchParameterResolver searchParameterResolver) { + super(searchParameterResolver); + this.registry = registry; + } - @Override - protected Iterable executeQueries(String dataType, List queries) { + @Override + protected Iterable executeQueries(String dataType, List queries) { if (queries == null || queries.isEmpty()) { return Collections.emptyList(); } - + List objects = new ArrayList<>(); for (SearchParameterMap map : queries) { objects.addAll(executeQuery(dataType, map)); @@ -38,10 +39,27 @@ protected Iterable executeQueries(String dataType, List executeQuery(String dataType, SearchParameterMap map) { + // TODO: Once HAPI breaks this out from the server dependencies + // we can include it on its own. + var hapiMap = new ca.uhn.fhir.jpa.searchparam.SearchParameterMap(); + try { + + var method = List.of(hapiMap.getClass().getMethods()).stream() + .filter(x -> x.getName().equals("put") && Modifier.isPrivate(x.getModifiers())).findFirst().get(); + method.setAccessible(true); + + for (var entry : map.entrySet()) { + method.invoke(hapiMap, entry.getKey(), entry.getValue()); + } + + } catch (Exception e) { + // TODO: Add logging. + } + IFhirResourceDao dao = this.registry.getResourceDao(dataType); - IBundleProvider bundleProvider = dao.search(map); - if (bundleProvider.size() == null) - { + + IBundleProvider bundleProvider = dao.search(hapiMap); + if (bundleProvider.size() == null) { return resolveResourceList(bundleProvider.getResources(0, 10000)); } if (bundleProvider.size() == 0) { diff --git a/cqf-ruler-dstu3/pom.xml b/cqf-ruler-dstu3/pom.xml index 35e26df1b..5e5e879d6 100644 --- a/cqf-ruler-dstu3/pom.xml +++ b/cqf-ruler-dstu3/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -46,21 +47,21 @@ - org.eclipse.jetty - jetty-maven-plugin - ${jetty_version} - - - - org.eclipse.jetty.server.Request.maxFormContentSize - -1 - - - - /cqf-ruler-dstu3 - true - - + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + + org.eclipse.jetty.server.Request.maxFormContentSize + -1 + + + + /cqf-ruler-dstu3 + true + + diff --git a/cqf-ruler-r4/pom.xml b/cqf-ruler-r4/pom.xml index da7f0e11f..9c98a50c2 100644 --- a/cqf-ruler-r4/pom.xml +++ b/cqf-ruler-r4/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -46,21 +47,21 @@ - org.eclipse.jetty - jetty-maven-plugin - ${jetty_version} - - - - org.eclipse.jetty.server.Request.maxFormContentSize - -1 - - - - /cqf-ruler-r4 - true - - + org.eclipse.jetty + jetty-maven-plugin + ${jetty.version} + + + + org.eclipse.jetty.server.Request.maxFormContentSize + -1 + + + + /cqf-ruler-r4 + true + + diff --git a/dstu3/pom.xml b/dstu3/pom.xml index 5c544a190..9d470f157 100644 --- a/dstu3/pom.xml +++ b/dstu3/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -11,15 +12,15 @@ dstu3 - - org.opencds.cqf - common - ${project.version} - + + org.opencds.cqf + common + ${project.version} + org.opencds.cqf tooling - ${cqf_tooling_version} + ${cqf-tooling.version} diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ActivityDefinitionBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ActivityDefinitionBuilder.java index 06b79da66..b27cede80 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ActivityDefinitionBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ActivityDefinitionBuilder.java @@ -1,23 +1,24 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.ActivityDefinition; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Enumerations; +import org.opencds.cqf.common.builders.BaseBuilder; public class ActivityDefinitionBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. public ActivityDefinitionBuilder() { super(new ActivityDefinition()); } - public ActivityDefinitionBuilder(ActivityDefinition activityDefinition ) { + public ActivityDefinitionBuilder(ActivityDefinition activityDefinition) { super(activityDefinition); } - public ActivityDefinitionBuilder buildIdentification(String url, String version, Enumerations.PublicationStatus status) { + public ActivityDefinitionBuilder buildIdentification(String url, String version, + Enumerations.PublicationStatus status) { complexProperty.setUrl(url); complexProperty.setVersion(version); complexProperty.setStatus(status); @@ -71,7 +72,8 @@ public ActivityDefinitionBuilder buildBodySite(Coding coding) { return this; } - public ActivityDefinitionBuilder buildDynamicValue(String description, String path, String language, String expression) { + public ActivityDefinitionBuilder buildDynamicValue(String description, String path, String language, + String expression) { ActivityDefinition.ActivityDefinitionDynamicValueComponent dynamicValueComponent = new ActivityDefinition.ActivityDefinitionDynamicValueComponent(); dynamicValueComponent.setDescription(description); dynamicValueComponent.setLanguage(language); @@ -81,7 +83,7 @@ public ActivityDefinitionBuilder buildDynamicValue(String description, String pa return this; } - public ActivityDefinitionBuilder buildCqlDynamicValue(String description, String path, String expression ) { + public ActivityDefinitionBuilder buildCqlDynamicValue(String description, String path, String expression) { return this.buildDynamicValue(description, path, "text/cql", expression); } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AnnotationBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AnnotationBuilder.java index 08292c6fc..f9800feeb 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AnnotationBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AnnotationBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.Date; + import org.hl7.fhir.dstu3.model.Annotation; import org.hl7.fhir.dstu3.model.Type; - -import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; public class AnnotationBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AttachmentBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AttachmentBuilder.java index cd7bf7b19..b561d5653 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AttachmentBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/AttachmentBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.List; + import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.Extension; - -import java.util.List; +import org.opencds.cqf.common.builders.BaseBuilder; public class AttachmentBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityBuilder.java index 4c0dc7c56..465e2bb2f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityBuilder.java @@ -1,11 +1,15 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.*; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.dstu3.model.Annotation; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanActivityBuilder extends BaseBuilder { public CarePlanActivityBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityDetailBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityDetailBuilder.java index 8e20944fe..66cd8d7ee 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityDetailBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanActivityDetailBuilder.java @@ -1,12 +1,16 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.exceptions.FHIRException; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.SimpleQuantity; +import org.hl7.fhir.dstu3.model.Type; +import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanActivityDetailBuilder extends BaseBuilder { public CarePlanActivityDetailBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanBuilder.java index f58f006fc..bce9baa5a 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CarePlanBuilder.java @@ -1,12 +1,18 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.exceptions.FHIRException; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.dstu3.model.Annotation; +import org.hl7.fhir.dstu3.model.CarePlan; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Identifier; +import org.hl7.fhir.dstu3.model.Period; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanBuilder extends BaseBuilder { public CarePlanBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodeableConceptBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodeableConceptBuilder.java index e155c90f7..c7bbea5b3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodeableConceptBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodeableConceptBuilder.java @@ -1,12 +1,12 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.CodeableConcept; -import org.hl7.fhir.dstu3.model.Coding; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CodeableConceptBuilder extends BaseBuilder { public CodeableConceptBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodingBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodingBuilder.java index 65fc8b05f..81725cb0b 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodingBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/CodingBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Coding; +import org.opencds.cqf.common.builders.BaseBuilder; public class CodingBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ExtensionBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ExtensionBuilder.java index 2ccdff7ee..cb812b094 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ExtensionBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ExtensionBuilder.java @@ -1,10 +1,9 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.Type; +import org.opencds.cqf.common.builders.BaseBuilder; public class ExtensionBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/IdentifierBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/IdentifierBuilder.java index b3242c0a8..70cc6d666 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/IdentifierBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/IdentifierBuilder.java @@ -1,11 +1,11 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Identifier; import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class IdentifierBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/JavaDateBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/JavaDateBuilder.java index 0baf78e16..5af82cd37 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/JavaDateBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/JavaDateBuilder.java @@ -1,9 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.opencds.cqf.cql.runtime.DateTime; import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; +import org.opencds.cqf.cql.engine.runtime.DateTime; + public class JavaDateBuilder extends BaseBuilder { public JavaDateBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/LibraryBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/LibraryBuilder.java index 008261578..d664ff27c 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/LibraryBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/LibraryBuilder.java @@ -1,21 +1,22 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.io.UnsupportedEncodingException; + import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.Library; import org.hl7.fhir.dstu3.model.codesystems.LibraryType; - -import java.io.UnsupportedEncodingException; +import org.opencds.cqf.common.builders.BaseBuilder; public class LibraryBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. - public LibraryBuilder(Library library ){ + public LibraryBuilder(Library library) { super(library); } + public LibraryBuilder() { this(new Library()); } @@ -41,12 +42,10 @@ public LibraryBuilder buildExperimental(boolean experimental) { } public LibraryBuilder buildType(LibraryType libraryType) { - CodeableConcept codeableConcept = - new CodeableConceptBuilder().buildCoding( - new CodingBuilder() - .buildCode(libraryType.getSystem(), libraryType.toCode(), libraryType.getDisplay()) - .build() - ).build(); + CodeableConcept codeableConcept = new CodeableConceptBuilder() + .buildCoding(new CodingBuilder() + .buildCode(libraryType.getSystem(), libraryType.toCode(), libraryType.getDisplay()).build()) + .build(); complexProperty.setType(codeableConcept); return this; @@ -55,7 +54,7 @@ public LibraryBuilder buildType(LibraryType libraryType) { public LibraryBuilder buildCqlContent(String cqlString) throws UnsupportedEncodingException { Attachment attachment = new Attachment(); attachment.setContentType("text/cql"); - byte[] cqlData =cqlString.getBytes("utf-8"); + byte[] cqlData = cqlString.getBytes("utf-8"); attachment.setData(cqlData); complexProperty.addContent(attachment); return this; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/MeasureReportBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/MeasureReportBuilder.java index dccab8af0..fd10b36fe 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/MeasureReportBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/MeasureReportBuilder.java @@ -1,13 +1,13 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.Date; + import org.hl7.fhir.dstu3.model.MeasureReport; import org.hl7.fhir.dstu3.model.Period; import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.exceptions.FHIRException; -import org.opencds.cqf.cql.runtime.Interval; - -import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; +import org.opencds.cqf.cql.engine.runtime.Interval; public class MeasureReportBuilder extends BaseBuilder { @@ -41,11 +41,7 @@ public MeasureReportBuilder buildPatientReference(String patientRef) { } public MeasureReportBuilder buildPeriod(Interval period) { - this.complexProperty.setPeriod( - new Period() - .setStart((Date) period.getStart()) - .setEnd((Date) period.getEnd()) - ); + this.complexProperty.setPeriod(new Period().setStart((Date) period.getStart()).setEnd((Date) period.getEnd())); return this; } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/OperationOutcomeBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/OperationOutcomeBuilder.java index 14e10667d..f3141ea12 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/OperationOutcomeBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/OperationOutcomeBuilder.java @@ -1,24 +1,18 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.CodeableConcept; import org.hl7.fhir.dstu3.model.OperationOutcome; +import org.opencds.cqf.common.builders.BaseBuilder; -public class OperationOutcomeBuilder extends BaseBuilder -{ - public OperationOutcomeBuilder() - { +public class OperationOutcomeBuilder extends BaseBuilder { + public OperationOutcomeBuilder() { super(new OperationOutcome()); } - public OperationOutcomeBuilder buildIssue(String severity, String code, String details) - { - complexProperty.addIssue( - new OperationOutcome.OperationOutcomeIssueComponent() - .setSeverity(OperationOutcome.IssueSeverity.fromCode(severity)) - .setCode(OperationOutcome.IssueType.fromCode(code)) - .setDetails(new CodeableConcept().setText(details)) - ); + public OperationOutcomeBuilder buildIssue(String severity, String code, String details) { + complexProperty.addIssue(new OperationOutcome.OperationOutcomeIssueComponent() + .setSeverity(OperationOutcome.IssueSeverity.fromCode(severity)) + .setCode(OperationOutcome.IssueType.fromCode(code)).setDetails(new CodeableConcept().setText(details))); return this; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PeriodBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PeriodBuilder.java index 78551a44d..ff188fc36 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PeriodBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PeriodBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.Period; - import java.util.Date; +import org.hl7.fhir.dstu3.model.Period; +import org.opencds.cqf.common.builders.BaseBuilder; + public class PeriodBuilder extends BaseBuilder { public PeriodBuilder() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PractitionerBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PractitionerBuilder.java index 823fba4a0..ac9206611 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PractitionerBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/PractitionerBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Practitioner; +import org.opencds.cqf.common.builders.BaseBuilder; public class PractitionerBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ProcedureRequestBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ProcedureRequestBuilder.java index 8c4f0171c..9c81bb3a2 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ProcedureRequestBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ProcedureRequestBuilder.java @@ -1,24 +1,23 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Coding; import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.ProcedureRequest; import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class ProcedureRequestBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. public ProcedureRequestBuilder() { - this( new ProcedureRequest()); + this(new ProcedureRequest()); } public ProcedureRequestBuilder(ProcedureRequest complexProperty) { super(complexProperty); } - public ProcedureRequestBuilder buildId(String id) { complexProperty.setId(id); return this; @@ -40,12 +39,8 @@ public ProcedureRequestBuilder buildStatus(String status) { } public ProcedureRequestBuilder buildCode(Coding coding) { - complexProperty.setCode( - new CodeableConceptBuilder() - .buildCoding(coding) - .build() - ); - return this; + complexProperty.setCode(new CodeableConceptBuilder().buildCoding(coding).build()); + return this; } public ProcedureRequestBuilder buildIntent(ProcedureRequest.ProcedureRequestIntent intent) { @@ -79,22 +74,13 @@ public ProcedureRequestBuilder buildPriority(String priority) { } public ProcedureRequestBuilder buildSubject(String subject) { - complexProperty.setSubject( - new ReferenceBuilder() - .buildReference(subject) - .build() - ); + complexProperty.setSubject(new ReferenceBuilder().buildReference(subject).build()); return this; } public ProcedureRequestBuilder buildRequester(String requester) { - ProcedureRequest.ProcedureRequestRequesterComponent requestRequesterComponent - = new ProcedureRequest.ProcedureRequestRequesterComponent(); - requestRequesterComponent.setAgent( - new ReferenceBuilder() - .buildReference(requester) - .build() - ); + ProcedureRequest.ProcedureRequestRequesterComponent requestRequesterComponent = new ProcedureRequest.ProcedureRequestRequesterComponent(); + requestRequesterComponent.setAgent(new ReferenceBuilder().buildReference(requester).build()); complexProperty.setRequester(requestRequesterComponent); return this; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ReferenceBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ReferenceBuilder.java index 2e671afb2..e29221903 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ReferenceBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ReferenceBuilder.java @@ -1,8 +1,8 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Identifier; import org.hl7.fhir.dstu3.model.Reference; +import org.opencds.cqf.common.builders.BaseBuilder; public class ReferenceBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RelatedArtifactBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RelatedArtifactBuilder.java index 167af1ec2..47ca67af4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RelatedArtifactBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RelatedArtifactBuilder.java @@ -1,9 +1,9 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.RelatedArtifact; import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class RelatedArtifactBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupActionBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupActionBuilder.java index 52e2b4335..7e89a2ed1 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupActionBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupActionBuilder.java @@ -1,11 +1,17 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.*; - import java.util.Collections; import java.util.List; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.RelatedArtifact; +import org.hl7.fhir.dstu3.model.RequestGroup; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; +import org.opencds.cqf.common.builders.BaseBuilder; + public class RequestGroupActionBuilder extends BaseBuilder { public RequestGroupActionBuilder() { @@ -50,7 +56,8 @@ public RequestGroupActionBuilder buildResourceTarget(Resource resource) { } public RequestGroupActionBuilder buildExtension(String extension) { - complexProperty.setExtension(Collections.singletonList(new Extension().setUrl("http://example.org").setValue(new StringType(extension)))); + complexProperty.setExtension(Collections + .singletonList(new Extension().setUrl("http://example.org").setValue(new StringType(extension)))); return this; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupBuilder.java index f8135a206..dd915e7bf 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/RequestGroupBuilder.java @@ -1,11 +1,11 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.List; + import org.hl7.fhir.dstu3.model.Extension; import org.hl7.fhir.dstu3.model.RequestGroup; import org.hl7.fhir.exceptions.FHIRException; - -import java.util.List; +import org.opencds.cqf.common.builders.BaseBuilder; public class RequestGroupBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructureMapGroupBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructureMapGroupBuilder.java index 4a4a23348..0579a8610 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructureMapGroupBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructureMapGroupBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructureMapGroupBuilder extends BaseBuilder { @@ -28,7 +28,6 @@ public StructureMapGroupBuilder buildDocumentation(String description) { return this; } - public StructureMapGroupBuilder buildInputSource(String name) { StructureMap.StructureMapGroupInputComponent input = new StructureMap.StructureMapGroupInputComponent(); input.setName(name); @@ -46,7 +45,8 @@ public StructureMapGroupBuilder buildInputTarget(String name, String fhirType) { return this; } - public StructureMapGroupBuilder buildRule(StructureMap.StructureMapGroupRuleComponent structureMapGroupRuleComponent) { + public StructureMapGroupBuilder buildRule( + StructureMap.StructureMapGroupRuleComponent structureMapGroupRuleComponent) { complexProperty.addRule(structureMapGroupRuleComponent); return this; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapBuilder.java index e80effacd..e0f612bbc 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.UUID; + import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.StructureMap; - -import java.util.UUID; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapBuilder extends BaseBuilder { @@ -12,7 +12,7 @@ public StructuredMapBuilder() { super(new StructureMap()); } - public StructuredMapBuilder(StructureMap activityDefinition ) { + public StructuredMapBuilder(StructureMap activityDefinition) { super(activityDefinition); } @@ -20,6 +20,7 @@ public StructuredMapBuilder buildUrl(String url) { complexProperty.setUrl(UUID.randomUUID().toString()); return this; } + public StructuredMapBuilder buildRandomUrl() { return buildUrl("urn:uuid:" + UUID.randomUUID().toString()); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleBuilder.java index a9b705515..be347e55e 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleBuilder extends BaseBuilder { @@ -30,17 +30,13 @@ public StructuredMapRuleBuilder buildTarget(StructureMap.StructureMapGroupRuleTa public StructuredMapRuleBuilder buildTargetSetValue(String target, String targetField, String value) { return buildTarget( - new StructuredMapRuleTargetBuilder() - .buildTransformSetValue(target, targetField, value) - .build()); + new StructuredMapRuleTargetBuilder().buildTransformSetValue(target, targetField, value).build()); } - public StructuredMapRuleBuilder buildTarget(String target, String targetField, StructureMap.StructureMapTransform transform, String...params) - { + public StructuredMapRuleBuilder buildTarget(String target, String targetField, + StructureMap.StructureMapTransform transform, String... params) { return buildTarget( - new StructuredMapRuleTargetBuilder() - .buildTransform(target, targetField, transform, params) - .build()); + new StructuredMapRuleTargetBuilder().buildTransform(target, targetField, transform, params).build()); } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleSourceBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleSourceBuilder.java index 7b7ddf9b0..86131d246 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleSourceBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleSourceBuilder.java @@ -1,13 +1,13 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleSourceBuilder extends BaseBuilder { public StructuredMapRuleSourceBuilder() { - this( new StructureMap.StructureMapGroupRuleSourceComponent() ); + this(new StructureMap.StructureMapGroupRuleSourceComponent()); } public StructuredMapRuleSourceBuilder(StructureMap.StructureMapGroupRuleSourceComponent complexProperty) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleTargetBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleTargetBuilder.java index 651c34f2f..b540fa1d5 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleTargetBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapRuleTargetBuilder.java @@ -1,13 +1,13 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleTargetBuilder extends BaseBuilder { public StructuredMapRuleTargetBuilder() { - this( new StructureMap.StructureMapGroupRuleTargetComponent() ); + this(new StructureMap.StructureMapGroupRuleTargetComponent()); } public StructuredMapRuleTargetBuilder(StructureMap.StructureMapGroupRuleTargetComponent complexProperty) { @@ -31,39 +31,33 @@ public StructuredMapRuleTargetBuilder buildElement(String status) { public StructuredMapRuleTargetBuilder buildTransformCopy(String source) { complexProperty.setTransform(StructureMap.StructureMapTransform.COPY); - StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = - new StructureMap.StructureMapGroupRuleTargetParameterComponent(); + StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = new StructureMap.StructureMapGroupRuleTargetParameterComponent(); structureMapGroupRuleTargetParameterComponent.setValue(new StringType(source)); complexProperty.addParameter(structureMapGroupRuleTargetParameterComponent); return this; } public StructuredMapRuleTargetBuilder buildTransformSetValue(String context, String targetField, String value) { - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) - .buildElement(targetField) - .buildTransformCopy(value); + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) + .buildElement(targetField).buildTransformCopy(value); } public StructuredMapRuleTargetBuilder buildTransformSetUuid(String context, String targetField) { complexProperty.setTransform(StructureMap.StructureMapTransform.UUID); - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) .buildElement(targetField); } - public StructuredMapRuleTargetBuilder buildTransform(String context, String targetField, StructureMap.StructureMapTransform transform, String...parameters) - { + public StructuredMapRuleTargetBuilder buildTransform(String context, String targetField, + StructureMap.StructureMapTransform transform, String... parameters) { complexProperty.setTransform(transform); - for (String param: parameters){ - StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = - new StructureMap.StructureMapGroupRuleTargetParameterComponent(); + for (String param : parameters) { + StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = new StructureMap.StructureMapGroupRuleTargetParameterComponent(); structureMapGroupRuleTargetParameterComponent.setValue(new StringType(param)); complexProperty.addParameter(structureMapGroupRuleTargetParameterComponent); } - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) .buildElement(targetField); } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapStructureBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapStructureBuilder.java index ff8832369..a19e8488d 100755 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapStructureBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/StructuredMapStructureBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.dstu3.builders;//package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapStructureBuilder extends BaseBuilder { @@ -15,15 +15,15 @@ public StructuredMapStructureBuilder(StructureMap.StructureMapStructureComponent super(complexProperty); } -// public StructuredMapStructureBuilder buildSource(String s) { -// complexProperty.setUrl("someUrl"); -// complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); -// return this; -// } -// -// public StructuredMapStructureBuilder buildTarget(String s) { -// complexProperty.setUrl("someUrl"); -// complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); -// return this; -// } + // public StructuredMapStructureBuilder buildSource(String s) { + // complexProperty.setUrl("someUrl"); + // complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); + // return this; + // } + // + // public StructuredMapStructureBuilder buildTarget(String s) { + // complexProperty.setUrl("someUrl"); + // complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); + // return this; + // } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetBuilder.java index 5efcd9505..a7044e7ef 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetBuilder.java @@ -1,9 +1,9 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class ValueSetBuilder extends BaseBuilder { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetComposeBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetComposeBuilder.java index 2e7103f53..bafc84010 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetComposeBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetComposeBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.ValueSet; - import java.util.List; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.opencds.cqf.common.builders.BaseBuilder; + public class ValueSetComposeBuilder extends BaseBuilder { public ValueSetComposeBuilder(ValueSet.ValueSetComposeComponent complexProperty) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetIncludesBuilder.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetIncludesBuilder.java index c1011497f..772af4aea 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetIncludesBuilder.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/builders/ValueSetIncludesBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.dstu3.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.dstu3.model.ValueSet; - import java.util.List; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.opencds.cqf.common.builders.BaseBuilder; + public class ValueSetIncludesBuilder extends BaseBuilder { public ValueSetIncludesBuilder(ValueSet.ConceptSetComponent complexProperty) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java index 5e2e97a70..a19151e6f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java @@ -1,10 +1,7 @@ package org.opencds.cqf.dstu3.config; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; import org.opencds.cqf.common.config.HapiProperties; import org.springframework.beans.factory.annotation.Autowired; @@ -13,12 +10,14 @@ import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; @Configuration -public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 -{ +public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 { protected final DataSource myDataSource; @Autowired @@ -27,20 +26,20 @@ public FhirServerConfigDstu3(DataSource myDataSource) { } @Override - public FhirContext fhirContextDstu3() { - FhirContext retVal = FhirContext.forDstu3(); + public FhirContext fhirContextDstu3() { + FhirContext retVal = FhirContext.forDstu3(); - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); + // Don't strip versions in some places + ParserOptions parserOptions = retVal.getParserOptions(); + parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.reference"); - return retVal; - } + return retVal; + } /** - * We override the paging provider definition so that we can customize - * the default/max page sizes for search results. You can set these however - * you want, although very large page sizes will require a lot of RAM. + * We override the paging provider definition so that we can customize the + * default/max page sizes for search results. You can set these however you + * want, although very large page sizes will require a lot of RAM. */ @Override public DatabaseBackedPagingProvider databaseBackedPagingProvider() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index 041319b6f..a3a4f845d 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -1,24 +1,36 @@ package org.opencds.cqf.dstu3.evaluation; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.ReferenceParam; -import org.hl7.fhir.dstu3.model.*; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; + +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.ListResource; +import org.hl7.fhir.dstu3.model.Measure; +import org.hl7.fhir.dstu3.model.MeasureReport; import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.Interval; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.dstu3.builders.MeasureReportBuilder; import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Field; -import java.util.*; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.ReferenceParam; public class MeasureEvaluation { @@ -99,35 +111,34 @@ private Iterable evaluateCriteria(Context context, Patient patient, try { Field privateField = Context.class.getDeclaredField("expressions"); privateField.setAccessible(true); - LinkedHashMap expressions = (LinkedHashMap)privateField.get(context); + LinkedHashMap expressions = (LinkedHashMap) privateField.get(context); expressions.clear(); - + } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } - + Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context); if (result == null) { return Collections.emptyList(); } if (result instanceof Boolean) { - if (((Boolean)result)) { + if (((Boolean) result)) { return Collections.singletonList(patient); - } - else { + } else { return Collections.emptyList(); } } - return (Iterable)result; + return (Iterable) result; } private boolean evaluatePopulationCriteria(Context context, Patient patient, - Measure.MeasureGroupPopulationComponent criteria, HashMap population, HashMap populationPatients, - Measure.MeasureGroupPopulationComponent exclusionCriteria, HashMap exclusionPopulation, HashMap exclusionPatients - ) { + Measure.MeasureGroupPopulationComponent criteria, HashMap population, + HashMap populationPatients, Measure.MeasureGroupPopulationComponent exclusionCriteria, + HashMap exclusionPopulation, HashMap exclusionPatients) { boolean inPopulation = false; if (criteria != null) { for (Resource resource : evaluateCriteria(context, patient, criteria)) { @@ -157,33 +168,36 @@ private boolean evaluatePopulationCriteria(Context context, Patient patient, return inPopulation; } - private void addPopulationCriteriaReport(MeasureReport report, MeasureReport.MeasureReportGroupComponent reportGroup, Measure.MeasureGroupPopulationComponent populationCriteria, int populationCount, Iterable patientPopulation) { + private void addPopulationCriteriaReport(MeasureReport report, + MeasureReport.MeasureReportGroupComponent reportGroup, + Measure.MeasureGroupPopulationComponent populationCriteria, int populationCount, + Iterable patientPopulation) { if (populationCriteria != null) { MeasureReport.MeasureReportGroupPopulationComponent populationReport = new MeasureReport.MeasureReportGroupPopulationComponent(); populationReport.setIdentifier(populationCriteria.getIdentifier()); populationReport.setCode(populationCriteria.getCode()); if (report.getType() == MeasureReport.MeasureReportType.PATIENTLIST && patientPopulation != null) { ListResource subjectList = new ListResource(); - subjectList.setId(UUID.randomUUID().toString()); + subjectList.setId(UUID.randomUUID().toString()); populationReport.setPatients(new Reference().setReference("#" + subjectList.getId())); for (Patient patient : patientPopulation) { ListResource.ListEntryComponent entry = new ListResource.ListEntryComponent() - .setItem(new Reference().setReference( - patient.getIdElement().getIdPart().startsWith("Patient/") ? - patient.getIdElement().getIdPart() : - String.format("Patient/%s", patient.getIdElement().getIdPart())) + .setItem(new Reference() + .setReference(patient.getIdElement().getIdPart().startsWith("Patient/") + ? patient.getIdElement().getIdPart() + : String.format("Patient/%s", patient.getIdElement().getIdPart())) .setDisplay(patient.getNameFirstRep().getNameAsSingleString())); subjectList.addEntry(entry); } report.addContained(subjectList); - } - populationReport.setCount(populationCount); + } + populationReport.setCount(populationCount); reportGroup.addPopulation(populationReport); } } - private MeasureReport evaluate(Measure measure, Context context, List patients, MeasureReport.MeasureReportType type) - { + private MeasureReport evaluate(Measure measure, Context context, List patients, + MeasureReport.MeasureReportType type) { MeasureReportBuilder reportBuilder = new MeasureReportBuilder(); reportBuilder.buildStatus("complete"); reportBuilder.buildType(type); @@ -195,8 +209,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p MeasureReport report = reportBuilder.build(); - HashMap resources = new HashMap<>(); - HashMap> codeToResourceMap = new HashMap<>(); + HashMap resources = new HashMap<>(); + HashMap> codeToResourceMap = new HashMap<>(); MeasureScoring measureScoring = MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode()); if (measureScoring == null) { @@ -209,7 +223,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p report.getGroup().add(reportGroup); // Declare variables to avoid a hash lookup on every patient - // TODO: Isn't quite right, there may be multiple initial populations for a ratio measure... + // TODO: Isn't quite right, there may be multiple initial populations for a + // ratio measure... Measure.MeasureGroupPopulationComponent initialPopulationCriteria = null; Measure.MeasureGroupPopulationComponent numeratorCriteria = null; Measure.MeasureGroupPopulationComponent numeratorExclusionCriteria = null; @@ -241,7 +256,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap measurePopulationExclusionPatients = null; for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) { - MeasurePopulationType populationType = MeasurePopulationType.fromCode(pop.getCode().getCodingFirstRep().getCode()); + MeasurePopulationType populationType = MeasurePopulationType + .fromCode(pop.getCode().getCodingFirstRep().getCode()); if (populationType != null) { switch (populationType) { case INITIALPOPULATION: @@ -314,36 +330,43 @@ private MeasureReport evaluate(Measure measure, Context context, List p for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); if (inInitialPopulation) { // Are they in the denominator? - boolean inDenominator = evaluatePopulationCriteria(context, patient, - denominatorCriteria, denominator, denominatorPatients, - denominatorExclusionCriteria, denominatorExclusion, denominatorExclusionPatients); - populateResourceMap(context, MeasurePopulationType.DENOMINATOR, resources, codeToResourceMap); + boolean inDenominator = evaluatePopulationCriteria(context, patient, denominatorCriteria, + denominator, denominatorPatients, denominatorExclusionCriteria, + denominatorExclusion, denominatorExclusionPatients); + populateResourceMap(context, MeasurePopulationType.DENOMINATOR, resources, + codeToResourceMap); if (inDenominator) { // Are they in the numerator? - boolean inNumerator = evaluatePopulationCriteria(context, patient, - numeratorCriteria, numerator, numeratorPatients, - numeratorExclusionCriteria, numeratorExclusion, numeratorExclusionPatients); - populateResourceMap(context, MeasurePopulationType.NUMERATOR, resources, codeToResourceMap); + boolean inNumerator = evaluatePopulationCriteria(context, patient, numeratorCriteria, + numerator, numeratorPatients, numeratorExclusionCriteria, numeratorExclusion, + numeratorExclusionPatients); + populateResourceMap(context, MeasurePopulationType.NUMERATOR, resources, + codeToResourceMap); if (!inNumerator && inDenominator && (denominatorExceptionCriteria != null)) { // Are they in the denominator exception? boolean inException = false; - for (Resource resource : evaluateCriteria(context, patient, denominatorExceptionCriteria)) { + for (Resource resource : evaluateCriteria(context, patient, + denominatorExceptionCriteria)) { inException = true; denominatorException.put(resource.getIdElement().getIdPart(), resource); denominator.remove(resource.getIdElement().getIdPart()); - populateResourceMap(context, MeasurePopulationType.DENOMINATOREXCEPTION, resources, codeToResourceMap); + populateResourceMap(context, MeasurePopulationType.DENOMINATOREXCEPTION, + resources, codeToResourceMap); } if (inException) { if (denominatorExceptionPatients != null) { - denominatorExceptionPatients.put(patient.getIdElement().getIdPart(), patient); + denominatorExceptionPatients.put(patient.getIdElement().getIdPart(), + patient); } if (denominatorPatients != null) { denominatorPatients.remove(patient.getIdElement().getIdPart()); @@ -356,7 +379,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p // Calculate actual measure score, Count(numerator) / Count(denominator) if (denominator != null && numerator != null && denominator.size() > 0) { - reportGroup.setMeasureScore(numerator.size() / (double)denominator.size()); + reportGroup.setMeasureScore(numerator.size() / (double) denominator.size()); } break; @@ -367,19 +390,23 @@ private MeasureReport evaluate(Measure measure, Context context, List p for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); if (inInitialPopulation) { // Are they in the measure population? boolean inMeasurePopulation = evaluatePopulationCriteria(context, patient, measurePopulationCriteria, measurePopulation, measurePopulationPatients, - measurePopulationExclusionCriteria, measurePopulationExclusion, measurePopulationExclusionPatients); + measurePopulationExclusionCriteria, measurePopulationExclusion, + measurePopulationExclusionPatients); if (inMeasurePopulation) { // TODO: Evaluate measure observations - for (Resource resource : evaluateCriteria(context, patient, measureObservationCriteria)) { + for (Resource resource : evaluateCriteria(context, patient, + measureObservationCriteria)) { measureObservation.put(resource.getIdElement().getIdPart(), resource); } } @@ -393,9 +420,11 @@ private MeasureReport evaluate(Measure measure, Context context, List p // For each patient in the patient list for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); } break; @@ -403,14 +432,30 @@ private MeasureReport evaluate(Measure measure, Context context, List p } // Add population reports for each group - addPopulationCriteriaReport(report, reportGroup, initialPopulationCriteria, initialPopulation != null ? initialPopulation.size() : 0, initialPopulationPatients != null ? initialPopulationPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, numeratorCriteria, numerator != null ? numerator.size() : 0, numeratorPatients != null ? numeratorPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, numeratorExclusionCriteria, numeratorExclusion != null ? numeratorExclusion.size() : 0, numeratorExclusionPatients != null ? numeratorExclusionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorCriteria, denominator != null ? denominator.size() : 0, denominatorPatients != null ? denominatorPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorExclusionCriteria, denominatorExclusion != null ? denominatorExclusion.size() : 0, denominatorExclusionPatients != null ? denominatorExclusionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorExceptionCriteria, denominatorException != null ? denominatorException.size() : 0, denominatorExceptionPatients != null ? denominatorExceptionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, measurePopulationCriteria, measurePopulation != null ? measurePopulation.size() : 0, measurePopulationPatients != null ? measurePopulationPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, measurePopulationExclusionCriteria, measurePopulationExclusion != null ? measurePopulationExclusion.size() : 0, measurePopulationExclusionPatients != null ? measurePopulationExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, initialPopulationCriteria, + initialPopulation != null ? initialPopulation.size() : 0, + initialPopulationPatients != null ? initialPopulationPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, numeratorCriteria, + numerator != null ? numerator.size() : 0, + numeratorPatients != null ? numeratorPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, numeratorExclusionCriteria, + numeratorExclusion != null ? numeratorExclusion.size() : 0, + numeratorExclusionPatients != null ? numeratorExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorCriteria, + denominator != null ? denominator.size() : 0, + denominatorPatients != null ? denominatorPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorExclusionCriteria, + denominatorExclusion != null ? denominatorExclusion.size() : 0, + denominatorExclusionPatients != null ? denominatorExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorExceptionCriteria, + denominatorException != null ? denominatorException.size() : 0, + denominatorExceptionPatients != null ? denominatorExceptionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, measurePopulationCriteria, + measurePopulation != null ? measurePopulation.size() : 0, + measurePopulationPatients != null ? measurePopulationPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, measurePopulationExclusionCriteria, + measurePopulationExclusion != null ? measurePopulationExclusion.size() : 0, + measurePopulationExclusionPatients != null ? measurePopulationExclusionPatients.values() : null); // TODO: Measure Observations... } @@ -440,10 +485,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p return report; } - private void populateResourceMap( - Context context, MeasurePopulationType type, HashMap resources, - HashMap> codeToResourceMap) - { + private void populateResourceMap(Context context, MeasurePopulationType type, HashMap resources, + HashMap> codeToResourceMap) { if (context.getEvaluatedResources().isEmpty()) { return; } @@ -455,9 +498,10 @@ private void populateResourceMap( HashSet codeHashSet = codeToResourceMap.get((type.toCode())); for (Object o : context.getEvaluatedResources()) { - if (o instanceof Resource){ - Resource r = (Resource)o; - String id = (r.getIdElement().getResourceType() != null ? (r.getIdElement().getResourceType() + "/") : "")+ r.getIdElement().getIdPart(); + if (o instanceof Resource) { + Resource r = (Resource) o; + String id = (r.getIdElement().getResourceType() != null ? (r.getIdElement().getResourceType() + "/") + : "") + r.getIdElement().getIdPart(); if (!codeHashSet.contains(id)) { codeHashSet.add(id); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java index e0cf032ad..94e6ee995 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java @@ -1,30 +1,27 @@ package org.opencds.cqf.dstu3.evaluation; -import java.util.ArrayList; import java.util.Date; import java.util.List; import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.elm.execution.Library; -import org.cqframework.cql.elm.execution.UsingDef; import org.hl7.fhir.dstu3.model.Measure; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.execution.LibraryLoader; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.cql.runtime.Interval; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.dstu3.helpers.LibraryHelper; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.DateHelper; import org.opencds.cqf.common.helpers.UsingHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.opencds.cqf.dstu3.helpers.LibraryHelper; import lombok.Data; @Data -public class MeasureEvaluationSeed -{ +public class MeasureEvaluationSeed { private Measure measure; private Context context; private Interval measurementPeriod; @@ -33,17 +30,15 @@ public class MeasureEvaluationSeed private EvaluationProviderFactory providerFactory; private DataProvider dataProvider; - public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { this.providerFactory = providerFactory; this.libraryLoader = libraryLoader; this.libraryResourceProvider = libraryResourceProvider; } - public void setup( - Measure measure, String periodStart, String periodEnd, - String productLine, String source, String user, String pass) - { + public void setup(Measure measure, String periodStart, String periodEnd, String productLine, String source, + String user, String pass) { this.measure = measure; LibraryHelper.loadLibraries(measure, this.libraryLoader, this.libraryResourceProvider); @@ -55,30 +50,30 @@ public void setup( context = new Context(library); context.registerLibraryLoader(libraryLoader); - List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); + List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); if (usingDefs.size() > 1) { - throw new IllegalArgumentException("Evaluation of Measure using multiple Models is not supported at this time."); + throw new IllegalArgumentException( + "Evaluation of Measure using multiple Models is not supported at this time."); } // If there are no Usings, there is probably not any place the Terminology - // actually used so I think the assumption that at least one provider exists is ok. + // actually used so I think the assumption that at least one provider exists is + // ok. TerminologyProvider terminologyProvider = null; if (usingDefs.size() > 0) { - // Creates a terminology provider based on the first using statement. This assumes the terminology + // Creates a terminology provider based on the first using statement. This + // assumes the terminology // server matches the FHIR version of the CQL. - terminologyProvider = this.providerFactory.createTerminologyProvider( - usingDefs.get(0).getLeft(), usingDefs.get(0).getMiddle(), - source, user, pass); + terminologyProvider = this.providerFactory.createTerminologyProvider(usingDefs.get(0).getLeft(), + usingDefs.get(0).getMiddle(), source, user, pass); context.registerTerminologyProvider(terminologyProvider); } - for (Triple def : usingDefs) - { - this.dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), terminologyProvider); - context.registerDataProvider( - def.getRight(), - dataProvider); + for (Triple def : usingDefs) { + this.dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), + terminologyProvider); + context.registerDataProvider(def.getRight(), dataProvider); } // resolve the measurement period diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index 0310f9ad6..7c446e3dd 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -1,22 +1,20 @@ package org.opencds.cqf.dstu3.evaluation; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.opencds.cqf.common.helpers.ClientHelper; -import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; -import org.opencds.cqf.cql.data.CompositeDataProvider; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; -import org.opencds.cqf.cql.searchparam.SearchParameterResolver; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.cql.terminology.fhir.Dstu3FhirTerminologyProvider; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; +import org.opencds.cqf.common.helpers.ClientHelper; import org.opencds.cqf.common.providers.Dstu3ApelonFhirTerminologyProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.terminology.Dstu3FhirTerminologyProvider; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; +import ca.uhn.fhir.rest.client.api.IGenericClient; // This class is a relatively dumb factory for data providers. It supports only // creating JPA providers for FHIR and only basic auth for terminology @@ -46,7 +44,8 @@ public DataProvider createDataProvider(String model, String version, String url, public DataProvider createDataProvider(String model, String version, TerminologyProvider terminologyProvider) { if (model.equals("FHIR") && version.equals("3.0.0")) { Dstu3FhirModelResolver modelResolver = new Dstu3FhirModelResolver(); - JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(this.fhirContext)); + JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, + new SearchParameterResolver(this.fhirContext)); retrieveProvider.setTerminologyProvider(terminologyProvider); retrieveProvider.setExpandValueSets(true); @@ -57,8 +56,9 @@ public DataProvider createDataProvider(String model, String version, Terminology String.format("Can't construct a data provider for model %s version %s", model, version)); } - public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { - if(url != null && !url.isEmpty()){ + public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, + String pass) { + if (url != null && !url.isEmpty()) { IGenericClient client = ClientHelper.getClient(FhirContext.forDstu3(), url, user, pass); if (url.contains("apelon.com")) { return new Dstu3ApelonFhirTerminologyProvider(client); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java index f67f1aaeb..5dd9205f8 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java @@ -1,8 +1,8 @@ package org.opencds.cqf.dstu3.helpers; -import org.opencds.cqf.cql.execution.Context; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Resource; +import org.opencds.cqf.cql.engine.execution.Context; /** * Created by Bryn on 5/7/2016. @@ -14,11 +14,14 @@ public Bundle bundle(Context context, String... expressionNames) { bundle.setType(Bundle.BundleType.COLLECTION); for (String expressionName : expressionNames) { Object result = context.resolveExpressionRef(expressionName).evaluate(context); - for (Object element : (Iterable)result) { + for (Object element : (Iterable) result) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); - entry.setResource((Resource)element); - // The null check for resourceType handles Lists, which don't have a resource type. - entry.setFullUrl((((Resource)element).getIdElement().getResourceType() != null ? (((Resource)element).getIdElement().getResourceType() + "/") : "") + ((Resource)element).getIdElement().getIdPart()); + entry.setResource((Resource) element); + // The null check for resourceType handles Lists, which don't have a resource + // type. + entry.setFullUrl((((Resource) element).getIdElement().getResourceType() != null + ? (((Resource) element).getIdElement().getResourceType() + "/") + : "") + ((Resource) element).getIdElement().getIdPart()); bundle.getEntry().add(entry); } } @@ -32,8 +35,11 @@ public Bundle bundle(Iterable resources) { for (Resource resource : resources) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); entry.setResource(resource); - // The null check for resourceType handles Lists, which don't have a resource type. - entry.setFullUrl((resource.getIdElement().getResourceType() != null ? (resource.getIdElement().getResourceType() + "/") : "") + resource.getIdElement().getIdPart()); + // The null check for resourceType handles Lists, which don't have a resource + // type. + entry.setFullUrl((resource.getIdElement().getResourceType() != null + ? (resource.getIdElement().getResourceType() + "/") + : "") + resource.getIdElement().getIdPart()); bundle.getEntry().add(entry); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 9339d0339..f35c50ff7 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -22,24 +22,22 @@ */ public class LibraryHelper { - public static LibraryLoader createLibraryLoader(LibraryResolutionProvider provider) { + public static LibraryLoader createLibraryLoader( + LibraryResolutionProvider provider) { ModelManager modelManager = new ModelManager(); LibraryManager libraryManager = new LibraryManager(modelManager); libraryManager.getLibrarySourceLoader().clearProviders(); - + libraryManager.getLibrarySourceLoader().registerProvider( - new LibrarySourceProvider( - provider, - x -> x.getContent(), - x -> x.getContentType(), - x -> x.getData())); + new LibrarySourceProvider( + provider, x -> x.getContent(), x -> x.getContentType(), x -> x.getData())); return new LibraryLoader(libraryManager, modelManager); } - - public static List loadLibraries(Measure measure, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static List loadLibraries(Measure measure, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { List libraries = new ArrayList(); // load libraries @@ -47,9 +45,8 @@ public static List loadLibraries(Meas // if library is contained in measure, load it into server if (ref.getReferenceElement().getIdPart().startsWith("#")) { for (Resource resource : measure.getContained()) { - if (resource instanceof org.hl7.fhir.dstu3.model.Library - && resource.getIdElement().getIdPart().equals(ref.getReferenceElement().getIdPart().substring(1))) - { + if (resource instanceof org.hl7.fhir.dstu3.model.Library && resource.getIdElement().getIdPart() + .equals(ref.getReferenceElement().getIdPart().substring(1))) { libraryResourceProvider.update((org.hl7.fhir.dstu3.model.Library) resource); } } @@ -62,9 +59,8 @@ public static List loadLibraries(Meas } org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(id); - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + libraries.add(libraryLoader + .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))); } VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); @@ -86,19 +82,23 @@ public static List loadLibraries(Meas return libraries; } - public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static Library resolveLibraryById(String libraryId, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { // Library library = null; org.hl7.fhir.dstu3.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId); - return libraryLoader.load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); + return libraryLoader + .load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); // for (Library l : libraryLoader.getLibraries()) { - // VersionedIdentifier vid = l.getIdentifier(); - // if (vid.getId().equals(fhirLibrary.getName()) && LibraryResourceHelper.compareVersions(fhirLibrary.getVersion(), vid.getVersion()) == 0) { - // library = l; - // break; - // } + // VersionedIdentifier vid = l.getIdentifier(); + // if (vid.getId().equals(fhirLibrary.getName()) && + // LibraryResourceHelper.compareVersions(fhirLibrary.getVersion(), + // vid.getVersion()) == 0) { + // library = l; + // break; + // } // } // if (library == null) { @@ -108,28 +108,32 @@ public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.e // return library; } - public static Library resolvePrimaryLibrary(Measure measure, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static Library resolvePrimaryLibrary(Measure measure, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { // default is the first library reference String id = measure.getLibraryFirstRep().getReferenceElement().getIdPart(); Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider); if (library == null) { - throw new IllegalArgumentException(String - .format("Could not resolve primary library for Measure/%s.", measure.getIdElement().getIdPart())); + throw new IllegalArgumentException(String.format("Could not resolve primary library for Measure/%s.", + measure.getIdElement().getIdPart())); } return library; } - public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { + public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { String id = planDefinition.getLibraryFirstRep().getReferenceElement().getIdPart(); Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider); if (library == null) { - throw new IllegalArgumentException(String.format("Could not resolve primary library for PlanDefinition/%s", planDefinition.getIdElement().getIdPart())); + throw new IllegalArgumentException(String.format("Could not resolve primary library for PlanDefinition/%s", + planDefinition.getIdElement().getIdPart())); } return library; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java index 07a410073..a793fcae8 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java @@ -1,19 +1,37 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import org.hl7.fhir.dstu3.model.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hl7.fhir.dstu3.model.ActivityDefinition; +import org.hl7.fhir.dstu3.model.Attachment; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.Communication; +import org.hl7.fhir.dstu3.model.CommunicationRequest; +import org.hl7.fhir.dstu3.model.DiagnosticReport; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.MedicationRequest; +import org.hl7.fhir.dstu3.model.Procedure; +import org.hl7.fhir.dstu3.model.ProcedureRequest; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.RelatedArtifact; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.SupplyRequest; import org.hl7.fhir.exceptions.FHIRException; -import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; -import org.opencds.cqf.cql.model.ModelResolver; import org.opencds.cqf.common.exceptions.ActivityDefinitionApplyException; - +import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; +import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.dstu3.helpers.Helper; - -import java.util.*; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** * Created by Bryn on 1/16/2017. @@ -24,25 +42,24 @@ public class ActivityDefinitionApplyProvider { private ModelResolver modelResolver; private IFhirResourceDao activityDefinitionDao; - public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProvider executionProvider, IFhirResourceDao activityDefinitionDao) { + public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProvider executionProvider, + IFhirResourceDao activityDefinitionDao) { this.modelResolver = new Dstu3FhirModelResolver(); this.executionProvider = executionProvider; this.activityDefinitionDao = activityDefinitionDao; } @Operation(name = "$apply", idempotent = true, type = ActivityDefinition.class) - public Resource apply(@IdParam IdType theId, @RequiredParam(name="patient") String patientId, - @OptionalParam(name="encounter") String encounterId, - @OptionalParam(name="practitioner") String practitionerId, - @OptionalParam(name="organization") String organizationId, - @OptionalParam(name="userType") String userType, - @OptionalParam(name="userLanguage") String userLanguage, - @OptionalParam(name="userTaskContext") String userTaskContext, - @OptionalParam(name="setting") String setting, - @OptionalParam(name="settingContext") String settingContext) - throws InternalErrorException, FHIRException, ClassNotFoundException, IllegalAccessException, - InstantiationException, ActivityDefinitionApplyException - { + public Resource apply(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, + @OptionalParam(name = "encounter") String encounterId, + @OptionalParam(name = "practitioner") String practitionerId, + @OptionalParam(name = "organization") String organizationId, + @OptionalParam(name = "userType") String userType, + @OptionalParam(name = "userLanguage") String userLanguage, + @OptionalParam(name = "userTaskContext") String userTaskContext, + @OptionalParam(name = "setting") String setting, + @OptionalParam(name = "settingContext") String settingContext) throws InternalErrorException, FHIRException, + ClassNotFoundException, IllegalAccessException, InstantiationException, ActivityDefinitionApplyException { ActivityDefinition activityDefinition; try { activityDefinition = this.activityDefinitionDao.read(theId); @@ -55,15 +72,13 @@ public Resource apply(@IdParam IdType theId, @RequiredParam(name="patient") Stri // For library use public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, String patientId, - String practitionerId, String organizationId) - throws FHIRException - { + String practitionerId, String organizationId) throws FHIRException { Resource result = null; try { // This is a little hacky... - result = (Resource) Class.forName("org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()).newInstance(); - } - catch (Exception e) { + result = (Resource) Class.forName("org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()) + .newInstance(); + } catch (Exception e) { e.printStackTrace(); throw new FHIRException("Could not find org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()); } @@ -100,15 +115,16 @@ public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, // TODO: Apply expression extensions on any element? - for (ActivityDefinition.ActivityDefinitionDynamicValueComponent dynamicValue : activityDefinition.getDynamicValue()) - { + for (ActivityDefinition.ActivityDefinitionDynamicValueComponent dynamicValue : activityDefinition + .getDynamicValue()) { if (dynamicValue.getExpression() != null) { /* - TODO: Passing the activityDefinition as context here because that's what will have the libraries, - but perhaps the "context" here should be the result resource? - */ - Object value = - executionProvider.evaluateInContext(activityDefinition, dynamicValue.getExpression(), patientId); + * TODO: Passing the activityDefinition as context here because that's what will + * have the libraries, but perhaps the "context" here should be the result + * resource? + */ + Object value = executionProvider.evaluateInContext(activityDefinition, dynamicValue.getExpression(), + patientId); // TODO need to verify type... yay if (value instanceof Boolean) { @@ -122,9 +138,7 @@ public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, } private ProcedureRequest resolveProcedureRequest(ActivityDefinition activityDefinition, String patientId, - String practitionerId, String organizationId) - throws ActivityDefinitionApplyException - { + String practitionerId, String organizationId) throws ActivityDefinitionApplyException { // status, intent, code, and subject are required ProcedureRequest procedureRequest = new ProcedureRequest(); procedureRequest.setStatus(ProcedureRequest.ProcedureRequestStatus.DRAFT); @@ -133,16 +147,12 @@ private ProcedureRequest resolveProcedureRequest(ActivityDefinition activityDefi if (practitionerId != null) { procedureRequest.setRequester( - new ProcedureRequest.ProcedureRequestRequesterComponent() - .setAgent(new Reference(practitionerId)) - ); + new ProcedureRequest.ProcedureRequestRequesterComponent().setAgent(new Reference(practitionerId))); } else if (organizationId != null) { procedureRequest.setRequester( - new ProcedureRequest.ProcedureRequestRequesterComponent() - .setAgent(new Reference(organizationId)) - ); + new ProcedureRequest.ProcedureRequestRequesterComponent().setAgent(new Reference(organizationId))); } if (activityDefinition.hasExtension()) { @@ -159,30 +169,29 @@ else if (!activityDefinition.hasCode() && !activityDefinition.hasDynamicValue()) } if (activityDefinition.hasBodySite()) { - procedureRequest.setBodySite( activityDefinition.getBodySite()); + procedureRequest.setBodySite(activityDefinition.getBodySite()); } if (activityDefinition.hasProduct()) { - throw new ActivityDefinitionApplyException("Product does not map to "+activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("Product does not map to " + activityDefinition.getKind()); } if (activityDefinition.hasDosage()) { - throw new ActivityDefinitionApplyException("Dosage does not map to "+activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("Dosage does not map to " + activityDefinition.getKind()); } return procedureRequest; } private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDefinition, String patientId) - throws ActivityDefinitionApplyException - { + throws ActivityDefinitionApplyException { // intent, medication, and subject are required MedicationRequest medicationRequest = new MedicationRequest(); medicationRequest.setIntent(MedicationRequest.MedicationRequestIntent.ORDER); medicationRequest.setSubject(new Reference(patientId)); if (activityDefinition.hasProduct()) { - medicationRequest.setMedication( activityDefinition.getProduct()); + medicationRequest.setMedication(activityDefinition.getProduct()); } else { @@ -190,7 +199,7 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe } if (activityDefinition.hasDosage()) { - medicationRequest.setDosageInstruction( activityDefinition.getDosage()); + medicationRequest.setDosageInstruction(activityDefinition.getDosage()); } if (activityDefinition.hasBodySite()) { @@ -209,29 +218,22 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe } private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition, String practionerId, - String organizationId) throws ActivityDefinitionApplyException - { + String organizationId) throws ActivityDefinitionApplyException { SupplyRequest supplyRequest = new SupplyRequest(); if (practionerId != null) { supplyRequest.setRequester( - new SupplyRequest.SupplyRequestRequesterComponent() - .setAgent(new Reference(practionerId)) - ); + new SupplyRequest.SupplyRequestRequesterComponent().setAgent(new Reference(practionerId))); } if (organizationId != null) { supplyRequest.setRequester( - new SupplyRequest.SupplyRequestRequesterComponent() - .setAgent(new Reference(organizationId)) - ); + new SupplyRequest.SupplyRequestRequesterComponent().setAgent(new Reference(organizationId))); } - if (activityDefinition.hasQuantity()){ - supplyRequest.setOrderedItem( - new SupplyRequest.SupplyRequestOrderedItemComponent() - .setQuantity( activityDefinition.getQuantity()) - ); + if (activityDefinition.hasQuantity()) { + supplyRequest.setOrderedItem(new SupplyRequest.SupplyRequestOrderedItemComponent() + .setQuantity(activityDefinition.getQuantity())); } else { @@ -243,15 +245,15 @@ private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition } if (activityDefinition.hasProduct()) { - throw new ActivityDefinitionApplyException("Product does not map to "+activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("Product does not map to " + activityDefinition.getKind()); } if (activityDefinition.hasDosage()) { - throw new ActivityDefinitionApplyException("Dosage does not map to "+activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("Dosage does not map to " + activityDefinition.getKind()); } if (activityDefinition.hasBodySite()) { - throw new ActivityDefinitionApplyException("Bodysite does not map to "+activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("Bodysite does not map to " + activityDefinition.getKind()); } return supplyRequest; @@ -286,7 +288,8 @@ private DiagnosticReport resolveDiagnosticReport(ActivityDefinition activityDefi } else { - throw new ActivityDefinitionApplyException("Missing required ActivityDefinition.code property for DiagnosticReport"); + throw new ActivityDefinitionApplyException( + "Missing required ActivityDefinition.code property for DiagnosticReport"); } if (activityDefinition.hasRelatedArtifact()) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java index 046ea486f..c588cff08 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java @@ -1,26 +1,36 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.rp.dstu3.BundleResourceProvider; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.Base; +import org.hl7.fhir.dstu3.model.BooleanType; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.DateTimeType; +import org.hl7.fhir.dstu3.model.DateType; +import org.hl7.fhir.dstu3.model.DecimalType; +import org.hl7.fhir.dstu3.model.Element; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.IntegerType; +import org.hl7.fhir.dstu3.model.Property; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.exceptions.FHIRException; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.TranslatorHelper; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.dstu3.evaluation.ProviderFactory; -import org.opencds.cqf.dstu3.helpers.LibraryHelper; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.DateTime; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; public class ApplyCqlOperationProvider { @@ -43,8 +53,7 @@ public Bundle apply(@IdParam IdType id) throws FHIRException { @Operation(name = "$apply-cql", type = Bundle.class) public Bundle apply(@OperationParam(name = "resourceBundle", min = 1, max = 1, type = Bundle.class) Bundle bundle) - throws FHIRException - { + throws FHIRException { return applyCql(bundle); } @@ -67,14 +76,15 @@ public Resource applyCqlToResource(Resource resource) throws FHIRException { List extension = getExtension(base); if (!extension.isEmpty()) { String cql = String.format("using FHIR version '3.0.0' define x: %s", extension.get(1)); - library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), new ModelManager()); + library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), + new ModelManager()); context = new Context(library); - context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", "3.0.0")); + context.registerDataProvider("http://hl7.org/fhir", + this.providerFactory.createDataProvider("FHIR", "3.0.0")); Object result = context.resolveExpressionRef("x").getExpression().evaluate(context); if (extension.get(0).equals("extension")) { resource.setProperty(child.getName(), resolveType(result, base.fhirType())); - } - else { + } else { String type = base.getChildByName(extension.get(0)).getTypeCode(); base.setProperty(extension.get(0), resolveType(result, type)); } @@ -92,13 +102,13 @@ private List getExtension(Base base) { if (childBase != null) { if (((Element) childBase).hasExtension()) { for (Extension extension : ((Element) childBase).getExtension()) { - if (extension.getUrl().equals("http://hl7.org/fhir/StructureDefinition/cqif-cqlExpression")) { + if (extension.getUrl() + .equals("http://hl7.org/fhir/StructureDefinition/cqif-cqlExpression")) { retVal.add(child.getName()); retVal.add(extension.getValue().primitiveValue()); } } - } - else if (childBase instanceof Extension) { + } else if (childBase instanceof Extension) { retVal.add(child.getName()); retVal.add(((Extension) childBase).getValue().primitiveValue()); } @@ -111,31 +121,27 @@ else if (childBase instanceof Extension) { private Base resolveType(Object source, String type) { if (source instanceof Integer) { return new IntegerType((Integer) source); - } - else if (source instanceof BigDecimal) { + } else if (source instanceof BigDecimal) { return new DecimalType((BigDecimal) source); - } - else if (source instanceof Boolean) { + } else if (source instanceof Boolean) { return new BooleanType().setValue((Boolean) source); - } - else if (source instanceof String) { + } else if (source instanceof String) { return new StringType((String) source); - } - else if (source instanceof DateTime) { + } else if (source instanceof DateTime) { if (type.equals("dateTime")) { return new DateTimeType().setValue(Date.from(((DateTime) source).getDateTime().toInstant())); } if (type.equals("date")) { return new DateType().setValue(Date.from(((DateTime) source).getDateTime().toInstant())); } - } - else if (source instanceof org.opencds.cqf.cql.runtime.Date) - { + } else if (source instanceof org.opencds.cqf.cql.engine.runtime.Date) { if (type.equals("dateTime")) { - return new DateTimeType().setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.runtime.Date) source).getDate())); + return new DateTimeType() + .setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.engine.runtime.Date) source).getDate())); } if (type.equals("date")) { - return new DateType().setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.runtime.Date) source).getDate())); + return new DateType() + .setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.engine.runtime.Date) source).getDate())); } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java index e81a033ed..a8bbeeb1b 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java @@ -1,5 +1,17 @@ package org.opencds.cqf.dstu3.providers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Endpoint; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.Parameters; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.opencds.cqf.dstu3.helpers.Helper; + import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; @@ -12,13 +24,6 @@ import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.dstu3.model.*; -import org.opencds.cqf.dstu3.helpers.Helper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - public class CacheValueSetsProvider { @@ -30,14 +35,10 @@ public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao concepts = new HashMap<>(); - for (ValueSet.ValueSetExpansionContainsComponent expansion : expandedValueSet.getExpansion().getContains()) - { + for (ValueSet.ValueSetExpansionContainsComponent expansion : expandedValueSet.getExpansion().getContains()) { if (!expansion.hasSystem()) { continue; } if (concepts.containsKey(expansion.getSystem())) { concepts.get(expansion.getSystem()) - .addConcept( - new ValueSet.ConceptReferenceComponent() - .setCode(expansion.hasCode() ? expansion.getCode() : null) - .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null) - ); + .addConcept(new ValueSet.ConceptReferenceComponent() + .setCode(expansion.hasCode() ? expansion.getCode() : null) + .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null)); } else { - concepts.put( - expansion.getSystem(), + concepts.put(expansion.getSystem(), new ValueSet.ConceptSetComponent().setSystem(expansion.getSystem()) - .addConcept( - new ValueSet.ConceptReferenceComponent() - .setCode(expansion.hasCode() ? expansion.getCode() : null) - .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null) - ) - ); + .addConcept(new ValueSet.ConceptReferenceComponent() + .setCode(expansion.hasCode() ? expansion.getCode() : null) + .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null))); } } - clean.setCompose( - new ValueSet.ValueSetComposeComponent() - .setInclude(new ArrayList<>(concepts.values())) - ); + clean.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(new ArrayList<>(concepts.values()))); return clean; } private ValueSet resolveValueSet(IGenericClient client, String valuesetId) { - ValueSet valueSet = client.fetchResourceFromUrl(ValueSet.class, client.getServerBase() + "/ValueSet/" + valuesetId); + ValueSet valueSet = client.fetchResourceFromUrl(ValueSet.class, + client.getServerBase() + "/ValueSet/" + valuesetId); boolean expand = false; if (valueSet.hasCompose()) { @@ -130,19 +122,11 @@ private ValueSet resolveValueSet(IGenericClient client, String valuesetId) { } if (expand) { - return getCachedValueSet( - client - .operation() - .onInstance(new IdType("ValueSet", valuesetId)) - .named("$expand") - .withNoParameters(Parameters.class) - .returnResourceType(ValueSet.class) - .execute() - ); + return getCachedValueSet(client.operation().onInstance(new IdType("ValueSet", valuesetId)).named("$expand") + .withNoParameters(Parameters.class).returnResourceType(ValueSet.class).execute()); } valueSet.setVersion(valueSet.getVersion() + "-cache"); return valueSet; } } - diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index 108b343aa..8e07424f7 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -1,57 +1,53 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.UriParam; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.dstu3.builders.OperationOutcomeBuilder; import org.opencds.cqf.dstu3.builders.RandomIdBuilder; -import org.hl7.fhir.dstu3.model.*; -public class CodeSystemUpdateProvider -{ +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.UriParam; + +public class CodeSystemUpdateProvider { private IFhirResourceDao valueSetDao; private IFhirResourceDao codeSystemDao; - public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, IFhirResourceDao codeSystemDao) - { + public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, + IFhirResourceDao codeSystemDao) { this.valueSetDao = valueSetDao; this.codeSystemDao = codeSystemDao; } /*** - * Update existing CodeSystems with the codes in all ValueSet resources. - * System level CodeSystem update operation + * Update existing CodeSystems with the codes in all ValueSet resources. System + * level CodeSystem update operation * - * @return FHIR OperationOutcome detailing the success or failure of the operation + * @return FHIR OperationOutcome detailing the success or failure of the + * operation */ @Operation(name = "$updateCodeSystems", idempotent = true) - public OperationOutcome updateCodeSystems() - { + public OperationOutcome updateCodeSystems() { IBundleProvider valuesets = this.valueSetDao.search(new SearchParameterMap()); OperationOutcome response = new OperationOutcome(); OperationOutcome outcome; - for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) - { + for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) { outcome = this.performCodeSystemUpdate((ValueSet) valueSet); - if (outcome.hasIssue()) - { - for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) - { + if (outcome.hasIssue()) { + for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { response.addIssue(issue); } } @@ -60,76 +56,65 @@ public OperationOutcome updateCodeSystems() return response; } - /*** + /*** * Update existing CodeSystems with the codes in the specified ValueSet. * - * This is for development environment purposes to enable ValueSet expansion and validation - * without complete CodeSystems. + * This is for development environment purposes to enable ValueSet expansion and + * validation without complete CodeSystems. * * @param theId the id of the ValueSet - * @return FHIR OperationOutcome detailing the success or failure of the operation + * @return FHIR OperationOutcome detailing the success or failure of the + * operation */ @Operation(name = "$updateCodeSystems", idempotent = true, type = ValueSet.class) - public OperationOutcome updateCodeSystems(@IdParam IdType theId) - { + public OperationOutcome updateCodeSystems(@IdParam IdType theId) { ValueSet vs = this.valueSetDao.read(theId); OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); - if (vs == null) - { + if (vs == null) { return responseBuilder.buildIssue("error", "notfound", "Unable to find Resource: " + theId.getId()).build(); } return performCodeSystemUpdate(vs); } - public OperationOutcome performCodeSystemUpdate(ValueSet vs) - { + public OperationOutcome performCodeSystemUpdate(ValueSet vs) { OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); List codeSystems = new ArrayList<>(); - if (vs.hasCompose() && vs.getCompose().hasInclude()) - { + if (vs.hasCompose() && vs.getCompose().hasInclude()) { CodeSystem codeSystem; - for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) - { - if (!csc.hasSystem()) continue; + for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { + if (!csc.hasSystem()) + continue; codeSystem = getCodeSystemByUrl(csc.getSystem()); - if (!csc.hasConcept()) continue; + if (!csc.hasConcept()) + continue; - updateCodeSystem( - codeSystem.setUrl(csc.getSystem()), - getUnionDistinctCodes(csc, codeSystem) - ); + updateCodeSystem(codeSystem.setUrl(csc.getSystem()), getUnionDistinctCodes(csc, codeSystem)); codeSystems.add(codeSystem.getUrl()); } } - return responseBuilder.buildIssue( - "information", - "informational", - "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems) - ).build(); + return responseBuilder.buildIssue("information", "informational", + "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); } /*** * Fetch CodeSystem matching the given url search parameter * * @param url The url of the CodeSystem to fetch - * @return The CodeSystem that matches the url parameter or a new CodeSystem with the url and id populated + * @return The CodeSystem that matches the url parameter or a new CodeSystem + * with the url and id populated */ - private CodeSystem getCodeSystemByUrl(String url) - { - IBundleProvider bundleProvider = - this.codeSystemDao.search( - new SearchParameterMap().add(CodeSystem.SP_URL, new UriParam(url)) - ); - - if (bundleProvider.size() >= 1) - { + private CodeSystem getCodeSystemByUrl(String url) { + IBundleProvider bundleProvider = this.codeSystemDao + .search(new SearchParameterMap().add(CodeSystem.SP_URL, new UriParam(url))); + + if (bundleProvider.size() >= 1) { return (CodeSystem) bundleProvider.getResources(0, 1).get(0); } @@ -140,41 +125,33 @@ private CodeSystem getCodeSystemByUrl(String url) * Perform union of codes within a ValueSet and CodeSystem * * @param valueSetCodes The codes contained within a ValueSet - * @param codeSystem A CodeSystem resource + * @param codeSystem A CodeSystem resource * @return List of distinct codes strings */ - private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) - { - if (!codeSystem.hasConcept()) - { - return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode).collect(Collectors.toList()); + private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) { + if (!codeSystem.hasConcept()) { + return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList()); } return Stream.concat( - valueSetCodes.getConcept().stream().map( - ValueSet.ConceptReferenceComponent::getCode).collect(Collectors.toList() - ).stream(), - codeSystem.getConcept().stream().map( - CodeSystem.ConceptDefinitionComponent::getCode).collect(Collectors.toList() - ).stream() - ) - .distinct() - .collect(Collectors.toList()); + valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList()).stream(), + codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) + .collect(Collectors.toList()).stream()) + .distinct().collect(Collectors.toList()); } /*** * Overwrite the given CodeSystem codes with the given codes * * @param codeSystem A CodeSystem resource - * @param codes List of (unique) code strings + * @param codes List of (unique) code strings */ - private void updateCodeSystem(CodeSystem codeSystem, List codes) - { - codeSystem.setConcept( - codes.stream().map( - x -> new CodeSystem.ConceptDefinitionComponent().setCode(x) - ) - .collect(Collectors.toList()) - ).setContent(CodeSystem.CodeSystemContentMode.COMPLETE).setStatus(Enumerations.PublicationStatus.ACTIVE); + private void updateCodeSystem(CodeSystem codeSystem, List codes) { + codeSystem + .setConcept(codes.stream().map(x -> new CodeSystem.ConceptDefinitionComponent().setCode(x)) + .collect(Collectors.toList())) + .setContent(CodeSystem.CodeSystemContentMode.COMPLETE).setStatus(Enumerations.PublicationStatus.ACTIVE); this.codeSystemDao.update(codeSystem); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java index 601a839fe..266f1d7bf 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java @@ -10,7 +10,6 @@ import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.CqlTranslatorException; -import org.cqframework.cql.elm.execution.UsingDef; import org.cqframework.cql.elm.tracking.TrackBack; import org.hl7.fhir.dstu3.model.ActivityDefinition; import org.hl7.fhir.dstu3.model.Bundle; @@ -30,19 +29,17 @@ import org.opencds.cqf.common.helpers.DateHelper; import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.common.helpers.UsingHelper; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.cql.runtime.Interval; -import org.opencds.cqf.cql.terminology.TerminologyProvider; +import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.opencds.cqf.dstu3.helpers.LibraryHelper; -import org.opencds.cqf.common.providers.LibraryResolutionProvider; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; /** * Created by Bryn on 1/16/2017. @@ -51,13 +48,13 @@ public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResolutionProvider; - public CqlExecutionProvider(LibraryResolutionProvider libraryResolutionProvider, EvaluationProviderFactory providerFactory) { + public CqlExecutionProvider(LibraryResolutionProvider libraryResolutionProvider, + EvaluationProviderFactory providerFactory) { this.providerFactory = providerFactory; this.libraryResolutionProvider = libraryResolutionProvider; } - - private LibraryResolutionProvider getLibraryResourceProvider() { + private LibraryResolutionProvider getLibraryResourceProvider() { return this.libraryResolutionProvider; } @@ -192,16 +189,16 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati @Operation(name = "$cql") public Bundle evaluate(@OperationParam(name = "code") String code, @OperationParam(name = "patientId") String patientId, - @OperationParam(name="periodStart") String periodStart, - @OperationParam(name="periodEnd") String periodEnd, - @OperationParam(name="productLine") String productLine, + @OperationParam(name = "periodStart") String periodStart, + @OperationParam(name = "periodEnd") String periodEnd, + @OperationParam(name = "productLine") String productLine, @OperationParam(name = "terminologyServiceUri") String terminologyServiceUri, @OperationParam(name = "terminologyUser") String terminologyUser, @OperationParam(name = "terminologyPass") String terminologyPass, @OperationParam(name = "context") String contextParam, @OperationParam(name = "parameters") Parameters parameters) { - if (patientId == null && contextParam != null && contextParam.equals("Patient") ) { + if (patientId == null && contextParam != null && contextParam.equals("Patient")) { throw new IllegalArgumentException("Must specify a patientId when executing in Patient context."); } @@ -221,8 +218,8 @@ public Bundle evaluate(@OperationParam(name = "code") String code, Parameters result = new Parameters(); TrackBack tb = cte.getLocator(); if (tb != null) { - String location = String.format("[%d:%d]",tb.getStartLine(), tb.getStartChar()); - result.addParameter().setName("location").setValue(new StringType(location)); + String location = String.format("[%d:%d]", tb.getStartLine(), tb.getStartChar()); + result.addParameter().setName("location").setValue(new StringType(location)); } result.setId("Error"); @@ -245,45 +242,43 @@ public Bundle evaluate(@OperationParam(name = "code") String code, org.cqframework.cql.elm.execution.Library library = TranslatorHelper.translateLibrary(translator); Context context = new Context(library); context.registerLibraryLoader(libraryLoader); - - List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); + + List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); if (usingDefs.size() > 1) { - throw new IllegalArgumentException("Evaluation of Measure using multiple Models is not supported at this time."); + throw new IllegalArgumentException( + "Evaluation of Measure using multiple Models is not supported at this time."); } // If there are no Usings, there is probably not any place the Terminology - // actually used so I think the assumption that at least one provider exists is ok. + // actually used so I think the assumption that at least one provider exists is + // ok. TerminologyProvider terminologyProvider = null; if (usingDefs.size() > 0) { - // Creates a terminology provider based on the first using statement. This assumes the terminology + // Creates a terminology provider based on the first using statement. This + // assumes the terminology // server matches the FHIR version of the CQL. - terminologyProvider = this.providerFactory.createTerminologyProvider( - usingDefs.get(0).getLeft(), usingDefs.get(0).getMiddle(), - terminologyServiceUri, terminologyUser, terminologyPass); + terminologyProvider = this.providerFactory.createTerminologyProvider(usingDefs.get(0).getLeft(), + usingDefs.get(0).getMiddle(), terminologyServiceUri, terminologyUser, terminologyPass); context.registerTerminologyProvider(terminologyProvider); } - for (Triple def : usingDefs) - { - DataProvider dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), terminologyProvider); - context.registerDataProvider( - def.getRight(), - dataProvider); + for (Triple def : usingDefs) { + DataProvider dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), + terminologyProvider); + context.registerDataProvider(def.getRight(), dataProvider); } - if (parameters != null) - { - for (Parameters.ParametersParameterComponent pc : parameters.getParameter()) - { + if (parameters != null) { + for (Parameters.ParametersParameterComponent pc : parameters.getParameter()) { context.setParameter(library.getLocalId(), pc.getName(), pc.getValue()); - } + } } if (periodStart != null && periodEnd != null) { // resolve the measurement period Interval measurementPeriod = new Interval(DateHelper.resolveRequestDate(periodStart, true), true, - DateHelper.resolveRequestDate(periodEnd, false), true); + DateHelper.resolveRequestDate(periodEnd, false), true); context.setParameter(null, "Measurement Period", new Interval(DateTime.fromJavaDate((Date) measurementPeriod.getStart()), true, @@ -294,50 +289,45 @@ public Bundle evaluate(@OperationParam(name = "code") String code, context.setParameter(null, "Product Line", productLine); } - context.setExpressionCaching(true); if (library.getStatements() != null) { for (org.cqframework.cql.elm.execution.ExpressionDef def : library.getStatements().getDef()) { context.enterContext(def.getContext()); if (patientId != null && !patientId.isEmpty()) { context.setContextValue(context.getCurrentContext(), patientId); - } - else { + } else { context.setContextValue(context.getCurrentContext(), "null"); } Parameters result = new Parameters(); try { result.setId(def.getName()); - String location = String.format("[%d:%d]", locations.get(def.getName()).get(0), locations.get(def.getName()).get(1)); + String location = String.format("[%d:%d]", locations.get(def.getName()).get(0), + locations.get(def.getName()).get(1)); result.addParameter().setName("location").setValue(new StringType(location)); - Object res = def instanceof org.cqframework.cql.elm.execution.FunctionDef ? "Definition successfully validated" : def.getExpression().evaluate(context); + Object res = def instanceof org.cqframework.cql.elm.execution.FunctionDef + ? "Definition successfully validated" + : def.getExpression().evaluate(context); if (res == null) { result.addParameter().setName("value").setValue(new StringType("null")); - } - else if (res instanceof List) { + } else if (res instanceof List) { if (((List) res).size() > 0 && ((List) res).get(0) instanceof Resource) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable)res)); - } - else { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } - } - else if (res instanceof Iterable) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable)res)); - } - else if (res instanceof Resource) { - result.addParameter().setName("value").setResource((Resource)res); - } - else { + } else if (res instanceof Iterable) { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } else if (res instanceof Resource) { + result.addParameter().setName("value").setResource((Resource) res); + } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } result.addParameter().setName("resultType").setValue(new StringType(resolveType(res))); - } - catch (RuntimeException re) { + } catch (RuntimeException re) { re.printStackTrace(); String message = re.getMessage() != null ? re.getMessage() : re.getClass().getName(); @@ -350,10 +340,11 @@ else if (res instanceof Resource) { return bundler.bundle(results); } - private Map> getLocations(org.hl7.elm.r1.Library library) { + private Map> getLocations(org.hl7.elm.r1.Library library) { Map> locations = new HashMap<>(); - if (library.getStatements() == null) return locations; + if (library.getStatements() == null) + return locations; for (org.hl7.elm.r1.ExpressionDef def : library.getStatements().getDef()) { int startLine = def.getTrackbacks().isEmpty() ? 0 : def.getTrackbacks().get(0).getStartLine(); @@ -368,9 +359,12 @@ private Map> getLocations(org.hl7.elm.r1.Library library private String resolveType(Object result) { String type = result == null ? "Null" : result.getClass().getSimpleName(); switch (type) { - case "BigDecimal": return "Decimal"; - case "ArrayList": return "List"; - case "FhirBundleCursor": return "Retrieve"; + case "BigDecimal": + return "Decimal"; + case "ArrayList": + return "List"; + case "FhirBundleCursor": + return "Retrieve"; } return type; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java index 515b9c6f3..cd4f52539 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java @@ -2,23 +2,32 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import com.google.common.base.Strings; +import com.vladsch.flexmark.ext.autolink.AutolinkExtension; +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension; +import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension; +import com.vladsch.flexmark.ext.tables.TablesExtension; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.parser.ParserEmulationProfile; +import com.vladsch.flexmark.util.ast.KeepType; +import com.vladsch.flexmark.util.ast.Node; +import com.vladsch.flexmark.util.data.MutableDataSet; -import java.util.List; - -import org.apache.commons.lang3.tuple.Triple; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; @@ -52,64 +61,59 @@ import org.hl7.fhir.dstu3.model.Type; import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.dstu3.helpers.LibraryHelper; import org.opencds.cqf.measure.stu3.CodeTerminologyRef; import org.opencds.cqf.measure.stu3.CqfMeasure; import org.opencds.cqf.measure.stu3.TerminologyRef; -import org.opencds.cqf.measure.stu3.VersionedTerminologyRef; import org.opencds.cqf.measure.stu3.TerminologyRef.TerminologyRefType; - -import com.vladsch.flexmark.util.ast.Node; -import com.vladsch.flexmark.html.*; -import com.vladsch.flexmark.parser.*; -import com.vladsch.flexmark.util.data.MutableDataSet; -import com.vladsch.flexmark.util.ast.KeepType; -import com.vladsch.flexmark.ext.autolink.*; -import com.vladsch.flexmark.ext.tables.*; -import com.vladsch.flexmark.ext.gfm.strikethrough.*; -import com.vladsch.flexmark.ext.gfm.tasklist.*; - +import org.opencds.cqf.measure.stu3.VersionedTerminologyRef; public class DataRequirementsProvider { // For creating the CQF measure we need to: // 1. Find the Primary Library Resource - // 2. Load the Primary Library as ELM. This will recursively load the dependent libraries as ELM by Name + // 2. Load the Primary Library as ELM. This will recursively load the dependent + // libraries as ELM by Name // 3. Load the Library Depedencies as Resources // 4. Update the Data Requirements on the Resources accordingly - // Since the Library Loader only exposes the loaded libraries as ELM, we actually have to load them twice. + // Since the Library Loader only exposes the loaded libraries as ELM, we + // actually have to load them twice. // Once via the loader, Once manually - public CqfMeasure createCqfMeasure(Measure measure, LibraryResolutionProvider libraryResourceProvider) { - Map> libraryMap = this.createLibraryMap(measure, libraryResourceProvider); + public CqfMeasure createCqfMeasure(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { + Map> libraryMap = this + .createLibraryMap(measure, libraryResourceProvider); return this.createCqfMeasure(measure, libraryMap); } - private Map> createLibraryMap(Measure measure, LibraryResolutionProvider libraryResourceProvider) { + private Map> createLibraryMap(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResourceProvider); List libraries = LibraryHelper.loadLibraries(measure, libraryLoader, libraryResourceProvider); Map> libraryMap = new HashMap<>(); for (Library library : libraries) { VersionedIdentifier vi = library.getIdentifier(); - org.hl7.fhir.dstu3.model.Library libraryResource = libraryResourceProvider.resolveLibraryByName(vi.getId(), vi.getVersion()); + org.hl7.fhir.dstu3.model.Library libraryResource = libraryResourceProvider.resolveLibraryByName(vi.getId(), + vi.getVersion()); libraryMap.put(vi, Pair.of(library, libraryResource)); } return libraryMap; } - private CqfMeasure createCqfMeasure(Measure measure, Map> libraryMap) - { - //Ensure All Data Requirements for all referenced libraries - org.hl7.fhir.dstu3.model.Library moduleDefinition = this.getDataRequirements(measure, - libraryMap.values().stream().map(x -> x.getRight()).filter(x -> x != null).collect(Collectors.toList())); - + private CqfMeasure createCqfMeasure(Measure measure, + Map> libraryMap) { + // Ensure All Data Requirements for all referenced libraries + org.hl7.fhir.dstu3.model.Library moduleDefinition = this.getDataRequirements(measure, libraryMap.values() + .stream().map(x -> x.getRight()).filter(x -> x != null).collect(Collectors.toList())); + CqfMeasure cqfMeasure = new CqfMeasure(measure); moduleDefinition.getRelatedArtifact().forEach(x -> cqfMeasure.addRelatedArtifact(x)); cqfMeasure.setDataRequirement(moduleDefinition.getDataRequirement()); cqfMeasure.setParameter(moduleDefinition.getParameter()); - + ArrayList citations = new ArrayList<>(); for (RelatedArtifact citation : cqfMeasure.getRelatedArtifact()) { if (citation.hasType() && citation.getType().toCode() == "citation" && citation.hasCitation()) { @@ -125,18 +129,19 @@ private CqfMeasure createCqfMeasure(Measure measure, Map functionStatements = new ArrayList<>(); List supplementalDataElements = new ArrayList<>(); List terminology = new ArrayList<>(); - List codes = new ArrayList<>(); - List codeSystems = new ArrayList<>(); - List valueSets = new ArrayList<>(); + List codes = new ArrayList<>(); + List codeSystems = new ArrayList<>(); + List valueSets = new ArrayList<>(); List dataCriteria = new ArrayList<>(); String primaryLibraryId = measure.getLibraryFirstRep().getReferenceElement().getIdPart(); - Library primaryLibrary = libraryMap.values().stream() - .filter(x -> x.getRight() != null) - .filter(x -> x.getRight().getIdElement() != null && x.getRight().getIdElement().getIdPart().equals(primaryLibraryId)) - .findFirst().get().getLeft(); + Library primaryLibrary = libraryMap.values().stream().filter(x -> x.getRight() != null) + .filter(x -> x.getRight().getIdElement() != null + && x.getRight().getIdElement().getIdPart().equals(primaryLibraryId)) + .findFirst().get().getLeft(); - for (Map.Entry> libraryEntry : libraryMap.entrySet()) { + for (Map.Entry> libraryEntry : libraryMap + .entrySet()) { Library library = libraryEntry.getValue().getLeft(); org.hl7.fhir.dstu3.model.Library libraryResource = libraryEntry.getValue().getRight(); Boolean isPrimaryLibrary = libraryResource != null && libraryResource.getId().equals(primaryLibraryId); @@ -149,16 +154,16 @@ private CqfMeasure createCqfMeasure(Measure measure, Map 0 ? "\r\n" : "") + cqlLines[i]); + if (!cqlLines[i].contains("define \"" + statement.getName() + "\":") + && !cqlLines[i].contains("define function \"" + statement.getName() + "\"(")) { + statementText = statementText + .concat((statementText.length() > 0 ? "\r\n" : "") + cqlLines[i]); } } if (statementText.startsWith("context")) { @@ -273,15 +283,15 @@ private CqfMeasure createCqfMeasure(Measure measure, Map x.getRight()).filter(x -> x != null).collect(Collectors.toList())); + cqfMeasure.setLibraries(libraryMap.values().stream().map(x -> x.getRight()).filter(x -> x != null) + .collect(Collectors.toList())); cqfMeasure.setCitations(citations); - Map>> criteriaMap = new HashMap<>(); // Index all usages of criteria for (int i = 0; i < cqfMeasure.getGroup().size(); i++) { @@ -365,7 +377,8 @@ public int compare(MeasureGroupPopulationComponent item, MeasureGroupPopulationC criteriaMap.put(criteria, new ArrayList>()); } - criteriaMap.get(criteria).add(Triple.of(i, mgpc.getCode().getCodingFirstRep().getCode(), mgpc.getDescription())); + criteriaMap.get(criteria) + .add(Triple.of(i, mgpc.getCode().getCodingFirstRep().getCode(), mgpc.getDescription())); } } } @@ -373,7 +386,8 @@ public int compare(MeasureGroupPopulationComponent item, MeasureGroupPopulationC // Find shared usages for (Entry>> entry : criteriaMap.entrySet()) { String criteria = entry.getKey(); - if (cqfMeasure.getGroup().size() == 1 || entry.getValue().stream().map(x -> x.getLeft()).distinct().count() > 1) { + if (cqfMeasure.getGroup().size() == 1 + || entry.getValue().stream().map(x -> x.getLeft()).distinct().count() > 1) { String code = entry.getValue().get(0).getMiddle(); String display = HQMFProvider.measurePopulationValueSetMap.get(code).displayName; cqfMeasure.addSharedPopulationCritiera(criteria, display, entry.getValue().get(0).getRight()); @@ -391,7 +405,8 @@ public int compare(MeasureGroupPopulationComponent item, MeasureGroupPopulationC List newMgpc = new ArrayList(); for (int j = 0; j < mgc.getPopulation().size(); j++) { MeasureGroupPopulationComponent mgpc = mgc.getPopulation().get(j); - if (mgpc.hasCriteria() && !mgpc.getCriteria().isEmpty() && !cqfMeasure.getSharedPopulationCritieria().getMap().containsKey(mgpc.getCriteria())) { + if (mgpc.hasCriteria() && !mgpc.getCriteria().isEmpty() + && !cqfMeasure.getSharedPopulationCritieria().getMap().containsKey(mgpc.getCriteria())) { String code = mgpc.getCode().getCodingFirstRep().getCode(); String display = HQMFProvider.measurePopulationValueSetMap.get(code).displayName; mgpc.setName(display); @@ -413,34 +428,29 @@ private CqfMeasure processMarkDown(CqfMeasure measure) { MutableDataSet options = new MutableDataSet(); options.setFrom(ParserEmulationProfile.GITHUB_DOC); - options.set(Parser.EXTENSIONS, Arrays.asList( - AutolinkExtension.create(), - //AnchorLinkExtension.create(), - //EmojiExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create(), - TaskListExtension.create() - )); - - // uncomment and define location of emoji images from https://github.com/arvida/emoji-cheat-sheet.com + options.set(Parser.EXTENSIONS, Arrays.asList(AutolinkExtension.create(), + // AnchorLinkExtension.create(), + // EmojiExtension.create(), + StrikethroughExtension.create(), TablesExtension.create(), TaskListExtension.create())); + + // uncomment and define location of emoji images from + // https://github.com/arvida/emoji-cheat-sheet.com // options.set(EmojiExtension.ROOT_IMAGE_PATH, ""); // Uncomment if GFM anchor links are desired in headings // options.set(AnchorLinkExtension.ANCHORLINKS_SET_ID, false); // options.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "anchor"); // options.set(AnchorLinkExtension.ANCHORLINKS_SET_NAME, true); - // options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, ""); + // options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, ""); // References compatibility options.set(Parser.REFERENCES_KEEP, KeepType.LAST); // Set GFM table parsing options - options.set(TablesExtension.COLUMN_SPANS, false) - .set(TablesExtension.MIN_HEADER_ROWS, 1) - .set(TablesExtension.MAX_HEADER_ROWS, 1) - .set(TablesExtension.APPEND_MISSING_COLUMNS, true) - .set(TablesExtension.DISCARD_EXTRA_COLUMNS, true) - .set(TablesExtension.WITH_CAPTION, false) + options.set(TablesExtension.COLUMN_SPANS, false).set(TablesExtension.MIN_HEADER_ROWS, 1) + .set(TablesExtension.MAX_HEADER_ROWS, 1).set(TablesExtension.APPEND_MISSING_COLUMNS, true) + .set(TablesExtension.DISCARD_EXTRA_COLUMNS, true).set(TablesExtension.WITH_CAPTION, false) .set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true); // Setup List Options for GitHub profile which is kramdown for documents @@ -448,21 +458,21 @@ private CqfMeasure processMarkDown(CqfMeasure measure) { options.set(HtmlRenderer.SOFT_BREAK, "
\n"); - Parser parser = Parser.builder(options).build(); HtmlRenderer renderer = HtmlRenderer.builder(options).build(); measure.setDescription(markdownToHtml(parser, renderer, measure.getDescription())); measure.setPurpose(markdownToHtml(parser, renderer, measure.getPurpose())); - // measure.setCopyright(markdownToHtml(parser, renderer, measure.getCopyright())); + // measure.setCopyright(markdownToHtml(parser, renderer, + // measure.getCopyright())); measure.setRationale(markdownToHtml(parser, renderer, measure.getRationale())); - measure.setClinicalRecommendationStatement(markdownToHtml(parser, renderer, measure.getClinicalRecommendationStatement())); + measure.setClinicalRecommendationStatement( + markdownToHtml(parser, renderer, measure.getClinicalRecommendationStatement())); measure.setGuidance(markdownToHtml(parser, renderer, measure.getGuidance())); - measure.setDefinition(measure.getDefinition().stream() - .map(x -> markdownToHtml(parser, renderer, x.getValueAsString())) - .map(x -> new MarkdownType(x)) - .collect(Collectors.toList())); + measure.setDefinition( + measure.getDefinition().stream().map(x -> markdownToHtml(parser, renderer, x.getValueAsString())) + .map(x -> new MarkdownType(x)).collect(Collectors.toList())); return measure; } @@ -476,12 +486,16 @@ private String markdownToHtml(Parser parser, HtmlRenderer renderer, String markd return renderer.render(document); } - public org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, LibraryResolutionProvider libraryResourceProvider){ - Map> libraryMap = this.createLibraryMap(measure, libraryResourceProvider); - return this.getDataRequirements(measure, libraryMap.values().stream().map(x -> x.getRight()).filter(x -> x != null).collect(Collectors.toList())); + public org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { + Map> libraryMap = this + .createLibraryMap(measure, libraryResourceProvider); + return this.getDataRequirements(measure, libraryMap.values().stream().map(x -> x.getRight()) + .filter(x -> x != null).collect(Collectors.toList())); } - private org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, Collection libraries){ + private org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, + Collection libraries) { List reqs = new ArrayList<>(); List dependencies = new ArrayList<>(); List parameters = new ArrayList<>(); @@ -499,8 +513,8 @@ private org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, Co List typeCoding = new ArrayList<>(); typeCoding.add(new Coding().setCode("module-definition")); - org.hl7.fhir.dstu3.model.Library library = - new org.hl7.fhir.dstu3.model.Library().setType(new CodeableConcept().setCoding(typeCoding)); + org.hl7.fhir.dstu3.model.Library library = new org.hl7.fhir.dstu3.model.Library() + .setType(new CodeableConcept().setCoding(typeCoding)); if (!dependencies.isEmpty()) { library.setRelatedArtifact(dependencies); @@ -517,8 +531,8 @@ private org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, Co return library; } - - public CqlTranslator getTranslator(org.hl7.fhir.dstu3.model.Library library, LibraryManager libraryManager, ModelManager modelManager) { + public CqlTranslator getTranslator(org.hl7.fhir.dstu3.model.Library library, LibraryManager libraryManager, + ModelManager modelManager) { Attachment cql = null; for (Attachment a : library.getContent()) { if (a.getContentType().equals("text/cql")) { @@ -568,15 +582,15 @@ public void ensureElm(org.hl7.fhir.dstu3.model.Library library, CqlTranslator tr library.getContent().add(elm); } - public void ensureRelatedArtifacts(org.hl7.fhir.dstu3.model.Library library, CqlTranslator translator, LibraryResolutionProvider libraryResourceProvider) - { + public void ensureRelatedArtifacts(org.hl7.fhir.dstu3.model.Library library, CqlTranslator translator, + LibraryResolutionProvider libraryResourceProvider) { library.getRelatedArtifact().clear(); org.hl7.elm.r1.Library elm = translator.toELM(); if (elm.getIncludes() != null && !elm.getIncludes().getDef().isEmpty()) { for (org.hl7.elm.r1.IncludeDef def : elm.getIncludes().getDef()) { library.addRelatedArtifact(new RelatedArtifact().setType(RelatedArtifact.RelatedArtifactType.DEPENDSON) - .setResource(new Reference().setReference( - libraryResourceProvider.resolveLibraryByName(def.getPath(), def.getVersion()).getId()))); + .setResource(new Reference().setReference(libraryResourceProvider + .resolveLibraryByName(def.getPath(), def.getVersion()).getId()))); } } @@ -616,15 +630,14 @@ public void ensureDataRequirements(org.hl7.fhir.dstu3.model.Library library, Cql reqs.add(dataReq); } - - // + // // org.hl7.elm.r1.Library elm = translator.toELM(); // Codes codes = elm.getCodes(); // for (CodeDef cd : codes.getDef()) { - // cd. + // cd. // } - library.setDataRequirement(reqs); + library.setDataRequirement(reqs); } public String getValueSetId(String valueSetName, CqlTranslator translator) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java index 90c8b6c61..7e390d73c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java @@ -1,5 +1,18 @@ package org.opencds.cqf.dstu3.providers; +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.dstu3.model.CodeSystem; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.ValueSet; +import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.opencds.cqf.cql.engine.runtime.Code; +import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.rp.dstu3.ValueSetResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -8,18 +21,6 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.opencds.cqf.cql.runtime.Code; -import org.opencds.cqf.cql.terminology.CodeSystemInfo; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.cql.terminology.ValueSetInfo; - -import java.util.ArrayList; -import java.util.List; /** * Created by Christopher Schuler on 7/17/2017. @@ -55,9 +56,12 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource boolean needsExpand = false; ValueSet vs = null; if (valueSet.getId().startsWith("http://") || valueSet.getId().startsWith("https://")) { - if (valueSet.getVersion() != null || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { + if (valueSet.getVersion() != null + || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { if (!(valueSet.getCodeSystems().size() == 1 && valueSet.getCodeSystems().get(0).getVersion() == null)) { - throw new UnsupportedOperationException(String.format("Could not expand value set %s; version and code system bindings are not supported at this time.", valueSet.getId())); + throw new UnsupportedOperationException(String.format( + "Could not expand value set %s; version and code system bindings are not supported at this time.", + valueSet.getId())); } } IBundleProvider bundleProvider = valueSetResourceProvider.getDao() diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index 1bd11bb20..eee2822b5 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -14,29 +14,29 @@ import org.hl7.fhir.dstu3.model.Narrative; import org.hl7.fhir.dstu3.model.Parameters; import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.common.providers.LibrarySourceProvider; +import org.opencds.cqf.library.stu3.NarrativeProvider; +import ca.uhn.fhir.jpa.rp.dstu3.LibraryResourceProvider; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.jpa.rp.dstu3.LibraryResourceProvider; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.common.providers.LibrarySourceProvider; -import org.opencds.cqf.library.stu3.NarrativeProvider; - public class LibraryOperationsProvider implements org.opencds.cqf.common.providers.LibraryResolutionProvider { private NarrativeProvider narrativeProvider; private DataRequirementsProvider dataRequirementsProvider; private LibraryResourceProvider libraryResourceProvider; - public LibraryOperationsProvider(LibraryResourceProvider libraryResourceProvider, NarrativeProvider narrativeProvider) { + public LibraryOperationsProvider(LibraryResourceProvider libraryResourceProvider, + NarrativeProvider narrativeProvider) { this.narrativeProvider = narrativeProvider; this.dataRequirementsProvider = new DataRequirementsProvider(); this.libraryResourceProvider = libraryResourceProvider; @@ -46,8 +46,7 @@ private ModelManager getModelManager() { return new ModelManager(); } - private LibraryManager getLibraryManager(ModelManager modelManager) - { + private LibraryManager getLibraryManager(ModelManager modelManager) { LibraryManager libraryManager = new LibraryManager(modelManager); libraryManager.getLibrarySourceLoader().clearProviders(); libraryManager.getLibrarySourceLoader().registerProvider(getLibrarySourceProvider()); @@ -59,11 +58,8 @@ private LibraryManager getLibraryManager(ModelManager modelManager) private LibrarySourceProvider getLibrarySourceProvider() { if (librarySourceProvider == null) { - librarySourceProvider = new LibrarySourceProvider( - this.getLibraryResolutionProvider(), - x -> x.getContent(), - x -> x.getContentType(), - x -> x.getData()); + librarySourceProvider = new LibrarySourceProvider(this.getLibraryResolutionProvider(), + x -> x.getContent(), x -> x.getContentType(), x -> x.getData()); } return librarySourceProvider; } @@ -76,16 +72,17 @@ private LibraryResolutionProvider getLibraryResolutionProvider() { public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, RequestDetails theRequestDetails, @IdParam IdType theId) { Library theResource = this.libraryResourceProvider.getDao().read(theId); - //this.formatCql(theResource); + // this.formatCql(theResource); ModelManager modelManager = this.getModelManager(); LibraryManager libraryManager = this.getLibraryManager(modelManager); - CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, modelManager); + CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, + modelManager); if (translator.getErrors().size() > 0) { throw new RuntimeException("Errors during library compilation."); } - + this.dataRequirementsProvider.ensureElm(theResource, translator); this.dataRequirementsProvider.ensureRelatedArtifacts(theResource, translator, this); this.dataRequirementsProvider.ensureDataRequirements(theResource, translator); @@ -98,21 +95,20 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ } @Operation(name = "$get-elm", idempotent = true, type = Library.class) - public Parameters getElm(@IdParam IdType theId, @OptionalParam(name="format") String format) { + public Parameters getElm(@IdParam IdType theId, @OptionalParam(name = "format") String format) { Library theResource = this.libraryResourceProvider.getDao().read(theId); // this.formatCql(theResource); ModelManager modelManager = this.getModelManager(); LibraryManager libraryManager = this.getLibraryManager(modelManager); - String elm = ""; - CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, modelManager); + CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, + modelManager); if (translator != null) { if (format.equals("json")) { elm = translator.toJson(); - } - else { + } else { elm = translator.toXml(); } } @@ -140,8 +136,7 @@ public void update(Library library) { public Library resolveLibraryById(String libraryId) { try { return this.libraryResourceProvider.getDao().read(new IdType(libraryId)); - } - catch (Exception e) { + } catch (Exception e) { throw new IllegalArgumentException(String.format("Could not resolve library id %s", libraryId)); } } @@ -149,7 +144,8 @@ public Library resolveLibraryById(String libraryId) { @Override public Library resolveLibraryByName(String libraryName, String libraryVersion) { Iterable libraries = getLibrariesByName(libraryName); - org.hl7.fhir.dstu3.model.Library library = LibraryResolutionProvider.selectFromList(libraries, libraryVersion, x -> x.getVersion()); + org.hl7.fhir.dstu3.model.Library library = LibraryResolutionProvider.selectFromList(libraries, libraryVersion, + x -> x.getVersion()); if (library == null) { throw new IllegalArgumentException(String.format("Could not resolve library name %s", libraryName)); @@ -170,12 +166,12 @@ private Iterable getLibrariesByName(String nam List resourceList = bundleProvider.getResources(0, bundleProvider.size()); return resolveLibraries(resourceList); } - - private Iterable resolveLibraries(List< IBaseResource > resourceList) { + + private Iterable resolveLibraries(List resourceList) { List ret = new ArrayList<>(); for (IBaseResource res : resourceList) { Class clazz = res.getClass(); - ret.add((org.hl7.fhir.dstu3.model.Library)clazz.cast(res)); + ret.add((org.hl7.fhir.dstu3.model.Library) clazz.cast(res)); } return ret; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java index 0935b12f9..a92e1705e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java @@ -30,10 +30,9 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.dstu3.evaluation.MeasureEvaluation; import org.opencds.cqf.dstu3.evaluation.MeasureEvaluationSeed; -import org.opencds.cqf.dstu3.evaluation.ProviderFactory; import org.opencds.cqf.dstu3.helpers.LibraryHelper; import org.opencds.cqf.library.stu3.NarrativeProvider; import org.opencds.cqf.measure.stu3.CqfMeasure; @@ -67,11 +66,12 @@ public class MeasureOperationsProvider { private DaoRegistry registry; private EvaluationProviderFactory factory; - private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class); - public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, LibraryResolutionProvider libraryResolutionProvider, - MeasureResourceProvider measureResourceProvider) { + public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, + NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, + LibraryResolutionProvider libraryResolutionProvider, + MeasureResourceProvider measureResourceProvider) { this.registry = registry; this.factory = factory; @@ -96,9 +96,11 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - theResource.getRelatedArtifact().removeIf(relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); + theResource.getRelatedArtifact().removeIf( + relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); // Ensure All Related Artifacts for all referenced Libraries if (!cqfMeasure.getRelatedArtifact().isEmpty()) { @@ -128,7 +130,8 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @Operation(name = "$get-narrative", idempotent = true, type = Measure.class) public Parameters getNarrative(@IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); Parameters p = new Parameters(); p.addParameter().setValue(new StringType(n.getDivAsString())); @@ -136,7 +139,8 @@ public Parameters getNarrative(@IdParam IdType theId) { } private String generateHQMF(Measure theResource) { - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); return this.hqmfProvider.generateHQMF(cqfMeasure); } @@ -156,7 +160,8 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name @OptionalParam(name = "source") String source, @OptionalParam(name = "user") String user, @OptionalParam(name = "pass") String pass) throws InternalErrorException, FHIRException { LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); + MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, + this.libraryResolutionProvider); Measure measure = this.measureResourceProvider.getDao().read(theId); if (measure == null) { @@ -166,24 +171,24 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name seed.setup(measure, periodStart, periodEnd, productLine, source, user, pass); // resolve report type - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); + MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + seed.getMeasurementPeriod()); if (reportType != null) { switch (reportType) { - case "patient": - return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - case "patient-list": - return evaluator.evaluatePatientListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); - case "population": - return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); - default: - throw new IllegalArgumentException("Invalid report type: " + reportType); + case "patient": + return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); + case "patient-list": + return evaluator.evaluatePatientListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); + case "population": + return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); + default: + throw new IllegalArgumentException("Invalid report type: " + reportType); } } // default report type is patient MeasureReport report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - if (productLine != null) - { + if (productLine != null) { Extension ext = new Extension(); ext.setUrl("http://hl7.org/fhir/us/cqframework/cqfmeasures/StructureDefinition/cqfm-productLine"); ext.setValue(new StringType(productLine)); @@ -195,34 +200,43 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name // @Operation(name = "$evaluate-measure-with-source", idempotent = true) // public MeasureReport evaluateMeasure(@IdParam IdType theId, - // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) Bundle sourceData, - // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, - // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { - // if (periodStart == null || periodEnd == null) { - // throw new IllegalArgumentException("periodStart and periodEnd are required for measure evaluation"); - // } - // LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResourceProvider); - // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResourceProvider); - // Measure measure = this.getDao().read(theId); - - // if (measure == null) { - // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); - // } - - // seed.setup(measure, periodStart, periodEnd, null, null, null, null); - // BundleDataProviderStu3 bundleProvider = new BundleDataProviderStu3(sourceData); - // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); - // seed.getContext().registerDataProvider("http://hl7.org/fhir", bundleProvider); - // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, seed.getMeasurementPeriod()); - // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), ""); + // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) + // Bundle sourceData, + // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, + // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { + // if (periodStart == null || periodEnd == null) { + // throw new IllegalArgumentException("periodStart and periodEnd are required + // for measure evaluation"); + // } + // LibraryLoader libraryLoader = + // LibraryHelper.createLibraryLoader(this.libraryResourceProvider); + // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, + // libraryLoader, this.libraryResourceProvider); + // Measure measure = this.getDao().read(theId); + + // if (measure == null) { + // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); + // } + + // seed.setup(measure, periodStart, periodEnd, null, null, null, null); + // BundleDataProviderStu3 bundleProvider = new + // BundleDataProviderStu3(sourceData); + // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); + // seed.getContext().registerDataProvider("http://hl7.org/fhir", + // bundleProvider); + // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, + // seed.getMeasurementPeriod()); + // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), + // ""); // } @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "topic") String topic, @RequiredParam(name = "patient") String patientRef) { - List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap().add("topic", - new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))).getResources(0, 1000); + List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap() + .add("topic", new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))) + .getResources(0, 1000); Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); @@ -254,9 +268,11 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS } LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); + MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, + this.libraryResolutionProvider); seed.setup(measure, periodStart, periodEnd, null, null, null, null); - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); + MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); @@ -329,7 +345,7 @@ public Parameters collectData(@IdParam IdType theId, @RequiredParam(name = "peri @OptionalParam(name = "lastReceivedOn") String lastReceivedOn) throws FHIRException { // TODO: Spec says that the periods are not required, but I am not sure what to // do when they aren't supplied so I made them required - MeasureReport report = evaluateMeasure(theId, periodStart, periodEnd, null, null, patientRef, null, + MeasureReport report = evaluateMeasure(theId, periodStart, periodEnd, null, null, patientRef, null, practitionerRef, lastReceivedOn, null, null, null); report.setGroup(null); @@ -374,7 +390,8 @@ private void addEvaluatedResourcesToParameters(Bundle contained, Parameters para private void resolveReferences(Resource resource, Parameters parameters, Map resourceMap) { List values; - for (BaseRuntimeChildDefinition child : this.measureResourceProvider.getContext().getResourceDefinition(resource).getChildren()) { + for (BaseRuntimeChildDefinition child : this.measureResourceProvider.getContext() + .getResourceDefinition(resource).getChildren()) { values = child.getAccessor().getValues(resource); if (values == null || values.isEmpty()) { continue; @@ -402,7 +419,7 @@ else if (values.get(0) instanceof Reference public org.hl7.fhir.dstu3.model.Library dataRequirements(@IdParam IdType theId, @RequiredParam(name = "startPeriod") String startPeriod, @RequiredParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { - + Measure measure = this.measureResourceProvider.getDao().read(theId); return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java index 145783036..40cec4e77 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java @@ -23,10 +23,10 @@ import org.hl7.fhir.exceptions.FHIRException; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.exceptions.NotImplementedException; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; -import org.opencds.cqf.cql.model.ModelResolver; -import org.opencds.cqf.cql.runtime.DateTime; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; +import org.opencds.cqf.cql.engine.model.ModelResolver; +import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.dstu3.builders.AttachmentBuilder; import org.opencds.cqf.dstu3.builders.CarePlanActivityBuilder; import org.opencds.cqf.dstu3.builders.CarePlanBuilder; @@ -53,16 +53,17 @@ public class PlanDefinitionApplyProvider { private ModelResolver modelResolver; private ActivityDefinitionApplyProvider activityDefinitionApplyProvider; - private IFhirResourceDao planDefintionDao; + private IFhirResourceDao planDefintionDao; private IFhirResourceDao activityDefinitionDao; private FhirContext fhirContext; private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); - public PlanDefinitionApplyProvider(FhirContext fhirContext, ActivityDefinitionApplyProvider activitydefinitionApplyProvider, - IFhirResourceDao planDefintionDao, IFhirResourceDao activityDefinitionDao, - CqlExecutionProvider executionProvider) { + public PlanDefinitionApplyProvider(FhirContext fhirContext, + ActivityDefinitionApplyProvider activitydefinitionApplyProvider, + IFhirResourceDao planDefintionDao, + IFhirResourceDao activityDefinitionDao, CqlExecutionProvider executionProvider) { this.executionProvider = executionProvider; this.modelResolver = new Dstu3FhirModelResolver(); this.activityDefinitionApplyProvider = activitydefinitionApplyProvider; @@ -76,19 +77,16 @@ public IFhirResourceDao getDao() { } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) - public CarePlan applyPlanDefinition( - @IdParam IdType theId, - @RequiredParam(name="patient") String patientId, - @OptionalParam(name="encounter") String encounterId, - @OptionalParam(name="practitioner") String practitionerId, - @OptionalParam(name="organization") String organizationId, - @OptionalParam(name="userType") String userType, - @OptionalParam(name="userLanguage") String userLanguage, - @OptionalParam(name="userTaskContext") String userTaskContext, - @OptionalParam(name="setting") String setting, - @OptionalParam(name="settingContext") String settingContext) - throws IOException, JAXBException, FHIRException - { + public CarePlan applyPlanDefinition(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, + @OptionalParam(name = "encounter") String encounterId, + @OptionalParam(name = "practitioner") String practitionerId, + @OptionalParam(name = "organization") String organizationId, + @OptionalParam(name = "userType") String userType, + @OptionalParam(name = "userLanguage") String userLanguage, + @OptionalParam(name = "userTaskContext") String userTaskContext, + @OptionalParam(name = "setting") String setting, + @OptionalParam(name = "settingContext") String settingContext) + throws IOException, JAXBException, FHIRException { PlanDefinition planDefinition = this.planDefintionDao.read(theId); if (planDefinition == null) { @@ -99,19 +97,20 @@ public CarePlan applyPlanDefinition( CarePlanBuilder builder = new CarePlanBuilder(); - builder - .buildDefinition(new Reference(planDefinition.getIdElement().getIdPart())) - .buildSubject(new Reference(patientId)) - .buildStatus(CarePlan.CarePlanStatus.DRAFT); + builder.buildDefinition(new Reference(planDefinition.getIdElement().getIdPart())) + .buildSubject(new Reference(patientId)).buildStatus(CarePlan.CarePlanStatus.DRAFT); - if (encounterId != null) builder.buildContext(new Reference(encounterId)); - if (practitionerId != null) builder.buildAuthor(new Reference(practitionerId)); - if (organizationId != null) builder.buildAuthor(new Reference(organizationId)); - if (userLanguage != null) builder.buildLanguage(userLanguage); + if (encounterId != null) + builder.buildContext(new Reference(encounterId)); + if (practitionerId != null) + builder.buildAuthor(new Reference(practitionerId)); + if (organizationId != null) + builder.buildAuthor(new Reference(organizationId)); + if (userLanguage != null) + builder.buildLanguage(userLanguage); - Session session = - new Session(planDefinition, builder, patientId, encounterId, practitionerId, - organizationId, userType, userLanguage, userTaskContext, setting, settingContext); + Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, + userType, userLanguage, userTaskContext, setting, settingContext); return resolveActions(session); } @@ -130,11 +129,12 @@ private CarePlan resolveActions(Session session) { private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { - logger.debug("Resolving definition "+ action.getDefinition().getReference()); + logger.debug("Resolving definition " + action.getDefinition().getReference()); Reference definition = action.getDefinition(); if (definition.getReference().startsWith(session.getPlanDefinition().fhirType())) { logger.error("Currently cannot resolve nested PlanDefinitions"); - throw new NotImplementedException("Plan Definition refers to sub Plan Definition, this is not yet supported"); + throw new NotImplementedException( + "Plan Definition refers to sub Plan Definition, this is not yet supported"); } else { @@ -144,53 +144,38 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct result = this.activityDefinitionApplyProvider.resolveActivityDefinition( (ActivityDefinition) resolveContained(session.getPlanDefinition(), action.getDefinition().getReferenceElement().getIdPart()), - session.getPatientId(), session.getPractionerId(), session.getOrganizationId() - ); - } - else { + session.getPatientId(), session.getPractionerId(), session.getOrganizationId()); + } else { result = this.activityDefinitionApplyProvider.apply( new IdType(action.getDefinition().getReferenceElement().getIdPart()), - session.getPatientId(), - session.getEncounterId(), - session.getPractionerId(), - session.getOrganizationId(), - null, - session.getUserLanguage(), - session.getUserTaskContext(), - session.getSetting(), - session.getSettingContext() - ); + session.getPatientId(), session.getEncounterId(), session.getPractionerId(), + session.getOrganizationId(), null, session.getUserLanguage(), + session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } if (result.getId() == null) { - logger.warn("ActivityDefinition %s returned resource with no id, setting one", action.getDefinition().getReferenceElement().getIdPart()); - result.setId( UUID.randomUUID().toString() ); + logger.warn("ActivityDefinition %s returned resource with no id, setting one", + action.getDefinition().getReferenceElement().getIdPart()); + result.setId(UUID.randomUUID().toString()); } - session.getCarePlanBuilder() - .buildContained(result) - .buildActivity( - new CarePlanActivityBuilder() - .buildReference( new Reference("#"+result.getId()) ) - .build() - ); + session.getCarePlanBuilder().buildContained(result).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); } catch (Exception e) { - logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", action.getDefinition(), e.toString()); + logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", + action.getDefinition(), e.toString()); } } } } private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitionActionComponent action) { - for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue: action.getDynamicValue()) - { + for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action.getDynamicValue()) { logger.info("Resolving dynamic value %s %s", dynamicValue.getPath(), dynamicValue.getExpression()); if (dynamicValue.hasExpression()) { - Object result = - executionProvider - .evaluateInContext(session.getPlanDefinition(), dynamicValue.getExpression(), session.getPatientId()); + Object result = executionProvider.evaluateInContext(session.getPlanDefinition(), + dynamicValue.getExpression(), session.getPatientId()); - if (dynamicValue.hasPath() && dynamicValue.getPath().equals("$this")) - { + if (dynamicValue.hasPath() && dynamicValue.getPath().equals("$this")) { session.setCarePlan((CarePlan) result); } @@ -198,10 +183,7 @@ private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitio // TODO - likely need more date tranformations if (result instanceof DateTime) { - result = - new JavaDateBuilder() - .buildFromDateTime((DateTime) result) - .build(); + result = new JavaDateBuilder().buildFromDateTime((DateTime) result).build(); } else if (result instanceof String) { @@ -215,7 +197,7 @@ else if (result instanceof String) { } private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionActionComponent action) { - for (PlanDefinition.PlanDefinitionActionConditionComponent condition: action.getCondition()) { + for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { // TODO start // TODO stop if (condition.hasDescription()) { @@ -234,8 +216,9 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc logger.info("Evaluating action condition expression " + condition.getExpression()); String cql = condition.getExpression(); - Object result = executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId()); - + Object result = executionProvider.evaluateInContext(session.getPlanDefinition(), cql, + session.getPatientId()); + if (result == null) { logger.warn("Expression Returned null"); return false; @@ -294,18 +277,18 @@ public CarePlan resolveCdsHooksPlanDefinition(Context context, PlanDefinition pl } private void resolveActions(List actions, Context context, - String patientId, RequestGroupBuilder requestGroupBuilder, - List actionComponents) - { + String patientId, RequestGroupBuilder requestGroupBuilder, + List actionComponents) { for (PlanDefinition.PlanDefinitionActionComponent action : actions) { boolean conditionsMet = true; - for (PlanDefinition.PlanDefinitionActionConditionComponent condition: action.getCondition()) { + for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { if (condition.getKind() == PlanDefinition.ActionConditionKind.APPLICABILITY) { if (!condition.hasExpression()) { continue; } - Object result = context.resolveExpressionRef(condition.getExpression()).getExpression().evaluate(context); + Object result = context.resolveExpressionRef(condition.getExpression()).getExpression() + .evaluate(context); if (!(result instanceof Boolean)) { continue; @@ -352,9 +335,11 @@ private void resolveActions(List a actionBuilder.buildType(action.getType()); } if (action.hasDefinition()) { - if (action.getDefinition().getReferenceElement().getResourceType().equals("ActivityDefinition")) { + if (action.getDefinition().getReferenceElement().getResourceType() + .equals("ActivityDefinition")) { if (action.getDefinition().getResource() != null) { - ActivityDefinition activityDefinition = (ActivityDefinition) action.getDefinition().getResource(); + ActivityDefinition activityDefinition = (ActivityDefinition) action.getDefinition() + .getResource(); ReferenceBuilder referenceBuilder = new ReferenceBuilder(); referenceBuilder.buildDisplay(activityDefinition.getDescription()); actionBuilder.buildResource(referenceBuilder.build()); @@ -364,53 +349,54 @@ private void resolveActions(List a } } - ActivityDefinition activityDefinition = - this.activityDefinitionDao.read(action.getDefinition().getReferenceElement()); + ActivityDefinition activityDefinition = this.activityDefinitionDao + .read(action.getDefinition().getReferenceElement()); if (activityDefinition.hasDescription()) { actionBuilder.buildDescripition(activityDefinition.getDescription()); } Resource resource = null; try { - resource = activityDefinitionApplyProvider.apply( - new IdType(action.getDefinition().getReferenceElement().getIdPart()), patientId, - null, null, null, null, - null, null, null, null - ).setId(UUID.randomUUID().toString()); - } catch (FHIRException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + resource = activityDefinitionApplyProvider + .apply(new IdType(action.getDefinition().getReferenceElement().getIdPart()), + patientId, null, null, null, null, null, null, null, null) + .setId(UUID.randomUUID().toString()); + } catch (FHIRException | ClassNotFoundException | InstantiationException + | IllegalAccessException e) { throw new RuntimeException("Error applying ActivityDefinition " + e.getMessage()); } Parameters inParams = new Parameters(); inParams.addParameter().setName("patient").setValue(new StringType(patientId)); - Parameters outParams = this.fhirContext.newRestfulGenericClient(HapiProperties.getServerBase()) - .operation() - .onInstance(new IdDt("ActivityDefinition", action.getDefinition().getId())) - .named("$apply") - .withParameters(inParams) - .useHttpGet() - .execute(); + Parameters outParams = this.fhirContext + .newRestfulGenericClient(HapiProperties.getServerBase()).operation() + .onInstance(new IdDt("ActivityDefinition", action.getDefinition().getId())) + .named("$apply").withParameters(inParams).useHttpGet().execute(); List response = outParams.getParameter(); resource = response.get(0).getResource().setId(UUID.randomUUID().toString()); actionBuilder.buildResourceTarget(resource); - actionBuilder.buildResource(new ReferenceBuilder().buildReference(resource.getId()).build()); + actionBuilder + .buildResource(new ReferenceBuilder().buildReference(resource.getId()).build()); } } - // Dynamic values populate the RequestGroup - there is a bit of hijacking going on here... + // Dynamic values populate the RequestGroup - there is a bit of hijacking going + // on here... if (action.hasDynamicValue()) { - for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action.getDynamicValue()) { + for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action + .getDynamicValue()) { if (dynamicValue.hasPath() && dynamicValue.hasExpression()) { if (dynamicValue.getPath().endsWith("title")) { // summary - String title = (String) context.resolveExpressionRef(dynamicValue.getExpression()).evaluate(context); + String title = (String) context.resolveExpressionRef(dynamicValue.getExpression()) + .evaluate(context); actionBuilder.buildTitle(title); - } - else if (dynamicValue.getPath().endsWith("description")) { // detail - String description = (String) context.resolveExpressionRef(dynamicValue.getExpression()).evaluate(context); + } else if (dynamicValue.getPath().endsWith("description")) { // detail + String description = (String) context + .resolveExpressionRef(dynamicValue.getExpression()).evaluate(context); actionBuilder.buildDescripition(description); - } - else if (dynamicValue.getPath().endsWith("extension")) { // indicator - String extension = (String) context.resolveExpressionRef(dynamicValue.getExpression()).evaluate(context); + } else if (dynamicValue.getPath().endsWith("extension")) { // indicator + String extension = (String) context + .resolveExpressionRef(dynamicValue.getExpression()).evaluate(context); actionBuilder.buildExtension(extension); } } @@ -439,7 +425,8 @@ public Resource resolveContained(DomainResource resource, String id) { } } - throw new RuntimeException(String.format("Resource %s does not contain resource with id %s", resource.fhirType(), id)); + throw new RuntimeException( + String.format("Resource %s does not contain resource with id %s", resource.fhirType(), id)); } } @@ -457,9 +444,8 @@ class Session { private String encounterId; public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String patientId, String encounterId, - String practitionerId, String organizationId, String userType, String userLanguage, - String userTaskContext, String setting, String settingContext) - { + String practitionerId, String organizationId, String userType, String userLanguage, String userTaskContext, + String setting, String settingContext) { this.patientId = patientId; this.planDefinition = planDefinition; this.carePlanBuilder = builder; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/Stu3BundleRetrieveProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/Stu3BundleRetrieveProvider.java index e6a67c78e..64861ec12 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/Stu3BundleRetrieveProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/Stu3BundleRetrieveProvider.java @@ -2,125 +2,139 @@ // import org.hl7.fhir.dstu3.model.Bundle; // import org.hl7.fhir.dstu3.model.Resource; -// import org.opencds.cqf.cql.elm.execution.InEvaluator; -// import org.opencds.cqf.cql.elm.execution.IncludesEvaluator; -// import org.opencds.cqf.cql.runtime.Code; -// import org.opencds.cqf.cql.runtime.DateTime; -// import org.opencds.cqf.cql.runtime.Interval; -// import org.opencds.cqf.cql.terminology.ValueSetInfo; +// import org.opencds.cqf.cql.engine.elm.execution.InEvaluator; +// import org.opencds.cqf.cql.engine.elm.execution.IncludesEvaluator; +// import org.opencds.cqf.cql.engine.runtime.Code; +// import org.opencds.cqf.cql.engine.runtime.DateTime; +// import org.opencds.cqf.cql.engine.runtime.Interval; +// import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; // import org.opencds.cqf.dstu3.helpers.DataProviderHelper; -// import org.opencds.cqf.cql.retrieve.RetrieveProvider; +// import org.opencds.cqf.cql.retrieve.RetrieveProvider; // import java.util.*; // public class Stu3BundleRetrieveProvider implements RetrieveProvider { - -// private Map> resourceMap; - -// public Stu3BundleRetrieveProvider(Bundle sourceData) { -// resourceMap = new HashMap<>(); - -// // populate map -// if (sourceData != null) { -// if (sourceData.hasEntry()) { -// for (Bundle.BundleEntryComponent entry : sourceData.getEntry()) { -// if (entry.hasResource()) { -// Resource resource = entry.getResource(); -// if (resourceMap.containsKey(resource.fhirType())) { -// resourceMap.get(resource.fhirType()).add(resource); -// } else { -// List resources = new ArrayList<>(); -// resources.add(resource); -// resourceMap.put(resource.fhirType(), resources); -// } -// } -// } -// } -// } -// } - -// @Override -// public Iterable retrieve(String context, String contextPath, Object contextValue, String dataType, String templateId, -// String codePath, Iterable codes, String valueSet, String datePath, -// String dateLowPath, String dateHighPath, Interval dateRange) -// { -// if (codePath == null && (codes != null || valueSet != null)) { -// throw new IllegalArgumentException("A code path must be provided when filtering on codes or a valueset."); -// } - -// if (dataType == null) { -// throw new IllegalArgumentException("A data type (i.e. Procedure, Valueset, etc...) must be specified for clinical data retrieval"); -// } - -// List resourcesOfType = resourceMap.get(dataType); - -// if (resourcesOfType == null) { -// return Collections.emptyList(); -// } - -// // no resources or no filtering -> return list -// if (resourcesOfType.isEmpty() || (dateRange == null && codePath == null)) { -// return resourcesOfType; -// } - -// List returnList = new ArrayList<>(); -// for (Object resource : resourcesOfType) { -// boolean includeResource = true; -// if (dateRange != null) { -// if (datePath != null) { -// if (dateHighPath != null || dateLowPath != null) { -// throw new IllegalArgumentException("If the datePath is specified, the dateLowPath and dateHighPath attributes must not be present."); -// } - -// Object dateObject = DataProviderHelper.getStu3DateTime(resolvePath(resource, datePath)); -// DateTime date = dateObject instanceof DateTime ? (DateTime) dateObject : null; -// Interval dateInterval = dateObject instanceof Interval ? (Interval) dateObject : null; -// String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, date)); -// if (date != null && !(InEvaluator.in(date, dateRange, precision))) { -// includeResource = false; -// } - -// else if (dateInterval != null && !IncludesEvaluator.includes(dateRange, dateInterval, precision)) { -// includeResource = false; -// } -// } else { -// if (dateHighPath == null && dateLowPath == null) { -// throw new IllegalArgumentException("If the datePath is not given, either the lowDatePath or highDatePath must be provided."); -// } - -// DateTime lowDate = dateLowPath == null ? null : (DateTime) DataProviderHelper.getStu3DateTime(resolvePath(resource, dateLowPath)); -// DateTime highDate = dateHighPath == null ? null : (DateTime) DataProviderHelper.getStu3DateTime(resolvePath(resource, dateHighPath)); - -// String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, lowDate, highDate)); - -// Interval interval = new Interval(lowDate, true, highDate, true); - -// if (!IncludesEvaluator.includes(dateRange, interval, precision)) { -// includeResource = false; -// } -// } -// } - -// if (codePath != null && !codePath.equals("") && includeResource) { -// if (valueSet != null && terminologyProvider != null) { -// if (valueSet.startsWith("urn:oid:")) { -// valueSet = valueSet.replace("urn:oid:", ""); -// } -// ValueSetInfo valueSetInfo = new ValueSetInfo().withId(valueSet); -// codes = terminologyProvider.expand(valueSetInfo); -// } -// if (codes != null) { -// Object codeObject = DataProviderHelper.getStu3Code(resolvePath(resource, codePath)); -// includeResource = DataProviderHelper.checkCodeMembership(codes, codeObject); -// } - -// if (includeResource) { -// returnList.add(resource); -// } -// } -// } - -// return returnList; -// } + +// private Map> resourceMap; + +// public Stu3BundleRetrieveProvider(Bundle sourceData) { +// resourceMap = new HashMap<>(); + +// // populate map +// if (sourceData != null) { +// if (sourceData.hasEntry()) { +// for (Bundle.BundleEntryComponent entry : sourceData.getEntry()) { +// if (entry.hasResource()) { +// Resource resource = entry.getResource(); +// if (resourceMap.containsKey(resource.fhirType())) { +// resourceMap.get(resource.fhirType()).add(resource); +// } else { +// List resources = new ArrayList<>(); +// resources.add(resource); +// resourceMap.put(resource.fhirType(), resources); +// } +// } +// } +// } +// } +// } + +// @Override +// public Iterable retrieve(String context, String contextPath, Object +// contextValue, String dataType, String templateId, +// String codePath, Iterable codes, String valueSet, String datePath, +// String dateLowPath, String dateHighPath, Interval dateRange) +// { +// if (codePath == null && (codes != null || valueSet != null)) { +// throw new IllegalArgumentException("A code path must be provided when +// filtering on codes or a valueset."); +// } + +// if (dataType == null) { +// throw new IllegalArgumentException("A data type (i.e. Procedure, Valueset, +// etc...) must be specified for clinical data retrieval"); +// } + +// List resourcesOfType = resourceMap.get(dataType); + +// if (resourcesOfType == null) { +// return Collections.emptyList(); +// } + +// // no resources or no filtering -> return list +// if (resourcesOfType.isEmpty() || (dateRange == null && codePath == null)) { +// return resourcesOfType; +// } + +// List returnList = new ArrayList<>(); +// for (Object resource : resourcesOfType) { +// boolean includeResource = true; +// if (dateRange != null) { +// if (datePath != null) { +// if (dateHighPath != null || dateLowPath != null) { +// throw new IllegalArgumentException("If the datePath is specified, the +// dateLowPath and dateHighPath attributes must not be present."); +// } + +// Object dateObject = DataProviderHelper.getStu3DateTime(resolvePath(resource, +// datePath)); +// DateTime date = dateObject instanceof DateTime ? (DateTime) dateObject : +// null; +// Interval dateInterval = dateObject instanceof Interval ? (Interval) +// dateObject : null; +// String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, +// date)); +// if (date != null && !(InEvaluator.in(date, dateRange, precision))) { +// includeResource = false; +// } + +// else if (dateInterval != null && !IncludesEvaluator.includes(dateRange, +// dateInterval, precision)) { +// includeResource = false; +// } +// } else { +// if (dateHighPath == null && dateLowPath == null) { +// throw new IllegalArgumentException("If the datePath is not given, either the +// lowDatePath or highDatePath must be provided."); +// } + +// DateTime lowDate = dateLowPath == null ? null : (DateTime) +// DataProviderHelper.getStu3DateTime(resolvePath(resource, dateLowPath)); +// DateTime highDate = dateHighPath == null ? null : (DateTime) +// DataProviderHelper.getStu3DateTime(resolvePath(resource, dateHighPath)); + +// String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, +// lowDate, highDate)); + +// Interval interval = new Interval(lowDate, true, highDate, true); + +// if (!IncludesEvaluator.includes(dateRange, interval, precision)) { +// includeResource = false; +// } +// } +// } + +// if (codePath != null && !codePath.equals("") && includeResource) { +// if (valueSet != null && terminologyProvider != null) { +// if (valueSet.startsWith("urn:oid:")) { +// valueSet = valueSet.replace("urn:oid:", ""); +// } +// ValueSetInfo valueSetInfo = new ValueSetInfo().withId(valueSet); +// codes = terminologyProvider.expand(valueSetInfo); +// } +// if (codes != null) { +// Object codeObject = DataProviderHelper.getStu3Code(resolvePath(resource, +// codePath)); +// includeResource = DataProviderHelper.checkCodeMembership(codes, codeObject); +// } + +// if (includeResource) { +// returnList.add(resource); +// } +// } +// } + +// return returnList; +// } // } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index 7c64afee3..6ef070ff3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -17,7 +17,7 @@ import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; -import org.opencds.cqf.cql.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.dstu3.evaluation.ProviderFactory; import org.opencds.cqf.dstu3.providers.ActivityDefinitionApplyProvider; import org.opencds.cqf.dstu3.providers.ApplyCqlOperationProvider; @@ -75,13 +75,12 @@ protected void initialize() throws ServletException { // Fhir Context this.fhirContext = appCtx.getBean(FhirContext.class); this.fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - this.fhirContext.registerCustomType(VersionedTerminologyRef.class); - this.fhirContext.registerCustomType(CodeTerminologyRef.class); - this.fhirContext.registerCustomType(PopulationCriteriaMap.class); - this.fhirContext.registerCustomType(CqfMeasure.class); + this.fhirContext.registerCustomType(VersionedTerminologyRef.class); + this.fhirContext.registerCustomType(CodeTerminologyRef.class); + this.fhirContext.registerCustomType(PopulationCriteriaMap.class); + this.fhirContext.registerCustomType(CqfMeasure.class); setFhirContext(this.fhirContext); - // System and Resource Daos IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); this.registry = appCtx.getBean(DaoRegistry.class); @@ -90,15 +89,20 @@ protected void initialize() throws ServletException { Object systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); registerProvider(systemProvider); - ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersDstu3", ResourceProviderFactory.class); + ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersDstu3", + ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, appCtx.getBean(DaoConfig.class)); + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, + appCtx.getBean(DaoConfig.class)); confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); setServerConformanceProvider(confProvider); - JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider(appCtx.getBean("terminologyService", ITermReadSvcDstu3.class), getFhirContext(), (ValueSetResourceProvider)this.getResourceProvider(ValueSet.class)); - EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, localSystemTerminologyProvider); + JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( + appCtx.getBean("terminologyService", ITermReadSvcDstu3.class), getFhirContext(), + (ValueSetResourceProvider) this.getResourceProvider(ValueSet.class)); + EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, + localSystemTerminologyProvider); resolveProviders(providerFactory, localSystemTerminologyProvider, this.registry); @@ -124,37 +128,36 @@ protected void initialize() throws ServletException { setDefaultResponseEncoding(HapiProperties.getDefaultEncoding()); /* - * This configures the server to page search results to and from - * the database, instead of only paging them to memory. This may mean - * a performance hit when performing searches that return lots of results, - * but makes the server much more scalable. + * This configures the server to page search results to and from the database, + * instead of only paging them to memory. This may mean a performance hit when + * performing searches that return lots of results, but makes the server much + * more scalable. */ setPagingProvider(appCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * This interceptor formats the output using nice colourful - * HTML output when the request is detected to come from a - * browser. + * This interceptor formats the output using nice colourful HTML output when the + * request is detected to come from a browser. */ - ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx.getBean(ResponseHighlighterInterceptor.class); + ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx + .getBean(ResponseHighlighterInterceptor.class); this.registerInterceptor(responseHighlighterInterceptor); /* * If you are hosting this server at a specific DNS name, the server will try to * figure out the FHIR base URL based on what the web container tells it, but - * this doesn't always work. If you are setting links in your search bundles that - * just refer to "localhost", you might want to use a server address strategy: + * this doesn't always work. If you are setting links in your search bundles + * that just refer to "localhost", you might want to use a server address + * strategy: */ String serverAddress = HapiProperties.getServerAddress(); - if (serverAddress != null && serverAddress.length() > 0) - { + if (serverAddress != null && serverAddress.length() > 0) { setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); } registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); - if (HapiProperties.getCorsEnabled()) - { + if (HapiProperties.getCorsEnabled()) { CorsConfiguration config = new CorsConfiguration(); config.addAllowedHeader("x-fhir-starter"); config.addAllowedHeader("Origin"); @@ -174,32 +177,33 @@ protected void initialize() throws ServletException { CorsInterceptor interceptor = new CorsInterceptor(config); registerInterceptor(interceptor); } - } - - protected NarrativeProvider getNarrativeProvider() { - return new NarrativeProvider(); - } + } + + protected NarrativeProvider getNarrativeProvider() { + return new NarrativeProvider(); + } - // Since resource provider resolution not lazy, the providers here must be resolved in the correct + // Since resource provider resolution not lazy, the providers here must be + // resolved in the correct // order of dependencies. - private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) - throws ServletException - { + private void resolveProviders(EvaluationProviderFactory providerFactory, + JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { NarrativeProvider narrativeProvider = this.getNarrativeProvider(); HQMFProvider hqmfProvider = new HQMFProvider(); // Code System Update - CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider( - this.getDao(ValueSet.class), - this.getDao(CodeSystem.class)); + CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), + this.getDao(CodeSystem.class)); this.registerProvider(csUpdate); // Cache Value Sets - CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), this.getDao(Endpoint.class)); + CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), + this.getDao(Endpoint.class)); this.registerProvider(cvs); - //Library processing - LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider((LibraryResourceProvider)this.getResourceProvider(Library.class), narrativeProvider); + // Library processing + LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider( + (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider); this.registerProvider(libraryProvider); // CQL Execution @@ -207,23 +211,27 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerm this.registerProvider(cql); // Bundle processing - ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, this.getDao(Bundle.class)); + ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, + this.getDao(Bundle.class)); this.registerProvider(bundleProvider); // Measure processing - MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, narrativeProvider, hqmfProvider, - libraryProvider, (MeasureResourceProvider)this.getResourceProvider(Measure.class)); + MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, + narrativeProvider, hqmfProvider, libraryProvider, + (MeasureResourceProvider) this.getResourceProvider(Measure.class)); this.registerProvider(measureProvider); // // ActivityDefinition processing - ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, this.getDao(ActivityDefinition.class)); + ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, + this.getDao(ActivityDefinition.class)); this.registerProvider(actDefProvider); - - JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, new SearchParameterResolver(this.fhirContext)); + JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, + new SearchParameterResolver(this.fhirContext)); // PlanDefinition processing - PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); + PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, + this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); this.registerProvider(planDefProvider); CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); @@ -236,9 +244,8 @@ protected IFhirResourceDao getDao(Class clazz) { return this.registry.getResourceDao(clazz); } - - protected BaseJpaResourceProvider getResourceProvider(Class clazz) { - return (BaseJpaResourceProvider ) this.getResourceProviders().stream() - .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); + protected BaseJpaResourceProvider getResourceProvider(Class clazz) { + return (BaseJpaResourceProvider) this.getResourceProviders().stream() + .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 269f7ce04..47092c902 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -37,10 +37,10 @@ import org.opencds.cqf.common.exceptions.InvalidRequestException; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; -import org.opencds.cqf.cql.data.CompositeDataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.execution.LibraryLoader; -import org.opencds.cqf.cql.model.Dstu3FhirModelResolver; +import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; import org.opencds.cqf.dstu3.helpers.LibraryHelper; import org.opencds.cqf.dstu3.providers.JpaTerminologyProvider; import org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider; @@ -52,8 +52,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @WebServlet(name = "cds-services") -public class CdsHooksServlet extends HttpServlet -{ +public class CdsHooksServlet extends HttpServlet { private FhirVersionEnum version = FhirVersionEnum.DSTU3; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); @@ -65,7 +64,6 @@ public class CdsHooksServlet extends HttpServlet private static JpaTerminologyProvider jpaTerminologyProvider; - // TODO: There's probably a way to wire this all up using Spring public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; @@ -86,23 +84,20 @@ public static void setSystemTerminologyProvider(JpaTerminologyProvider jpaTermin // CORS Pre-flight @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { setAccessControlHeaders(resp); resp.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); resp.setHeader("X-Content-Type-Options", "nosniff"); - + resp.setStatus(HttpServletResponse.SC_OK); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException - { + throws ServletException, IOException { logger.info(request.getRequestURI()); - if (!request.getRequestURL().toString().endsWith("cds-services")) - { + if (!request.getRequestURL().toString().endsWith("cds-services")) { logger.error(request.getRequestURI()); throw new ServletException("This servlet is not configured to handle GET requests."); } @@ -114,56 +109,48 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException - { + throws ServletException, IOException { logger.info(request.getRequestURI()); try { // validate that we are dealing with JSON - if (!request.getContentType().startsWith("application/json")) - { - throw new ServletException( - String.format( - "Invalid content type %s. Please use application/json.", - request.getContentType() - ) - ); + if (!request.getContentType().startsWith("application/json")) { + throw new ServletException(String.format("Invalid content type %s. Please use application/json.", + request.getContentType())); } - String baseUrl = - request.getRequestURL().toString() - .replace(request.getPathInfo(), "").replace(request.getServletPath(), "") + "/fhir"; + String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") + .replace(request.getServletPath(), "") + "/fhir"; String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); - Request cdsHooksRequest = - new Request( - service, - parser.parse(request.getReader()).getAsJsonObject(), - JsonHelper.getObjectRequired(getService(service), "prefetch") - ); + Request cdsHooksRequest = new Request(service, parser.parse(request.getReader()).getAsJsonObject(), + JsonHelper.getObjectRequired(getService(service), "prefetch")); logger.info(cdsHooksRequest.getRequestJson().toString()); Hook hook = HookFactory.createHook(cdsHooksRequest); - PlanDefinition planDefinition = planDefinitionProvider.getDao().read(new IdType(hook.getRequest().getServiceName())); + PlanDefinition planDefinition = planDefinitionProvider.getDao() + .read(new IdType(hook.getRequest().getServiceName())); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResolutionProvider); - Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader,libraryResolutionProvider); + Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, + libraryResolutionProvider); Dstu3FhirModelResolver resolver = new Dstu3FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); Context context = new Context(library); - context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote provider case + context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote + // provider case context.registerTerminologyProvider(jpaTerminologyProvider); context.registerLibraryLoader(libraryLoader); context.setContextValue("Patient", hook.getRequest().getContext().getPatientId().replace("Patient/", "")); context.setExpressionCaching(true); - - EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, FhirContext.forDstu3().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, planDefinition); - + EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, + FhirContext.forDstu3().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, + planDefinition); this.setAccessControlHeaders(response); @@ -176,15 +163,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) logger.info(jsonResponse); response.getWriter().println(jsonResponse); - } - catch (BaseServerResponseException e){ + } catch (BaseServerResponseException e) { this.setAccessControlHeaders(response); switch (e.getStatusCode()) { case 401: case 403: case 404: - response.getWriter().println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); + response.getWriter() + .println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); response.getWriter().println(e.getMessage()); response.setStatus(412); break; @@ -193,8 +180,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println(e.getMessage()); response.setStatus(500); } - } - catch(Exception e) { + } catch (Exception e) { this.setAccessControlHeaders(response); StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); @@ -205,61 +191,50 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } - private JsonObject getService(String service) - { + private JsonObject getService(String service) { JsonArray services = getServices().get("services").getAsJsonArray(); List ids = new ArrayList<>(); - for (JsonElement element : services) - { - if (element.isJsonObject() && element.getAsJsonObject().has("id")) - { + for (JsonElement element : services) { + if (element.isJsonObject() && element.getAsJsonObject().has("id")) { ids.add(element.getAsJsonObject().get("id").getAsString()); - if (element.isJsonObject() && element.getAsJsonObject().get("id").getAsString().equals(service)) - { + if (element.isJsonObject() && element.getAsJsonObject().get("id").getAsString().equals(service)) { return element.getAsJsonObject(); } } } - throw new InvalidRequestException("Cannot resolve service: " + service + "\nAvailable services: " + ids.toString()); + throw new InvalidRequestException( + "Cannot resolve service: " + service + "\nAvailable services: " + ids.toString()); } - private JsonObject getServices() - { - return new DiscoveryResolutionStu3(FhirContext.forDstu3().newRestfulGenericClient(HapiProperties.getServerAddress())).resolve().getAsJson(); + private JsonObject getServices() { + return new DiscoveryResolutionStu3( + FhirContext.forDstu3().newRestfulGenericClient(HapiProperties.getServerAddress())).resolve() + .getAsJson(); } - private String toJsonResponse(List cards) - { + private String toJsonResponse(List cards) { JsonObject ret = new JsonObject(); JsonArray cardArray = new JsonArray(); - for (CdsCard card : cards) - { + for (CdsCard card : cards) { cardArray.add(card.toJson()); } ret.add("cards", cardArray); Gson gson = new GsonBuilder().setPrettyPrinting().create(); - return gson.toJson(ret); + return gson.toJson(ret); } - private void setAccessControlHeaders(HttpServletResponse resp) { - if (HapiProperties.getCorsEnabled()) - { + if (HapiProperties.getCorsEnabled()) { resp.setHeader("Access-Control-Allow-Origin", HapiProperties.getCorsAllowedOrigin()); - resp.setHeader("Access-Control-Allow-Methods", String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); - resp.setHeader("Access-Control-Allow-Headers", - String.join(", ", Arrays.asList( - "x-fhir-starter", - "Origin", - "Accept", - "X-Requested-With", - "Content-Type", - "Authorization", - "Cache-Control"))); - resp.setHeader("Access-Control-Expose-Headers", String.join(", ", Arrays.asList("Location", "Content-Location"))); + resp.setHeader("Access-Control-Allow-Methods", + String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); + resp.setHeader("Access-Control-Allow-Headers", String.join(", ", Arrays.asList("x-fhir-starter", "Origin", + "Accept", "X-Requested-With", "Content-Type", "Authorization", "Cache-Control"))); + resp.setHeader("Access-Control-Expose-Headers", + String.join(", ", Arrays.asList("Location", "Content-Location"))); resp.setHeader("Access-Control-Max-Age", "86400"); } } diff --git a/pom.xml b/pom.xml index 728edafe4..0248a7306 100644 --- a/pom.xml +++ b/pom.xml @@ -52,14 +52,14 @@ UTF-8 UTF-8 - 9.4.14.v20181114 - 2.10.1 - 10.14.2.0 - 4.1.0 - 1.0.4 - 1.3.12.1 - 1.3.19 - 1.0.3 + 9.4.14.v20181114 + 2.10.1 + 10.14.2.0 + 4.2.0 + 1.1.0-SNAPSHOT + 1.4.0-SNAPSHOT + 1.4.9-SNAPSHOT + 1.1.0-SNAPSHOT true @@ -95,7 +95,7 @@ org.opencds.cqf tooling - ${cqf_tooling_version} + ${cqf-tooling.version} org.slf4j @@ -105,9 +105,9 @@ - org.opencds.cqf - cql-engine - ${cql_engine_version} + org.opencds.cqf.cql + engine + ${cql-engine.version} org.slf4j @@ -117,9 +117,9 @@ - org.opencds.cqf - cql-engine-fhir - ${cql_engine_version} + org.opencds.cqf.cql + engine.fhir + ${cql-engine.version} org.slf4j @@ -131,76 +131,76 @@ info.cqframework cql-to-elm - ${cqframework_version} + ${cqframework.version} info.cqframework cql-formatter - ${cqframework_version} + ${cqframework.version} org.opencds.cqf cds - ${cds_hooks_version} + ${cds-hooks.version} com.fasterxml.jackson.core jackson-databind - ${jackson_version} + ${jackson.version} com.fasterxml.jackson.core jackson-core - ${jackson_version} + ${jackson.version} ca.uhn.hapi.fhir hapi-fhir-base - ${hapi_version} + ${hapi.version} ca.uhn.hapi.fhir hapi-fhir-structures-dstu2 - ${hapi_version} + ${hapi.version} ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 - ${hapi_version} + ${hapi.version} ca.uhn.hapi.fhir hapi-fhir-converter - ${hapi_version} + ${hapi.version} ca.uhn.hapi.fhir hapi-fhir-jpaserver-base - ${hapi_version} + ${hapi.version} ca.uhn.hapi.fhir hapi-fhir-testpage-overlay - ${hapi_version} + ${hapi.version} war provided ca.uhn.hapi.fhir hapi-fhir-testpage-overlay - ${hapi_version} + ${hapi.version} classes provided @@ -256,17 +256,17 @@ org.apache.derby derby - ${derby_version} + ${derby.version} org.apache.derby derbynet - ${derby_version} + ${derby.version} org.apache.derby derbyclient - ${derby_version} + ${derby.version} @@ -291,7 +291,7 @@ org.eclipse.jetty jetty-servlets - ${jetty_version} + ${jetty.version} @@ -358,7 +358,7 @@ org.eclipse.jetty jetty-maven-plugin - ${jetty_version} + ${jetty.version} org.apache.maven.plugins @@ -447,7 +447,7 @@ - + org.apache.maven.plugins maven-failsafe-plugin 2.19.1 @@ -463,7 +463,7 @@ - + org.apache.maven.plugins maven-surefire-plugin 2.21.0 @@ -479,22 +479,22 @@ 1.1.0 true - resolveCiFriendliesOnly + ossrh - flatten - process-resources - - flatten - + flatten + process-resources + + flatten + - flatten.clean - clean - - clean - + flatten.clean + clean + + clean + diff --git a/r4/pom.xml b/r4/pom.xml index ad47d59a4..13c4ef01b 100644 --- a/r4/pom.xml +++ b/r4/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 @@ -19,7 +20,7 @@ org.opencds.cqf tooling - ${cqf_tooling_version} + ${cqf-tooling.version} diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ActivityDefinitionBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ActivityDefinitionBuilder.java index df65187a8..3dad08b25 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ActivityDefinitionBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ActivityDefinitionBuilder.java @@ -1,24 +1,25 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.ActivityDefinition; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Expression; +import org.opencds.cqf.common.builders.BaseBuilder; public class ActivityDefinitionBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. public ActivityDefinitionBuilder() { super(new ActivityDefinition()); } - public ActivityDefinitionBuilder(ActivityDefinition activityDefinition ) { + public ActivityDefinitionBuilder(ActivityDefinition activityDefinition) { super(activityDefinition); } - public ActivityDefinitionBuilder buildIdentification(String url, String version, Enumerations.PublicationStatus status) { + public ActivityDefinitionBuilder buildIdentification(String url, String version, + Enumerations.PublicationStatus status) { complexProperty.setUrl(url); complexProperty.setVersion(version); complexProperty.setStatus(status); @@ -72,20 +73,17 @@ public ActivityDefinitionBuilder buildBodySite(Coding coding) { return this; } - public ActivityDefinitionBuilder buildDynamicValue(String description, String path, String language, String expression) { + public ActivityDefinitionBuilder buildDynamicValue(String description, String path, String language, + String expression) { ActivityDefinition.ActivityDefinitionDynamicValueComponent dynamicValueComponent = new ActivityDefinition.ActivityDefinitionDynamicValueComponent(); dynamicValueComponent.setPath(path); - dynamicValueComponent.setExpression( - new Expression() - .setDescription(description) - .setLanguage(Expression.ExpressionLanguage.fromCode(language).toCode()) - .setExpression(expression) - ); + dynamicValueComponent.setExpression(new Expression().setDescription(description) + .setLanguage(Expression.ExpressionLanguage.fromCode(language).toCode()).setExpression(expression)); complexProperty.addDynamicValue(dynamicValueComponent); return this; } - public ActivityDefinitionBuilder buildCqlDynamicValue(String description, String path, String expression ) { + public ActivityDefinitionBuilder buildCqlDynamicValue(String description, String path, String expression) { return this.buildDynamicValue(description, path, "text/cql", expression); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/AnnotationBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/AnnotationBuilder.java index 94aeea7fc..a3c658812 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/AnnotationBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/AnnotationBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.Date; + import org.hl7.fhir.r4.model.Annotation; import org.hl7.fhir.r4.model.Type; - -import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; public class AnnotationBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/AttachmentBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/AttachmentBuilder.java index c006e6f3f..0cc6ae7a7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/AttachmentBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/AttachmentBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.List; + import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.Extension; - -import java.util.List; +import org.opencds.cqf.common.builders.BaseBuilder; public class AttachmentBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityBuilder.java index 297f1d660..8b363d91b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityBuilder.java @@ -1,11 +1,15 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.*; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.r4.model.Annotation; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanActivityBuilder extends BaseBuilder { public CarePlanActivityBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityDetailBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityDetailBuilder.java index 580193c70..abe108c33 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityDetailBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanActivityDetailBuilder.java @@ -1,12 +1,17 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.*; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SimpleQuantity; +import org.hl7.fhir.r4.model.Type; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanActivityDetailBuilder extends BaseBuilder { public CarePlanActivityDetailBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanBuilder.java index b0078ef18..5a3d4901a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/CarePlanBuilder.java @@ -1,12 +1,19 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.*; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Annotation; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Period; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CarePlanBuilder extends BaseBuilder { public CarePlanBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/CodeableConceptBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/CodeableConceptBuilder.java index 6aa8619b8..267ef2af2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/CodeableConceptBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/CodeableConceptBuilder.java @@ -1,12 +1,12 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; - import java.util.ArrayList; import java.util.List; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.opencds.cqf.common.builders.BaseBuilder; + public class CodeableConceptBuilder extends BaseBuilder { public CodeableConceptBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/CodingBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/CodingBuilder.java index cbf65548f..655aff2c8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/CodingBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/CodingBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.Coding; +import org.opencds.cqf.common.builders.BaseBuilder; public class CodingBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ExtensionBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ExtensionBuilder.java index fed3fdff8..fb3c569fc 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ExtensionBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ExtensionBuilder.java @@ -1,9 +1,9 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Type; +import org.opencds.cqf.common.builders.BaseBuilder; public class ExtensionBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/IdentifierBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/IdentifierBuilder.java index e67cf5fcb..f614e53d9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/IdentifierBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/IdentifierBuilder.java @@ -1,11 +1,11 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Period; import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class IdentifierBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/JavaDateBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/JavaDateBuilder.java index 30ef94af2..3c8443e7c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/JavaDateBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/JavaDateBuilder.java @@ -1,9 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.opencds.cqf.cql.runtime.DateTime; import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; +import org.opencds.cqf.cql.engine.runtime.DateTime; + public class JavaDateBuilder extends BaseBuilder { public JavaDateBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/LibraryBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/LibraryBuilder.java index 82bdcaddb..be08a20cc 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/LibraryBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/LibraryBuilder.java @@ -1,21 +1,22 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.io.UnsupportedEncodingException; + import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.codesystems.LibraryType; - -import java.io.UnsupportedEncodingException; +import org.opencds.cqf.common.builders.BaseBuilder; public class LibraryBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. - public LibraryBuilder(Library library ){ + public LibraryBuilder(Library library) { super(library); } + public LibraryBuilder() { this(new Library()); } @@ -41,12 +42,10 @@ public LibraryBuilder buildExperimental(boolean experimental) { } public LibraryBuilder buildType(LibraryType libraryType) { - CodeableConcept codeableConcept = - new CodeableConceptBuilder().buildCoding( - new CodingBuilder() - .buildCode(libraryType.getSystem(), libraryType.toCode(), libraryType.getDisplay()) - .build() - ).build(); + CodeableConcept codeableConcept = new CodeableConceptBuilder() + .buildCoding(new CodingBuilder() + .buildCode(libraryType.getSystem(), libraryType.toCode(), libraryType.getDisplay()).build()) + .build(); complexProperty.setType(codeableConcept); return this; @@ -55,7 +54,7 @@ public LibraryBuilder buildType(LibraryType libraryType) { public LibraryBuilder buildCqlContent(String cqlString) throws UnsupportedEncodingException { Attachment attachment = new Attachment(); attachment.setContentType("text/cql"); - byte[] cqlData =cqlString.getBytes("utf-8"); + byte[] cqlData = cqlString.getBytes("utf-8"); attachment.setData(cqlData); complexProperty.addContent(attachment); return this; diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/MeasureReportBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/MeasureReportBuilder.java index f1892e414..c04e1559b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/MeasureReportBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/MeasureReportBuilder.java @@ -1,14 +1,14 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.Date; + import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Period; import org.hl7.fhir.r4.model.Reference; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.cql.runtime.Interval; - -import java.util.Date; +import org.opencds.cqf.common.builders.BaseBuilder; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; public class MeasureReportBuilder extends BaseBuilder { public MeasureReportBuilder() { @@ -48,19 +48,13 @@ public MeasureReportBuilder buildPatientReference(String patientRef) { public MeasureReportBuilder buildPeriod(Interval period) { Object start = period.getStart(); if (start instanceof DateTime) { - this.complexProperty.setPeriod( - new Period() - .setStart(Date.from(((DateTime) start).getDateTime().toInstant())) - .setEnd(Date.from(((DateTime) period.getEnd()).getDateTime().toInstant())) - ); - } - else if (start instanceof Date) { - this.complexProperty.setPeriod( - new Period() - .setStart((Date) period.getStart()) - .setEnd((Date) period.getEnd()) - ); - } + this.complexProperty + .setPeriod(new Period().setStart(Date.from(((DateTime) start).getDateTime().toInstant())) + .setEnd(Date.from(((DateTime) period.getEnd()).getDateTime().toInstant()))); + } else if (start instanceof Date) { + this.complexProperty + .setPeriod(new Period().setStart((Date) period.getStart()).setEnd((Date) period.getEnd())); + } return this; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/OperationOutcomeBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/OperationOutcomeBuilder.java index d87afdbb0..503569545 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/OperationOutcomeBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/OperationOutcomeBuilder.java @@ -1,24 +1,18 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.OperationOutcome; +import org.opencds.cqf.common.builders.BaseBuilder; -public class OperationOutcomeBuilder extends BaseBuilder -{ - public OperationOutcomeBuilder() - { +public class OperationOutcomeBuilder extends BaseBuilder { + public OperationOutcomeBuilder() { super(new OperationOutcome()); } - public OperationOutcomeBuilder buildIssue(String severity, String code, String details) - { - complexProperty.addIssue( - new OperationOutcome.OperationOutcomeIssueComponent() - .setSeverity(OperationOutcome.IssueSeverity.fromCode(severity)) - .setCode(OperationOutcome.IssueType.fromCode(code)) - .setDetails(new CodeableConcept().setText(details)) - ); + public OperationOutcomeBuilder buildIssue(String severity, String code, String details) { + complexProperty.addIssue(new OperationOutcome.OperationOutcomeIssueComponent() + .setSeverity(OperationOutcome.IssueSeverity.fromCode(severity)) + .setCode(OperationOutcome.IssueType.fromCode(code)).setDetails(new CodeableConcept().setText(details))); return this; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/PeriodBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/PeriodBuilder.java index 1e72904b9..8cc6335a8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/PeriodBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/PeriodBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.Period; - import java.util.Date; +import org.hl7.fhir.r4.model.Period; +import org.opencds.cqf.common.builders.BaseBuilder; + public class PeriodBuilder extends BaseBuilder { public PeriodBuilder() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/PractitionerBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/PractitionerBuilder.java index ddcb66676..560f0521e 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/PractitionerBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/PractitionerBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.Practitioner; +import org.opencds.cqf.common.builders.BaseBuilder; public class PractitionerBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ReferenceBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ReferenceBuilder.java index 2699295e3..fd01a55e2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ReferenceBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ReferenceBuilder.java @@ -1,8 +1,8 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Reference; +import org.opencds.cqf.common.builders.BaseBuilder; public class ReferenceBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/RelatedArtifactBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/RelatedArtifactBuilder.java index c70053d85..c6b88e246 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/RelatedArtifactBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/RelatedArtifactBuilder.java @@ -1,9 +1,9 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.RelatedArtifact; +import org.opencds.cqf.common.builders.BaseBuilder; public class RelatedArtifactBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupActionBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupActionBuilder.java index 8bcc47996..eb0d4256b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupActionBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupActionBuilder.java @@ -1,11 +1,17 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.*; - import java.util.Collections; import java.util.List; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.RelatedArtifact; +import org.hl7.fhir.r4.model.RequestGroup; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; +import org.opencds.cqf.common.builders.BaseBuilder; + public class RequestGroupActionBuilder extends BaseBuilder { public RequestGroupActionBuilder() { @@ -50,7 +56,8 @@ public RequestGroupActionBuilder buildResourceTarget(Resource resource) { } public RequestGroupActionBuilder buildExtension(String extension) { - complexProperty.setExtension(Collections.singletonList(new Extension().setUrl("http://example.org").setValue(new StringType(extension)))); + complexProperty.setExtension(Collections + .singletonList(new Extension().setUrl("http://example.org").setValue(new StringType(extension)))); return this; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java index 7bab5c9e2..517034dd7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java @@ -1,11 +1,11 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.List; + import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.RequestGroup; - -import java.util.List; +import org.opencds.cqf.common.builders.BaseBuilder; public class RequestGroupBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ServiceRequestBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ServiceRequestBuilder.java index 3190b7bc4..3de92916d 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ServiceRequestBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ServiceRequestBuilder.java @@ -1,25 +1,24 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ServiceRequest; +import org.opencds.cqf.common.builders.BaseBuilder; public class ServiceRequestBuilder extends BaseBuilder { - // TODO - this is a start, but should be extended for completeness. + // TODO - this is a start, but should be extended for completeness. public ServiceRequestBuilder() { - this( new ServiceRequest()); + this(new ServiceRequest()); } public ServiceRequestBuilder(ServiceRequest complexProperty) { super(complexProperty); } - public ServiceRequestBuilder buildId(String id) { complexProperty.setId(id); return this; @@ -41,12 +40,8 @@ public ServiceRequestBuilder buildStatus(String status) { } public ServiceRequestBuilder buildCode(Coding coding) { - complexProperty.setCode( - new CodeableConceptBuilder() - .buildCoding(coding) - .build() - ); - return this; + complexProperty.setCode(new CodeableConceptBuilder().buildCoding(coding).build()); + return this; } public ServiceRequestBuilder buildIntent(ServiceRequest.ServiceRequestIntent intent) { @@ -80,11 +75,7 @@ public ServiceRequestBuilder buildPriority(String priority) { } public ServiceRequestBuilder buildSubject(String subject) { - complexProperty.setSubject( - new ReferenceBuilder() - .buildReference(subject) - .build() - ); + complexProperty.setSubject(new ReferenceBuilder().buildReference(subject).build()); return this; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructureMapGroupBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructureMapGroupBuilder.java index 401bdecc7..8a03f5123 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructureMapGroupBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructureMapGroupBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructureMapGroupBuilder extends BaseBuilder { @@ -28,7 +28,6 @@ public StructureMapGroupBuilder buildDocumentation(String description) { return this; } - public StructureMapGroupBuilder buildInputSource(String name) { StructureMap.StructureMapGroupInputComponent input = new StructureMap.StructureMapGroupInputComponent(); input.setName(name); @@ -46,7 +45,8 @@ public StructureMapGroupBuilder buildInputTarget(String name, String fhirType) { return this; } - public StructureMapGroupBuilder buildRule(StructureMap.StructureMapGroupRuleComponent structureMapGroupRuleComponent) { + public StructureMapGroupBuilder buildRule( + StructureMap.StructureMapGroupRuleComponent structureMapGroupRuleComponent) { complexProperty.addRule(structureMapGroupRuleComponent); return this; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapBuilder.java index 9233a5dce..9722a0c95 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import java.util.UUID; + import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.StructureMap; - -import java.util.UUID; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapBuilder extends BaseBuilder { @@ -12,7 +12,7 @@ public StructuredMapBuilder() { super(new StructureMap()); } - public StructuredMapBuilder(StructureMap activityDefinition ) { + public StructuredMapBuilder(StructureMap activityDefinition) { super(activityDefinition); } @@ -20,6 +20,7 @@ public StructuredMapBuilder buildUrl(String url) { complexProperty.setUrl(UUID.randomUUID().toString()); return this; } + public StructuredMapBuilder buildRandomUrl() { return buildUrl("urn:uuid:" + UUID.randomUUID().toString()); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleBuilder.java index 02154dc2b..3ed6e7f80 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleBuilder extends BaseBuilder { @@ -30,17 +30,13 @@ public StructuredMapRuleBuilder buildTarget(StructureMap.StructureMapGroupRuleTa public StructuredMapRuleBuilder buildTargetSetValue(String target, String targetField, String value) { return buildTarget( - new StructuredMapRuleTargetBuilder() - .buildTransformSetValue(target, targetField, value) - .build()); + new StructuredMapRuleTargetBuilder().buildTransformSetValue(target, targetField, value).build()); } - public StructuredMapRuleBuilder buildTarget(String target, String targetField, StructureMap.StructureMapTransform transform, String...params) - { + public StructuredMapRuleBuilder buildTarget(String target, String targetField, + StructureMap.StructureMapTransform transform, String... params) { return buildTarget( - new StructuredMapRuleTargetBuilder() - .buildTransform(target, targetField, transform, params) - .build()); + new StructuredMapRuleTargetBuilder().buildTransform(target, targetField, transform, params).build()); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleSourceBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleSourceBuilder.java index 0ca525bfd..20a6b77d9 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleSourceBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleSourceBuilder.java @@ -1,13 +1,13 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleSourceBuilder extends BaseBuilder { public StructuredMapRuleSourceBuilder() { - this( new StructureMap.StructureMapGroupRuleSourceComponent() ); + this(new StructureMap.StructureMapGroupRuleSourceComponent()); } public StructuredMapRuleSourceBuilder(StructureMap.StructureMapGroupRuleSourceComponent complexProperty) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleTargetBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleTargetBuilder.java index cd7fde9a6..fb2f72c4f 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleTargetBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapRuleTargetBuilder.java @@ -1,13 +1,13 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapRuleTargetBuilder extends BaseBuilder { public StructuredMapRuleTargetBuilder() { - this( new StructureMap.StructureMapGroupRuleTargetComponent() ); + this(new StructureMap.StructureMapGroupRuleTargetComponent()); } public StructuredMapRuleTargetBuilder(StructureMap.StructureMapGroupRuleTargetComponent complexProperty) { @@ -31,39 +31,33 @@ public StructuredMapRuleTargetBuilder buildElement(String status) { public StructuredMapRuleTargetBuilder buildTransformCopy(String source) { complexProperty.setTransform(StructureMap.StructureMapTransform.COPY); - StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = - new StructureMap.StructureMapGroupRuleTargetParameterComponent(); + StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = new StructureMap.StructureMapGroupRuleTargetParameterComponent(); structureMapGroupRuleTargetParameterComponent.setValue(new StringType(source)); complexProperty.addParameter(structureMapGroupRuleTargetParameterComponent); return this; } public StructuredMapRuleTargetBuilder buildTransformSetValue(String context, String targetField, String value) { - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) - .buildElement(targetField) - .buildTransformCopy(value); + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) + .buildElement(targetField).buildTransformCopy(value); } public StructuredMapRuleTargetBuilder buildTransformSetUuid(String context, String targetField) { complexProperty.setTransform(StructureMap.StructureMapTransform.UUID); - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) .buildElement(targetField); } - public StructuredMapRuleTargetBuilder buildTransform(String context, String targetField, StructureMap.StructureMapTransform transform, String...parameters) - { + public StructuredMapRuleTargetBuilder buildTransform(String context, String targetField, + StructureMap.StructureMapTransform transform, String... parameters) { complexProperty.setTransform(transform); - for (String param: parameters){ - StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = - new StructureMap.StructureMapGroupRuleTargetParameterComponent(); + for (String param : parameters) { + StructureMap.StructureMapGroupRuleTargetParameterComponent structureMapGroupRuleTargetParameterComponent = new StructureMap.StructureMapGroupRuleTargetParameterComponent(); structureMapGroupRuleTargetParameterComponent.setValue(new StringType(param)); complexProperty.addParameter(structureMapGroupRuleTargetParameterComponent); } - return buildContext(context) - .buildContextType(StructureMap.StructureMapContextType.VARIABLE) + return buildContext(context).buildContextType(StructureMap.StructureMapContextType.VARIABLE) .buildElement(targetField); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapStructureBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapStructureBuilder.java index f345b6d83..0813ed915 100755 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapStructureBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/StructuredMapStructureBuilder.java @@ -1,7 +1,7 @@ package org.opencds.cqf.r4.builders;//package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; import org.hl7.fhir.r4.model.StructureMap; +import org.opencds.cqf.common.builders.BaseBuilder; public class StructuredMapStructureBuilder extends BaseBuilder { @@ -15,15 +15,15 @@ public StructuredMapStructureBuilder(StructureMap.StructureMapStructureComponent super(complexProperty); } -// public StructuredMapStructureBuilder buildSource(String s) { -// complexProperty.setUrl("someUrl"); -// complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); -// return this; -// } -// -// public StructuredMapStructureBuilder buildTarget(String s) { -// complexProperty.setUrl("someUrl"); -// complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); -// return this; -// } + // public StructuredMapStructureBuilder buildSource(String s) { + // complexProperty.setUrl("someUrl"); + // complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); + // return this; + // } + // + // public StructuredMapStructureBuilder buildTarget(String s) { + // complexProperty.setUrl("someUrl"); + // complexProperty.setMode( StructureMap.StructureMapModelMode.SOURCE); + // return this; + // } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetBuilder.java index fcad1867c..88df8809f 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetBuilder.java @@ -1,9 +1,9 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; +import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.exceptions.FHIRException; +import org.opencds.cqf.common.builders.BaseBuilder; public class ValueSetBuilder extends BaseBuilder { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetComposeBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetComposeBuilder.java index a6ec229bc..7f0122d4b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetComposeBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetComposeBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.ValueSet; - import java.util.List; +import org.hl7.fhir.r4.model.ValueSet; +import org.opencds.cqf.common.builders.BaseBuilder; + public class ValueSetComposeBuilder extends BaseBuilder { public ValueSetComposeBuilder(ValueSet.ValueSetComposeComponent complexProperty) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetIncludesBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetIncludesBuilder.java index 6017fdc9e..e5d1d0131 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetIncludesBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/ValueSetIncludesBuilder.java @@ -1,10 +1,10 @@ package org.opencds.cqf.r4.builders; -import org.opencds.cqf.common.builders.BaseBuilder; -import org.hl7.fhir.r4.model.ValueSet; - import java.util.List; +import org.hl7.fhir.r4.model.ValueSet; +import org.opencds.cqf.common.builders.BaseBuilder; + public class ValueSetIncludesBuilder extends BaseBuilder { public ValueSetIncludesBuilder(ValueSet.ConceptSetComponent complexProperty) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java b/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java index d687b5214..7e8b3e8ac 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java +++ b/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java @@ -1,10 +1,7 @@ package org.opencds.cqf.r4.config; -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.ParserOptions; -import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; -import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; import org.opencds.cqf.common.config.HapiProperties; import org.springframework.beans.factory.annotation.Autowired; @@ -13,12 +10,14 @@ import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import javax.persistence.EntityManagerFactory; -import javax.sql.DataSource; +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.ParserOptions; +import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; @Configuration -public class FhirServerConfigR4 extends BaseJavaConfigR4 -{ +public class FhirServerConfigR4 extends BaseJavaConfigR4 { protected final DataSource myDataSource; @Autowired @@ -27,20 +26,20 @@ public FhirServerConfigR4(DataSource myDataSource) { } @Override - public FhirContext fhirContextR4() { - FhirContext retVal = FhirContext.forR4(); + public FhirContext fhirContextR4() { + FhirContext retVal = FhirContext.forR4(); - // Don't strip versions in some places - ParserOptions parserOptions = retVal.getParserOptions(); - parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.what"); + // Don't strip versions in some places + ParserOptions parserOptions = retVal.getParserOptions(); + parserOptions.setDontStripVersionsFromReferencesAtPaths("AuditEvent.entity.what"); - return retVal; - } + return retVal; + } /** - * We override the paging provider definition so that we can customize - * the default/max page sizes for search results. You can set these however - * you want, although very large page sizes will require a lot of RAM. + * We override the paging provider definition so that we can customize the + * default/max page sizes for search results. You can set these however you + * want, although very large page sizes will require a lot of RAM. */ @Override public DatabaseBackedPagingProvider databaseBackedPagingProvider() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 8cfa09444..34bce7221 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -1,25 +1,37 @@ package org.opencds.cqf.r4.evaluation; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.ReferenceParam; -import org.hl7.fhir.r4.model.*; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Quantity; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.Interval; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.r4.builders.MeasureReportBuilder; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.Field; -import java.util.*; +import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.ReferenceParam; public class MeasureEvaluation { @@ -42,17 +54,19 @@ public MeasureReport evaluatePatientMeasure(Measure measure, Context context, St return evaluatePopulationMeasure(measure, context); } - Iterable patientRetrieve = provider.retrieve("Patient", "id", patientId, "Patient", null, null, null, null, null, null, null, null); + Iterable patientRetrieve = provider.retrieve("Patient", "id", patientId, "Patient", null, null, null, + null, null, null, null, null); Patient patient = null; if (patientRetrieve.iterator().hasNext()) { patient = (Patient) patientRetrieve.iterator().next(); } - return evaluate(measure, context, patient == null ? Collections.emptyList() : Collections.singletonList(patient), MeasureReport.MeasureReportType.INDIVIDUAL); + return evaluate(measure, context, + patient == null ? Collections.emptyList() : Collections.singletonList(patient), + MeasureReport.MeasureReportType.INDIVIDUAL); } - public MeasureReport evaluateSubjectListMeasure(Measure measure, Context context, String practitionerRef) - { + public MeasureReport evaluateSubjectListMeasure(Measure measure, Context context, String practitionerRef) { logger.info("Generating patient-list report"); List patients = practitionerRef == null ? getAllPatients() : getPractitionerPatients(practitionerRef); @@ -61,14 +75,8 @@ public MeasureReport evaluateSubjectListMeasure(Measure measure, Context context private List getPractitionerPatients(String practitionerRef) { SearchParameterMap map = new SearchParameterMap(); - map.add( - "general-practitioner", - new ReferenceParam( - practitionerRef.startsWith("Practitioner/") - ? practitionerRef - : "Practitioner/" + practitionerRef - ) - ); + map.add("general-practitioner", new ReferenceParam( + practitionerRef.startsWith("Practitioner/") ? practitionerRef : "Practitioner/" + practitionerRef)); List patients = new ArrayList<>(); IBundleProvider patientProvider = registry.getResourceDao("Patient").search(map); @@ -91,7 +99,8 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY); } - private Iterable evaluateCriteria(Context context, Patient patient, Measure.MeasureGroupPopulationComponent pop) { + private Iterable evaluateCriteria(Context context, Patient patient, + Measure.MeasureGroupPopulationComponent pop) { if (!pop.hasCriteria()) { return Collections.emptyList(); } @@ -103,9 +112,9 @@ private Iterable evaluateCriteria(Context context, Patient patient, Me try { Field privateField = Context.class.getDeclaredField("expressions"); privateField.setAccessible(true); - LinkedHashMap expressions = (LinkedHashMap)privateField.get(context); + LinkedHashMap expressions = (LinkedHashMap) privateField.get(context); expressions.clear(); - + } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -114,23 +123,22 @@ private Iterable evaluateCriteria(Context context, Patient patient, Me if (result == null) { Collections.emptyList(); } - + if (result instanceof Boolean) { - if (((Boolean)result)) { + if (((Boolean) result)) { return Collections.singletonList(patient); - } - else { + } else { return Collections.emptyList(); } } - return (Iterable)result; + return (Iterable) result; } private boolean evaluatePopulationCriteria(Context context, Patient patient, - Measure.MeasureGroupPopulationComponent criteria, HashMap population, HashMap populationPatients, - Measure.MeasureGroupPopulationComponent exclusionCriteria, HashMap exclusionPopulation, HashMap exclusionPatients - ) { + Measure.MeasureGroupPopulationComponent criteria, HashMap population, + HashMap populationPatients, Measure.MeasureGroupPopulationComponent exclusionCriteria, + HashMap exclusionPopulation, HashMap exclusionPatients) { boolean inPopulation = false; if (criteria != null) { for (Resource resource : evaluateCriteria(context, patient, criteria)) { @@ -160,7 +168,10 @@ private boolean evaluatePopulationCriteria(Context context, Patient patient, return inPopulation; } - private void addPopulationCriteriaReport(MeasureReport report, MeasureReport.MeasureReportGroupComponent reportGroup, Measure.MeasureGroupPopulationComponent populationCriteria, int populationCount, Iterable patientPopulation) { + private void addPopulationCriteriaReport(MeasureReport report, + MeasureReport.MeasureReportGroupComponent reportGroup, + Measure.MeasureGroupPopulationComponent populationCriteria, int populationCount, + Iterable patientPopulation) { if (populationCriteria != null) { MeasureReport.MeasureReportGroupPopulationComponent populationReport = new MeasureReport.MeasureReportGroupPopulationComponent(); populationReport.setCode(populationCriteria.getCode()); @@ -170,36 +181,37 @@ private void addPopulationCriteriaReport(MeasureReport report, MeasureReport.Mea populationReport.setSubjectResults(new Reference().setReference("#" + SUBJECTLIST.getId())); for (Patient patient : patientPopulation) { ListResource.ListEntryComponent entry = new ListResource.ListEntryComponent() - .setItem(new Reference().setReference( - patient.getIdElement().getIdPart().startsWith("Patient/") ? - patient.getIdElement().getIdPart() : - String.format("Patient/%s", patient.getIdElement().getIdPart())) + .setItem(new Reference() + .setReference(patient.getIdElement().getIdPart().startsWith("Patient/") + ? patient.getIdElement().getIdPart() + : String.format("Patient/%s", patient.getIdElement().getIdPart())) .setDisplay(patient.getNameFirstRep().getNameAsSingleString())); SUBJECTLIST.addEntry(entry); } report.addContained(SUBJECTLIST); } - populationReport.setCount(populationCount); + populationReport.setCount(populationCount); reportGroup.addPopulation(populationReport); } } - private MeasureReport evaluate(Measure measure, Context context, List patients, MeasureReport.MeasureReportType type) - { + private MeasureReport evaluate(Measure measure, Context context, List patients, + MeasureReport.MeasureReportType type) { MeasureReportBuilder reportBuilder = new MeasureReportBuilder(); reportBuilder.buildStatus("complete"); reportBuilder.buildType(type); - reportBuilder.buildMeasureReference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart()); + reportBuilder.buildMeasureReference( + measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart()); if (type == MeasureReport.MeasureReportType.INDIVIDUAL && !patients.isEmpty()) { - IdType patientId = patients.get(0).getIdElement(); + IdType patientId = patients.get(0).getIdElement(); reportBuilder.buildPatientReference(patientId.getResourceType() + "/" + patientId.getIdPart()); } reportBuilder.buildPeriod(measurementPeriod); MeasureReport report = reportBuilder.build(); - HashMap resources = new HashMap<>(); - HashMap> codeToResourceMap = new HashMap<>(); + HashMap resources = new HashMap<>(); + HashMap> codeToResourceMap = new HashMap<>(); MeasureScoring measureScoring = MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode()); if (measureScoring == null) { @@ -207,12 +219,13 @@ private MeasureReport evaluate(Measure measure, Context context, List p } for (Measure.MeasureGroupComponent group : measure.getGroup()) { - MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent(); - reportGroup.setId(group.getId()); + MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent(); + reportGroup.setId(group.getId()); report.getGroup().add(reportGroup); // Declare variables to avoid a hash lookup on every patient - // TODO: Isn't quite right, there may be multiple initial populations for a ratio measure... + // TODO: Isn't quite right, there may be multiple initial populations for a + // ratio measure... Measure.MeasureGroupPopulationComponent initialPopulationCriteria = null; Measure.MeasureGroupPopulationComponent numeratorCriteria = null; Measure.MeasureGroupPopulationComponent numeratorExclusionCriteria = null; @@ -244,7 +257,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap measurePopulationExclusionPatients = null; for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) { - MeasurePopulationType populationType = MeasurePopulationType.fromCode(pop.getCode().getCodingFirstRep().getCode()); + MeasurePopulationType populationType = MeasurePopulationType + .fromCode(pop.getCode().getCodingFirstRep().getCode()); if (populationType != null) { switch (populationType) { case INITIALPOPULATION: @@ -317,36 +331,43 @@ private MeasureReport evaluate(Measure measure, Context context, List p for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); if (inInitialPopulation) { // Are they in the denominator? - boolean inDenominator = evaluatePopulationCriteria(context, patient, - denominatorCriteria, denominator, denominatorPatients, - denominatorExclusionCriteria, denominatorExclusion, denominatorExclusionPatients); - populateResourceMap(context, MeasurePopulationType.DENOMINATOR, resources, codeToResourceMap); + boolean inDenominator = evaluatePopulationCriteria(context, patient, denominatorCriteria, + denominator, denominatorPatients, denominatorExclusionCriteria, + denominatorExclusion, denominatorExclusionPatients); + populateResourceMap(context, MeasurePopulationType.DENOMINATOR, resources, + codeToResourceMap); if (inDenominator) { // Are they in the numerator? - boolean inNumerator = evaluatePopulationCriteria(context, patient, - numeratorCriteria, numerator, numeratorPatients, - numeratorExclusionCriteria, numeratorExclusion, numeratorExclusionPatients); - populateResourceMap(context, MeasurePopulationType.NUMERATOR, resources, codeToResourceMap); + boolean inNumerator = evaluatePopulationCriteria(context, patient, numeratorCriteria, + numerator, numeratorPatients, numeratorExclusionCriteria, numeratorExclusion, + numeratorExclusionPatients); + populateResourceMap(context, MeasurePopulationType.NUMERATOR, resources, + codeToResourceMap); if (!inNumerator && inDenominator && (denominatorExceptionCriteria != null)) { // Are they in the denominator exception? boolean inException = false; - for (Resource resource : evaluateCriteria(context, patient, denominatorExceptionCriteria)) { + for (Resource resource : evaluateCriteria(context, patient, + denominatorExceptionCriteria)) { inException = true; denominatorException.put(resource.getIdElement().getIdPart(), resource); denominator.remove(resource.getIdElement().getIdPart()); - populateResourceMap(context, MeasurePopulationType.DENOMINATOREXCEPTION, resources, codeToResourceMap); + populateResourceMap(context, MeasurePopulationType.DENOMINATOREXCEPTION, + resources, codeToResourceMap); } if (inException) { if (denominatorExceptionPatients != null) { - denominatorExceptionPatients.put(patient.getIdElement().getIdPart(), patient); + denominatorExceptionPatients.put(patient.getIdElement().getIdPart(), + patient); } if (denominatorPatients != null) { denominatorPatients.remove(patient.getIdElement().getIdPart()); @@ -359,7 +380,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p // Calculate actual measure score, Count(numerator) / Count(denominator) if (denominator != null && numerator != null && denominator.size() > 0) { - reportGroup.setMeasureScore(new Quantity(numerator.size() / (double)denominator.size())); + reportGroup.setMeasureScore(new Quantity(numerator.size() / (double) denominator.size())); } break; @@ -370,19 +391,23 @@ private MeasureReport evaluate(Measure measure, Context context, List p for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); if (inInitialPopulation) { // Are they in the measure population? boolean inMeasurePopulation = evaluatePopulationCriteria(context, patient, measurePopulationCriteria, measurePopulation, measurePopulationPatients, - measurePopulationExclusionCriteria, measurePopulationExclusion, measurePopulationExclusionPatients); + measurePopulationExclusionCriteria, measurePopulationExclusion, + measurePopulationExclusionPatients); if (inMeasurePopulation) { // TODO: Evaluate measure observations - for (Resource resource : evaluateCriteria(context, patient, measureObservationCriteria)) { + for (Resource resource : evaluateCriteria(context, patient, + measureObservationCriteria)) { measureObservation.put(resource.getIdElement().getIdPart(), resource); } } @@ -396,9 +421,11 @@ private MeasureReport evaluate(Measure measure, Context context, List p // For each patient in the patient list for (Patient patient : patients) { // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, - initialPopulation, initialPopulationPatients, null, null, null); - populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, + null); + populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, + codeToResourceMap); } break; @@ -406,14 +433,30 @@ private MeasureReport evaluate(Measure measure, Context context, List p } // Add population reports for each group - addPopulationCriteriaReport(report, reportGroup, initialPopulationCriteria, initialPopulation != null ? initialPopulation.size() : 0, initialPopulationPatients != null ? initialPopulationPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, numeratorCriteria, numerator != null ? numerator.size() : 0, numeratorPatients != null ? numeratorPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, numeratorExclusionCriteria, numeratorExclusion != null ? numeratorExclusion.size() : 0, numeratorExclusionPatients != null ? numeratorExclusionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorCriteria, denominator != null ? denominator.size() : 0, denominatorPatients != null ? denominatorPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorExclusionCriteria, denominatorExclusion != null ? denominatorExclusion.size() : 0, denominatorExclusionPatients != null ? denominatorExclusionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, denominatorExceptionCriteria, denominatorException != null ? denominatorException.size() : 0, denominatorExceptionPatients != null ? denominatorExceptionPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, measurePopulationCriteria, measurePopulation != null ? measurePopulation.size() : 0, measurePopulationPatients != null ? measurePopulationPatients.values() : null); - addPopulationCriteriaReport(report, reportGroup, measurePopulationExclusionCriteria, measurePopulationExclusion != null ? measurePopulationExclusion.size() : 0, measurePopulationExclusionPatients != null ? measurePopulationExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, initialPopulationCriteria, + initialPopulation != null ? initialPopulation.size() : 0, + initialPopulationPatients != null ? initialPopulationPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, numeratorCriteria, + numerator != null ? numerator.size() : 0, + numeratorPatients != null ? numeratorPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, numeratorExclusionCriteria, + numeratorExclusion != null ? numeratorExclusion.size() : 0, + numeratorExclusionPatients != null ? numeratorExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorCriteria, + denominator != null ? denominator.size() : 0, + denominatorPatients != null ? denominatorPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorExclusionCriteria, + denominatorExclusion != null ? denominatorExclusion.size() : 0, + denominatorExclusionPatients != null ? denominatorExclusionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, denominatorExceptionCriteria, + denominatorException != null ? denominatorException.size() : 0, + denominatorExceptionPatients != null ? denominatorExceptionPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, measurePopulationCriteria, + measurePopulation != null ? measurePopulation.size() : 0, + measurePopulationPatients != null ? measurePopulationPatients.values() : null); + addPopulationCriteriaReport(report, reportGroup, measurePopulationExclusionCriteria, + measurePopulationExclusion != null ? measurePopulationExclusion.size() : 0, + measurePopulationExclusionPatients != null ? measurePopulationExclusionPatients.values() : null); // TODO: Measure Observations... } @@ -443,10 +486,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p return report; } - private void populateResourceMap( - Context context, MeasurePopulationType type, HashMap resources, - HashMap> codeToResourceMap) - { + private void populateResourceMap(Context context, MeasurePopulationType type, HashMap resources, + HashMap> codeToResourceMap) { if (context.getEvaluatedResources().isEmpty()) { return; } @@ -458,9 +499,10 @@ private void populateResourceMap( HashSet codeHashSet = codeToResourceMap.get((type.toCode())); for (Object o : context.getEvaluatedResources()) { - if (o instanceof Resource){ - Resource r = (Resource)o; - String id = (r.getIdElement().getResourceType() != null ? (r.getIdElement().getResourceType() + "/") : "") + r.getIdElement().getIdPart(); + if (o instanceof Resource) { + Resource r = (Resource) o; + String id = (r.getIdElement().getResourceType() != null ? (r.getIdElement().getResourceType() + "/") + : "") + r.getIdElement().getIdPart(); if (!codeHashSet.contains(id)) { codeHashSet.add(id); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java index ebe5a6c7e..70d21c228 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java @@ -5,25 +5,23 @@ import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.elm.execution.Library; -import org.cqframework.cql.elm.execution.UsingDef; import org.hl7.fhir.r4.model.Measure; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.execution.LibraryLoader; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.cql.runtime.Interval; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.r4.helpers.LibraryHelper; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.DateHelper; import org.opencds.cqf.common.helpers.UsingHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.opencds.cqf.r4.helpers.LibraryHelper; import lombok.Data; @Data -public class MeasureEvaluationSeed -{ +public class MeasureEvaluationSeed { private Measure measure; private Context context; private Interval measurementPeriod; @@ -32,17 +30,15 @@ public class MeasureEvaluationSeed private EvaluationProviderFactory providerFactory; private DataProvider dataProvider; - public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { this.providerFactory = providerFactory; this.libraryLoader = libraryLoader; this.libraryResourceProvider = libraryResourceProvider; } - public void setup( - Measure measure, String periodStart, String periodEnd, - String productLine, String source, String user, String pass) - { + public void setup(Measure measure, String periodStart, String periodEnd, String productLine, String source, + String user, String pass) { this.measure = measure; LibraryHelper.loadLibraries(measure, this.libraryLoader, this.libraryResourceProvider); @@ -54,33 +50,32 @@ public void setup( context = new Context(library); context.registerLibraryLoader(libraryLoader); - List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); + List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); if (usingDefs.size() > 1) { - throw new IllegalArgumentException("Evaluation of Measure using multiple Models is not supported at this time."); + throw new IllegalArgumentException( + "Evaluation of Measure using multiple Models is not supported at this time."); } // If there are no Usings, there is probably not any place the Terminology - // actually used so I think the assumption that at least one provider exists is ok. + // actually used so I think the assumption that at least one provider exists is + // ok. TerminologyProvider terminologyProvider = null; if (usingDefs.size() > 0) { - // Creates a terminology provider based on the first using statement. This assumes the terminology + // Creates a terminology provider based on the first using statement. This + // assumes the terminology // server matches the FHIR version of the CQL. - terminologyProvider = this.providerFactory.createTerminologyProvider( - usingDefs.get(0).getLeft(), usingDefs.get(0).getMiddle(), - source, user, pass); + terminologyProvider = this.providerFactory.createTerminologyProvider(usingDefs.get(0).getLeft(), + usingDefs.get(0).getMiddle(), source, user, pass); context.registerTerminologyProvider(terminologyProvider); } - for (Triple def : usingDefs) - { - this.dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), terminologyProvider); - context.registerDataProvider( - def.getRight(), - dataProvider); + for (Triple def : usingDefs) { + this.dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), + terminologyProvider); + context.registerDataProvider(def.getRight(), dataProvider); } - // resolve the measurement period measurementPeriod = new Interval(DateHelper.resolveRequestDate(periodStart, true), true, DateHelper.resolveRequestDate(periodEnd, false), true); diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index 870a27acf..e6b654fe8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -1,22 +1,19 @@ package org.opencds.cqf.r4.evaluation; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.opencds.cqf.common.helpers.ClientHelper; -import org.opencds.cqf.common.providers.Dstu3ApelonFhirTerminologyProvider; -import org.opencds.cqf.cql.data.CompositeDataProvider; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.model.R4FhirModelResolver; -import org.opencds.cqf.cql.searchparam.SearchParameterResolver; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.cql.terminology.fhir.Dstu3FhirTerminologyProvider; -import org.opencds.cqf.cql.terminology.fhir.R4FhirTerminologyProvider; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; +import org.opencds.cqf.common.helpers.ClientHelper; import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.client.api.IGenericClient; // This class is a relatively dumb factory for data providers. It supports only // creating JPA providers for FHIR, and only basic auth for terminology @@ -45,7 +42,8 @@ public DataProvider createDataProvider(String model, String version, String url, public DataProvider createDataProvider(String model, String version, TerminologyProvider terminologyProvider) { if (model.equals("FHIR") && version.equals("4.0.0")) { R4FhirModelResolver modelResolver = new R4FhirModelResolver(); - JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(this.fhirContext)); + JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, + new SearchParameterResolver(this.fhirContext)); retrieveProvider.setTerminologyProvider(terminologyProvider); retrieveProvider.setExpandValueSets(true); @@ -56,8 +54,9 @@ public DataProvider createDataProvider(String model, String version, Terminology String.format("Can't construct a data provider for model %s version %s", model, version)); } - public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, String pass) { - if(url != null && !url.isEmpty()){ + public TerminologyProvider createTerminologyProvider(String model, String version, String url, String user, + String pass) { + if (url != null && !url.isEmpty()) { IGenericClient client = ClientHelper.getClient(FhirContext.forR4(), url, user, pass); if (url.contains("apelon.com")) { return new R4ApelonFhirTerminologyProvider(client); diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java index db1aea85c..2ef3511c3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java @@ -1,8 +1,8 @@ package org.opencds.cqf.r4.helpers; -import org.opencds.cqf.cql.execution.Context; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.cql.engine.execution.Context; /** * Created by Bryn on 5/7/2016. @@ -14,11 +14,14 @@ public Bundle bundle(Context context, String... expressionNames) { bundle.setType(Bundle.BundleType.COLLECTION); for (String expressionName : expressionNames) { Object result = context.resolveExpressionRef(expressionName).evaluate(context); - for (Object element : (Iterable)result) { + for (Object element : (Iterable) result) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); - entry.setResource((Resource)element); - // The null check for resourceType handles Lists, which don't have a resource type. - entry.setFullUrl((((Resource)element).getIdElement().getResourceType() != null ? (((Resource)element).getIdElement().getResourceType() + "/") : "") + ((Resource)element).getIdElement().getIdPart()); + entry.setResource((Resource) element); + // The null check for resourceType handles Lists, which don't have a resource + // type. + entry.setFullUrl((((Resource) element).getIdElement().getResourceType() != null + ? (((Resource) element).getIdElement().getResourceType() + "/") + : "") + ((Resource) element).getIdElement().getIdPart()); bundle.getEntry().add(entry); } } @@ -32,8 +35,11 @@ public Bundle bundle(Iterable resources) { for (Resource resource : resources) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); entry.setResource(resource); - // The null check for resourceType handles Lists, which don't have a resource type. - entry.setFullUrl((resource.getIdElement().getResourceType() != null ? (resource.getIdElement().getResourceType() + "/") : "") + resource.getIdElement().getIdPart()); + // The null check for resourceType handles Lists, which don't have a resource + // type. + entry.setFullUrl((resource.getIdElement().getResourceType() != null + ? (resource.getIdElement().getResourceType() + "/") + : "") + resource.getIdElement().getIdPart()); bundle.getEntry().add(entry); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 042d837e9..0bd5ea646 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -10,7 +10,6 @@ import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.PlanDefinition; -import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.common.evaluation.LibraryLoader; @@ -26,20 +25,17 @@ public static LibraryLoader createLibraryLoader(LibraryResolutionProvider( - provider, - x -> x.getContent(), - x -> x.getContentType(), - x -> x.getData())); + new LibrarySourceProvider(provider, + x -> x.getContent(), x -> x.getContentType(), x -> x.getData())); return new LibraryLoader(libraryManager, modelManager); } - - public static List loadLibraries(Measure measure, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static List loadLibraries(Measure measure, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { List libraries = new ArrayList(); // load libraries @@ -83,19 +79,23 @@ public static List loadLibraries(Meas return libraries; } - public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static Library resolveLibraryById(String libraryId, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { // Library library = null; org.hl7.fhir.r4.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId); - return libraryLoader.load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); + return libraryLoader + .load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); // for (Library l : libraryLoader.getLibraries()) { - // VersionedIdentifier vid = l.getIdentifier(); - // if (vid.getId().equals(fhirLibrary.getName()) && LibraryResourceHelper.compareVersions(fhirLibrary.getVersion(), vid.getVersion()) == 0) { - // library = l; - // break; - // } + // VersionedIdentifier vid = l.getIdentifier(); + // if (vid.getId().equals(fhirLibrary.getName()) && + // LibraryResourceHelper.compareVersions(fhirLibrary.getVersion(), + // vid.getVersion()) == 0) { + // library = l; + // break; + // } // } // if (library == null) { @@ -105,28 +105,32 @@ public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.e // return library; } - public static Library resolvePrimaryLibrary(Measure measure, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) - { + public static Library resolvePrimaryLibrary(Measure measure, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { // default is the first library reference String id = CanonicalHelper.getId(measure.getLibrary().get(0)); Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider); if (library == null) { - throw new IllegalArgumentException(String - .format("Could not resolve primary library for Measure/%s.", measure.getIdElement().getIdPart())); + throw new IllegalArgumentException(String.format("Could not resolve primary library for Measure/%s.", + measure.getIdElement().getIdPart())); } return library; } - public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, org.opencds.cqf.cql.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { + public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, + org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + LibraryResolutionProvider libraryResourceProvider) { String id = CanonicalHelper.getId(planDefinition.getLibrary().get(0)); Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider); if (library == null) { - throw new IllegalArgumentException(String.format("Could not resolve primary library for PlanDefinition/%s", planDefinition.getIdElement().getIdPart())); + throw new IllegalArgumentException(String.format("Could not resolve primary library for PlanDefinition/%s", + planDefinition.getIdElement().getIdPart())); } return library; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java index 14ba9da33..a8cb9719a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java @@ -1,18 +1,37 @@ package org.opencds.cqf.r4.providers; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import org.hl7.fhir.r4.model.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import org.hl7.fhir.exceptions.FHIRException; -import org.opencds.cqf.cql.model.R4FhirModelResolver; -import org.opencds.cqf.cql.model.ModelResolver; +import org.hl7.fhir.r4.model.ActivityDefinition; +import org.hl7.fhir.r4.model.Attachment; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Communication; +import org.hl7.fhir.r4.model.CommunicationRequest; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.hl7.fhir.r4.model.Procedure; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.RelatedArtifact; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ServiceRequest; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.SupplyRequest; import org.opencds.cqf.common.exceptions.ActivityDefinitionApplyException; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; +import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.r4.helpers.Helper; - -import java.util.*; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** * Created by Bryn on 1/16/2017. @@ -65,33 +84,33 @@ public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, } switch (result.fhirType()) { - case "ServiceRequest": - result = resolveServiceRequest(activityDefinition, patientId, practitionerId, organizationId); - break; + case "ServiceRequest": + result = resolveServiceRequest(activityDefinition, patientId, practitionerId, organizationId); + break; - case "MedicationRequest": - result = resolveMedicationRequest(activityDefinition, patientId); - break; + case "MedicationRequest": + result = resolveMedicationRequest(activityDefinition, patientId); + break; - case "SupplyRequest": - result = resolveSupplyRequest(activityDefinition, practitionerId, organizationId); - break; + case "SupplyRequest": + result = resolveSupplyRequest(activityDefinition, practitionerId, organizationId); + break; - case "Procedure": - result = resolveProcedure(activityDefinition, patientId); - break; + case "Procedure": + result = resolveProcedure(activityDefinition, patientId); + break; - case "DiagnosticReport": - result = resolveDiagnosticReport(activityDefinition, patientId); - break; + case "DiagnosticReport": + result = resolveDiagnosticReport(activityDefinition, patientId); + break; - case "Communication": - result = resolveCommunication(activityDefinition, patientId); - break; + case "Communication": + result = resolveCommunication(activityDefinition, patientId); + break; - case "CommunicationRequest": - result = resolveCommunicationRequest(activityDefinition, patientId); - break; + case "CommunicationRequest": + result = resolveCommunicationRequest(activityDefinition, patientId); + break; } // TODO: Apply expression extensions on any element? diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java index 5ca16ab23..cc2f81d89 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java @@ -1,22 +1,36 @@ package org.opencds.cqf.r4.providers; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; +import java.math.BigDecimal; +import java.util.AbstractMap; +import java.util.Date; + import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; -import org.hl7.fhir.r4.model.*; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DateType; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Element; +import org.hl7.fhir.r4.model.Expression; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Property; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.TranslatorHelper; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.r4.helpers.LibraryHelper; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.DateTime; -import java.math.BigDecimal; -import java.util.*; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; public class ApplyCqlOperationProvider { @@ -39,8 +53,7 @@ public Bundle apply(@IdParam IdType id) throws FHIRException { @Operation(name = "$apply-cql", type = Bundle.class) public Bundle apply(@OperationParam(name = "resourceBundle", min = 1, max = 1, type = Bundle.class) Bundle bundle) - throws FHIRException - { + throws FHIRException { return applyCql(bundle); } @@ -63,14 +76,15 @@ public Resource applyCqlToResource(Resource resource) throws FHIRException { AbstractMap.SimpleEntry extensions = getExtension(base); if (extensions != null) { String cql = String.format("using FHIR version '4.0.0' define x: %s", extensions.getValue()); - library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), new ModelManager()); + library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), + new ModelManager()); context = new Context(library); - context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", "4.0.0")); + context.registerDataProvider("http://hl7.org/fhir", + this.providerFactory.createDataProvider("FHIR", "4.0.0")); Object result = context.resolveExpressionRef("x").getExpression().evaluate(context); if (extensions.getKey().equals("extension")) { resource.setProperty(child.getName(), resolveType(result, base.fhirType())); - } - else { + } else { String type = base.getChildByName(extensions.getKey()).getTypeCode(); base.setProperty(extensions.getKey(), resolveType(result, type)); } @@ -88,12 +102,13 @@ private AbstractMap.SimpleEntry getExtension(Base base) { if (((Element) childBase).hasExtension()) { for (Extension extension : ((Element) childBase).getExtension()) { if (extension.getUrl().equals("http://hl7.org/fhir/StructureDefinition/cqf-expression")) { - return new AbstractMap.SimpleEntry<>(child.getName(), ((Expression) extension.getValue()).getExpression()); + return new AbstractMap.SimpleEntry<>(child.getName(), + ((Expression) extension.getValue()).getExpression()); } } - } - else if (childBase instanceof Extension) { - return new AbstractMap.SimpleEntry<>(child.getName(), ((Expression) ((Extension) childBase).getValue()).getExpression()); + } else if (childBase instanceof Extension) { + return new AbstractMap.SimpleEntry<>(child.getName(), + ((Expression) ((Extension) childBase).getValue()).getExpression()); } } } @@ -104,31 +119,27 @@ else if (childBase instanceof Extension) { private Base resolveType(Object source, String type) { if (source instanceof Integer) { return new IntegerType((Integer) source); - } - else if (source instanceof BigDecimal) { + } else if (source instanceof BigDecimal) { return new DecimalType((BigDecimal) source); - } - else if (source instanceof Boolean) { + } else if (source instanceof Boolean) { return new BooleanType().setValue((Boolean) source); - } - else if (source instanceof String) { + } else if (source instanceof String) { return new StringType((String) source); - } - else if (source instanceof DateTime) { + } else if (source instanceof DateTime) { if (type.equals("dateTime")) { return new DateTimeType().setValue(Date.from(((DateTime) source).getDateTime().toInstant())); } if (type.equals("date")) { return new DateType().setValue(Date.from(((DateTime) source).getDateTime().toInstant())); } - } - else if (source instanceof org.opencds.cqf.cql.runtime.Date) - { + } else if (source instanceof org.opencds.cqf.cql.engine.runtime.Date) { if (type.equals("dateTime")) { - return new DateTimeType().setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.runtime.Date) source).getDate())); + return new DateTimeType() + .setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.engine.runtime.Date) source).getDate())); } if (type.equals("date")) { - return new DateType().setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.runtime.Date) source).getDate())); + return new DateType() + .setValue(java.sql.Date.valueOf(((org.opencds.cqf.cql.engine.runtime.Date) source).getDate())); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java index 471622d77..4da693746 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java @@ -1,5 +1,17 @@ package org.opencds.cqf.r4.providers; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Endpoint; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.ValueSet; +import org.opencds.cqf.r4.helpers.Helper; + import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; @@ -12,13 +24,6 @@ import ca.uhn.fhir.rest.param.StringAndListParam; import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.r4.model.*; -import org.opencds.cqf.r4.helpers.Helper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - public class CacheValueSetsProvider { @@ -30,14 +35,10 @@ public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao concepts = new HashMap<>(); - for (ValueSet.ValueSetExpansionContainsComponent expansion : expandedValueSet.getExpansion().getContains()) - { + for (ValueSet.ValueSetExpansionContainsComponent expansion : expandedValueSet.getExpansion().getContains()) { if (!expansion.hasSystem()) { continue; } if (concepts.containsKey(expansion.getSystem())) { concepts.get(expansion.getSystem()) - .addConcept( - new ValueSet.ConceptReferenceComponent() - .setCode(expansion.hasCode() ? expansion.getCode() : null) - .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null) - ); + .addConcept(new ValueSet.ConceptReferenceComponent() + .setCode(expansion.hasCode() ? expansion.getCode() : null) + .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null)); } else { - concepts.put( - expansion.getSystem(), + concepts.put(expansion.getSystem(), new ValueSet.ConceptSetComponent().setSystem(expansion.getSystem()) - .addConcept( - new ValueSet.ConceptReferenceComponent() - .setCode(expansion.hasCode() ? expansion.getCode() : null) - .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null) - ) - ); + .addConcept(new ValueSet.ConceptReferenceComponent() + .setCode(expansion.hasCode() ? expansion.getCode() : null) + .setDisplay(expansion.hasDisplay() ? expansion.getDisplay() : null))); } } - clean.setCompose( - new ValueSet.ValueSetComposeComponent() - .setInclude(new ArrayList<>(concepts.values())) - ); + clean.setCompose(new ValueSet.ValueSetComposeComponent().setInclude(new ArrayList<>(concepts.values()))); return clean; } private ValueSet resolveValueSet(IGenericClient client, String valuesetId) { - ValueSet valueSet = client.fetchResourceFromUrl(ValueSet.class, client.getServerBase() + "/ValueSet/" + valuesetId); + ValueSet valueSet = client.fetchResourceFromUrl(ValueSet.class, + client.getServerBase() + "/ValueSet/" + valuesetId); boolean expand = false; if (valueSet.hasCompose()) { @@ -130,19 +122,11 @@ private ValueSet resolveValueSet(IGenericClient client, String valuesetId) { } if (expand) { - return getCachedValueSet( - client - .operation() - .onInstance(new IdType("ValueSet", valuesetId)) - .named("$expand") - .withNoParameters(Parameters.class) - .returnResourceType(ValueSet.class) - .execute() - ); + return getCachedValueSet(client.operation().onInstance(new IdType("ValueSet", valuesetId)).named("$expand") + .withNoParameters(Parameters.class).returnResourceType(ValueSet.class).execute()); } valueSet.setVersion(valueSet.getVersion() + "-cache"); return valueSet; } } - diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index 295564536..f9a10da4e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -1,57 +1,53 @@ package org.opencds.cqf.r4.providers; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.UriParam; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.r4.builders.OperationOutcomeBuilder; import org.opencds.cqf.r4.builders.RandomIdBuilder; -import org.hl7.fhir.r4.model.*; -public class CodeSystemUpdateProvider -{ +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.UriParam; + +public class CodeSystemUpdateProvider { private IFhirResourceDao valueSetDao; private IFhirResourceDao codeSystemDao; - public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, IFhirResourceDao codeSystemDao) - { + public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, + IFhirResourceDao codeSystemDao) { this.valueSetDao = valueSetDao; this.codeSystemDao = codeSystemDao; } /*** - * Update existing CodeSystems with the codes in all ValueSet resources. - * System level CodeSystem update operation + * Update existing CodeSystems with the codes in all ValueSet resources. System + * level CodeSystem update operation * - * @return FHIR OperationOutcome detailing the success or failure of the operation + * @return FHIR OperationOutcome detailing the success or failure of the + * operation */ @Operation(name = "$updateCodeSystems", idempotent = true) - public OperationOutcome updateCodeSystems() - { + public OperationOutcome updateCodeSystems() { IBundleProvider valuesets = this.valueSetDao.search(new SearchParameterMap()); OperationOutcome response = new OperationOutcome(); OperationOutcome outcome; - for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) - { + for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) { outcome = this.performCodeSystemUpdate((ValueSet) valueSet); - if (outcome.hasIssue()) - { - for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) - { + if (outcome.hasIssue()) { + for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { response.addIssue(issue); } } @@ -60,76 +56,65 @@ public OperationOutcome updateCodeSystems() return response; } - /*** + /*** * Update existing CodeSystems with the codes in the specified ValueSet. * - * This is for development environment purposes to enable ValueSet expansion and validation - * without complete CodeSystems. + * This is for development environment purposes to enable ValueSet expansion and + * validation without complete CodeSystems. * * @param theId the id of the ValueSet - * @return FHIR OperationOutcome detailing the success or failure of the operation + * @return FHIR OperationOutcome detailing the success or failure of the + * operation */ @Operation(name = "$updateCodeSystems", idempotent = true, type = ValueSet.class) - public OperationOutcome updateCodeSystems(@IdParam IdType theId) - { + public OperationOutcome updateCodeSystems(@IdParam IdType theId) { ValueSet vs = this.valueSetDao.read(theId); OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); - if (vs == null) - { + if (vs == null) { return responseBuilder.buildIssue("error", "notfound", "Unable to find Resource: " + theId.getId()).build(); } return performCodeSystemUpdate(vs); } - public OperationOutcome performCodeSystemUpdate(ValueSet vs) - { + public OperationOutcome performCodeSystemUpdate(ValueSet vs) { OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); List codeSystems = new ArrayList<>(); - if (vs.hasCompose() && vs.getCompose().hasInclude()) - { + if (vs.hasCompose() && vs.getCompose().hasInclude()) { CodeSystem codeSystem; - for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) - { - if (!csc.hasSystem()) continue; + for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { + if (!csc.hasSystem()) + continue; codeSystem = getCodeSystemByUrl(csc.getSystem()); - if (!csc.hasConcept()) continue; + if (!csc.hasConcept()) + continue; - updateCodeSystem( - codeSystem.setUrl(csc.getSystem()), - getUnionDistinctCodes(csc, codeSystem) - ); + updateCodeSystem(codeSystem.setUrl(csc.getSystem()), getUnionDistinctCodes(csc, codeSystem)); codeSystems.add(codeSystem.getUrl()); } } - return responseBuilder.buildIssue( - "information", - "informational", - "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems) - ).build(); + return responseBuilder.buildIssue("information", "informational", + "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); } /*** * Fetch CodeSystem matching the given url search parameter * * @param url The url of the CodeSystem to fetch - * @return The CodeSystem that matches the url parameter or a new CodeSystem with the url and id populated + * @return The CodeSystem that matches the url parameter or a new CodeSystem + * with the url and id populated */ - private CodeSystem getCodeSystemByUrl(String url) - { - IBundleProvider bundleProvider = - this.codeSystemDao.search( - new SearchParameterMap().add(CodeSystem.SP_URL, new UriParam(url)) - ); - - if (bundleProvider.size() >= 1) - { + private CodeSystem getCodeSystemByUrl(String url) { + IBundleProvider bundleProvider = this.codeSystemDao + .search(new SearchParameterMap().add(CodeSystem.SP_URL, new UriParam(url))); + + if (bundleProvider.size() >= 1) { return (CodeSystem) bundleProvider.getResources(0, 1).get(0); } @@ -140,41 +125,33 @@ private CodeSystem getCodeSystemByUrl(String url) * Perform union of codes within a ValueSet and CodeSystem * * @param valueSetCodes The codes contained within a ValueSet - * @param codeSystem A CodeSystem resource + * @param codeSystem A CodeSystem resource * @return List of distinct codes strings */ - private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) - { - if (!codeSystem.hasConcept()) - { - return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode).collect(Collectors.toList()); + private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) { + if (!codeSystem.hasConcept()) { + return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList()); } return Stream.concat( - valueSetCodes.getConcept().stream().map( - ValueSet.ConceptReferenceComponent::getCode).collect(Collectors.toList() - ).stream(), - codeSystem.getConcept().stream().map( - CodeSystem.ConceptDefinitionComponent::getCode).collect(Collectors.toList() - ).stream() - ) - .distinct() - .collect(Collectors.toList()); + valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList()).stream(), + codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) + .collect(Collectors.toList()).stream()) + .distinct().collect(Collectors.toList()); } /*** * Overwrite the given CodeSystem codes with the given codes * * @param codeSystem A CodeSystem resource - * @param codes List of (unique) code strings + * @param codes List of (unique) code strings */ - private void updateCodeSystem(CodeSystem codeSystem, List codes) - { - codeSystem.setConcept( - codes.stream().map( - x -> new CodeSystem.ConceptDefinitionComponent().setCode(x) - ) - .collect(Collectors.toList()) - ).setContent(CodeSystem.CodeSystemContentMode.COMPLETE).setStatus(Enumerations.PublicationStatus.ACTIVE); + private void updateCodeSystem(CodeSystem codeSystem, List codes) { + codeSystem + .setConcept(codes.stream().map(x -> new CodeSystem.ConceptDefinitionComponent().setCode(x)) + .collect(Collectors.toList())) + .setContent(CodeSystem.CodeSystemContentMode.COMPLETE).setStatus(Enumerations.PublicationStatus.ACTIVE); this.codeSystemDao.update(codeSystem); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java index 5db425443..42b0b7a41 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java @@ -10,7 +10,6 @@ import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.CqlTranslatorException; -import org.cqframework.cql.elm.execution.UsingDef; import org.cqframework.cql.elm.tracking.TrackBack; import org.hl7.fhir.r4.model.ActivityDefinition; import org.hl7.fhir.r4.model.Bundle; @@ -30,11 +29,11 @@ import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.common.helpers.UsingHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.data.DataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.runtime.DateTime; -import org.opencds.cqf.cql.runtime.Interval; -import org.opencds.cqf.cql.terminology.TerminologyProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; @@ -49,12 +48,12 @@ public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResourceProvider; - public CqlExecutionProvider(LibraryResolutionProvider libraryResourceProvider, EvaluationProviderFactory providerFactory) { + public CqlExecutionProvider(LibraryResolutionProvider libraryResourceProvider, + EvaluationProviderFactory providerFactory) { this.providerFactory = providerFactory; this.libraryResourceProvider = libraryResourceProvider; } - private LibraryResolutionProvider getLibraryResourceProvider() { return this.libraryResourceProvider; } @@ -80,7 +79,6 @@ private List cleanReferences(List references) { return cleanRefs; } - private Iterable getLibraryReferences(DomainResource instance) { List references = new ArrayList<>(); @@ -122,7 +120,7 @@ else if (instance instanceof Measure) { return cleanReferences(references); } - + private String buildIncludes(Iterable references) { StringBuilder builder = new StringBuilder(); for (CanonicalType reference : references) { @@ -135,11 +133,10 @@ private String buildIncludes(Iterable references) { // TODO: This assumes the libraries resource id is the same as the library name, // need to work this out better - Library lib =this.libraryResourceProvider.resolveLibraryById(CanonicalHelper.getId(reference)); + Library lib = this.libraryResourceProvider.resolveLibraryById(CanonicalHelper.getId(reference)); if (lib.hasName()) { builder.append(lib.getName()); - } - else { + } else { throw new RuntimeException("Library name unknown"); } @@ -167,12 +164,12 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati String source = String.format( "library LocalLibrary using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' called FHIRHelpers %s parameter %s %s parameter \"%%context\" %s define Expression: %s", buildIncludes(libraries), instance.fhirType(), instance.fhirType(), instance.fhirType(), cql); - + LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); - + org.cqframework.cql.elm.execution.Library library = TranslatorHelper.translateLibrary(source, libraryLoader.getLibraryManager(), libraryLoader.getModelManager()); - + // resolve execution context Context context = setupContext(instance, patientId, libraryLoader, library); return context.resolveExpressionRef("Expression").evaluate(context); @@ -183,14 +180,14 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati if (aliasedExpression) { Object result = null; for (CanonicalType reference : libraries) { - Library lib =this.libraryResourceProvider.resolveLibraryById(CanonicalHelper.getId(reference)); - if (lib == null) - { + Library lib = this.libraryResourceProvider.resolveLibraryById(CanonicalHelper.getId(reference)); + if (lib == null) { throw new RuntimeException("Library with id " + reference.getIdBase() + "not found"); } LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); // resolve primary library - org.cqframework.cql.elm.execution.Library library = LibraryHelper.resolveLibraryById(lib.getId(), libraryLoader, this.libraryResourceProvider); + org.cqframework.cql.elm.execution.Library library = LibraryHelper.resolveLibraryById(lib.getId(), + libraryLoader, this.libraryResourceProvider); // resolve execution context Context context = setupContext(instance, patientId, libraryLoader, library); @@ -200,13 +197,11 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati } } throw new RuntimeException("Could not find Expression in Referenced Libraries"); - } - else { + } else { return evaluateInContext(instance, cql, patientId); } } - private Context setupContext(DomainResource instance, String patientId, LibraryLoader libraryLoader, org.cqframework.cql.elm.execution.Library library) { // Provide the instance as the value of the '%context' parameter, as well as the @@ -226,17 +221,17 @@ private Context setupContext(DomainResource instance, String patientId, LibraryL @Operation(name = "$cql") public Bundle evaluate(@OperationParam(name = "code") String code, @OperationParam(name = "patientId") String patientId, - @OperationParam(name="periodStart") String periodStart, - @OperationParam(name="periodEnd") String periodEnd, - @OperationParam(name="productLine") String productLine, + @OperationParam(name = "periodStart") String periodStart, + @OperationParam(name = "periodEnd") String periodEnd, + @OperationParam(name = "productLine") String productLine, @OperationParam(name = "terminologyServiceUri") String terminologyServiceUri, @OperationParam(name = "terminologyUser") String terminologyUser, @OperationParam(name = "terminologyPass") String terminologyPass, - @OperationParam(name = "context") String contextParam, - @OperationParam(name = "executionResults") String executionResults, + @OperationParam(name = "context") String contextParam, + @OperationParam(name = "executionResults") String executionResults, @OperationParam(name = "parameters") Parameters parameters) { - if (patientId == null && contextParam != null && contextParam.equals("Patient") ) { + if (patientId == null && contextParam != null && contextParam.equals("Patient")) { throw new IllegalArgumentException("Must specify a patientId when executing in Patient context."); } @@ -256,8 +251,8 @@ public Bundle evaluate(@OperationParam(name = "code") String code, Parameters result = new Parameters(); TrackBack tb = cte.getLocator(); if (tb != null) { - String location = String.format("[%d:%d]",tb.getStartLine(), tb.getStartChar()); - result.addParameter().setName("location").setValue(new StringType(location)); + String location = String.format("[%d:%d]", tb.getStartLine(), tb.getStartChar()); + result.addParameter().setName("location").setValue(new StringType(location)); } result.setId("Error"); @@ -280,45 +275,43 @@ public Bundle evaluate(@OperationParam(name = "code") String code, org.cqframework.cql.elm.execution.Library library = TranslatorHelper.translateLibrary(translator); Context context = new Context(library); context.registerLibraryLoader(libraryLoader); - - List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); + + List> usingDefs = UsingHelper.getUsingUrlAndVersion(library.getUsings()); if (usingDefs.size() > 1) { - throw new IllegalArgumentException("Evaluation of Measure using multiple Models is not supported at this time."); + throw new IllegalArgumentException( + "Evaluation of Measure using multiple Models is not supported at this time."); } // If there are no Usings, there is probably not any place the Terminology - // actually used so I think the assumption that at least one provider exists is ok. + // actually used so I think the assumption that at least one provider exists is + // ok. TerminologyProvider terminologyProvider = null; if (usingDefs.size() > 0) { - // Creates a terminology provider based on the first using statement. This assumes the terminology + // Creates a terminology provider based on the first using statement. This + // assumes the terminology // server matches the FHIR version of the CQL. - terminologyProvider = this.providerFactory.createTerminologyProvider( - usingDefs.get(0).getLeft(), usingDefs.get(0).getMiddle(), - terminologyServiceUri, terminologyUser, terminologyPass); + terminologyProvider = this.providerFactory.createTerminologyProvider(usingDefs.get(0).getLeft(), + usingDefs.get(0).getMiddle(), terminologyServiceUri, terminologyUser, terminologyPass); context.registerTerminologyProvider(terminologyProvider); } - for (Triple def : usingDefs) - { - DataProvider dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), terminologyProvider); - context.registerDataProvider( - def.getRight(), - dataProvider); + for (Triple def : usingDefs) { + DataProvider dataProvider = this.providerFactory.createDataProvider(def.getLeft(), def.getMiddle(), + terminologyProvider); + context.registerDataProvider(def.getRight(), dataProvider); } - if (parameters != null) - { - for (Parameters.ParametersParameterComponent pc : parameters.getParameter()) - { + if (parameters != null) { + for (Parameters.ParametersParameterComponent pc : parameters.getParameter()) { context.setParameter(library.getLocalId(), pc.getName(), pc.getValue()); - } + } } if (periodStart != null && periodEnd != null) { // resolve the measurement period Interval measurementPeriod = new Interval(DateHelper.resolveRequestDate(periodStart, true), true, - DateHelper.resolveRequestDate(periodEnd, false), true); + DateHelper.resolveRequestDate(periodEnd, false), true); context.setParameter(null, "Measurement Period", new Interval(DateTime.fromJavaDate((Date) measurementPeriod.getStart()), true, @@ -329,60 +322,58 @@ public Bundle evaluate(@OperationParam(name = "code") String code, context.setParameter(null, "Product Line", productLine); } - context.setExpressionCaching(true); if (library.getStatements() != null) { for (org.cqframework.cql.elm.execution.ExpressionDef def : library.getStatements().getDef()) { context.enterContext(def.getContext()); if (patientId != null && !patientId.isEmpty()) { context.setContextValue(context.getCurrentContext(), patientId); - } - else { + } else { context.setContextValue(context.getCurrentContext(), "null"); } Parameters result = new Parameters(); try { result.setId(def.getName()); - String location = String.format("[%d:%d]", locations.get(def.getName()).get(0), locations.get(def.getName()).get(1)); + String location = String.format("[%d:%d]", locations.get(def.getName()).get(0), + locations.get(def.getName()).get(1)); result.addParameter().setName("location").setValue(new StringType(location)); - Object res = def instanceof org.cqframework.cql.elm.execution.FunctionDef ? "Definition successfully validated" : def.getExpression().evaluate(context); + Object res = def instanceof org.cqframework.cql.elm.execution.FunctionDef + ? "Definition successfully validated" + : def.getExpression().evaluate(context); if (res == null) { result.addParameter().setName("value").setValue(new StringType("null")); - } - else if (res instanceof List) { + } else if (res instanceof List) { if (((List) res).size() > 0 && ((List) res).get(0) instanceof Resource) { - if (executionResults != null && executionResults.equals("Summary")) { - result.addParameter().setName("value").setValue(new StringType(((Resource)((List) res).get(0)).getIdElement().getResourceType() + "/" + ((Resource)((List) res).get(0)).getIdElement().getIdPart())); - } - else { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable)res)); - } - } - else { + if (executionResults != null && executionResults.equals("Summary")) { + result.addParameter().setName("value") + .setValue(new StringType(((Resource) ((List) res).get(0)).getIdElement() + .getResourceType() + "/" + + ((Resource) ((List) res).get(0)).getIdElement().getIdPart())); + } else { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } + } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } - } - else if (res instanceof Iterable) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable)res)); - } - else if (res instanceof Resource) { - if (executionResults != null && executionResults.equals("Summary")) { - result.addParameter().setName("value").setValue(new StringType(((Resource)res).getIdElement().getResourceType() + "/" + ((Resource)res).getIdElement().getIdPart())); - } - else { - result.addParameter().setName("value").setResource((Resource)res); - } - } - else { + } else if (res instanceof Iterable) { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } else if (res instanceof Resource) { + if (executionResults != null && executionResults.equals("Summary")) { + result.addParameter().setName("value") + .setValue(new StringType(((Resource) res).getIdElement().getResourceType() + "/" + + ((Resource) res).getIdElement().getIdPart())); + } else { + result.addParameter().setName("value").setResource((Resource) res); + } + } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } result.addParameter().setName("resultType").setValue(new StringType(resolveType(res))); - } - catch (RuntimeException re) { + } catch (RuntimeException re) { re.printStackTrace(); String message = re.getMessage() != null ? re.getMessage() : re.getClass().getName(); @@ -395,10 +386,11 @@ else if (res instanceof Resource) { return bundler.bundle(results); } - private Map> getLocations(org.hl7.elm.r1.Library library) { + private Map> getLocations(org.hl7.elm.r1.Library library) { Map> locations = new HashMap<>(); - if (library.getStatements() == null) return locations; + if (library.getStatements() == null) + return locations; for (org.hl7.elm.r1.ExpressionDef def : library.getStatements().getDef()) { int startLine = def.getTrackbacks().isEmpty() ? 0 : def.getTrackbacks().get(0).getStartLine(); @@ -413,9 +405,12 @@ private Map> getLocations(org.hl7.elm.r1.Library library private String resolveType(Object result) { String type = result == null ? "Null" : result.getClass().getSimpleName(); switch (type) { - case "BigDecimal": return "Decimal"; - case "ArrayList": return "List"; - case "FhirBundleCursor": return "Retrieve"; + case "BigDecimal": + return "Decimal"; + case "ArrayList": + return "List"; + case "FhirBundleCursor": + return "Retrieve"; } return type; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java index d1d57028f..0dd36db48 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java @@ -56,58 +56,64 @@ import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.execution.LibraryLoader; -import org.opencds.cqf.measure.r4.CqfMeasure; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.measure.r4.CodeTerminologyRef; -import org.opencds.cqf.measure.r4.VersionedTerminologyRef; +import org.opencds.cqf.measure.r4.CqfMeasure; import org.opencds.cqf.measure.r4.TerminologyRef; import org.opencds.cqf.measure.r4.TerminologyRef.TerminologyRefType; +import org.opencds.cqf.measure.r4.VersionedTerminologyRef; import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.opencds.cqf.r4.helpers.LibraryHelper; - public class DataRequirementsProvider { // For creating the CQF measure we need to: // 1. Find the Primary Library Resource - // 2. Load the Primary Library as ELM. This will recursively load the dependent libraries as ELM by Name + // 2. Load the Primary Library as ELM. This will recursively load the dependent + // libraries as ELM by Name // 3. Load the Library Depedencies as Resources // 4. Update the Data Requirements on the Resources accordingly - // Since the Library Loader only exposes the loaded libraries as ELM, we actually have to load them twice. + // Since the Library Loader only exposes the loaded libraries as ELM, we + // actually have to load them twice. // Once via the loader, Once manually - public CqfMeasure createCqfMeasure(Measure measure, LibraryResolutionProvider libraryResourceProvider) { - Map> libraryMap = this.createLibraryMap(measure, libraryResourceProvider); + public CqfMeasure createCqfMeasure(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { + Map> libraryMap = this + .createLibraryMap(measure, libraryResourceProvider); return this.createCqfMeasure(measure, libraryMap); } - private Map> createLibraryMap(Measure measure, LibraryResolutionProvider libraryResourceProvider) { + private Map> createLibraryMap(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResourceProvider); List libraries = LibraryHelper.loadLibraries(measure, libraryLoader, libraryResourceProvider); Map> libraryMap = new HashMap<>(); for (Library library : libraries) { VersionedIdentifier vi = library.getIdentifier(); - org.hl7.fhir.r4.model.Library libraryResource = libraryResourceProvider.resolveLibraryByName(vi.getId(), vi.getVersion()); + org.hl7.fhir.r4.model.Library libraryResource = libraryResourceProvider.resolveLibraryByName(vi.getId(), + vi.getVersion()); libraryMap.put(vi, Pair.of(library, libraryResource)); } return libraryMap; } - private CqfMeasure createCqfMeasure(Measure measure, Map> libraryMap) - { - //Ensure All Data Requirements for all referenced libraries + private CqfMeasure createCqfMeasure(Measure measure, + Map> libraryMap) { + // Ensure All Data Requirements for all referenced libraries org.hl7.fhir.r4.model.Library moduleDefinition = this.getDataRequirements(measure, - libraryMap.values().stream().map(Pair::getRight).filter(Objects::nonNull).collect(Collectors.toList())); - + libraryMap.values().stream().map(Pair::getRight).filter(Objects::nonNull).collect(Collectors.toList())); + CqfMeasure cqfMeasure = new CqfMeasure(measure); moduleDefinition.getRelatedArtifact().forEach(cqfMeasure::addRelatedArtifact); cqfMeasure.setDataRequirement(moduleDefinition.getDataRequirement()); cqfMeasure.setParameter(moduleDefinition.getParameter()); - + ArrayList citations = new ArrayList<>(); for (RelatedArtifact citation : cqfMeasure.getRelatedArtifact()) { - if (citation.hasType() && Objects.equals(citation.getType().toCode(), "citation") && citation.hasCitation()) { + if (citation.hasType() && Objects.equals(citation.getType().toCode(), "citation") + && citation.hasCitation()) { citations.add(citation); } } @@ -120,18 +126,19 @@ private CqfMeasure createCqfMeasure(Measure measure, Map functionStatements = new ArrayList<>(); List supplementalDataElements = new ArrayList<>(); List terminology = new ArrayList<>(); - List codes = new ArrayList<>(); - List codeSystems = new ArrayList<>(); - List valueSets = new ArrayList<>(); + List codes = new ArrayList<>(); + List codeSystems = new ArrayList<>(); + List valueSets = new ArrayList<>(); List dataCriteria = new ArrayList<>(); String primaryLibraryId = CanonicalHelper.getId(measure.getLibrary().get(0)); - Library primaryLibrary = libraryMap.values().stream() - .filter(x -> x.getRight() != null) - .filter(x -> x.getRight().getIdElement() != null && x.getRight().getIdElement().getIdPart().equals(primaryLibraryId)) - .findFirst().get().getLeft(); + Library primaryLibrary = libraryMap.values().stream().filter(x -> x.getRight() != null) + .filter(x -> x.getRight().getIdElement() != null + && x.getRight().getIdElement().getIdPart().equals(primaryLibraryId)) + .findFirst().get().getLeft(); - for (Map.Entry> libraryEntry : libraryMap.entrySet()) { + for (Map.Entry> libraryEntry : libraryMap + .entrySet()) { Library library = libraryEntry.getValue().getLeft(); org.hl7.fhir.r4.model.Library libraryResource = libraryEntry.getValue().getRight(); Boolean isPrimaryLibrary = libraryResource != null && libraryResource.getId().equals(primaryLibraryId); @@ -144,16 +151,16 @@ private CqfMeasure createCqfMeasure(Measure measure, Map 0 ? "\r\n" : "") + cqlLines[i]); + if (!cqlLines[i].contains("define \"" + statement.getName() + "\":") + && !cqlLines[i].contains("define function \"" + statement.getName() + "\"(")) { + statementText = statementText + .concat((statementText.length() > 0 ? "\r\n" : "") + cqlLines[i]); } } if (statementText.startsWith("context")) { continue; } Measure.MeasureGroupPopulationComponent def = new Measure.MeasureGroupPopulationComponent(); - def.setCriteria(new Expression().setName(libraryNamespace + statement.getName() + signature).setExpression(statementText)); + def.setCriteria(new Expression().setName(libraryNamespace + statement.getName() + signature) + .setExpression(statementText)); if (statement.getClass() == FunctionDef.class) { - functionStatements.add(def); - } - else { - definitionStatements.add(def); + functionStatements.add(def); + } else { + definitionStatements.add(def); } for (Measure.MeasureGroupComponent group : populationStatements) { for (Measure.MeasureGroupPopulationComponent population : group.getPopulation()) { - if (population.hasCriteria() && population.getCriteria().hasExpression() && population.getCriteria().getExpression().equalsIgnoreCase(statement.getName())) - { + if (population.hasCriteria() && population.getCriteria().hasExpression() + && population.getCriteria().getExpression().equalsIgnoreCase(statement.getName())) { String code = population.getCode().getCodingFirstRep().getCode(); String display = HQMFProvider.measurePopulationValueSetMap.get(code).displayName; population.setCriteria(new Expression().setName(display).setExpression(statementText)); @@ -284,16 +295,16 @@ private CqfMeasure createCqfMeasure(Measure measure, Map>> criteriaMap = new HashMap<>(); // Index all usages of criteria for (int i = 0; i < cqfMeasure.getGroup().size(); i++) { @@ -352,7 +363,8 @@ private CqfMeasure createCqfMeasure(Measure measure, Map()); } - criteriaMap.get(criteria).add(Triple.of(i, mgpc.getCode().getCodingFirstRep().getCode(), mgpc.getDescription())); + criteriaMap.get(criteria) + .add(Triple.of(i, mgpc.getCode().getCodingFirstRep().getCode(), mgpc.getDescription())); } } } @@ -360,7 +372,8 @@ private CqfMeasure createCqfMeasure(Measure measure, Map>> entry : criteriaMap.entrySet()) { String criteria = entry.getKey(); - if (cqfMeasure.getGroup().size() == 1 || entry.getValue().stream().map(Triple::getLeft).distinct().count() > 1) { + if (cqfMeasure.getGroup().size() == 1 + || entry.getValue().stream().map(Triple::getLeft).distinct().count() > 1) { String code = entry.getValue().get(0).getMiddle(); String display = HQMFProvider.measurePopulationValueSetMap.get(code).displayName; cqfMeasure.addSharedPopulationCritiera(criteria, display, entry.getValue().get(0).getRight()); @@ -378,7 +391,8 @@ private CqfMeasure createCqfMeasure(Measure measure, Map newMgpc = new ArrayList<>(); for (int j = 0; j < mgc.getPopulation().size(); j++) { Measure.MeasureGroupPopulationComponent mgpc = mgc.getPopulation().get(j); - if (mgpc.hasCriteria() && mgpc.getCriteria().hasExpression() && !cqfMeasure.getSharedPopulationCritieria().containsKey(mgpc.getCriteria().getExpression())) { + if (mgpc.hasCriteria() && mgpc.getCriteria().hasExpression() && !cqfMeasure + .getSharedPopulationCritieria().containsKey(mgpc.getCriteria().getExpression())) { String code = mgpc.getCode().getCodingFirstRep().getCode(); String display = HQMFProvider.measurePopulationValueSetMap.get(code).displayName; mgpc.getCriteria().setName(display); @@ -398,34 +412,29 @@ private CqfMeasure processMarkDown(CqfMeasure measure) { MutableDataSet options = new MutableDataSet(); options.setFrom(ParserEmulationProfile.GITHUB_DOC); - options.set(Parser.EXTENSIONS, Arrays.asList( - AutolinkExtension.create(), - //AnchorLinkExtension.create(), - //EmojiExtension.create(), - StrikethroughExtension.create(), - TablesExtension.create(), - TaskListExtension.create() - )); - - // uncomment and define location of emoji images from https://github.com/arvida/emoji-cheat-sheet.com + options.set(Parser.EXTENSIONS, Arrays.asList(AutolinkExtension.create(), + // AnchorLinkExtension.create(), + // EmojiExtension.create(), + StrikethroughExtension.create(), TablesExtension.create(), TaskListExtension.create())); + + // uncomment and define location of emoji images from + // https://github.com/arvida/emoji-cheat-sheet.com // options.set(EmojiExtension.ROOT_IMAGE_PATH, ""); // Uncomment if GFM anchor links are desired in headings // options.set(AnchorLinkExtension.ANCHORLINKS_SET_ID, false); // options.set(AnchorLinkExtension.ANCHORLINKS_ANCHOR_CLASS, "anchor"); // options.set(AnchorLinkExtension.ANCHORLINKS_SET_NAME, true); - // options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, ""); + // options.set(AnchorLinkExtension.ANCHORLINKS_TEXT_PREFIX, ""); // References compatibility options.set(Parser.REFERENCES_KEEP, KeepType.LAST); // Set GFM table parsing options - options.set(TablesExtension.COLUMN_SPANS, false) - .set(TablesExtension.MIN_HEADER_ROWS, 1) - .set(TablesExtension.MAX_HEADER_ROWS, 1) - .set(TablesExtension.APPEND_MISSING_COLUMNS, true) - .set(TablesExtension.DISCARD_EXTRA_COLUMNS, true) - .set(TablesExtension.WITH_CAPTION, false) + options.set(TablesExtension.COLUMN_SPANS, false).set(TablesExtension.MIN_HEADER_ROWS, 1) + .set(TablesExtension.MAX_HEADER_ROWS, 1).set(TablesExtension.APPEND_MISSING_COLUMNS, true) + .set(TablesExtension.DISCARD_EXTRA_COLUMNS, true).set(TablesExtension.WITH_CAPTION, false) .set(TablesExtension.HEADER_SEPARATOR_COLUMN_MATCH, true); // Setup List Options for GitHub profile which is kramdown for documents @@ -433,21 +442,21 @@ private CqfMeasure processMarkDown(CqfMeasure measure) { options.set(HtmlRenderer.SOFT_BREAK, "
\n"); - Parser parser = Parser.builder(options).build(); HtmlRenderer renderer = HtmlRenderer.builder(options).build(); measure.setDescription(markdownToHtml(parser, renderer, measure.getDescription())); measure.setPurpose(markdownToHtml(parser, renderer, measure.getPurpose())); - // measure.setCopyright(markdownToHtml(parser, renderer, measure.getCopyright())); + // measure.setCopyright(markdownToHtml(parser, renderer, + // measure.getCopyright())); measure.setRationale(markdownToHtml(parser, renderer, measure.getRationale())); - measure.setClinicalRecommendationStatement(markdownToHtml(parser, renderer, measure.getClinicalRecommendationStatement())); + measure.setClinicalRecommendationStatement( + markdownToHtml(parser, renderer, measure.getClinicalRecommendationStatement())); measure.setGuidance(markdownToHtml(parser, renderer, measure.getGuidance())); - measure.setDefinition(measure.getDefinition().stream() - .map(x -> markdownToHtml(parser, renderer, x.getValueAsString())) - .map(MarkdownType::new) - .collect(Collectors.toList())); + measure.setDefinition( + measure.getDefinition().stream().map(x -> markdownToHtml(parser, renderer, x.getValueAsString())) + .map(MarkdownType::new).collect(Collectors.toList())); return measure; } @@ -461,12 +470,16 @@ private String markdownToHtml(Parser parser, HtmlRenderer renderer, String markd return renderer.render(document); } - public org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, LibraryResolutionProvider libraryResourceProvider){ - Map> libraryMap = this.createLibraryMap(measure, libraryResourceProvider); - return this.getDataRequirements(measure, libraryMap.values().stream().map(Pair::getRight).filter(Objects::nonNull).collect(Collectors.toList())); + public org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, + LibraryResolutionProvider libraryResourceProvider) { + Map> libraryMap = this + .createLibraryMap(measure, libraryResourceProvider); + return this.getDataRequirements(measure, + libraryMap.values().stream().map(Pair::getRight).filter(Objects::nonNull).collect(Collectors.toList())); } - private org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, Collection libraries){ + private org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, + Collection libraries) { List reqs = new ArrayList<>(); List dependencies = new ArrayList<>(); List parameters = new ArrayList<>(); @@ -484,8 +497,8 @@ private org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, Colle List typeCoding = new ArrayList<>(); typeCoding.add(new Coding().setCode("module-definition")); - org.hl7.fhir.r4.model.Library library = - new org.hl7.fhir.r4.model.Library().setType(new CodeableConcept().setCoding(typeCoding)); + org.hl7.fhir.r4.model.Library library = new org.hl7.fhir.r4.model.Library() + .setType(new CodeableConcept().setCoding(typeCoding)); if (!dependencies.isEmpty()) { library.setRelatedArtifact(dependencies); @@ -502,8 +515,8 @@ private org.hl7.fhir.r4.model.Library getDataRequirements(Measure measure, Colle return library; } - - public CqlTranslator getTranslator(org.hl7.fhir.r4.model.Library library, LibraryManager libraryManager, ModelManager modelManager) { + public CqlTranslator getTranslator(org.hl7.fhir.r4.model.Library library, LibraryManager libraryManager, + ModelManager modelManager) { Attachment cql = null; for (Attachment a : library.getContent()) { if (a.getContentType().equals("text/cql")) { @@ -551,16 +564,17 @@ public void ensureElm(org.hl7.fhir.r4.model.Library library, CqlTranslator trans library.getContent().add(elm); } - public void ensureRelatedArtifacts(org.hl7.fhir.r4.model.Library library, CqlTranslator translator, LibraryResolutionProvider libraryResourceProvider) - { + public void ensureRelatedArtifacts(org.hl7.fhir.r4.model.Library library, CqlTranslator translator, + LibraryResolutionProvider libraryResourceProvider) { library.getRelatedArtifact().clear(); org.hl7.elm.r1.Library elm = translator.toELM(); if (elm.getIncludes() != null && !elm.getIncludes().getDef().isEmpty()) { for (org.hl7.elm.r1.IncludeDef def : elm.getIncludes().getDef()) { - org.hl7.fhir.r4.model.Library relatedLibrary = libraryResourceProvider.resolveLibraryByName(def.getPath(), def.getVersion()); + org.hl7.fhir.r4.model.Library relatedLibrary = libraryResourceProvider + .resolveLibraryByName(def.getPath(), def.getVersion()); library.addRelatedArtifact(new RelatedArtifact().setType(RelatedArtifact.RelatedArtifactType.DEPENDSON) - .setResource(relatedLibrary.getIdElement().getResourceType() + "/" + relatedLibrary.getIdElement().getIdPart()) - ); + .setResource(relatedLibrary.getIdElement().getResourceType() + "/" + + relatedLibrary.getIdElement().getIdPart())); } } @@ -598,15 +612,14 @@ public void ensureDataRequirements(org.hl7.fhir.r4.model.Library library, CqlTra reqs.add(dataReq); } - - // + // // org.hl7.elm.r1.Library elm = translator.toELM(); // Codes codes = elm.getCodes(); // for (CodeDef cd : codes.getDef()) { - // cd. + // cd. // } - library.setDataRequirement(reqs); + library.setDataRequirement(reqs); } public String getValueSetId(String valueSetName, CqlTranslator translator) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java index c9f8944f6..f7a66ce3c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java @@ -50,9 +50,8 @@ import org.opencds.cqf.common.providers.InMemoryLibraryResourceProvider; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.library.r4.NarrativeProvider; -import org.opencds.cqf.measure.r4.CqfMeasure; import org.opencds.cqf.measure.r4.CodeTerminologyRef; -import org.opencds.cqf.measure.r4.VersionedTerminologyRef; +import org.opencds.cqf.measure.r4.CqfMeasure; import org.opencds.cqf.measure.r4.TerminologyRef; import org.opencds.cqf.measure.r4.TerminologyRef.TerminologyRefType; import org.w3c.dom.Document; @@ -99,16 +98,24 @@ public CodeMapping(String code, String displayName, String criteriaName, String public static Map measurePopulationValueSetMap = new HashMap() { { - put("initial-population", new CodeMapping("IPOP", "Initial Population", "initialPopulationCriteria", "initialPopulation")); + put("initial-population", + new CodeMapping("IPOP", "Initial Population", "initialPopulationCriteria", "initialPopulation")); put("numerator", new CodeMapping("NUMER", "Numerator", "numeratorCriteria", "numerator")); - put("numerator-exclusion", new CodeMapping("NUMEX", "Numerator Exclusion", "numeratorExclusionCriteria", "numeratorExclusions")); + put("numerator-exclusion", new CodeMapping("NUMEX", "Numerator Exclusion", "numeratorExclusionCriteria", + "numeratorExclusions")); put("denominator", new CodeMapping("DENOM", "Denominator", "denominatorCriteria", "denominator")); - put("denominator-exclusion", new CodeMapping("DENEX", "Denominator Exclusion", "denominatorExclusionCritieria", "denominatorExclusions")); - put("denominator-exception", new CodeMapping("DENEXCEP", "Denominator Exception", "denominatorExceptionCriteria", "denominatorExceptions")); - // TODO: Figure out what the codes for these are (MPOP, MPOPEX, MPOPEXCEP are guesses) - put("measure-population", new CodeMapping("MPOP", "Measure Population", "measurePopulationCriteria", "measurePopulation")); - put("measure-population-exclusion", new CodeMapping("MPOPEX", "Measure Population Exclusion", "measurePopulationExclusionCriteria", "measurePopulationExclusions")); - put("measure-observation", new CodeMapping("MOBS", "Measure Observation", "measureObservationCriteria", "measureObservations")); + put("denominator-exclusion", new CodeMapping("DENEX", "Denominator Exclusion", + "denominatorExclusionCritieria", "denominatorExclusions")); + put("denominator-exception", new CodeMapping("DENEXCEP", "Denominator Exception", + "denominatorExceptionCriteria", "denominatorExceptions")); + // TODO: Figure out what the codes for these are (MPOP, MPOPEX, MPOPEXCEP are + // guesses) + put("measure-population", + new CodeMapping("MPOP", "Measure Population", "measurePopulationCriteria", "measurePopulation")); + put("measure-population-exclusion", new CodeMapping("MPOPEX", "Measure Population Exclusion", + "measurePopulationExclusionCriteria", "measurePopulationExclusions")); + put("measure-observation", new CodeMapping("MOBS", "Measure Observation", "measureObservationCriteria", + "measureObservations")); } }; @@ -133,18 +140,16 @@ private XMLBuilder2 createQualityMeasureDocumentElement(CqfMeasure m) { XMLBuilder2 builder = XMLBuilder2.create("QualityMeasureDocument").ns("urn:hl7-org:v3") .ns("cql-ext", "urn:hhs-cql:hqmf-n1-extensions:v1") - .ns("xsi", "http://www.w3.org/2001/XMLSchema-instance") - .elem("typeId").a("extension", "POQM_HD000001UV02").a("root", "2.16.840.1.113883.1.3").up() - .elem("templateId").elem("item").a("extension", "2018-05-01").a("root", "2.16.840.1.113883.10.20.28.1.2").up().up() - .elem("id").a("root", id).up().up().elem("code") - .a("code", "57024-2").a("codeSystem", "2.16.840.1.113883.6.1").elem("displayName") - .a("value", "Health Quality Measure Document").up().up() - .elem("title").a("value", m.hasTitle() ? m.getTitle() : "None").up() - .elem("text").a("value", m.hasDescription() ? m.getDescription() : "None").up() - .elem("statusCode").a("code", "COMPLETED").up() - .elem("setId").a("root", setId).up() - .elem("versionNumber").a("value", m.hasVersion() ? m.getVersion() : "None").up() - .root(); + .ns("xsi", "http://www.w3.org/2001/XMLSchema-instance").elem("typeId") + .a("extension", "POQM_HD000001UV02").a("root", "2.16.840.1.113883.1.3").up().elem("templateId") + .elem("item").a("extension", "2018-05-01").a("root", "2.16.840.1.113883.10.20.28.1.2").up().up() + .elem("id").a("root", id).up().up().elem("code").a("code", "57024-2") + .a("codeSystem", "2.16.840.1.113883.6.1").elem("displayName") + .a("value", "Health Quality Measure Document").up().up().elem("title") + .a("value", m.hasTitle() ? m.getTitle() : "None").up().elem("text") + .a("value", m.hasDescription() ? m.getDescription() : "None").up().elem("statusCode") + .a("code", "COMPLETED").up().elem("setId").a("root", setId).up().elem("versionNumber") + .a("value", m.hasVersion() ? m.getVersion() : "None").up().root(); return builder; } @@ -155,14 +160,13 @@ private String resolveSetId(CqfMeasure m) { if (id != null && id.hasValue() && !id.getValue().isEmpty()) { return id.getValue(); } - + return m.getName(); } private void addDefinitions(XMLBuilder2 xml, CqfMeasure m) { - if (m.hasTerminology()) - { + if (m.hasTerminology()) { for (TerminologyRef t : m.getTerminology()) { if (t.getType() == TerminologyRefType.VALUESET) { this.addValueSet(xml, t); @@ -171,7 +175,7 @@ private void addDefinitions(XMLBuilder2 xml, CqfMeasure m) { for (TerminologyRef t : m.getTerminology()) { if (t.getType() == TerminologyRefType.CODE) { - this.addDirectReferenceCode(xml, (CodeTerminologyRef)t); + this.addDirectReferenceCode(xml, (CodeTerminologyRef) t); } } } @@ -179,12 +183,12 @@ private void addDefinitions(XMLBuilder2 xml, CqfMeasure m) { private void addValueSet(XMLBuilder2 xml, TerminologyRef t) { xml.root().elem("definition").elem("valueSet").a("classCode", "OBS").a("moodCode", "DEF").elem("id") - .a("root", t.getId()).up() - .elem("title").a("value", t.getName()); + .a("root", t.getId()).up().elem("title").a("value", t.getName()); } private void addDirectReferenceCode(XMLBuilder2 xml, CodeTerminologyRef t) { - XMLBuilder2 temp = xml.root().elem("definition").elem("cql-ext:code").a("code", t.getId()).a("codeSystem", t.getcodeSystemId()).a("codeSystemName", t.getcodeSystemName()); + XMLBuilder2 temp = xml.root().elem("definition").elem("cql-ext:code").a("code", t.getId()) + .a("codeSystem", t.getcodeSystemId()).a("codeSystemName", t.getcodeSystemName()); if (t.getdisplayName() != null) { temp.elem("displayName").a("value", t.getdisplayName()).up(); } @@ -212,7 +216,7 @@ private String addRelatedDocuments(XMLBuilder2 xml, CqfMeasure m, String primary primaryLibraryName = name; guid = primaryLibraryGuid; } - + this.addRelatedDocument(xml, name + "-" + version, guid); } @@ -251,12 +255,9 @@ private void addMeasurePeriod(XMLBuilder2 xml, Period p) { } private Identifier getIdentifierFor(CqfMeasure m, String identifierCode) { - for (Identifier i : m.getIdentifier()) - { - if (i.hasType()) - { - if(i.getType().getCodingFirstRep().getCode().equalsIgnoreCase(identifierCode)) - { + for (Identifier i : m.getIdentifier()) { + if (i.hasType()) { + if (i.getType().getCodingFirstRep().getCode().equalsIgnoreCase(identifierCode)) { return i; } } @@ -272,14 +273,14 @@ private void addSubjectOfs(XMLBuilder2 xml, CqfMeasure m) { // CMS Identifier Identifier cms = this.getIdentifierFor(m, "CMS"); - if (cms!= null) { - this.addMeasureAttributeWithNullAndText(xml, "OTH","eCQM Identifier", "text/plain", cms.getValue()); + if (cms != null) { + this.addMeasureAttributeWithNullAndText(xml, "OTH", "eCQM Identifier", "text/plain", cms.getValue()); } // NQF Identifer Identifier nqf = this.getIdentifierFor(m, "NQF"); if (nqf != null) { - this.addMeasureAttributeWithNullAndText(xml, "OTH","NQF Number", "text/plain", nqf.getValue()); + this.addMeasureAttributeWithNullAndText(xml, "OTH", "NQF Number", "text/plain", nqf.getValue()); } // Copyright @@ -361,25 +362,28 @@ private void addSubjectOfs(XMLBuilder2 xml, CqfMeasure m) { this.addMeasureAttributeWithCodeAndTextValue(xml, "TRANF", codeSystem, "Transmission Format", "text/plain", "TBD"); - // TODO: Groups - It seems the HQMF measure supports descriptions for only one group, the FHIR measure has descriptions per group + // TODO: Groups - It seems the HQMF measure supports descriptions for only one + // group, the FHIR measure has descriptions per group if (m.hasGroup()) { MeasureGroupComponent mgc = m.getGroupFirstRep(); this.addGroupMeasureAttributes(xml, codeSystem, mgc); } - // TODO: Stratification - The HQMF measure has a description of the stratification, the FHIR measure does not. + // TODO: Stratification - The HQMF measure has a description of the + // stratification, the FHIR measure does not. - // TODO: Supplemental Data Elements - The HQMF measure has a description of the elements, the FHIR measure does not. + // TODO: Supplemental Data Elements - The HQMF measure has a description of the + // elements, the FHIR measure does not. } private void addGroupMeasureAttributes(XMLBuilder2 xml, String codeSystem, MeasureGroupComponent mgc) { - for (Map.Entry entry : measurePopulationValueSetMap.entrySet()) { + for (Map.Entry entry : measurePopulationValueSetMap.entrySet()) { String key = entry.getKey(); MeasureGroupPopulationComponent mgpc = GetPopulationForKey(key, mgc); if (mgpc != null) { - this.addMeasureAttributeWithCodeAndTextValue(xml, - entry.getValue().code, codeSystem, entry.getValue().displayName, - "text/plain", mgpc.hasDescription() ? mgpc.getDescription() : "None"); + this.addMeasureAttributeWithCodeAndTextValue(xml, entry.getValue().code, codeSystem, + entry.getValue().displayName, "text/plain", + mgpc.hasDescription() ? mgpc.getDescription() : "None"); } } } @@ -395,39 +399,44 @@ private MeasureGroupPopulationComponent GetPopulationForKey(String key, MeasureG return null; } - private void addMeasureAttributeWithCodeAndTextValue(XMLBuilder2 xml, String code, String codeSystem, String displayName, String mediaType, String value) { + private void addMeasureAttributeWithCodeAndTextValue(XMLBuilder2 xml, String code, String codeSystem, + String displayName, String mediaType, String value) { XMLBuilder2 temp = this.addMeasureAttribute(xml); this.addMeasureAttributeCode(temp, code, codeSystem, displayName); - this.addMeasureAttributeValue(temp, mediaType, value, "ED"); + this.addMeasureAttributeValue(temp, mediaType, value, "ED"); } - private void addMeasureAttributeWithCodeAndCodeValue(XMLBuilder2 xml, String code, String codeSystem, String displayName, String valueCode, String valueCodeSystem, String valueDisplayName) { + private void addMeasureAttributeWithCodeAndCodeValue(XMLBuilder2 xml, String code, String codeSystem, + String displayName, String valueCode, String valueCodeSystem, String valueDisplayName) { XMLBuilder2 temp = this.addMeasureAttribute(xml); this.addMeasureAttributeCode(temp, code, codeSystem, displayName); this.addMeasureAttributeValue(temp, valueCode, valueCodeSystem, "CD", valueDisplayName); } - private void addMeasureAttributeWithNullAndText(XMLBuilder2 xml, String nullFlavor, String originalText, String mediaType, String value) { + private void addMeasureAttributeWithNullAndText(XMLBuilder2 xml, String nullFlavor, String originalText, + String mediaType, String value) { XMLBuilder2 temp = this.addMeasureAttribute(xml); this.addMeasureAttributeCode(temp, nullFlavor, originalText); - this.addMeasureAttributeValue(temp, mediaType, value, "ED"); + this.addMeasureAttributeValue(temp, mediaType, value, "ED"); } private XMLBuilder2 addMeasureAttribute(XMLBuilder2 xml) { - return xml.root().elem("subjectOf") - .elem("measureAttribute"); + return xml.root().elem("subjectOf").elem("measureAttribute"); } private void addMeasureAttributeCode(XMLBuilder2 xml, String code, String codeSystem, String displayName) { - xml.elem("code").a("code", code).a("codeSystem", codeSystem).elem("displayName").a("value", displayName).up().up(); + xml.elem("code").a("code", code).a("codeSystem", codeSystem).elem("displayName").a("value", displayName).up() + .up(); } private void addMeasureAttributeCode(XMLBuilder2 xml, String nullFlavor, String originalText) { xml.elem("code").a("nullFlavor", nullFlavor).elem("originalText").a("value", originalText).up().up(); } - private void addMeasureAttributeValue(XMLBuilder2 xml, String code, String codeSystem, String xsiType, String displayName) { - xml.elem("value").a("code", code).a("codeSystem", codeSystem).a("xsi:type", xsiType).elem("displayName").a("value", displayName).up().up(); + private void addMeasureAttributeValue(XMLBuilder2 xml, String code, String codeSystem, String xsiType, + String displayName) { + xml.elem("value").a("code", code).a("codeSystem", codeSystem).a("xsi:type", xsiType).elem("displayName") + .a("value", displayName).up().up(); } private void addMeasureAttributeValue(XMLBuilder2 xml, String mediaType, String value, String xsiType) { @@ -438,9 +447,8 @@ private void addComponentOfs(XMLBuilder2 xml, CqfMeasure m) { // TODO: Where's the quality measure set? Hedis? String qualityMeasureSetId = "a0f96a17-36f0-46d4-bbd5-ad265d81bc95"; - xml.root().elem("componentOf").elem("qualityMeasureSet").a("classCode", "ACT") - .elem("id").a("root", qualityMeasureSetId).up() - .elem("title").a("value", "None"); + xml.root().elem("componentOf").elem("qualityMeasureSet").a("classCode", "ACT").elem("id") + .a("root", qualityMeasureSetId).up().elem("title").a("value", "None"); } private void addComponents(XMLBuilder2 xml, CqfMeasure m, String documentGuid, String documentName) { @@ -448,42 +456,44 @@ private void addComponents(XMLBuilder2 xml, CqfMeasure m, String documentGuid, S this.addPopulationCriteriaSection(xml, m, documentGuid, documentName); } - private void addDataCriteriaSection(XMLBuilder2 xml, CqfMeasure m) { this.addDataCriteriaHeader(xml); // if (m.hasDataCriteria()) { - // for (StringType s : m.getDataCriteria()) - // { + // for (StringType s : m.getDataCriteria()) + // { - // } + // } // } } private XMLBuilder2 addDataCriteriaHeader(XMLBuilder2 xml) { - return xml.root().elem("component") - .elem("dataCriteriaSection") - .elem("templateId") - .elem("item").a("extension","2018-05-01").a("root", "2.16.840.1.113883.10.20.28.2.6").up().up() - .elem("code").a("code","57025-9").a("codeSystem","2.16.840.1.113883.6.1").up() - .elem("title").a("value", "Data Criteria Section").up() - .elem("text").up(); + return xml.root().elem("component").elem("dataCriteriaSection").elem("templateId").elem("item") + .a("extension", "2018-05-01").a("root", "2.16.840.1.113883.10.20.28.2.6").up().up().elem("code") + .a("code", "57025-9").a("codeSystem", "2.16.840.1.113883.6.1").up().elem("title") + .a("value", "Data Criteria Section").up().elem("text").up(); } // Unlike other functions, this function expects the xml builder to be located - // at the correct spot. It's also expected to reset the xmlBuilder to the correct spot. - // private void addDataCriteriaEntry(XMLBuilder2 xml, String localVariableName, String criteriaName, String classCode, String itemExtension, String itemRoot, - // String idExtension, String idRoot, String code, String codeSystem, String codeSystemName, String codeDisplayName, String title, String statusCode, String valueSet) { - // xml.elem("entry").a("typeCode", "DRIV") - // .elem("localVariableName").a("value", localVariableName).up() - // .elem(criteriaName).a("classCode", classCode).a("moodCode", "EVN").up() - // .elem("templateId").elem("item").a("extension", itemExtension).a("root", itemRoot).up().up() - // .elem("id").a("extension", idExtension).a("root", idRoot).up() - // .elem("code").a("code", code).a("codeSystem", codeSystem).a("codeSystemName", codeSystemName) - // .elem("displayName").a("value", codeDisplayName).up().up() - // .elem("title").a("value", title).up() - // .elem("statusCode").a("code", statusCode).up() - // .elem("value").a("valueSet", valueSet).a("xsi:type", "CD").up().up(); + // at the correct spot. It's also expected to reset the xmlBuilder to the + // correct spot. + // private void addDataCriteriaEntry(XMLBuilder2 xml, String localVariableName, + // String criteriaName, String classCode, String itemExtension, String itemRoot, + // String idExtension, String idRoot, String code, String codeSystem, String + // codeSystemName, String codeDisplayName, String title, String statusCode, + // String valueSet) { + // xml.elem("entry").a("typeCode", "DRIV") + // .elem("localVariableName").a("value", localVariableName).up() + // .elem(criteriaName).a("classCode", classCode).a("moodCode", "EVN").up() + // .elem("templateId").elem("item").a("extension", itemExtension).a("root", + // itemRoot).up().up() + // .elem("id").a("extension", idExtension).a("root", idRoot).up() + // .elem("code").a("code", code).a("codeSystem", codeSystem).a("codeSystemName", + // codeSystemName) + // .elem("displayName").a("value", codeDisplayName).up().up() + // .elem("title").a("value", title).up() + // .elem("statusCode").a("code", statusCode).up() + // .elem("value").a("valueSet", valueSet).a("xsi:type", "CD").up().up(); // } private void addPopulationCriteriaSection(XMLBuilder2 xml, CqfMeasure m, String documentGuid, String documentName) { @@ -496,60 +506,59 @@ private void addPopulationCriteriaSection(XMLBuilder2 xml, CqfMeasure m, String for (MeasureGroupPopulationComponent mgpc : mgc.getPopulation()) { String key = mgpc.getCode().getCoding().get(0).getCode(); CodeMapping mapping = measurePopulationValueSetMap.get(key); - this.addPopulationCriteriaComponentCriteria(readyForComponents, mapping.criteriaName, mapping.criteriaExtension, mapping.code, documentName + ".\"" + mgpc.getCriteria() + "\"", documentGuid); + this.addPopulationCriteriaComponentCriteria(readyForComponents, mapping.criteriaName, + mapping.criteriaExtension, mapping.code, documentName + ".\"" + mgpc.getCriteria() + "\"", + documentGuid); } if (m.hasSupplementalData()) { for (MeasureSupplementalDataComponent sde : m.getSupplementalData()) { - this.addPopulationCriteriaComponentSDE(readyForComponents, UUID.randomUUID().toString(), documentName + ".\"" + sde.getCriteria() + "\"", documentGuid); + this.addPopulationCriteriaComponentSDE(readyForComponents, UUID.randomUUID().toString(), + documentName + ".\"" + sde.getCriteria() + "\"", documentGuid); } } - } + } } } // Unlike other functions, this function expects the xml builder to be located // at the correct spot. - private void addPopulationCriteriaComponentCriteria(XMLBuilder2 xml, String criteriaName, String criteriaIdExtension, - String code, String criteriaReferenceIdExtension, String criteriaReferenceIdRoot) { - xml.elem("component").a("typeCode", "COMP") - .elem(criteriaName).a("classCode", "OBS").a("moodCode", "EVN") - .elem("id").a("extension", criteriaIdExtension).a("root", UUID.randomUUID().toString()).up() - .elem("code").a("code", code).a("codeSystem", "2.16.840.1.113883.5.4").a("codeSystemName", "Act Code").up() - .elem("precondition").a("typeCode", "PRCN") - .elem("criteriaReference").a("classCode", "OBS").a("moodCode", "EVN") - .elem("id").a("extension", criteriaReferenceIdExtension).a("root", criteriaReferenceIdRoot).up().up().up().up().up(); + private void addPopulationCriteriaComponentCriteria(XMLBuilder2 xml, String criteriaName, + String criteriaIdExtension, String code, String criteriaReferenceIdExtension, + String criteriaReferenceIdRoot) { + xml.elem("component").a("typeCode", "COMP").elem(criteriaName).a("classCode", "OBS").a("moodCode", "EVN") + .elem("id").a("extension", criteriaIdExtension).a("root", UUID.randomUUID().toString()).up() + .elem("code").a("code", code).a("codeSystem", "2.16.840.1.113883.5.4").a("codeSystemName", "Act Code") + .up().elem("precondition").a("typeCode", "PRCN").elem("criteriaReference").a("classCode", "OBS") + .a("moodCode", "EVN").elem("id").a("extension", criteriaReferenceIdExtension) + .a("root", criteriaReferenceIdRoot).up().up().up().up().up(); } // Unlike other functions, this function expects the xml builder to be located // at the correct spot. - private void addPopulationCriteriaComponentSDE(XMLBuilder2 xml, String sdeIdRoot, String criteriaReferenceIdExtension, String criteriaReferenceIdRoot) { - xml.elem("component").a("typeCode", "COMP") - .elem("cql-ext:supplementalDataElement") - .elem("id").a("extension", "Supplemental Data Elements").a("root", sdeIdRoot).up() - .elem("code").a("code", "SDE").a("codeSystem", "2.16.840.1.113883.5.4").a("codeSystemName", "Act Code").up() - .elem("precondition").a("typeCode", "PRCN") - .elem("criteriaReference").a("classCode", "OBS").a("moodCode", "EVN") - .elem("id").a("extension", criteriaReferenceIdExtension).a("root", criteriaReferenceIdRoot).up().up().up().up().up(); + private void addPopulationCriteriaComponentSDE(XMLBuilder2 xml, String sdeIdRoot, + String criteriaReferenceIdExtension, String criteriaReferenceIdRoot) { + xml.elem("component").a("typeCode", "COMP").elem("cql-ext:supplementalDataElement").elem("id") + .a("extension", "Supplemental Data Elements").a("root", sdeIdRoot).up().elem("code").a("code", "SDE") + .a("codeSystem", "2.16.840.1.113883.5.4").a("codeSystemName", "Act Code").up().elem("precondition") + .a("typeCode", "PRCN").elem("criteriaReference").a("classCode", "OBS").a("moodCode", "EVN").elem("id") + .a("extension", criteriaReferenceIdExtension).a("root", criteriaReferenceIdRoot).up().up().up().up() + .up(); } private XMLBuilder2 addPopulationCriteriaHeader(XMLBuilder2 xml, String crtieriaName, String criteriaRoot) { - return xml.root().elem("component") - .elem("populationCriteriaSection") - .elem("templateId") - .elem("item").a("extension","2017-08-01").a("root", "2.16.840.1.113883.10.20.28.2.7").up().up() - .elem("id").a("extension", crtieriaName).a("root", criteriaRoot).up() - .elem("code").a("code","57026-7").a("codeSystem","2.16.840.1.113883.6.1").up() - .elem("title").a("value", "Population Criteria Section").up() - .elem("text").up(); + return xml.root().elem("component").elem("populationCriteriaSection").elem("templateId").elem("item") + .a("extension", "2017-08-01").a("root", "2.16.840.1.113883.10.20.28.2.7").up().up().elem("id") + .a("extension", crtieriaName).a("root", criteriaRoot).up().elem("code").a("code", "57026-7") + .a("codeSystem", "2.16.840.1.113883.6.1").up().elem("title").a("value", "Population Criteria Section") + .up().elem("text").up(); } private void addResponsibleParties(XMLBuilder2 xml, CqfMeasure m) { List contributors = m.getAuthor(); if (contributors != null) { - for (ContactDetail c : contributors) - { + for (ContactDetail c : contributors) { // TODO: Hard-coded NCQA's OID this.addResponsibleParty(xml, "author", "2.16.840.1.113883.3.464", c.getName()); } @@ -560,8 +569,7 @@ private void addResponsibleParties(XMLBuilder2 xml, CqfMeasure m) { this.addResponsibleParty(xml, "publisher", "2.16.840.1.113883.3.464", m.getPublisher()); } - - // Add verifier + // Add verifier // TODO: Not present on the FHIR resource - need an extension? // TODO: Hard-coded to National Quality Forum this.addResponsibleParty(xml, "verifier", "2.16.840.1.113883.3.560", "National Quality Forum"); @@ -569,14 +577,10 @@ private void addResponsibleParties(XMLBuilder2 xml, CqfMeasure m) { } private void addResponsibleParty(XMLBuilder2 xml, String type, String oid, String name) { - xml.root().elem(type) - .elem("responsibleParty").a("classCode", "ASSIGNED") - .elem("representedResponsibleOrganization").a("classCode","ORG").a("determinerCode","INSTANCE") - .elem("id") - .elem("item").a("root", oid).up().up() - .elem("name") - .elem("item") - .elem("part").a("value", name); + xml.root().elem(type).elem("responsibleParty").a("classCode", "ASSIGNED") + .elem("representedResponsibleOrganization").a("classCode", "ORG").a("determinerCode", "INSTANCE") + .elem("id").elem("item").a("root", oid).up().up().elem("name").elem("item").elem("part") + .a("value", name); } @@ -591,30 +595,27 @@ private String writeDocument(Document d) { transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); - + StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); return writer.toString(); - } - catch (Exception e) { + } catch (Exception e) { return null; } } - private boolean validateHQMF(String xml) { try { return this.validateXML(this.loadHQMFSchema(), xml); - } - catch (SAXException e) { + } catch (SAXException e) { return false; } } - private boolean validateXML(Schema schema, String xml){ + private boolean validateXML(Schema schema, String xml) { try { Validator validator = schema.newValidator(); validator.validate(new StreamSource(new StringReader(xml))); @@ -631,22 +632,22 @@ private Schema loadHQMFSchema() throws SAXException { return factory.newSchema(hqmfSchema); } - // args[0] == relative path to json measure -> i.e. measure/mesure-demo.json (optional) - // args[1] == path to resource output -> i.e. library/library-demo.json(optional) + // args[0] == relative path to json measure -> i.e. measure/mesure-demo.json + // (optional) + // args[1] == path to resource output -> i.e. + // library/library-demo.json(optional) // args[2] == path to hqmf output -> i.e. hqmf.xml(optional) // args[3] == path to narrative output -> i.e. output.html(optional) public static void main(String[] args) { try { - List strings = Arrays.asList( - "hqmf/examples/input/measure-ann.json", - "hqmf/examples/input/library-common.json", - "hqmf/examples/input/library-ann.json" - ); + List strings = Arrays.asList("hqmf/examples/input/measure-ann.json", + "hqmf/examples/input/library-common.json", "hqmf/examples/input/library-ann.json"); List paths = strings.stream().map(x -> Paths.get(toUri(x))).collect(Collectors.toList()); - // Path pathToLibrary = Paths.get(HQMFProvider.class.getClassLoader().getResource("narratives/examples/library/CMS146.json").toURI()); + // Path pathToLibrary = + // Paths.get(HQMFProvider.class.getClassLoader().getResource("narratives/examples/library/CMS146.json").toURI()); Path pathToOutput = Paths.get("src/main/resources/hqmf/hqmf.xml").toAbsolutePath(); Path pathToNarrativeOutput = Paths.get("src/main/resources/narratives/output.html").toAbsolutePath(); @@ -656,42 +657,43 @@ public static void main(String[] args) { if (args.length >= 4) { pathToNarrativeOutput = Paths.get(new URI(args[3])); } - + if (args.length >= 3) { pathToOutput = Paths.get(new URI(args[2])); } // if (args.length >= 2) { - // pathToLibrary = Paths.get(new URI(args[1])); + // pathToLibrary = Paths.get(new URI(args[1])); // } // if (args.length >= 1) { - // pathToMeasure = Paths.get(new URI(args[0])); + // pathToMeasure = Paths.get(new URI(args[0])); // } HQMFProvider provider = new HQMFProvider(); DataRequirementsProvider dataRequirementsProvider = new DataRequirementsProvider(); - NarrativeProvider narrativeProvider = new NarrativeProvider(pathToProp.toUri().toString());; - + NarrativeProvider narrativeProvider = new NarrativeProvider(pathToProp.toUri().toString()); + ; + FhirContext context = FhirContext.forDstu3(); - //IParser parser = pathToMeasure.toString().endsWith("json") ? context.newJsonParser() : context.newXmlParser(); + // IParser parser = pathToMeasure.toString().endsWith("json") ? + // context.newJsonParser() : context.newXmlParser(); IParser parser = context.newJsonParser(); - List resources = paths.stream() - .map(x -> toReader(x)) - .filter(x -> x != null) - .map(x -> parser.parseResource(x)) - .collect(Collectors.toList()); + List resources = paths.stream().map(x -> toReader(x)).filter(x -> x != null) + .map(x -> parser.parseResource(x)).collect(Collectors.toList()); - Measure measure = (Measure)resources.stream().filter(x -> (x instanceof Measure)).findFirst().get(); - List libraries = resources.stream().filter(x -> (x instanceof Library)).map(x -> (Library)x).collect(Collectors.toList()); + Measure measure = (Measure) resources.stream().filter(x -> (x instanceof Measure)).findFirst().get(); + List libraries = resources.stream().filter(x -> (x instanceof Library)).map(x -> (Library) x) + .collect(Collectors.toList()); - LibraryResolutionProvider lrp = new InMemoryLibraryResourceProvider(libraries, x -> x.getIdElement().getIdPart(), x -> x.getName(), x -> x.getVersion()); + LibraryResolutionProvider lrp = new InMemoryLibraryResourceProvider(libraries, + x -> x.getIdElement().getIdPart(), x -> x.getName(), x -> x.getVersion()); CqfMeasure cqfMeasure = dataRequirementsProvider.createCqfMeasure(measure, lrp); - + String result = provider.generateHQMF(cqfMeasure); PrintWriter writer = new PrintWriter(new File(pathToOutput.toString()), "UTF-8"); @@ -699,23 +701,19 @@ public static void main(String[] args) { writer.println(); writer.close(); - - Narrative narrative = narrativeProvider.getNarrative(context, cqfMeasure); String narrativeContent = narrative.getDivAsString(); - + Path pathToHTML = Paths.get(toUri("narratives/templates/hqmf.html")); org.jsoup.nodes.Document htmlDoc = Jsoup.parse(pathToHTML.toFile(), "UTF-8"); - + htmlDoc.title(measure.getName()); htmlDoc.body().html(narrativeContent); - + writer = new PrintWriter(new File(pathToNarrativeOutput.toString()), "UTF-8"); writer.write(htmlDoc.outerHtml()); writer.close(); - } - catch (Exception e) - { + } catch (Exception e) { e.printStackTrace(); return; } @@ -724,19 +722,16 @@ public static void main(String[] args) { public static Reader toReader(Path p) { try { return new FileReader(p.toFile()); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); return null; } } - public static URI toUri(String s) { try { return HQMFProvider.class.getClassLoader().getResource(s).toURI(); - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); return null; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java index 6394c868e..4715c185b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java @@ -1,25 +1,26 @@ package org.opencds.cqf.r4.providers; +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; +import org.opencds.cqf.cql.engine.runtime.Code; +import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.rp.r4.ValueSetResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.ValueSet; -import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.opencds.cqf.cql.runtime.Code; -import org.opencds.cqf.cql.terminology.CodeSystemInfo; -import org.opencds.cqf.cql.terminology.TerminologyProvider; -import org.opencds.cqf.cql.terminology.ValueSetInfo; - -import java.util.ArrayList; -import java.util.List; public class JpaTerminologyProvider implements TerminologyProvider { @@ -27,7 +28,8 @@ public class JpaTerminologyProvider implements TerminologyProvider { private FhirContext context; private ValueSetResourceProvider valueSetResourceProvider; - public JpaTerminologyProvider(ITermReadSvcR4 terminologySvcR4, FhirContext context, ValueSetResourceProvider valueSetResourceProvider) { + public JpaTerminologyProvider(ITermReadSvcR4 terminologySvcR4, FhirContext context, + ValueSetResourceProvider valueSetResourceProvider) { this.terminologySvcR4 = terminologySvcR4; this.context = context; this.valueSetResourceProvider = valueSetResourceProvider; @@ -36,7 +38,8 @@ public JpaTerminologyProvider(ITermReadSvcR4 terminologySvcR4, FhirContext conte @Override public synchronized boolean in(Code code, ValueSetInfo valueSet) throws ResourceNotFoundException { for (Code c : expand(valueSet)) { - if (c == null) continue; + if (c == null) + continue; if (c.getCode().equals(code.getCode()) && c.getSystem().equals(code.getSystem())) { return true; } @@ -50,24 +53,25 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource boolean needsExpand = false; ValueSet vs; if (valueSet.getId().startsWith("http://") || valueSet.getId().startsWith("https://")) { - if (valueSet.getVersion() != null || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { + if (valueSet.getVersion() != null + || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { if (!(valueSet.getCodeSystems().size() == 1 && valueSet.getCodeSystems().get(0).getVersion() == null)) { - throw new UnsupportedOperationException(String.format("Could not expand value set %s; version and code system bindings are not supported at this time.", valueSet.getId())); + throw new UnsupportedOperationException(String.format( + "Could not expand value set %s; version and code system bindings are not supported at this time.", + valueSet.getId())); } } - IBundleProvider bundleProvider = valueSetResourceProvider.getDao().search(new SearchParameterMap().add(ValueSet.SP_URL, new UriParam(valueSet.getId()))); + IBundleProvider bundleProvider = valueSetResourceProvider.getDao() + .search(new SearchParameterMap().add(ValueSet.SP_URL, new UriParam(valueSet.getId()))); List valueSets = bundleProvider.getResources(0, bundleProvider.size()); if (valueSets.isEmpty()) { throw new IllegalArgumentException(String.format("Could not resolve value set %s.", valueSet.getId())); - } - else if (valueSets.size() == 1) { + } else if (valueSets.size() == 1) { vs = (ValueSet) valueSets.get(0); - } - else { + } else { throw new IllegalArgumentException("Found more than 1 ValueSet with url: " + valueSet.getId()); } - } - else { + } else { vs = valueSetResourceProvider.getDao().read(new IdType(valueSet.getId())); } if (vs != null) { @@ -97,7 +101,7 @@ else if (valueSets.size() == 1) { return codes; } - + } List expansion = terminologySvcR4.expandValueSet(valueSet.getId()); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 816b05097..568a41a8c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -8,6 +8,7 @@ import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Narrative; @@ -17,25 +18,24 @@ import org.opencds.cqf.common.providers.LibrarySourceProvider; import org.opencds.cqf.library.r4.NarrativeProvider; +import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.instance.model.api.IBaseResource; - public class LibraryOperationsProvider implements LibraryResolutionProvider { private NarrativeProvider narrativeProvider; private DataRequirementsProvider dataRequirementsProvider; private LibraryResourceProvider libraryResourceProvider; - public LibraryOperationsProvider(LibraryResourceProvider libraryResourceProvider, NarrativeProvider narrativeProvider) { + public LibraryOperationsProvider(LibraryResourceProvider libraryResourceProvider, + NarrativeProvider narrativeProvider) { this.narrativeProvider = narrativeProvider; this.dataRequirementsProvider = new DataRequirementsProvider(); this.libraryResourceProvider = libraryResourceProvider; @@ -45,8 +45,7 @@ private ModelManager getModelManager() { return new ModelManager(); } - private LibraryManager getLibraryManager(ModelManager modelManager) - { + private LibraryManager getLibraryManager(ModelManager modelManager) { LibraryManager libraryManager = new LibraryManager(modelManager); libraryManager.getLibrarySourceLoader().clearProviders(); libraryManager.getLibrarySourceLoader().registerProvider(getLibrarySourceProvider()); @@ -59,10 +58,7 @@ private LibraryManager getLibraryManager(ModelManager modelManager) private LibrarySourceProvider getLibrarySourceProvider() { if (librarySourceProvider == null) { librarySourceProvider = new LibrarySourceProvider( - getLibraryResourceProvider(), - x -> x.getContent(), - x -> x.getContentType(), - x -> x.getData()); + getLibraryResourceProvider(), x -> x.getContent(), x -> x.getContentType(), x -> x.getData()); } return librarySourceProvider; } @@ -75,33 +71,34 @@ private LibraryResolutionProvider getLibraryResou public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, RequestDetails theRequestDetails, @IdParam IdType theId) { Library theResource = this.libraryResourceProvider.getDao().read(theId); - //this.formatCql(theResource); + // this.formatCql(theResource); ModelManager modelManager = this.getModelManager(); LibraryManager libraryManager = this.getLibraryManager(modelManager); - CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, modelManager); + CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, + modelManager); if (translator.getErrors().size() > 0) { throw new RuntimeException("Errors during library compilation."); } - + this.dataRequirementsProvider.ensureElm(theResource, translator); this.dataRequirementsProvider.ensureRelatedArtifacts(theResource, translator, this); this.dataRequirementsProvider.ensureDataRequirements(theResource, translator); - try { - Narrative n = this.narrativeProvider.getNarrative(this.libraryResourceProvider.getContext(), theResource); - theResource.setText(n); - } catch (Exception e) { - //Ignore the exception so the resource still gets updated - } + try { + Narrative n = this.narrativeProvider.getNarrative(this.libraryResourceProvider.getContext(), theResource); + theResource.setText(n); + } catch (Exception e) { + // Ignore the exception so the resource still gets updated + } return this.libraryResourceProvider.update(theRequest, theResource, theId, theRequestDetails.getConditionalUrl(RestOperationTypeEnum.UPDATE), theRequestDetails); } @Operation(name = "$get-elm", idempotent = true, type = Library.class) - public Parameters getElm(@IdParam IdType theId, @OptionalParam(name="format") String format) { + public Parameters getElm(@IdParam IdType theId, @OptionalParam(name = "format") String format) { Library theResource = this.libraryResourceProvider.getDao().read(theId); // this.formatCql(theResource); @@ -109,12 +106,12 @@ public Parameters getElm(@IdParam IdType theId, @OptionalParam(name="format") St LibraryManager libraryManager = this.getLibraryManager(modelManager); String elm = ""; - CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, modelManager); + CqlTranslator translator = this.dataRequirementsProvider.getTranslator(theResource, libraryManager, + modelManager); if (translator != null) { if (format.equals("json")) { elm = translator.toJson(); - } - else { + } else { elm = translator.toXml(); } } @@ -142,8 +139,7 @@ public void update(Library library) { public Library resolveLibraryById(String libraryId) { try { return this.libraryResourceProvider.getDao().read(new IdType(libraryId)); - } - catch (Exception e) { + } catch (Exception e) { throw new IllegalArgumentException(String.format("Could not resolve library id %s", libraryId)); } } @@ -151,7 +147,8 @@ public Library resolveLibraryById(String libraryId) { @Override public Library resolveLibraryByName(String libraryName, String libraryVersion) { Iterable libraries = getLibrariesByName(libraryName); - org.hl7.fhir.r4.model.Library library = LibraryResolutionProvider.selectFromList(libraries, libraryVersion, x -> x.getVersion()); + org.hl7.fhir.r4.model.Library library = LibraryResolutionProvider.selectFromList(libraries, libraryVersion, + x -> x.getVersion()); if (library == null) { throw new IllegalArgumentException(String.format("Could not resolve library name %s", libraryName)); @@ -172,12 +169,12 @@ private Iterable getLibrariesByName(String name) List resourceList = bundleProvider.getResources(0, bundleProvider.size()); return resolveLibraries(resourceList); } - - private Iterable resolveLibraries(List< IBaseResource > resourceList) { + + private Iterable resolveLibraries(List resourceList) { List ret = new ArrayList<>(); for (IBaseResource res : resourceList) { Class clazz = res.getClass(); - ret.add((org.hl7.fhir.r4.model.Library)clazz.cast(res)); + ret.add((org.hl7.fhir.r4.model.Library) clazz.cast(res)); } return ret; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 5ef69d175..a46ade1b4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -7,6 +7,10 @@ import javax.servlet.http.HttpServletRequest; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -22,14 +26,10 @@ import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.library.r4.NarrativeProvider; import org.opencds.cqf.measure.r4.CqfMeasure; import org.opencds.cqf.r4.evaluation.MeasureEvaluation; @@ -65,11 +65,12 @@ public class MeasureOperationsProvider { private DaoRegistry registry; private EvaluationProviderFactory factory; - private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class); - public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, LibraryResolutionProvider libraryResolutionProvider, - MeasureResourceProvider measureResourceProvider) { + public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, + NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, + LibraryResolutionProvider libraryResolutionProvider, + MeasureResourceProvider measureResourceProvider) { this.registry = registry; this.factory = factory; @@ -94,9 +95,11 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - theResource.getRelatedArtifact().removeIf(relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); + theResource.getRelatedArtifact().removeIf( + relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); // Ensure All Related Artifacts for all referenced Libraries if (!cqfMeasure.getRelatedArtifact().isEmpty()) { @@ -116,12 +119,12 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ } } - try { - Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); - theResource.setText(n.copy()); - } catch (Exception e) { - //Ignore the exception so the resource still gets updated - } + try { + Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); + theResource.setText(n.copy()); + } catch (Exception e) { + // Ignore the exception so the resource still gets updated + } return this.measureResourceProvider.update(theRequest, theResource, theId, theRequestDetails.getConditionalUrl(RestOperationTypeEnum.UPDATE), theRequestDetails); @@ -130,7 +133,8 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @Operation(name = "$get-narrative", idempotent = true, type = Measure.class) public Parameters getNarrative(@IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); Parameters p = new Parameters(); p.addParameter().setValue(new StringType(n.getDivAsString())); @@ -138,7 +142,8 @@ public Parameters getNarrative(@IdParam IdType theId) { } private String generateHQMF(Measure theResource) { - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); return this.hqmfProvider.generateHQMF(cqfMeasure); } @@ -158,7 +163,8 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name @OptionalParam(name = "source") String source, @OptionalParam(name = "user") String user, @OptionalParam(name = "pass") String pass) throws InternalErrorException, FHIRException { LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); + MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, + this.libraryResolutionProvider); Measure measure = this.measureResourceProvider.getDao().read(theId); if (measure == null) { @@ -168,24 +174,24 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name seed.setup(measure, periodStart, periodEnd, productLine, source, user, pass); // resolve report type - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); + MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + seed.getMeasurementPeriod()); if (reportType != null) { switch (reportType) { - case "patient": - return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - case "patient-list": - return evaluator.evaluateSubjectListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); - case "population": - return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); - default: - throw new IllegalArgumentException("Invalid report type: " + reportType); + case "patient": + return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); + case "patient-list": + return evaluator.evaluateSubjectListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); + case "population": + return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); + default: + throw new IllegalArgumentException("Invalid report type: " + reportType); } } // default report type is patient MeasureReport report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - if (productLine != null) - { + if (productLine != null) { Extension ext = new Extension(); ext.setUrl("http://hl7.org/fhir/us/cqframework/cqfmeasures/StructureDefinition/cqfm-productLine"); ext.setValue(new StringType(productLine)); @@ -197,34 +203,43 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name // @Operation(name = "$evaluate-measure-with-source", idempotent = true) // public MeasureReport evaluateMeasure(@IdParam IdType theId, - // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) Bundle sourceData, - // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, - // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { - // if (periodStart == null || periodEnd == null) { - // throw new IllegalArgumentException("periodStart and periodEnd are required for measure evaluation"); - // } - // LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResourceProvider); - // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResourceProvider); - // Measure measure = this.getDao().read(theId); - - // if (measure == null) { - // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); - // } - - // seed.setup(measure, periodStart, periodEnd, null, null, null, null); - // BundleDataProviderStu3 bundleProvider = new BundleDataProviderStu3(sourceData); - // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); - // seed.getContext().registerDataProvider("http://hl7.org/fhir", bundleProvider); - // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, seed.getMeasurementPeriod()); - // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), ""); + // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) + // Bundle sourceData, + // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, + // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { + // if (periodStart == null || periodEnd == null) { + // throw new IllegalArgumentException("periodStart and periodEnd are required + // for measure evaluation"); + // } + // LibraryLoader libraryLoader = + // LibraryHelper.createLibraryLoader(this.libraryResourceProvider); + // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, + // libraryLoader, this.libraryResourceProvider); + // Measure measure = this.getDao().read(theId); + + // if (measure == null) { + // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); + // } + + // seed.setup(measure, periodStart, periodEnd, null, null, null, null); + // BundleDataProviderStu3 bundleProvider = new + // BundleDataProviderStu3(sourceData); + // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); + // seed.getContext().registerDataProvider("http://hl7.org/fhir", + // bundleProvider); + // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, + // seed.getMeasurementPeriod()); + // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), + // ""); // } @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, @RequiredParam(name = "periodEnd") String periodEnd, @RequiredParam(name = "topic") String topic, @RequiredParam(name = "patient") String patientRef) { - List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap().add("topic", - new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))).getResources(0, 1000); + List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap() + .add("topic", new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))) + .getResources(0, 1000); Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); @@ -248,7 +263,9 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS if (measure.hasTitle()) { section.setTitle(measure.getTitle()); } - CodeableConcept improvementNotation = new CodeableConcept().addCoding(new Coding().setCode("increase").setSystem("http://terminology.hl7.org/CodeSystem/measure-improvement-notation")); // defaulting to "increase" + CodeableConcept improvementNotation = new CodeableConcept().addCoding(new Coding().setCode("increase") + .setSystem("http://terminology.hl7.org/CodeSystem/measure-improvement-notation")); // defaulting to + // "increase" if (measure.hasImprovementNotation()) { improvementNotation = measure.getImprovementNotation(); section.setText(new Narrative().setStatus(Narrative.NarrativeStatus.GENERATED) @@ -256,9 +273,11 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS } LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); + MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, + this.libraryResolutionProvider); seed.setup(measure, periodStart, periodEnd, null, null, null, null); - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); + MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); @@ -376,7 +395,8 @@ private void addEvaluatedResourcesToParameters(Bundle contained, Parameters para private void resolveReferences(Resource resource, Parameters parameters, Map resourceMap) { List values; - for (BaseRuntimeChildDefinition child : this.measureResourceProvider.getContext().getResourceDefinition(resource).getChildren()) { + for (BaseRuntimeChildDefinition child : this.measureResourceProvider.getContext() + .getResourceDefinition(resource).getChildren()) { values = child.getAccessor().getValues(resource); if (values == null || values.isEmpty()) { continue; @@ -404,7 +424,7 @@ else if (values.get(0) instanceof Reference public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId, @RequiredParam(name = "startPeriod") String startPeriod, @RequiredParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { - + Measure measure = this.measureResourceProvider.getDao().read(theId); return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index dcbb46c91..d49758a3c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -23,10 +23,10 @@ import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.exceptions.NotImplementedException; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.model.ModelResolver; -import org.opencds.cqf.cql.model.R4FhirModelResolver; -import org.opencds.cqf.cql.runtime.DateTime; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; +import org.opencds.cqf.cql.engine.model.ModelResolver; +import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.r4.builders.AttachmentBuilder; import org.opencds.cqf.r4.builders.CarePlanActivityBuilder; import org.opencds.cqf.r4.builders.CarePlanBuilder; @@ -54,16 +54,17 @@ public class PlanDefinitionApplyProvider { private ModelResolver modelResolver; private ActivityDefinitionApplyProvider activityDefinitionApplyProvider; - private IFhirResourceDao planDefintionDao; + private IFhirResourceDao planDefintionDao; private IFhirResourceDao activityDefinitionDao; private FhirContext fhirContext; private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); - public PlanDefinitionApplyProvider(FhirContext fhirContext, ActivityDefinitionApplyProvider activitydefinitionApplyProvider, - IFhirResourceDao planDefintionDao, IFhirResourceDao activityDefinitionDao, - CqlExecutionProvider executionProvider) { + public PlanDefinitionApplyProvider(FhirContext fhirContext, + ActivityDefinitionApplyProvider activitydefinitionApplyProvider, + IFhirResourceDao planDefintionDao, + IFhirResourceDao activityDefinitionDao, CqlExecutionProvider executionProvider) { this.executionProvider = executionProvider; this.modelResolver = new R4FhirModelResolver(); this.activityDefinitionApplyProvider = activitydefinitionApplyProvider; @@ -77,19 +78,16 @@ public IFhirResourceDao getDao() { } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) - public CarePlan applyPlanDefinition( - @IdParam IdType theId, - @RequiredParam(name="patient") String patientId, - @OptionalParam(name="encounter") String encounterId, - @OptionalParam(name="practitioner") String practitionerId, - @OptionalParam(name="organization") String organizationId, - @OptionalParam(name="userType") String userType, - @OptionalParam(name="userLanguage") String userLanguage, - @OptionalParam(name="userTaskContext") String userTaskContext, - @OptionalParam(name="setting") String setting, - @OptionalParam(name="settingContext") String settingContext) - throws IOException, JAXBException, FHIRException - { + public CarePlan applyPlanDefinition(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, + @OptionalParam(name = "encounter") String encounterId, + @OptionalParam(name = "practitioner") String practitionerId, + @OptionalParam(name = "organization") String organizationId, + @OptionalParam(name = "userType") String userType, + @OptionalParam(name = "userLanguage") String userLanguage, + @OptionalParam(name = "userTaskContext") String userTaskContext, + @OptionalParam(name = "setting") String setting, + @OptionalParam(name = "settingContext") String settingContext) + throws IOException, JAXBException, FHIRException { PlanDefinition planDefinition = this.planDefintionDao.read(theId); if (planDefinition == null) { @@ -100,19 +98,20 @@ public CarePlan applyPlanDefinition( CarePlanBuilder builder = new CarePlanBuilder(); - builder - .buildInstantiatesCanonical(planDefinition.getIdElement().getIdPart()) - .buildSubject(new Reference(patientId)) - .buildStatus(CarePlan.CarePlanStatus.DRAFT); + builder.buildInstantiatesCanonical(planDefinition.getIdElement().getIdPart()) + .buildSubject(new Reference(patientId)).buildStatus(CarePlan.CarePlanStatus.DRAFT); - if (encounterId != null) builder.buildEncounter(new Reference(encounterId)); - if (practitionerId != null) builder.buildAuthor(new Reference(practitionerId)); - if (organizationId != null) builder.buildAuthor(new Reference(organizationId)); - if (userLanguage != null) builder.buildLanguage(userLanguage); + if (encounterId != null) + builder.buildEncounter(new Reference(encounterId)); + if (practitionerId != null) + builder.buildAuthor(new Reference(practitionerId)); + if (organizationId != null) + builder.buildAuthor(new Reference(organizationId)); + if (userLanguage != null) + builder.buildLanguage(userLanguage); - Session session = - new Session(planDefinition, builder, patientId, encounterId, practitionerId, - organizationId, userType, userLanguage, userTaskContext, setting, settingContext); + Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, + userType, userLanguage, userTaskContext, setting, settingContext); return resolveActions(session); } @@ -131,11 +130,12 @@ private CarePlan resolveActions(Session session) { private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { - logger.debug("Resolving definition "+ action.getDefinitionCanonicalType().getValue()); + logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); String definition = action.getDefinitionCanonicalType().getValue(); if (definition.startsWith(session.getPlanDefinition().fhirType())) { logger.error("Currently cannot resolve nested PlanDefinitions"); - throw new NotImplementedException("Plan Definition refers to sub Plan Definition, this is not yet supported"); + throw new NotImplementedException( + "Plan Definition refers to sub Plan Definition, this is not yet supported"); } else { @@ -145,53 +145,38 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct result = this.activityDefinitionApplyProvider.resolveActivityDefinition( (ActivityDefinition) resolveContained(session.getPlanDefinition(), action.getDefinitionCanonicalType().getValue()), - session.getPatientId(), session.getPractionerId(), session.getOrganizationId() - ); - } - else { + session.getPatientId(), session.getPractionerId(), session.getOrganizationId()); + } else { result = this.activityDefinitionApplyProvider.apply( new IdType(CanonicalHelper.getId(action.getDefinitionCanonicalType())), - session.getPatientId(), - session.getEncounterId(), - session.getPractionerId(), - session.getOrganizationId(), - null, - session.getUserLanguage(), - session.getUserTaskContext(), - session.getSetting(), - session.getSettingContext() - ); + session.getPatientId(), session.getEncounterId(), session.getPractionerId(), + session.getOrganizationId(), null, session.getUserLanguage(), + session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } if (result.getId() == null) { - logger.warn("ActivityDefinition %s returned resource with no id, setting one", action.getDefinitionCanonicalType().getId()); - result.setId( UUID.randomUUID().toString() ); + logger.warn("ActivityDefinition %s returned resource with no id, setting one", + action.getDefinitionCanonicalType().getId()); + result.setId(UUID.randomUUID().toString()); } - session.getCarePlanBuilder() - .buildContained(result) - .buildActivity( - new CarePlanActivityBuilder() - .buildReference( new Reference("#"+result.getId()) ) - .build() - ); + session.getCarePlanBuilder().buildContained(result).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); } catch (Exception e) { - logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", action.getDefinition(), e.toString()); + logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", + action.getDefinition(), e.toString()); } } } } private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitionActionComponent action) { - for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue: action.getDynamicValue()) - { + for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action.getDynamicValue()) { logger.info("Resolving dynamic value %s %s", dynamicValue.getPath(), dynamicValue.getExpression()); if (dynamicValue.hasExpression()) { - Object result = - executionProvider - .evaluateInContext(session.getPlanDefinition(), dynamicValue.getExpression().getExpression(), session.getPatientId()); + Object result = executionProvider.evaluateInContext(session.getPlanDefinition(), + dynamicValue.getExpression().getExpression(), session.getPatientId()); - if (dynamicValue.hasPath() && dynamicValue.getPath().equals("$this")) - { + if (dynamicValue.hasPath() && dynamicValue.getPath().equals("$this")) { session.setCarePlan((CarePlan) result); } @@ -199,10 +184,7 @@ private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitio // TODO - likely need more date tranformations if (result instanceof DateTime) { - result = - new JavaDateBuilder() - .buildFromDateTime((DateTime) result) - .build(); + result = new JavaDateBuilder().buildFromDateTime((DateTime) result).build(); } else if (result instanceof String) { @@ -221,15 +203,18 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc meetsConditions(session, containedAction); } } - for (PlanDefinition.PlanDefinitionActionConditionComponent condition: action.getCondition()) { + for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { // TODO start // TODO stop if (condition.hasExpression() && condition.getExpression().hasDescription()) { logger.info("Resolving condition with description: " + condition.getExpression().getDescription()); } if (condition.getKind() == PlanDefinition.ActionConditionKind.APPLICABILITY) { - if (condition.hasExpression() && condition.getExpression().hasLanguage() && !condition.getExpression().getLanguage().equals("text/cql") && !condition.getExpression().getLanguage().equals("text/cql.name")) { - logger.warn("An action language other than CQL was found: " + condition.getExpression().getLanguage()); + if (condition.hasExpression() && condition.getExpression().hasLanguage() + && !condition.getExpression().getLanguage().equals("text/cql") + && !condition.getExpression().getLanguage().equals("text/cql.name")) { + logger.warn( + "An action language other than CQL was found: " + condition.getExpression().getLanguage()); continue; } @@ -242,11 +227,10 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc String cql = condition.getExpression().getExpression(); String language = condition.getExpression().getLanguage(); Object result = null; - result = (language.equals("text/cql.name")) - ? - executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId(), true) - : - executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId()); + result = (language.equals("text/cql.name")) + ? executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId(), + true) + : executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId()); if (result == null) { logger.warn("Expression Returned null"); @@ -306,12 +290,11 @@ public CarePlan resolveCdsHooksPlanDefinition(Context context, PlanDefinition pl } private void resolveActions(List actions, Context context, - String patientId, RequestGroupBuilder requestGroupBuilder, - List actionComponents) - { + String patientId, RequestGroupBuilder requestGroupBuilder, + List actionComponents) { for (PlanDefinition.PlanDefinitionActionComponent action : actions) { boolean conditionsMet = true; - for (PlanDefinition.PlanDefinitionActionConditionComponent condition: action.getCondition()) { + for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { if (condition.getKind() == PlanDefinition.ActionConditionKind.APPLICABILITY) { if (!condition.hasExpression()) { continue; @@ -321,7 +304,8 @@ private void resolveActions(List a continue; } - Object result = context.resolveExpressionRef(condition.getExpression().getExpression()).getExpression().evaluate(context); + Object result = context.resolveExpressionRef(condition.getExpression().getExpression()) + .getExpression().evaluate(context); if (!(result instanceof Boolean)) { continue; @@ -369,53 +353,57 @@ private void resolveActions(List a } if (action.hasDefinition()) { if (action.getDefinitionCanonicalType().getValue().contains("ActivityDefinition")) { - ActivityDefinition activityDefinition = - this.activityDefinitionDao.read(new IdType("ActivityDefinition", action.getDefinitionCanonicalType().getId())); + ActivityDefinition activityDefinition = this.activityDefinitionDao.read( + new IdType("ActivityDefinition", action.getDefinitionCanonicalType().getId())); if (activityDefinition.hasDescription()) { actionBuilder.buildDescripition(activityDefinition.getDescription()); } Resource resource; try { - resource = this.activityDefinitionApplyProvider.apply( - new IdType(action.getDefinitionCanonicalType().getId()), patientId, - null, null, null, null, - null, null, null, null - ).setId(UUID.randomUUID().toString()); - } catch (FHIRException | ClassNotFoundException | InstantiationException | IllegalAccessException e) { + resource = this.activityDefinitionApplyProvider + .apply(new IdType(action.getDefinitionCanonicalType().getId()), patientId, null, + null, null, null, null, null, null, null) + .setId(UUID.randomUUID().toString()); + } catch (FHIRException | ClassNotFoundException | InstantiationException + | IllegalAccessException e) { throw new RuntimeException("Error applying ActivityDefinition " + e.getMessage()); } Parameters inParams = new Parameters(); inParams.addParameter().setName("patient").setValue(new StringType(patientId)); - Parameters outParams = this.fhirContext.newRestfulGenericClient(HapiProperties.getServerBase()) - .operation() + Parameters outParams = this.fhirContext + .newRestfulGenericClient(HapiProperties.getServerBase()).operation() .onInstance(new IdDt("ActivityDefinition", action.getDefinition().getId())) - .named("$apply") - .withParameters(inParams) - .useHttpGet() - .execute(); + .named("$apply").withParameters(inParams).useHttpGet().execute(); List response = outParams.getParameter(); resource = response.get(0).getResource().setId(UUID.randomUUID().toString()); actionBuilder.buildResourceTarget(resource); - actionBuilder.buildResource(new ReferenceBuilder().buildReference(resource.getId()).build()); + actionBuilder + .buildResource(new ReferenceBuilder().buildReference(resource.getId()).build()); } } - // Dynamic values populate the RequestGroup - there is a bit of hijacking going on here... + // Dynamic values populate the RequestGroup - there is a bit of hijacking going + // on here... if (action.hasDynamicValue()) { - for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action.getDynamicValue()) { + for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action + .getDynamicValue()) { if (dynamicValue.hasPath() && dynamicValue.hasExpression()) { if (dynamicValue.getPath().endsWith("title")) { // summary - String title = (String) context.resolveExpressionRef(dynamicValue.getExpression().getExpression()).evaluate(context); + String title = (String) context + .resolveExpressionRef(dynamicValue.getExpression().getExpression()) + .evaluate(context); actionBuilder.buildTitle(title); - } - else if (dynamicValue.getPath().endsWith("description")) { // detail - String description = (String) context.resolveExpressionRef(dynamicValue.getExpression().getExpression()).evaluate(context); + } else if (dynamicValue.getPath().endsWith("description")) { // detail + String description = (String) context + .resolveExpressionRef(dynamicValue.getExpression().getExpression()) + .evaluate(context); actionBuilder.buildDescripition(description); - } - else if (dynamicValue.getPath().endsWith("extension")) { // indicator - String extension = (String) context.resolveExpressionRef(dynamicValue.getExpression().getExpression()).evaluate(context); + } else if (dynamicValue.getPath().endsWith("extension")) { // indicator + String extension = (String) context + .resolveExpressionRef(dynamicValue.getExpression().getExpression()) + .evaluate(context); actionBuilder.buildExtension(extension); } } @@ -444,7 +432,8 @@ public Resource resolveContained(DomainResource resource, String id) { } } - throw new RuntimeException(String.format("Resource %s does not contain resource with id %s", resource.fhirType(), id)); + throw new RuntimeException( + String.format("Resource %s does not contain resource with id %s", resource.fhirType(), id)); } } @@ -462,9 +451,8 @@ class Session { private String encounterId; public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String patientId, String encounterId, - String practitionerId, String organizationId, String userType, String userLanguage, - String userTaskContext, String setting, String settingContext) - { + String practitionerId, String organizationId, String userType, String userLanguage, String userTaskContext, + String setting, String settingContext) { this.patientId = patientId; this.planDefinition = planDefinition; this.carePlanBuilder = builder; diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 0f98252db..b255c90a9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -17,7 +17,7 @@ import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; -import org.opencds.cqf.cql.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.library.r4.NarrativeProvider; import org.opencds.cqf.measure.r4.CodeTerminologyRef; import org.opencds.cqf.measure.r4.CqfMeasure; @@ -74,14 +74,13 @@ protected void initialize() throws ServletException { // Fhir Context this.fhirContext = appCtx.getBean(FhirContext.class); - this.fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - this.fhirContext.registerCustomType(VersionedTerminologyRef.class); - this.fhirContext.registerCustomType(CodeTerminologyRef.class); - this.fhirContext.registerCustomType(PopulationCriteriaMap.class); - this.fhirContext.registerCustomType(CqfMeasure.class); + this.fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + this.fhirContext.registerCustomType(VersionedTerminologyRef.class); + this.fhirContext.registerCustomType(CodeTerminologyRef.class); + this.fhirContext.registerCustomType(PopulationCriteriaMap.class); + this.fhirContext.registerCustomType(CqfMeasure.class); setFhirContext(this.fhirContext); - // System and Resource Daos IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoR4", IFhirSystemDao.class); this.registry = appCtx.getBean(DaoRegistry.class); @@ -90,16 +89,20 @@ protected void initialize() throws ServletException { Object systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); registerProvider(systemProvider); - - ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); + ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", + ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, appCtx.getBean(DaoConfig.class)); + JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, + appCtx.getBean(DaoConfig.class)); confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); setServerConformanceProvider(confProvider); - JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider(appCtx.getBean("terminologyService", ITermReadSvcR4.class), getFhirContext(), (ValueSetResourceProvider)this.getResourceProvider(ValueSet.class)); - EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, localSystemTerminologyProvider); + JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( + appCtx.getBean("terminologyService", ITermReadSvcR4.class), getFhirContext(), + (ValueSetResourceProvider) this.getResourceProvider(ValueSet.class)); + EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, + localSystemTerminologyProvider); resolveProviders(providerFactory, localSystemTerminologyProvider, this.registry); @@ -127,37 +130,36 @@ protected void initialize() throws ServletException { setDefaultResponseEncoding(HapiProperties.getDefaultEncoding()); /* - * This configures the server to page search results to and from - * the database, instead of only paging them to memory. This may mean - * a performance hit when performing searches that return lots of results, - * but makes the server much more scalable. + * This configures the server to page search results to and from the database, + * instead of only paging them to memory. This may mean a performance hit when + * performing searches that return lots of results, but makes the server much + * more scalable. */ setPagingProvider(appCtx.getBean(DatabaseBackedPagingProvider.class)); /* - * This interceptor formats the output using nice colourful - * HTML output when the request is detected to come from a - * browser. + * This interceptor formats the output using nice colourful HTML output when the + * request is detected to come from a browser. */ - ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx.getBean(ResponseHighlighterInterceptor.class); + ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx + .getBean(ResponseHighlighterInterceptor.class); this.registerInterceptor(responseHighlighterInterceptor); /* * If you are hosting this server at a specific DNS name, the server will try to * figure out the FHIR base URL based on what the web container tells it, but - * this doesn't always work. If you are setting links in your search bundles that - * just refer to "localhost", you might want to use a server address strategy: + * this doesn't always work. If you are setting links in your search bundles + * that just refer to "localhost", you might want to use a server address + * strategy: */ String serverAddress = HapiProperties.getServerAddress(); - if (serverAddress != null && serverAddress.length() > 0) - { + if (serverAddress != null && serverAddress.length() > 0) { setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); } registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); - if (HapiProperties.getCorsEnabled()) - { + if (HapiProperties.getCorsEnabled()) { CorsConfiguration config = new CorsConfiguration(); config.addAllowedHeader("x-fhir-starter"); config.addAllowedHeader("Origin"); @@ -179,30 +181,31 @@ protected void initialize() throws ServletException { } } - protected NarrativeProvider getNarrativeProvider() { - return new NarrativeProvider(); - } + protected NarrativeProvider getNarrativeProvider() { + return new NarrativeProvider(); + } - // Since resource provider resolution not lazy, the providers here must be resolved in the correct + // Since resource provider resolution not lazy, the providers here must be + // resolved in the correct // order of dependencies. - private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) - throws ServletException - { + private void resolveProviders(EvaluationProviderFactory providerFactory, + JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { NarrativeProvider narrativeProvider = this.getNarrativeProvider(); HQMFProvider hqmfProvider = new HQMFProvider(); // Code System Update - CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider( - this.getDao(ValueSet.class), - this.getDao(CodeSystem.class)); + CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), + this.getDao(CodeSystem.class)); this.registerProvider(csUpdate); // Cache Value Sets - CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), this.getDao(Endpoint.class)); + CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), + this.getDao(Endpoint.class)); this.registerProvider(cvs); - //Library processing - LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider((LibraryResourceProvider)this.getResourceProvider(Library.class), narrativeProvider); + // Library processing + LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider( + (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider); this.registerProvider(libraryProvider); // CQL Execution @@ -210,22 +213,27 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerm this.registerProvider(cql); // Bundle processing - ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, this.getDao(Bundle.class)); + ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, + this.getDao(Bundle.class)); this.registerProvider(bundleProvider); // Measure processing - MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, narrativeProvider, hqmfProvider, - libraryProvider, (MeasureResourceProvider)this.getResourceProvider(Measure.class)); + MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, + narrativeProvider, hqmfProvider, libraryProvider, + (MeasureResourceProvider) this.getResourceProvider(Measure.class)); this.registerProvider(measureProvider); // // ActivityDefinition processing - ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, this.getDao(ActivityDefinition.class)); + ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, + this.getDao(ActivityDefinition.class)); this.registerProvider(actDefProvider); - JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, new SearchParameterResolver(this.fhirContext)); + JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, + new SearchParameterResolver(this.fhirContext)); // PlanDefinition processing - PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); + PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, + this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); this.registerProvider(planDefProvider); CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); @@ -238,9 +246,8 @@ protected IFhirResourceDao getDao(Class clazz) { return this.registry.getResourceDao(clazz); } - - protected BaseJpaResourceProvider getResourceProvider(Class clazz) { - return (BaseJpaResourceProvider ) this.getResourceProviders().stream() - .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); + protected BaseJpaResourceProvider getResourceProvider(Class clazz) { + return (BaseJpaResourceProvider) this.getResourceProviders().stream() + .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 3be55726f..bdbaf3162 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -1,13 +1,29 @@ package org.opencds.cqf.r4.servlet; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.apache.http.entity.ContentType; +import org.cqframework.cql.elm.execution.Library; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.PlanDefinition; import org.opencds.cqf.cds.discovery.DiscoveryResolutionR4; import org.opencds.cqf.cds.evaluation.EvaluationContext; import org.opencds.cqf.cds.evaluation.R4EvaluationContext; @@ -17,42 +33,26 @@ import org.opencds.cqf.cds.request.JsonHelper; import org.opencds.cqf.cds.request.Request; import org.opencds.cqf.cds.response.CdsCard; -import com.google.gson.*; -import org.apache.http.entity.ContentType; -import org.cqframework.cql.elm.execution.Library; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.PlanDefinition; -import org.opencds.cqf.cql.data.CompositeDataProvider; -import org.opencds.cqf.cql.execution.Context; -import org.opencds.cqf.cql.execution.LibraryLoader; -import org.opencds.cqf.cql.model.ModelResolver; -import org.opencds.cqf.cql.model.R4FhirModelResolver; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.exceptions.InvalidRequestException; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.r4.helpers.LibraryHelper; import org.opencds.cqf.r4.providers.JpaTerminologyProvider; import org.opencds.cqf.r4.providers.PlanDefinitionApplyProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @WebServlet(name = "cds-services") -public class CdsHooksServlet extends HttpServlet -{ +public class CdsHooksServlet extends HttpServlet { private FhirVersionEnum version = FhirVersionEnum.R4; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); @@ -64,7 +64,6 @@ public class CdsHooksServlet extends HttpServlet private static JpaTerminologyProvider jpaTerminologyProvider; - // TODO: There's probably a way to wire this all up using Spring public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; @@ -85,23 +84,20 @@ public static void setSystemTerminologyProvider(JpaTerminologyProvider jpaTermin // CORS Pre-flight @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { + protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { setAccessControlHeaders(resp); resp.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); resp.setHeader("X-Content-Type-Options", "nosniff"); - + resp.setStatus(HttpServletResponse.SC_OK); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException - { + throws ServletException, IOException { logger.info(request.getRequestURI()); - if (!request.getRequestURL().toString().endsWith("cds-services")) - { + if (!request.getRequestURL().toString().endsWith("cds-services")) { logger.error(request.getRequestURI()); throw new ServletException("This servlet is not configured to handle GET requests."); } @@ -113,56 +109,48 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException - { + throws ServletException, IOException { logger.info(request.getRequestURI()); try { // validate that we are dealing with JSON - if (!request.getContentType().startsWith("application/json")) - { - throw new ServletException( - String.format( - "Invalid content type %s. Please use application/json.", - request.getContentType() - ) - ); + if (!request.getContentType().startsWith("application/json")) { + throw new ServletException(String.format("Invalid content type %s. Please use application/json.", + request.getContentType())); } - String baseUrl = - request.getRequestURL().toString() - .replace(request.getPathInfo(), "").replace(request.getServletPath(), "") + "/fhir"; + String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") + .replace(request.getServletPath(), "") + "/fhir"; String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); - Request cdsHooksRequest = - new Request( - service, - parser.parse(request.getReader()).getAsJsonObject(), - JsonHelper.getObjectRequired(getService(service), "prefetch") - ); + Request cdsHooksRequest = new Request(service, parser.parse(request.getReader()).getAsJsonObject(), + JsonHelper.getObjectRequired(getService(service), "prefetch")); logger.info(cdsHooksRequest.getRequestJson().toString()); Hook hook = HookFactory.createHook(cdsHooksRequest); - PlanDefinition planDefinition = planDefinitionProvider.getDao().read(new IdType(hook.getRequest().getServiceName())); + PlanDefinition planDefinition = planDefinitionProvider.getDao() + .read(new IdType(hook.getRequest().getServiceName())); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResolutionProvider); - Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, libraryResolutionProvider); + Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, + libraryResolutionProvider); - R4FhirModelResolver resolver = new R4FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); Context context = new Context(library); - context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote provider case + context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote + // provider case context.registerTerminologyProvider(jpaTerminologyProvider); context.registerLibraryLoader(libraryLoader); context.setContextValue("Patient", hook.getRequest().getContext().getPatientId().replace("Patient/", "")); context.setExpressionCaching(true); - - EvaluationContext evaluationContext = new R4EvaluationContext(hook, version, FhirContext.forR4().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, planDefinition); + EvaluationContext evaluationContext = new R4EvaluationContext(hook, version, + FhirContext.forR4().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, + planDefinition); this.setAccessControlHeaders(response); @@ -175,15 +163,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) logger.info(jsonResponse); response.getWriter().println(jsonResponse); - } - catch (BaseServerResponseException e){ + } catch (BaseServerResponseException e) { this.setAccessControlHeaders(response); switch (e.getStatusCode()) { case 401: case 403: case 404: - response.getWriter().println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); + response.getWriter() + .println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); response.getWriter().println(e.getMessage()); response.setStatus(412); break; @@ -192,8 +180,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println(e.getMessage()); response.setStatus(500); } - } - catch(Exception e) { + } catch (Exception e) { this.setAccessControlHeaders(response); StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); @@ -204,61 +191,49 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } - private JsonObject getService(String service) - { + private JsonObject getService(String service) { JsonArray services = getServices().get("services").getAsJsonArray(); List ids = new ArrayList<>(); - for (JsonElement element : services) - { - if (element.isJsonObject() && element.getAsJsonObject().has("id")) - { + for (JsonElement element : services) { + if (element.isJsonObject() && element.getAsJsonObject().has("id")) { ids.add(element.getAsJsonObject().get("id").getAsString()); - if (element.isJsonObject() && element.getAsJsonObject().get("id").getAsString().equals(service)) - { + if (element.isJsonObject() && element.getAsJsonObject().get("id").getAsString().equals(service)) { return element.getAsJsonObject(); } } } - throw new InvalidRequestException("Cannot resolve service: " + service + "\nAvailable services: " + ids.toString()); + throw new InvalidRequestException( + "Cannot resolve service: " + service + "\nAvailable services: " + ids.toString()); } - private JsonObject getServices() - { - return new DiscoveryResolutionR4(FhirContext.forR4().newRestfulGenericClient(HapiProperties.getServerAddress())).resolve().getAsJson(); + private JsonObject getServices() { + return new DiscoveryResolutionR4(FhirContext.forR4().newRestfulGenericClient(HapiProperties.getServerAddress())) + .resolve().getAsJson(); } - private String toJsonResponse(List cards) - { + private String toJsonResponse(List cards) { JsonObject ret = new JsonObject(); JsonArray cardArray = new JsonArray(); - for (CdsCard card : cards) - { + for (CdsCard card : cards) { cardArray.add(card.toJson()); } ret.add("cards", cardArray); Gson gson = new GsonBuilder().setPrettyPrinting().create(); - return gson.toJson(ret); + return gson.toJson(ret); } - private void setAccessControlHeaders(HttpServletResponse resp) { - if (HapiProperties.getCorsEnabled()) - { + if (HapiProperties.getCorsEnabled()) { resp.setHeader("Access-Control-Allow-Origin", HapiProperties.getCorsAllowedOrigin()); - resp.setHeader("Access-Control-Allow-Methods", String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); - resp.setHeader("Access-Control-Allow-Headers", - String.join(", ", Arrays.asList( - "x-fhir-starter", - "Origin", - "Accept", - "X-Requested-With", - "Content-Type", - "Authorization", - "Cache-Control"))); - resp.setHeader("Access-Control-Expose-Headers", String.join(", ", Arrays.asList("Location", "Content-Location"))); + resp.setHeader("Access-Control-Allow-Methods", + String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); + resp.setHeader("Access-Control-Allow-Headers", String.join(", ", Arrays.asList("x-fhir-starter", "Origin", + "Accept", "X-Requested-With", "Content-Type", "Authorization", "Cache-Control"))); + resp.setHeader("Access-Control-Expose-Headers", + String.join(", ", Arrays.asList("Location", "Content-Location"))); resp.setHeader("Access-Control-Max-Age", "86400"); } } From ecc296f192b33aa705af155b586e905924091f03 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 29 Apr 2020 16:15:14 -0600 Subject: [PATCH 027/198] Update schematron dependency --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0248a7306..fa3b1e371 100644 --- a/pom.xml +++ b/pom.xml @@ -272,7 +272,7 @@ com.helger ph-schematron - 5.0.4 + 5.6.0 Saxon-HE From 6088ab7d837657f87b83fe3a6de9de1d051215f5 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 29 Apr 2020 19:28:02 -0600 Subject: [PATCH 028/198] Update operation params --- .../ActivityDefinitionApplyProvider.java | 22 ++++++------ .../providers/CacheValueSetsProvider.java | 5 +-- .../providers/LibraryOperationsProvider.java | 3 +- .../providers/MeasureOperationsProvider.java | 35 ++++++++++--------- .../PlanDefinitionApplyProvider.java | 19 +++++----- .../ActivityDefinitionApplyProvider.java | 22 ++++++------ .../r4/providers/CacheValueSetsProvider.java | 5 +-- .../providers/LibraryOperationsProvider.java | 3 +- .../providers/MeasureOperationsProvider.java | 35 ++++++++++--------- .../PlanDefinitionApplyProvider.java | 19 +++++----- 10 files changed, 90 insertions(+), 78 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java index a793fcae8..d26f3c83c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -50,16 +51,17 @@ public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProv } @Operation(name = "$apply", idempotent = true, type = ActivityDefinition.class) - public Resource apply(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, - @OptionalParam(name = "encounter") String encounterId, - @OptionalParam(name = "practitioner") String practitionerId, - @OptionalParam(name = "organization") String organizationId, - @OptionalParam(name = "userType") String userType, - @OptionalParam(name = "userLanguage") String userLanguage, - @OptionalParam(name = "userTaskContext") String userTaskContext, - @OptionalParam(name = "setting") String setting, - @OptionalParam(name = "settingContext") String settingContext) throws InternalErrorException, FHIRException, - ClassNotFoundException, IllegalAccessException, InstantiationException, ActivityDefinitionApplyException { + public Resource apply(@IdParam IdType theId, @OperationParam(name = "patient") String patientId, + @OperationParam(name = "encounter") String encounterId, + @OperationParam(name = "practitioner") String practitionerId, + @OperationParam(name = "organization") String organizationId, + @OperationParam(name = "userType") String userType, + @OperationParam(name = "userLanguage") String userLanguage, + @OperationParam(name = "userTaskContext") String userTaskContext, + @OperationParam(name = "setting") String setting, + @OperationParam(name = "settingContext") String settingContext) + throws InternalErrorException, FHIRException, ClassNotFoundException, IllegalAccessException, + InstantiationException, ActivityDefinitionApplyException { ActivityDefinition activityDefinition; try { activityDefinition = this.activityDefinitionDao.read(theId); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java index a8bbeeb1b..eaaabbb78 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -37,8 +38,8 @@ public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao measures = this.measureResourceProvider.getDao().search(new SearchParameterMap() .add("topic", new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))) .getResources(0, 1000); @@ -339,10 +340,10 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) - public Parameters collectData(@IdParam IdType theId, @RequiredParam(name = "periodStart") String periodStart, - @RequiredParam(name = "periodEnd") String periodEnd, @OptionalParam(name = "patient") String patientRef, - @OptionalParam(name = "practitioner") String practitionerRef, - @OptionalParam(name = "lastReceivedOn") String lastReceivedOn) throws FHIRException { + public Parameters collectData(@IdParam IdType theId, @OperationParam(name = "periodStart") String periodStart, + @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "patient") String patientRef, + @OperationParam(name = "practitioner") String practitionerRef, + @OperationParam(name = "lastReceivedOn") String lastReceivedOn) throws FHIRException { // TODO: Spec says that the periods are not required, but I am not sure what to // do when they aren't supplied so I made them required MeasureReport report = evaluateMeasure(theId, periodStart, periodEnd, null, null, patientRef, null, @@ -417,8 +418,8 @@ else if (values.get(0) instanceof Reference // TODO - this needs a lot of work @Operation(name = "$data-requirements", idempotent = true, type = Measure.class) public org.hl7.fhir.dstu3.model.Library dataRequirements(@IdParam IdType theId, - @RequiredParam(name = "startPeriod") String startPeriod, - @RequiredParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { + @OperationParam(name = "startPeriod") String startPeriod, + @OperationParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { Measure measure = this.measureResourceProvider.getDao().read(theId); return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java index 40cec4e77..8ee8451b7 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java @@ -44,6 +44,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; @@ -77,15 +78,15 @@ public IFhirResourceDao getDao() { } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) - public CarePlan applyPlanDefinition(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, - @OptionalParam(name = "encounter") String encounterId, - @OptionalParam(name = "practitioner") String practitionerId, - @OptionalParam(name = "organization") String organizationId, - @OptionalParam(name = "userType") String userType, - @OptionalParam(name = "userLanguage") String userLanguage, - @OptionalParam(name = "userTaskContext") String userTaskContext, - @OptionalParam(name = "setting") String setting, - @OptionalParam(name = "settingContext") String settingContext) + public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name = "patient") String patientId, + @OperationParam(name = "encounter") String encounterId, + @OperationParam(name = "practitioner") String practitionerId, + @OperationParam(name = "organization") String organizationId, + @OperationParam(name = "userType") String userType, + @OperationParam(name = "userLanguage") String userLanguage, + @OperationParam(name = "userTaskContext") String userTaskContext, + @OperationParam(name = "setting") String setting, + @OperationParam(name = "settingContext") String settingContext) throws IOException, JAXBException, FHIRException { PlanDefinition planDefinition = this.planDefintionDao.read(theId); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java index a8cb9719a..1605a891c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -50,16 +51,17 @@ public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProv } @Operation(name = "$apply", idempotent = true, type = ActivityDefinition.class) - public Resource apply(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, - @OptionalParam(name = "encounter") String encounterId, - @OptionalParam(name = "practitioner") String practitionerId, - @OptionalParam(name = "organization") String organizationId, - @OptionalParam(name = "userType") String userType, - @OptionalParam(name = "userLanguage") String userLanguage, - @OptionalParam(name = "userTaskContext") String userTaskContext, - @OptionalParam(name = "setting") String setting, - @OptionalParam(name = "settingContext") String settingContext) throws InternalErrorException, FHIRException, - ClassNotFoundException, IllegalAccessException, InstantiationException, ActivityDefinitionApplyException { + public Resource apply(@IdParam IdType theId, @OperationParam(name = "patient") String patientId, + @OperationParam(name = "encounter") String encounterId, + @OperationParam(name = "practitioner") String practitionerId, + @OperationParam(name = "organization") String organizationId, + @OperationParam(name = "userType") String userType, + @OperationParam(name = "userLanguage") String userLanguage, + @OperationParam(name = "userTaskContext") String userTaskContext, + @OperationParam(name = "setting") String setting, + @OperationParam(name = "settingContext") String settingContext) + throws InternalErrorException, FHIRException, ClassNotFoundException, IllegalAccessException, + InstantiationException, ActivityDefinitionApplyException { ActivityDefinition activityDefinition; try { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java index 4da693746..a15e66056 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -37,8 +38,8 @@ public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao measures = this.measureResourceProvider.getDao().search(new SearchParameterMap() .add("topic", new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))) .getResources(0, 1000); @@ -344,10 +345,10 @@ public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodS } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) - public Parameters collectData(@IdParam IdType theId, @RequiredParam(name = "periodStart") String periodStart, - @RequiredParam(name = "periodEnd") String periodEnd, @OptionalParam(name = "patient") String patientRef, - @OptionalParam(name = "practitioner") String practitionerRef, - @OptionalParam(name = "lastReceivedOn") String lastReceivedOn) throws FHIRException { + public Parameters collectData(@IdParam IdType theId, @OperationParam(name = "periodStart") String periodStart, + @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "patient") String patientRef, + @OperationParam(name = "practitioner") String practitionerRef, + @OperationParam(name = "lastReceivedOn") String lastReceivedOn) throws FHIRException { // TODO: Spec says that the periods are not required, but I am not sure what to // do when they aren't supplied so I made them required MeasureReport report = evaluateMeasure(theId, periodStart, periodEnd, null, null, patientRef, null, @@ -422,8 +423,8 @@ else if (values.get(0) instanceof Reference // TODO - this needs a lot of work @Operation(name = "$data-requirements", idempotent = true, type = Measure.class) public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId, - @RequiredParam(name = "startPeriod") String startPeriod, - @RequiredParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { + @OperationParam(name = "startPeriod") String startPeriod, + @OperationParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException { Measure measure = this.measureResourceProvider.getDao().read(theId); return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index d49758a3c..a337eb482 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -45,6 +45,7 @@ import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.annotation.RequiredParam; @@ -78,15 +79,15 @@ public IFhirResourceDao getDao() { } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) - public CarePlan applyPlanDefinition(@IdParam IdType theId, @RequiredParam(name = "patient") String patientId, - @OptionalParam(name = "encounter") String encounterId, - @OptionalParam(name = "practitioner") String practitionerId, - @OptionalParam(name = "organization") String organizationId, - @OptionalParam(name = "userType") String userType, - @OptionalParam(name = "userLanguage") String userLanguage, - @OptionalParam(name = "userTaskContext") String userTaskContext, - @OptionalParam(name = "setting") String setting, - @OptionalParam(name = "settingContext") String settingContext) + public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name = "patient") String patientId, + @OperationParam(name = "encounter") String encounterId, + @OperationParam(name = "practitioner") String practitionerId, + @OperationParam(name = "organization") String organizationId, + @OperationParam(name = "userType") String userType, + @OperationParam(name = "userLanguage") String userLanguage, + @OperationParam(name = "userTaskContext") String userTaskContext, + @OperationParam(name = "setting") String setting, + @OperationParam(name = "settingContext") String settingContext) throws IOException, JAXBException, FHIRException { PlanDefinition planDefinition = this.planDefintionDao.read(theId); From d8af1657edfa4285c8abadc814e61dd75bc087cf Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 29 Apr 2020 20:51:29 -0600 Subject: [PATCH 029/198] Fixes for some reflection hacks --- .../cqf/common/retrieve/JpaFhirRetrieveProvider.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index a44c01daf..dd9f9b828 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -1,10 +1,12 @@ package org.opencds.cqf.common.retrieve; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.cql.engine.fhir.retrieve.SearchParamFhirRetrieveProvider; @@ -44,8 +46,10 @@ protected Collection executeQuery(String dataType, SearchParameterMap ma var hapiMap = new ca.uhn.fhir.jpa.searchparam.SearchParameterMap(); try { - var method = List.of(hapiMap.getClass().getMethods()).stream() - .filter(x -> x.getName().equals("put") && Modifier.isPrivate(x.getModifiers())).findFirst().get(); + var methods = hapiMap.getClass().getDeclaredMethods(); + var methodList = List.of(methods); + List puts = methodList.stream().filter(x -> x.getName().equals("put")).collect(Collectors.toList()); + var method = puts.get(0); method.setAccessible(true); for (var entry : map.entrySet()) { @@ -54,6 +58,7 @@ protected Collection executeQuery(String dataType, SearchParameterMap ma } catch (Exception e) { // TODO: Add logging. + System.out.println(e.getMessage()); } IFhirResourceDao dao = this.registry.getResourceDao(dataType); From 0e85e297e78e4562a8c451c077b540976cab932d Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 11 May 2020 22:41:10 -0600 Subject: [PATCH 030/198] Support for FHIR 4.0.1 and 3.0.2 --- .../opencds/cqf/dstu3/evaluation/ProviderFactory.java | 2 +- .../dstu3/providers/ApplyCqlOperationProvider.java | 10 +++++++--- .../cqf/dstu3/providers/CqlExecutionProvider.java | 10 +++++++--- .../org/opencds/cqf/dstu3/servlet/BaseServlet.java | 4 ++-- .../opencds/cqf/r4/evaluation/ProviderFactory.java | 2 +- .../cqf/r4/providers/ApplyCqlOperationProvider.java | 10 +++++++--- .../cqf/r4/providers/CqlExecutionProvider.java | 11 ++++++++--- .../java/org/opencds/cqf/r4/servlet/BaseServlet.java | 4 ++-- 8 files changed, 35 insertions(+), 18 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index 7c446e3dd..fe5cbe955 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -42,7 +42,7 @@ public DataProvider createDataProvider(String model, String version, String url, } public DataProvider createDataProvider(String model, String version, TerminologyProvider terminologyProvider) { - if (model.equals("FHIR") && version.equals("3.0.0")) { + if (model.equals("FHIR") && version.startsWith("3")) { Dstu3FhirModelResolver modelResolver = new Dstu3FhirModelResolver(); JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(this.fhirContext)); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java index c588cff08..ff6b5160c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java @@ -27,6 +27,7 @@ import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.runtime.DateTime; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -36,10 +37,12 @@ public class ApplyCqlOperationProvider { private EvaluationProviderFactory providerFactory; private IFhirResourceDao bundleDao; + FhirContext context; - public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao) { + public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao, FhirContext context) { this.providerFactory = providerFactory; this.bundleDao = bundleDao; + this.context = context; } @Operation(name = "$apply-cql", type = Bundle.class) @@ -70,17 +73,18 @@ public Bundle applyCql(Bundle bundle) throws FHIRException { public Resource applyCqlToResource(Resource resource) throws FHIRException { Library library; Context context; + String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); for (Property child : resource.children()) { for (Base base : child.getValues()) { if (base != null) { List extension = getExtension(base); if (!extension.isEmpty()) { - String cql = String.format("using FHIR version '3.0.0' define x: %s", extension.get(1)); + String cql = String.format("using FHIR version '"+ fhirVersion + "' define x: %s", extension.get(1)); library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), new ModelManager()); context = new Context(library); context.registerDataProvider("http://hl7.org/fhir", - this.providerFactory.createDataProvider("FHIR", "3.0.0")); + this.providerFactory.createDataProvider("FHIR", fhirVersion)); Object result = context.resolveExpressionRef("x").getExpression().evaluate(context); if (extension.get(0).equals("extension")) { resource.setProperty(child.getName(), resolveType(result, base.fhirType())); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java index 266f1d7bf..937742392 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java @@ -38,6 +38,7 @@ import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.opencds.cqf.dstu3.helpers.LibraryHelper; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -47,11 +48,13 @@ public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResolutionProvider; + private FhirContext context; public CqlExecutionProvider(LibraryResolutionProvider libraryResolutionProvider, - EvaluationProviderFactory providerFactory) { + EvaluationProviderFactory providerFactory, FhirContext context) { this.providerFactory = providerFactory; this.libraryResolutionProvider = libraryResolutionProvider; + this.context = context; } private LibraryResolutionProvider getLibraryResourceProvider() { @@ -157,13 +160,14 @@ private String buildIncludes(Iterable references) { */ public Object evaluateInContext(DomainResource instance, String cql, String patientId) { Iterable libraries = getLibraryReferences(instance); + String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); // Provide the instance as the value of the '%context' parameter, as well as the // value of a parameter named the same as the resource // This enables expressions to access the resource by root, as well as through // the %context attribute String source = String.format( - "library LocalLibrary using FHIR version '3.0.0' include FHIRHelpers version '3.0.0' called FHIRHelpers %s parameter %s %s parameter \"%%context\" %s define Expression: %s", + "library LocalLibrary using FHIR version '"+ fhirVersion + "' include FHIRHelpers version '"+ fhirVersion + "' called FHIRHelpers %s parameter %s %s parameter \"%%context\" %s define Expression: %s", buildIncludes(libraries), instance.fhirType(), instance.fhirType(), instance.fhirType(), cql); // String source = String.format("library LocalLibrary using FHIR version '1.8' // include FHIRHelpers version '1.8' called FHIRHelpers %s parameter %s %s @@ -182,7 +186,7 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati context.registerLibraryLoader(libraryLoader); context.setContextValue("Patient", patientId); - context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", "3.0.0")); + context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", fhirVersion)); return context.resolveExpressionRef("Expression").evaluate(context); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index 6ef070ff3..f9f731a58 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -207,12 +207,12 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, this.registerProvider(libraryProvider); // CQL Execution - CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory); + CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory, this.fhirContext); this.registerProvider(cql); // Bundle processing ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, - this.getDao(Bundle.class)); + this.getDao(Bundle.class), this.fhirContext); this.registerProvider(bundleProvider); // Measure processing diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index e6b654fe8..885fe3071 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -40,7 +40,7 @@ public DataProvider createDataProvider(String model, String version, String url, } public DataProvider createDataProvider(String model, String version, TerminologyProvider terminologyProvider) { - if (model.equals("FHIR") && version.equals("4.0.0")) { + if (model.equals("FHIR") && version.startsWith("4")) { R4FhirModelResolver modelResolver = new R4FhirModelResolver(); JpaFhirRetrieveProvider retrieveProvider = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(this.fhirContext)); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java index cc2f81d89..8aa6cfd3a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java @@ -27,6 +27,7 @@ import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.runtime.DateTime; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; @@ -36,10 +37,12 @@ public class ApplyCqlOperationProvider { private EvaluationProviderFactory providerFactory; private IFhirResourceDao bundleDao; + private FhirContext context; - public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao) { + public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao, FhirContext context) { this.providerFactory = providerFactory; this.bundleDao = bundleDao; + this.context = context; } @Operation(name = "$apply-cql", type = Bundle.class) @@ -70,17 +73,18 @@ public Bundle applyCql(Bundle bundle) throws FHIRException { public Resource applyCqlToResource(Resource resource) throws FHIRException { Library library; Context context; + String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); for (Property child : resource.children()) { for (Base base : child.getValues()) { if (base != null) { AbstractMap.SimpleEntry extensions = getExtension(base); if (extensions != null) { - String cql = String.format("using FHIR version '4.0.0' define x: %s", extensions.getValue()); + String cql = String.format("using FHIR version '"+ fhirVersion + "' define x: %s", extensions.getValue()); library = TranslatorHelper.translateLibrary(cql, new LibraryManager(new ModelManager()), new ModelManager()); context = new Context(library); context.registerDataProvider("http://hl7.org/fhir", - this.providerFactory.createDataProvider("FHIR", "4.0.0")); + this.providerFactory.createDataProvider("FHIR", fhirVersion)); Object result = context.resolveExpressionRef("x").getExpression().evaluate(context); if (extensions.getKey().equals("extension")) { resource.setProperty(child.getName(), resolveType(result, base.fhirType())); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java index 42b0b7a41..c583a9a25 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java @@ -38,6 +38,7 @@ import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -47,11 +48,13 @@ public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResourceProvider; + private FhirContext context; public CqlExecutionProvider(LibraryResolutionProvider libraryResourceProvider, - EvaluationProviderFactory providerFactory) { + EvaluationProviderFactory providerFactory, FhirContext context) { this.providerFactory = providerFactory; this.libraryResourceProvider = libraryResourceProvider; + this.context = context; } private LibraryResolutionProvider getLibraryResourceProvider() { @@ -161,8 +164,10 @@ private String buildIncludes(Iterable references) { public Object evaluateInContext(DomainResource instance, String cql, String patientId) { Iterable libraries = getLibraryReferences(instance); + String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); + String source = String.format( - "library LocalLibrary using FHIR version '4.0.0' include FHIRHelpers version '4.0.0' called FHIRHelpers %s parameter %s %s parameter \"%%context\" %s define Expression: %s", + "library LocalLibrary using FHIR version '" + fhirVersion + "' include FHIRHelpers version '"+ fhirVersion +"' called FHIRHelpers %s parameter %s %s parameter \"%%context\" %s define Expression: %s", buildIncludes(libraries), instance.fhirType(), instance.fhirType(), instance.fhirType(), cql); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); @@ -214,7 +219,7 @@ private Context setupContext(DomainResource instance, String patientId, LibraryL context.setExpressionCaching(true); context.registerLibraryLoader(libraryLoader); context.setContextValue("Patient", patientId); - context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", "4.0.0")); + context.registerDataProvider("http://hl7.org/fhir", this.providerFactory.createDataProvider("FHIR", this.context.getVersion().getVersion().getFhirVersionString())); return context; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index b255c90a9..8b1904a91 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -209,12 +209,12 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, this.registerProvider(libraryProvider); // CQL Execution - CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory); + CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory, this.fhirContext); this.registerProvider(cql); // Bundle processing ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, - this.getDao(Bundle.class)); + this.getDao(Bundle.class), this.fhirContext); this.registerProvider(bundleProvider); // Measure processing From a764ebc669b71bd0cc3f712509f945994b4cbe71 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 12 May 2020 09:30:30 -0600 Subject: [PATCH 031/198] Revert 3.0.2 changes, translator doesn't yet support it. --- .../opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java | 3 ++- .../org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java index ff6b5160c..a86928ab4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java @@ -73,7 +73,8 @@ public Bundle applyCql(Bundle bundle) throws FHIRException { public Resource applyCqlToResource(Resource resource) throws FHIRException { Library library; Context context; - String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); + // String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); + String fhirVersion = "3.0.0"; for (Property child : resource.children()) { for (Base base : child.getValues()) { if (base != null) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java index 937742392..c47a05adc 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java @@ -160,7 +160,8 @@ private String buildIncludes(Iterable references) { */ public Object evaluateInContext(DomainResource instance, String cql, String patientId) { Iterable libraries = getLibraryReferences(instance); - String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); + //String fhirVersion = this.context.getVersion().getVersion().getFhirVersionString(); + String fhirVersion = "3.0.0"; // Provide the instance as the value of the '%context' parameter, as well as the // value of a parameter named the same as the resource From 4d3ebd9d2e5cb2f44362a92cad66afa63fc62417 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 15 May 2020 13:59:26 -0600 Subject: [PATCH 032/198] Fixes for databases and codesystems --- .dockerignore | 3 +- .../providers/CodeSystemUpdateProvider.java | 89 +++++++++----- dstu3/src/main/resources/hapi.properties | 112 +++++++++++++++++- .../providers/CodeSystemUpdateProvider.java | 86 +++++++++----- r4/src/main/resources/hapi.properties | 112 +++++++++++++++++- 5 files changed, 327 insertions(+), 75 deletions(-) diff --git a/.dockerignore b/.dockerignore index fc8480418..bb60c5754 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,5 +5,4 @@ .project examples target -derby.log -!target/jpaserver_derby_files \ No newline at end of file +derby.log \ No newline at end of file diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index 8e07424f7..44b013420 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -1,7 +1,13 @@ package org.opencds.cqf.dstu3.providers; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,15 +47,15 @@ public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, @Operation(name = "$updateCodeSystems", idempotent = true) public OperationOutcome updateCodeSystems() { IBundleProvider valuesets = this.valueSetDao.search(new SearchParameterMap()); - OperationOutcome response = new OperationOutcome(); - OperationOutcome outcome; - for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) { - outcome = this.performCodeSystemUpdate((ValueSet) valueSet); - if (outcome.hasIssue()) { - for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { - response.addIssue(issue); - } + List valueSets = valuesets.getResources(0, valuesets.size()).stream().map(x -> (ValueSet)x).collect(Collectors.toList()); + + OperationOutcome outcome = this.performCodeSystemUpdate(valueSets); + + OperationOutcome response = new OperationOutcome(); + if (outcome.hasIssue()) { + for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { + response.addIssue(issue); } } @@ -75,32 +81,55 @@ public OperationOutcome updateCodeSystems(@IdParam IdType theId) { return responseBuilder.buildIssue("error", "notfound", "Unable to find Resource: " + theId.getId()).build(); } - return performCodeSystemUpdate(vs); + return performCodeSystemUpdate(Collections.singletonList(vs)); } - public OperationOutcome performCodeSystemUpdate(ValueSet vs) { + public OperationOutcome performCodeSystemUpdate(List valueSets) { OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); List codeSystems = new ArrayList<>(); + + // Possible for this to run out of memory with really large ValueSets and CodeSystems. + Map> codesBySystem = new HashMap<>(); + for (var vs : valueSets){ + if (vs.hasCompose() && vs.getCompose().hasInclude()) { - CodeSystem codeSystem; for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { - if (!csc.hasSystem()) + if (!csc.hasSystem() || !csc.hasConcept()){ continue; + } - codeSystem = getCodeSystemByUrl(csc.getSystem()); + var system = csc.getSystem(); + if (!codesBySystem.containsKey(system)){ + codesBySystem.put(system, new HashSet<>()); + } - if (!csc.hasConcept()) - continue; + var codes = codesBySystem.get(system); + + codes.addAll(csc.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList())); + } + } - updateCodeSystem(codeSystem.setUrl(csc.getSystem()), getUnionDistinctCodes(csc, codeSystem)); + } + + for(var entry : codesBySystem.entrySet()) { + var system = entry.getKey(); + var codeSystem = getCodeSystemByUrl(system); + updateCodeSystem(codeSystem.setUrl(system), getUnionDistinctCodes(entry.getValue(), codeSystem)); + + codeSystems.add(codeSystem.getUrl()); - codeSystems.add(codeSystem.getUrl()); - } } - return responseBuilder.buildIssue("information", "informational", - "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); + if (codeSystems.size() > 0) { + return responseBuilder.buildIssue("information", "informational", + "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); + } + else { + return responseBuilder.buildIssue("information", "informational", + "No code systems were updated").build(); + } } /*** @@ -128,17 +157,15 @@ private CodeSystem getCodeSystemByUrl(String url) { * @param codeSystem A CodeSystem resource * @return List of distinct codes strings */ - private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) { + private Set getUnionDistinctCodes(Set valueSetCodes, CodeSystem codeSystem) { if (!codeSystem.hasConcept()) { - return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) - .collect(Collectors.toList()); + return valueSetCodes; } - return Stream.concat( - valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) - .collect(Collectors.toList()).stream(), - codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) - .collect(Collectors.toList()).stream()) - .distinct().collect(Collectors.toList()); + + valueSetCodes.addAll(codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) + .collect(Collectors.toUnmodifiableSet())); + + return valueSetCodes; } /*** @@ -147,7 +174,7 @@ private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSet * @param codeSystem A CodeSystem resource * @param codes List of (unique) code strings */ - private void updateCodeSystem(CodeSystem codeSystem, List codes) { + private void updateCodeSystem(CodeSystem codeSystem, Set codes) { codeSystem .setConcept(codes.stream().map(x -> new CodeSystem.ConceptDefinitionComponent().setCode(x)) .collect(Collectors.toList())) @@ -156,4 +183,4 @@ private void updateCodeSystem(CodeSystem codeSystem, List codes) { this.codeSystemDao.update(codeSystem); } -} +} \ No newline at end of file diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index e20a61a60..d7e10f5ce 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -11,15 +11,23 @@ server_address=http://localhost:8080/cqf-ruler-dstu3/fhir/ # setting above should also be changed. server.base=/cqf-ruler-dstu3/fhir + + +enable_index_missing_fields=false +auto_create_placeholder_reference_targets=false +enforce_referential_integrity_on_write=false +enforce_referential_integrity_on_delete=false default_encoding=JSON etag_support=ENABLED -reuse_cached_search_results_millis=-1 +reuse_cached_search_results_millis=60000 +retain_cached_searches_mins=60 default_page_size=20 max_page_size=200 allow_override_default_search_params=true allow_contains_searches=true allow_multiple_delete=true allow_external_references=true +allow_cascading_deletes=true allow_placeholder_references=true expunge_enabled=true persistence_unit_name=HAPI_PU @@ -27,14 +35,47 @@ logger.name=fhirtest.access logger.format=Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}] logger.error_format=ERROR - ${requestVerb} ${requestUrl} logger.log_exceptions=true -datasource.driver=org.apache.derby.jdbc.EmbeddedDriver -datasource.url=jdbc:derby:directory:target/jpaserver_derby_files_dstu3;create=true +datasource.driver=org.h2.Driver +datasource.url=jdbc:h2:file:./target/database/h2/dstu3 datasource.username= datasource.password= server.name=Local Tester server.id=home test.port= -hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect + +################################################### +# Binary Storage (104857600 = 100mb) +################################################### +max_binary_size=104857600 + +################################################### +# Validation +################################################### +# Should all incoming requests be validated +validation.requests.enabled=false +# Should outgoing responses be validated +validation.responses.enabled=false + +################################################### +# Search Features +################################################### +filter_search.enabled=true +graphql.enabled=true +# See FhirPathFilterInterceptor +fhirpath_interceptor.enabled=false + +################################################### +# Supported Resources +################################################### +# Enable the following property if you want to customize the +# list of resources that is supported by the server (i.e. to +# disable specific resources) +#supported_resource_types=Patient,Observation,Encounter + +################################################### +# Database Settings +################################################### +hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory hibernate.format_sql=false hibernate.show_sql=false @@ -45,8 +86,67 @@ hibernate.cache.use_second_level_cache=false hibernate.cache.use_structured_entries=false hibernate.cache.use_minimal_puts=false hibernate.search.default.directory_provider=filesystem -hibernate.search.default.indexBase=target/lucenefiles_dstu3 +hibernate.search.default.indexBase=target/lucenefiles hibernate.search.lucene_version=LUCENE_CURRENT tester.config.refuse_to_fetch_third_party_urls=false + +################################################## +# ElasticSearch +# Note that using ElasticSearch is disabled by +# default and the server will use Lucene instead. +################################################## +elasticsearch.enabled=false +elasticsearch.rest_url=http://localhost:9200 +elasticsearch.username=SomeUsername +elasticsearch.password=SomePassword +elasticsearch.required_index_status=YELLOW +elasticsearch.schema_management_strategy=CREATE +# Immediately refresh indexes after every write. This is very bad for +# performance, but can be helpful for testing. +elasticsearch.debug.refresh_after_write=false +elasticsearch.debug.pretty_print_json_log=false + + +################################################## +# Binary Storage Operations +################################################## +binary_storage.enabled=true + +################################################## +# Bulk Data Specification +################################################## +bulk.export.enabled=true + +################################################## +# CORS Settings +################################################## cors.enabled=true -cors.allowed_origin=* \ No newline at end of file +cors.allowCredentials=true +# Supports multiple, comma separated allowed origin entries +# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca +cors.allow_origin=* + +################################################## +# Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) +################################################## +#allowed_bundle_types=COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET + +################################################## +# Subscriptions +################################################## + +# Enable REST Hook Subscription Channel +subscription.resthook.enabled=false + +# Enable Email Subscription Channel +subscription.email.enabled=false +email.enabled=false +email.from=some@test.com +email.host= +email.port=0 +email.username= +email.password= + +# Enable Websocket Subscription Channel +subscription.websocket.enabled=false + diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index f9a10da4e..096fe6539 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -1,7 +1,12 @@ package org.opencds.cqf.r4.providers; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,15 +46,15 @@ public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, @Operation(name = "$updateCodeSystems", idempotent = true) public OperationOutcome updateCodeSystems() { IBundleProvider valuesets = this.valueSetDao.search(new SearchParameterMap()); - OperationOutcome response = new OperationOutcome(); - OperationOutcome outcome; - for (IBaseResource valueSet : valuesets.getResources(0, valuesets.size())) { - outcome = this.performCodeSystemUpdate((ValueSet) valueSet); - if (outcome.hasIssue()) { - for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { - response.addIssue(issue); - } + List valueSets = valuesets.getResources(0, valuesets.size()).stream().map(x -> (ValueSet)x).collect(Collectors.toList()); + + OperationOutcome outcome = this.performCodeSystemUpdate(valueSets); + + OperationOutcome response = new OperationOutcome(); + if (outcome.hasIssue()) { + for (OperationOutcome.OperationOutcomeIssueComponent issue : outcome.getIssue()) { + response.addIssue(issue); } } @@ -75,32 +80,55 @@ public OperationOutcome updateCodeSystems(@IdParam IdType theId) { return responseBuilder.buildIssue("error", "notfound", "Unable to find Resource: " + theId.getId()).build(); } - return performCodeSystemUpdate(vs); + return performCodeSystemUpdate(Collections.singletonList(vs)); } - public OperationOutcome performCodeSystemUpdate(ValueSet vs) { + public OperationOutcome performCodeSystemUpdate(List valueSets) { OperationOutcomeBuilder responseBuilder = new OperationOutcomeBuilder(); List codeSystems = new ArrayList<>(); + + // Possible for this to run out of memory with really large ValueSets and CodeSystems. + Map> codesBySystem = new HashMap<>(); + for (var vs : valueSets){ + if (vs.hasCompose() && vs.getCompose().hasInclude()) { - CodeSystem codeSystem; for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { - if (!csc.hasSystem()) + if (!csc.hasSystem() || !csc.hasConcept()){ continue; + } - codeSystem = getCodeSystemByUrl(csc.getSystem()); + var system = csc.getSystem(); + if (!codesBySystem.containsKey(system)){ + codesBySystem.put(system, new HashSet<>()); + } - if (!csc.hasConcept()) - continue; + var codes = codesBySystem.get(system); + + codes.addAll(csc.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toList())); + } + } - updateCodeSystem(codeSystem.setUrl(csc.getSystem()), getUnionDistinctCodes(csc, codeSystem)); + } + + for(var entry : codesBySystem.entrySet()) { + var system = entry.getKey(); + var codeSystem = getCodeSystemByUrl(system); + updateCodeSystem(codeSystem.setUrl(system), getUnionDistinctCodes(entry.getValue(), codeSystem)); + + codeSystems.add(codeSystem.getUrl()); - codeSystems.add(codeSystem.getUrl()); - } } - return responseBuilder.buildIssue("information", "informational", - "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); + if (codeSystems.size() > 0) { + return responseBuilder.buildIssue("information", "informational", + "Successfully updated the following CodeSystems: " + String.join(", ", codeSystems)).build(); + } + else { + return responseBuilder.buildIssue("information", "informational", + "No code systems were updated").build(); + } } /*** @@ -128,17 +156,15 @@ private CodeSystem getCodeSystemByUrl(String url) { * @param codeSystem A CodeSystem resource * @return List of distinct codes strings */ - private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSetCodes, CodeSystem codeSystem) { + private Set getUnionDistinctCodes(Set valueSetCodes, CodeSystem codeSystem) { if (!codeSystem.hasConcept()) { - return valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) - .collect(Collectors.toList()); + return valueSetCodes; } - return Stream.concat( - valueSetCodes.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) - .collect(Collectors.toList()).stream(), - codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) - .collect(Collectors.toList()).stream()) - .distinct().collect(Collectors.toList()); + + valueSetCodes.addAll(codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) + .collect(Collectors.toUnmodifiableSet())); + + return valueSetCodes; } /*** @@ -147,7 +173,7 @@ private List getUnionDistinctCodes(ValueSet.ConceptSetComponent valueSet * @param codeSystem A CodeSystem resource * @param codes List of (unique) code strings */ - private void updateCodeSystem(CodeSystem codeSystem, List codes) { + private void updateCodeSystem(CodeSystem codeSystem, Set codes) { codeSystem .setConcept(codes.stream().map(x -> new CodeSystem.ConceptDefinitionComponent().setCode(x)) .collect(Collectors.toList())) diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 2b9ab943a..97c7f3d66 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -11,15 +11,23 @@ server_address=http://localhost:8080/cqf-ruler-r4/fhir/ # setting above should also be changed. server.base=/cqf-ruler-r4/fhir + + +enable_index_missing_fields=false +auto_create_placeholder_reference_targets=false +enforce_referential_integrity_on_write=false +enforce_referential_integrity_on_delete=false default_encoding=JSON etag_support=ENABLED -reuse_cached_search_results_millis=-1 +reuse_cached_search_results_millis=60000 +retain_cached_searches_mins=60 default_page_size=20 max_page_size=200 allow_override_default_search_params=true allow_contains_searches=true allow_multiple_delete=true allow_external_references=true +allow_cascading_deletes=true allow_placeholder_references=true expunge_enabled=true persistence_unit_name=HAPI_PU @@ -27,14 +35,47 @@ logger.name=fhirtest.access logger.format=Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}] logger.error_format=ERROR - ${requestVerb} ${requestUrl} logger.log_exceptions=true -datasource.driver=org.apache.derby.jdbc.EmbeddedDriver -datasource.url=jdbc:derby:directory:target/jpaserver_derby_files_r4;create=true +datasource.driver=org.h2.Driver +datasource.url=jdbc:h2:file:./target/database/h2/r4 datasource.username= datasource.password= server.name=Local Tester server.id=home test.port= -hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect + +################################################### +# Binary Storage (104857600 = 100mb) +################################################### +max_binary_size=104857600 + +################################################### +# Validation +################################################### +# Should all incoming requests be validated +validation.requests.enabled=false +# Should outgoing responses be validated +validation.responses.enabled=false + +################################################### +# Search Features +################################################### +filter_search.enabled=true +graphql.enabled=true +# See FhirPathFilterInterceptor +fhirpath_interceptor.enabled=false + +################################################### +# Supported Resources +################################################### +# Enable the following property if you want to customize the +# list of resources that is supported by the server (i.e. to +# disable specific resources) +#supported_resource_types=Patient,Observation,Encounter + +################################################### +# Database Settings +################################################### +hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory hibernate.format_sql=false hibernate.show_sql=false @@ -45,8 +86,67 @@ hibernate.cache.use_second_level_cache=false hibernate.cache.use_structured_entries=false hibernate.cache.use_minimal_puts=false hibernate.search.default.directory_provider=filesystem -hibernate.search.default.indexBase=target/lucenefiles_r4 +hibernate.search.default.indexBase=target/lucenefiles hibernate.search.lucene_version=LUCENE_CURRENT tester.config.refuse_to_fetch_third_party_urls=false + +################################################## +# ElasticSearch +# Note that using ElasticSearch is disabled by +# default and the server will use Lucene instead. +################################################## +elasticsearch.enabled=false +elasticsearch.rest_url=http://localhost:9200 +elasticsearch.username=SomeUsername +elasticsearch.password=SomePassword +elasticsearch.required_index_status=YELLOW +elasticsearch.schema_management_strategy=CREATE +# Immediately refresh indexes after every write. This is very bad for +# performance, but can be helpful for testing. +elasticsearch.debug.refresh_after_write=false +elasticsearch.debug.pretty_print_json_log=false + + +################################################## +# Binary Storage Operations +################################################## +binary_storage.enabled=true + +################################################## +# Bulk Data Specification +################################################## +bulk.export.enabled=true + +################################################## +# CORS Settings +################################################## cors.enabled=true -cors.allowed_origin=* \ No newline at end of file +cors.allowCredentials=true +# Supports multiple, comma separated allowed origin entries +# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca +cors.allow_origin=* + +################################################## +# Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) +################################################## +#allowed_bundle_types=COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET + +################################################## +# Subscriptions +################################################## + +# Enable REST Hook Subscription Channel +subscription.resthook.enabled=false + +# Enable Email Subscription Channel +subscription.email.enabled=false +email.enabled=false +email.from=some@test.com +email.host= +email.port=0 +email.username= +email.password= + +# Enable Websocket Subscription Channel +subscription.websocket.enabled=false + From 94836f984f30e3dad6c1b89c121778201d96e976 Mon Sep 17 00:00:00 2001 From: ZackAustin Date: Thu, 14 May 2020 00:26:32 -0600 Subject: [PATCH 033/198] Fix for measure evaluation null result when evaluating criteria. --- .../java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 34bce7221..1a70ddf92 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -121,7 +121,7 @@ private Iterable evaluateCriteria(Context context, Patient patient, } Object result = context.resolveExpressionRef(pop.getCriteria().getExpression()).evaluate(context); if (result == null) { - Collections.emptyList(); + return Collections.emptyList(); } if (result instanceof Boolean) { From ecaa5e9788fbdcc399eba30d1c8277bf11fb7573 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 20 May 2020 15:04:53 -0600 Subject: [PATCH 034/198] Log CDS request body before instantiating cds request. --- .../cqf/common/config/FhirServerConfig.java | 6 ++---- .../opencds/cqf/dstu3/servlet/BaseServlet.java | 11 ++++------- .../cqf/dstu3/servlet/CdsHooksServlet.java | 15 ++++++--------- .../org/opencds/cqf/r4/servlet/BaseServlet.java | 12 ++++-------- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index e07f77c7c..1a861e418 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -31,11 +31,9 @@ public class FhirServerConfig { public FhirServerConfig() { ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); - ourLog.info( - "Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); + ourLog.info("Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); ourLog.info("Server configured to " + (this.expungeEnabled ? "enable" : "disable") + " expunges"); - ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") - + " placeholder references"); + ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index f9f731a58..9d48a91af 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -139,16 +139,13 @@ protected void initialize() throws ServletException { * This interceptor formats the output using nice colourful HTML output when the * request is detected to come from a browser. */ - ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx - .getBean(ResponseHighlighterInterceptor.class); + ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx.getBean(ResponseHighlighterInterceptor.class); this.registerInterceptor(responseHighlighterInterceptor); /* - * If you are hosting this server at a specific DNS name, the server will try to - * figure out the FHIR base URL based on what the web container tells it, but - * this doesn't always work. If you are setting links in your search bundles - * that just refer to "localhost", you might want to use a server address - * strategy: + * If you are hosting this server at a specific DNS name, the server will try to figure out the FHIR base URL + * based on what the web container tells it, but this doesn't always work. If you are setting links in your + * search bundles that just refer to "localhost", you might want to use a server address strategy: */ String serverAddress = HapiProperties.getServerAddress(); if (serverAddress != null && serverAddress.length() > 0) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 47092c902..aec8c244e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -108,15 +108,13 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) } @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info(request.getRequestURI()); try { // validate that we are dealing with JSON if (!request.getContentType().startsWith("application/json")) { - throw new ServletException(String.format("Invalid content type %s. Please use application/json.", - request.getContentType())); + throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); } String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") @@ -124,10 +122,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); - Request cdsHooksRequest = new Request(service, parser.parse(request.getReader()).getAsJsonObject(), - JsonHelper.getObjectRequired(getService(service), "prefetch")); + JsonObject requestJson = parser.parse(request.getReader()).getAsJsonObject(); + logger.info(requestJson.toString()); - logger.info(cdsHooksRequest.getRequestJson().toString()); + Request cdsHooksRequest = new Request(service, requestJson, JsonHelper.getObjectRequired(getService(service), "prefetch")); Hook hook = HookFactory.createHook(cdsHooksRequest); @@ -170,8 +168,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) case 401: case 403: case 404: - response.getWriter() - .println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); + response.getWriter().println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); response.getWriter().println(e.getMessage()); response.setStatus(412); break; diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 8b1904a91..9f95e22a4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -89,8 +89,7 @@ protected void initialize() throws ServletException { Object systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); registerProvider(systemProvider); - ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", - ResourceProviderFactory.class); + ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, @@ -101,8 +100,7 @@ protected void initialize() throws ServletException { JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( appCtx.getBean("terminologyService", ITermReadSvcR4.class), getFhirContext(), (ValueSetResourceProvider) this.getResourceProvider(ValueSet.class)); - EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, - localSystemTerminologyProvider); + EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, localSystemTerminologyProvider); resolveProviders(providerFactory, localSystemTerminologyProvider, this.registry); @@ -194,13 +192,11 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, HQMFProvider hqmfProvider = new HQMFProvider(); // Code System Update - CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), - this.getDao(CodeSystem.class)); + CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), this.getDao(CodeSystem.class)); this.registerProvider(csUpdate); // Cache Value Sets - CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), - this.getDao(Endpoint.class)); + CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), this.getDao(Endpoint.class)); this.registerProvider(cvs); // Library processing From 1fab99d9c41f7cd171c3c05f30378f9e4535c928 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 20 May 2020 15:06:44 -0600 Subject: [PATCH 035/198] Clean up logs --- cqf-ruler-dstu3/pom.xml | 3 +++ cqf-ruler-r4/pom.xml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/cqf-ruler-dstu3/pom.xml b/cqf-ruler-dstu3/pom.xml index 5e5e879d6..36d1c2b1c 100644 --- a/cqf-ruler-dstu3/pom.xml +++ b/cqf-ruler-dstu3/pom.xml @@ -60,6 +60,9 @@ /cqf-ruler-dstu3 true + + ^$ + ^$ diff --git a/cqf-ruler-r4/pom.xml b/cqf-ruler-r4/pom.xml index 9c98a50c2..7c19c0d86 100644 --- a/cqf-ruler-r4/pom.xml +++ b/cqf-ruler-r4/pom.xml @@ -60,6 +60,9 @@ /cqf-ruler-r4 true + + ^$ + ^$ From f1faa14ae2166dff894eeaaa55ee50d034c24cd1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 20 May 2020 15:13:57 -0600 Subject: [PATCH 036/198] Update maven war plugin, filter startup log --- cqf-ruler-dstu3/pom.xml | 3 +-- cqf-ruler-r4/pom.xml | 1 - pom.xml | 5 +++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cqf-ruler-dstu3/pom.xml b/cqf-ruler-dstu3/pom.xml index 36d1c2b1c..507a0b53b 100644 --- a/cqf-ruler-dstu3/pom.xml +++ b/cqf-ruler-dstu3/pom.xml @@ -30,7 +30,6 @@ org.apache.maven.plugins maven-war-plugin - 2.6 @@ -60,7 +59,7 @@ /cqf-ruler-dstu3 true - + ^$ ^$ diff --git a/cqf-ruler-r4/pom.xml b/cqf-ruler-r4/pom.xml index 7c19c0d86..8678c84c7 100644 --- a/cqf-ruler-r4/pom.xml +++ b/cqf-ruler-r4/pom.xml @@ -30,7 +30,6 @@ org.apache.maven.plugins maven-war-plugin - 2.6 diff --git a/pom.xml b/pom.xml index fa3b1e371..43938aa8f 100644 --- a/pom.xml +++ b/pom.xml @@ -369,6 +369,11 @@ 11 + + org.apache.maven.plugins + maven-war-plugin + 3.2.3 + org.apache.maven.plugins maven-javadoc-plugin From fd68058fb87a6c87550d447ef9dcc7e8caad5c61 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 20 May 2020 15:28:07 -0600 Subject: [PATCH 037/198] Update Jetty plugin, remove unused dependencies --- .../cqf/common/config/HapiProperties.java | 6 ++--- pom.xml | 25 +------------------ 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 44ca4578d..ccd965262 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -232,9 +232,9 @@ public static String getDataSourceDriver() { return HapiProperties.getProperty(DATASOURCE_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver"); } - public static Object getDriver() { - return new org.apache.derby.jdbc.EmbeddedDriver(); - } + // public static Object getDriver() { + // return new org.apache.derby.jdbc.EmbeddedDriver(); + // } public static Integer getDataSourceMaxPoolSize() { return HapiProperties.getIntegerProperty(DATASOURCE_MAX_POOL_SIZE, 10); diff --git a/pom.xml b/pom.xml index 43938aa8f..66f4b598d 100644 --- a/pom.xml +++ b/pom.xml @@ -52,9 +52,8 @@ UTF-8 UTF-8 - 9.4.14.v20181114 + 9.4.28.v20200408 2.10.1 - 10.14.2.0 4.2.0 1.1.0-SNAPSHOT 1.4.0-SNAPSHOT @@ -241,34 +240,12 @@ 2.5.0 - - org.postgresql - postgresql - 9.4.1212.jre7 - - com.h2database h2 1.4.197 - - org.apache.derby - derby - ${derby.version} - - - org.apache.derby - derbynet - ${derby.version} - - - org.apache.derby - derbyclient - ${derby.version} - - com.helger ph-schematron From 82361d31ca3a85f0c29feddbb93bd6e6816e8b3b Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 20 May 2020 17:19:20 -0600 Subject: [PATCH 038/198] Fix null pointer no content header exception --- .../main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java | 4 ++++ .../java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 2 +- r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java | 4 ++++ .../main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index 9d48a91af..e34b9595d 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -57,6 +57,7 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; public class BaseServlet extends RestfulServer { @@ -142,6 +143,9 @@ protected void initialize() throws ServletException { ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx.getBean(ResponseHighlighterInterceptor.class); this.registerInterceptor(responseHighlighterInterceptor); + LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); + this.registerInterceptor(loggingInterceptor); + /* * If you are hosting this server at a specific DNS name, the server will try to figure out the FHIR base URL * based on what the web container tells it, but this doesn't always work. If you are setting links in your diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index aec8c244e..dad0fe15f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -113,7 +113,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) try { // validate that we are dealing with JSON - if (!request.getContentType().startsWith("application/json")) { + if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) { throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 9f95e22a4..082e1d977 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -54,6 +54,7 @@ import ca.uhn.fhir.jpa.util.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; @@ -143,6 +144,9 @@ protected void initialize() throws ServletException { .getBean(ResponseHighlighterInterceptor.class); this.registerInterceptor(responseHighlighterInterceptor); + LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); + this.registerInterceptor(loggingInterceptor); + /* * If you are hosting this server at a specific DNS name, the server will try to * figure out the FHIR base URL based on what the web container tells it, but diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index bdbaf3162..267cc3521 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -114,7 +114,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) try { // validate that we are dealing with JSON - if (!request.getContentType().startsWith("application/json")) { + if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) { throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); } From 7e32d8794a80c2379e763574c3541c6308d3eba4 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 21 May 2020 12:56:54 -0600 Subject: [PATCH 039/198] Better handling of cds-hooks exceptions --- .../cqf/dstu3/servlet/CdsHooksServlet.java | 82 +++++++++++++------ .../cqf/r4/servlet/CdsHooksServlet.java | 74 ++++++++++++----- 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index dad0fe15f..627064e9c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -40,6 +40,7 @@ import org.opencds.cqf.cql.engine.data.CompositeDataProvider; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; import org.opencds.cqf.dstu3.helpers.LibraryHelper; import org.opencds.cqf.dstu3.providers.JpaTerminologyProvider; @@ -108,13 +109,15 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) } @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { logger.info(request.getRequestURI()); try { // validate that we are dealing with JSON if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) { - throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); + throw new ServletException(String.format("Invalid content type %s. Please use application/json.", + request.getContentType())); } String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") @@ -125,7 +128,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) JsonObject requestJson = parser.parse(request.getReader()).getAsJsonObject(); logger.info(requestJson.toString()); - Request cdsHooksRequest = new Request(service, requestJson, JsonHelper.getObjectRequired(getService(service), "prefetch")); + Request cdsHooksRequest = new Request(service, requestJson, + JsonHelper.getObjectRequired(getService(service), "prefetch")); Hook hook = HookFactory.createHook(cdsHooksRequest); @@ -163,31 +167,63 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println(jsonResponse); } catch (BaseServerResponseException e) { this.setAccessControlHeaders(response); - - switch (e.getStatusCode()) { - case 401: - case 403: - case 404: - response.getWriter().println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); - response.getWriter().println(e.getMessage()); - response.setStatus(412); - break; - default: - response.getWriter().println("ERROR: Unhandled error. FHIR server returned: " + e.getStatusCode()); - response.getWriter().println(e.getMessage()); - response.setStatus(500); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception connecting to remote server."); + this.printMessageAndCause(e, response); + this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.printStackTrack(e, response); + } catch (DataProviderException e) { + this.setAccessControlHeaders(response); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception in DataProvider."); + this.printMessageAndCause(e, response); + if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { + this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); } + + this.printStackTrack(e, response); } catch (Exception e) { - this.setAccessControlHeaders(response); - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String exceptionAsString = sw.toString(); - response.getWriter().println("ERROR: Unhandled error."); - response.getWriter().println(exceptionAsString); - response.setStatus(500); + throw new ServletException("ERROR: Exception in cds-hooks processing.", e); + } + } + + private void handleServerResponseExecption(BaseServerResponseException e, HttpServletResponse response) + throws IOException { + switch (e.getStatusCode()) { + case 401: + case 403: + response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); + response.getWriter().println( + "Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access."); + response.setStatus(412); + break; + case 404: + response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); + response.getWriter().println("Ensure the resource exists on the remote server."); + response.setStatus(412); + break; + default: + response.getWriter().println("Unhandled Error in Remote FHIR server: " + e.getStatusCode()); } } + private void printMessageAndCause(Exception e, HttpServletResponse response) throws IOException { + if (e.getMessage() != null) { + response.getWriter().println(e.getMessage()); + } + + if (e.getCause() != null && e.getCause().getMessage() != null) { + response.getWriter().println(e.getCause().getMessage()); + } + } + + private void printStackTrack(Exception e, HttpServletResponse response) throws IOException { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String exceptionAsString = sw.toString(); + response.getWriter().println(exceptionAsString); + } + private JsonObject getService(String service) { JsonArray services = getServices().get("services").getAsJsonArray(); List ids = new ArrayList<>(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 267cc3521..39ca77a73 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -40,6 +40,7 @@ import org.opencds.cqf.cql.engine.data.CompositeDataProvider; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; +import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.r4.helpers.LibraryHelper; import org.opencds.cqf.r4.providers.JpaTerminologyProvider; @@ -165,32 +166,63 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println(jsonResponse); } catch (BaseServerResponseException e) { this.setAccessControlHeaders(response); - - switch (e.getStatusCode()) { - case 401: - case 403: - case 404: - response.getWriter() - .println("ERROR: Precondition Failed. FHIR server returned: " + e.getStatusCode()); - response.getWriter().println(e.getMessage()); - response.setStatus(412); - break; - default: - response.getWriter().println("ERROR: Unhandled error. FHIR server returned: " + e.getStatusCode()); - response.getWriter().println(e.getMessage()); - response.setStatus(500); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception connecting to remote server."); + this.printMessageAndCause(e, response); + this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.printStackTrack(e, response); + } catch (DataProviderException e) { + this.setAccessControlHeaders(response); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception in DataProvider."); + this.printMessageAndCause(e, response); + if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { + this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); } + + this.printStackTrack(e, response); } catch (Exception e) { - this.setAccessControlHeaders(response); - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - String exceptionAsString = sw.toString(); - response.getWriter().println("ERROR: Unhandled error."); - response.getWriter().println(exceptionAsString); - response.setStatus(500); + throw new ServletException("ERROR: Exception in cds-hooks processing.", e); + } + } + + private void handleServerResponseExecption(BaseServerResponseException e, HttpServletResponse response) + throws IOException { + switch (e.getStatusCode()) { + case 401: + case 403: + response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); + response.getWriter().println( + "Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access."); + response.setStatus(412); + break; + case 404: + response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); + response.getWriter().println("Ensure the resource exists on the remote server."); + response.setStatus(412); + break; + default: + response.getWriter().println("Unhandled Error in Remote FHIR server: " + e.getStatusCode()); } } + private void printMessageAndCause(Exception e, HttpServletResponse response) throws IOException { + if (e.getMessage() != null) { + response.getWriter().println(e.getMessage()); + } + + if (e.getCause() != null && e.getCause().getMessage() != null) { + response.getWriter().println(e.getCause().getMessage()); + } + } + + private void printStackTrack(Exception e, HttpServletResponse response) throws IOException { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + String exceptionAsString = sw.toString(); + response.getWriter().println(exceptionAsString); + } + private JsonObject getService(String service) { JsonArray services = getServices().get("services").getAsJsonArray(); List ids = new ArrayList<>(); From 88aec26b0a29b9798a65270d8365b73ae9744eb1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 21 May 2020 18:52:40 -0600 Subject: [PATCH 040/198] Logging for cds-hooks client --- dstu3/pom.xml | 5 ----- pom.xml | 11 ++++++----- r4/pom.xml | 5 ----- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/dstu3/pom.xml b/dstu3/pom.xml index 9d470f157..26e1f9038 100644 --- a/dstu3/pom.xml +++ b/dstu3/pom.xml @@ -17,10 +17,5 @@ common ${project.version} - - org.opencds.cqf - tooling - ${cqf-tooling.version} - diff --git a/pom.xml b/pom.xml index 66f4b598d..75d5eb887 100644 --- a/pom.xml +++ b/pom.xml @@ -59,6 +59,7 @@ 1.4.0-SNAPSHOT 1.4.9-SNAPSHOT 1.1.0-SNAPSHOT + 1.7.30 true @@ -212,11 +213,11 @@ 1.2.3 - - - - - + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + diff --git a/r4/pom.xml b/r4/pom.xml index 13c4ef01b..2bb79b120 100644 --- a/r4/pom.xml +++ b/r4/pom.xml @@ -17,10 +17,5 @@ common ${project.version} - - org.opencds.cqf - tooling - ${cqf-tooling.version} - From 9f1b63ad44b131b97089ac24d30729c26b6ad276 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 3 Jun 2020 11:40:37 -0600 Subject: [PATCH 041/198] Added logback config file to WEB-INF/classes --- .../main/webapp/WEB-INF/classes/logback.xml | 40 +++++++++++++++++++ .../main/webapp/WEB-INF/classes/logback.xml | 40 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 cqf-ruler-dstu3/src/main/webapp/WEB-INF/classes/logback.xml create mode 100644 cqf-ruler-r4/src/main/webapp/WEB-INF/classes/logback.xml diff --git a/cqf-ruler-dstu3/src/main/webapp/WEB-INF/classes/logback.xml b/cqf-ruler-dstu3/src/main/webapp/WEB-INF/classes/logback.xml new file mode 100644 index 000000000..396046d85 --- /dev/null +++ b/cqf-ruler-dstu3/src/main/webapp/WEB-INF/classes/logback.xml @@ -0,0 +1,40 @@ + + + + + INFO + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + LogMessages.html + + + %thread%level%logger%msg + + + + + + messages.log + + messages.%d{yyyy-MM-dd}.log.zip + 10 + 1GB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/cqf-ruler-r4/src/main/webapp/WEB-INF/classes/logback.xml b/cqf-ruler-r4/src/main/webapp/WEB-INF/classes/logback.xml new file mode 100644 index 000000000..396046d85 --- /dev/null +++ b/cqf-ruler-r4/src/main/webapp/WEB-INF/classes/logback.xml @@ -0,0 +1,40 @@ + + + + + INFO + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + LogMessages.html + + + %thread%level%logger%msg + + + + + + messages.log + + messages.%d{yyyy-MM-dd}.log.zip + 10 + 1GB + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] %msg%n + + + + + + + + + + \ No newline at end of file From 3a4107dd5f1e008fc16c1b72f58f91312f9d5078 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 3 Jun 2020 15:36:34 -0600 Subject: [PATCH 042/198] Registered DebugMap with Context of CdsHooksServlet's doPost --- .../cqf/dstu3/helpers/LibraryHelper.java | 3 +- .../cqf/dstu3/servlet/CdsHooksServlet.java | 33 ++++++++++--------- .../cqf/r4/servlet/CdsHooksServlet.java | 8 +++++ 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index f35c50ff7..203087535 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -124,8 +124,7 @@ public static Library resolvePrimaryLibrary(Measure measure, return library; } - public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, - org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, + public static Library resolvePrimaryLibrary(PlanDefinition planDefinition, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { String id = planDefinition.getLibraryFirstRep().getReferenceElement().getIdPart(); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 627064e9c..9f14d1be9 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -38,6 +38,9 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.debug.DebugAction; +import org.opencds.cqf.cql.engine.debug.DebugLocator; +import org.opencds.cqf.cql.engine.debug.DebugMap; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; @@ -109,40 +112,40 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) } @Override - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info(request.getRequestURI()); try { // validate that we are dealing with JSON if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) { - throw new ServletException(String.format("Invalid content type %s. Please use application/json.", - request.getContentType())); + throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); } String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") - .replace(request.getServletPath(), "") + "/fhir"; + .replace(request.getServletPath(), "") + "/fhir"; String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); JsonObject requestJson = parser.parse(request.getReader()).getAsJsonObject(); logger.info(requestJson.toString()); - Request cdsHooksRequest = new Request(service, requestJson, - JsonHelper.getObjectRequired(getService(service), "prefetch")); + Request cdsHooksRequest = new Request(service, requestJson, JsonHelper.getObjectRequired(getService(service), "prefetch")); Hook hook = HookFactory.createHook(cdsHooksRequest); - PlanDefinition planDefinition = planDefinitionProvider.getDao() - .read(new IdType(hook.getRequest().getServiceName())); + PlanDefinition planDefinition = planDefinitionProvider.getDao().read(new IdType(hook.getRequest().getServiceName())); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResolutionProvider); - Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, - libraryResolutionProvider); + Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, libraryResolutionProvider); Dstu3FhirModelResolver resolver = new Dstu3FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); + DebugMap debugMap = new DebugMap(); + debugMap.setIsLoggingEnabled(true); + debugMap.addDebugEntry(library.getIdentifier().getId(), new DebugLocator(DebugLocator.DebugLocatorType.NODE_TYPE, "Retrieve"), DebugAction.WATCH); + Context context = new Context(library); + context.setDebugMap(debugMap); context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote // provider case context.registerTerminologyProvider(jpaTerminologyProvider); @@ -150,9 +153,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) context.setContextValue("Patient", hook.getRequest().getContext().getPatientId().replace("Patient/", "")); context.setExpressionCaching(true); - EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, - FhirContext.forDstu3().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, - planDefinition); + EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, FhirContext.forDstu3().newRestfulGenericClient(baseUrl), + jpaTerminologyProvider, context, library, planDefinition); this.setAccessControlHeaders(response); @@ -193,8 +195,7 @@ private void handleServerResponseExecption(BaseServerResponseException e, HttpSe case 401: case 403: response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); - response.getWriter().println( - "Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access."); + response.getWriter().println("Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access."); response.setStatus(412); break; case 404: diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 39ca77a73..c23ce9552 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -38,6 +38,9 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.debug.DebugAction; +import org.opencds.cqf.cql.engine.debug.DebugLocator; +import org.opencds.cqf.cql.engine.debug.DebugMap; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; @@ -141,7 +144,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) R4FhirModelResolver resolver = new R4FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); + DebugMap debugMap = new DebugMap(); + debugMap.setIsLoggingEnabled(true); + debugMap.addDebugEntry(library.getIdentifier().getId(), new DebugLocator(DebugLocator.DebugLocatorType.NODE_TYPE, "Retrieve"), DebugAction.WATCH); + Context context = new Context(library); + context.setDebugMap(debugMap); context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote // provider case context.registerTerminologyProvider(jpaTerminologyProvider); From bc25cef1a6fde39fc7dddd1b218a7400dd94cb4d Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 3 Jun 2020 16:33:57 -0600 Subject: [PATCH 043/198] Removed addDebugEntry --- .../java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 6 +++--- .../java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 9f14d1be9..099381bfb 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -140,12 +140,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Dstu3FhirModelResolver resolver = new Dstu3FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); + Context context = new Context(library); + DebugMap debugMap = new DebugMap(); debugMap.setIsLoggingEnabled(true); - debugMap.addDebugEntry(library.getIdentifier().getId(), new DebugLocator(DebugLocator.DebugLocatorType.NODE_TYPE, "Retrieve"), DebugAction.WATCH); - - Context context = new Context(library); context.setDebugMap(debugMap); + context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote // provider case context.registerTerminologyProvider(jpaTerminologyProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index c23ce9552..c1710a5d6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -144,12 +144,12 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) R4FhirModelResolver resolver = new R4FhirModelResolver(); CompositeDataProvider provider = new CompositeDataProvider(resolver, fhirRetrieveProvider); + Context context = new Context(library); + DebugMap debugMap = new DebugMap(); debugMap.setIsLoggingEnabled(true); - debugMap.addDebugEntry(library.getIdentifier().getId(), new DebugLocator(DebugLocator.DebugLocatorType.NODE_TYPE, "Retrieve"), DebugAction.WATCH); - - Context context = new Context(library); context.setDebugMap(debugMap); + context.registerDataProvider("http://hl7.org/fhir", provider); // TODO make sure tooling handles remote // provider case context.registerTerminologyProvider(jpaTerminologyProvider); From 9a367a52459227aad1708279039a67c4704ad265 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sat, 6 Jun 2020 13:56:39 -0600 Subject: [PATCH 044/198] Fixes for build warnings and spelling errors --- .../cqf/common/helpers/UsingHelper.java | 2 + .../retrieve/JpaFhirRetrieveProvider.java | 8 ++- .../dstu3/evaluation/MeasureEvaluation.java | 15 ++-- .../cqf/dstu3/helpers/FhirMeasureBundler.java | 3 +- .../ActivityDefinitionApplyProvider.java | 16 ++--- .../providers/CacheValueSetsProvider.java | 6 +- .../providers/CodeSystemUpdateProvider.java | 3 - .../dstu3/providers/CqlExecutionProvider.java | 10 ++- .../providers/DataRequirementsProvider.java | 7 +- .../cqf/dstu3/providers/HQMFProvider.java | 70 +++++++++---------- .../providers/LibraryOperationsProvider.java | 1 - .../providers/MeasureOperationsProvider.java | 19 +++-- .../PlanDefinitionApplyProvider.java | 30 ++++---- .../cqf/dstu3/servlet/BaseServlet.java | 5 +- .../cqf/dstu3/servlet/CdsHooksServlet.java | 11 ++- .../cqf/r4/evaluation/MeasureEvaluation.java | 10 +-- .../cqf/r4/helpers/FhirMeasureBundler.java | 3 +- .../ActivityDefinitionApplyProvider.java | 15 ++-- .../r4/providers/CacheValueSetsProvider.java | 6 +- .../providers/CodeSystemUpdateProvider.java | 2 - .../r4/providers/CqlExecutionProvider.java | 5 +- .../providers/DataRequirementsProvider.java | 5 +- .../cqf/r4/providers/HQMFProvider.java | 68 +++++++++--------- .../providers/LibraryOperationsProvider.java | 1 - .../providers/MeasureOperationsProvider.java | 8 +-- .../PlanDefinitionApplyProvider.java | 30 ++++---- .../opencds/cqf/r4/servlet/BaseServlet.java | 3 + .../cqf/r4/servlet/CdsHooksServlet.java | 11 ++- 28 files changed, 175 insertions(+), 198 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java b/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java index ce8377bf2..b77aaab76 100644 --- a/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java +++ b/common/src/main/java/org/opencds/cqf/common/helpers/UsingHelper.java @@ -13,6 +13,8 @@ public class UsingHelper { private static Map urlsByModelName = new HashMap() { + private static final long serialVersionUID = 1L; + { put("FHIR", "http://hl7.org/fhir"); put("QDM", "urn:healthit-gov:qdm:v5_4"); diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index dd9f9b828..24c383c17 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -1,7 +1,6 @@ package org.opencds.cqf.common.retrieve; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -12,6 +11,8 @@ import org.opencds.cqf.cql.engine.fhir.retrieve.SearchParamFhirRetrieveProvider; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterMap; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; @@ -19,6 +20,8 @@ public class JpaFhirRetrieveProvider extends SearchParamFhirRetrieveProvider { + private static final Logger logger = LoggerFactory.getLogger(JpaFhirRetrieveProvider.class); + DaoRegistry registry; public JpaFhirRetrieveProvider(DaoRegistry registry, SearchParameterResolver searchParameterResolver) { @@ -57,8 +60,7 @@ protected Collection executeQuery(String dataType, SearchParameterMap ma } } catch (Exception e) { - // TODO: Add logging. - System.out.println(e.getMessage()); + logger.warn("Error converting search parameter map", e); } IFhirResourceDao dao = this.registry.getResourceDao(dataType); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index a3a4f845d..3f0939ee1 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -19,7 +19,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; -import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.dstu3.builders.MeasureReportBuilder; @@ -36,12 +35,10 @@ public class MeasureEvaluation { private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluation.class); - private DataProvider provider; private Interval measurementPeriod; private DaoRegistry registry; - public MeasureEvaluation(DataProvider provider, DaoRegistry registry, Interval measurementPeriod) { - this.provider = provider; + public MeasureEvaluation(DaoRegistry registry, Interval measurementPeriod) { this.registry = registry; this.measurementPeriod = measurementPeriod; } @@ -99,6 +96,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY); } + @SuppressWarnings("unchecked") private Iterable evaluateCriteria(Context context, Patient patient, Measure.MeasureGroupPopulationComponent pop) { if (!pop.hasCriteria()) { @@ -115,8 +113,7 @@ private Iterable evaluateCriteria(Context context, Patient patient, expressions.clear(); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + logger.warn("Error resetting expression cache", e); } Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context); @@ -132,7 +129,7 @@ private Iterable evaluateCriteria(Context context, Patient patient, } } - return (Iterable) result; + return (Iterable) result; } private boolean evaluatePopulationCriteria(Context context, Patient patient, @@ -317,6 +314,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } break; case MEASUREOBSERVATION: + measureObservation = new HashMap<>(); break; } } @@ -419,8 +417,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p // For each patient in the patient list for (Patient patient : patients) { - // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + evaluatePopulationCriteria(context, patient, initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, null); populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java index 5dd9205f8..63c907a11 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/FhirMeasureBundler.java @@ -9,12 +9,13 @@ */ public class FhirMeasureBundler { // Adds the resources returned from the given expressions to a bundle + @SuppressWarnings("unchecked") public Bundle bundle(Context context, String... expressionNames) { Bundle bundle = new Bundle(); bundle.setType(Bundle.BundleType.COLLECTION); for (String expressionName : expressionNames) { Object result = context.resolveExpressionRef(expressionName).evaluate(context); - for (Object element : (Iterable) result) { + for (Object element : (Iterable) result) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); entry.setResource((Resource) element); // The null check for resourceType handles Lists, which don't have a resource diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java index d26f3c83c..31930aeb4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java @@ -30,8 +30,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** @@ -77,9 +75,7 @@ public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, String practitionerId, String organizationId) throws FHIRException { Resource result = null; try { - // This is a little hacky... - result = (Resource) Class.forName("org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()) - .newInstance(); + result = (Resource) Class.forName("org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()).getConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); throw new FHIRException("Could not find org.hl7.fhir.dstu3.model." + activityDefinition.getKind().toCode()); @@ -205,7 +201,7 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe } if (activityDefinition.hasBodySite()) { - throw new ActivityDefinitionApplyException("Bodysite does not map to " + activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("BodySite does not map to " + activityDefinition.getKind()); } if (activityDefinition.hasCode()) { @@ -219,13 +215,13 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe return medicationRequest; } - private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition, String practionerId, + private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition, String practitionerId, String organizationId) throws ActivityDefinitionApplyException { SupplyRequest supplyRequest = new SupplyRequest(); - if (practionerId != null) { + if (practitionerId != null) { supplyRequest.setRequester( - new SupplyRequest.SupplyRequestRequesterComponent().setAgent(new Reference(practionerId))); + new SupplyRequest.SupplyRequestRequesterComponent().setAgent(new Reference(practitionerId))); } if (organizationId != null) { @@ -255,7 +251,7 @@ private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition } if (activityDefinition.hasBodySite()) { - throw new ActivityDefinitionApplyException("Bodysite does not map to " + activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("BodySite does not map to " + activityDefinition.getKind()); } return supplyRequest; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java index eaaabbb78..825848f0f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java @@ -17,8 +17,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; @@ -28,10 +26,10 @@ public class CacheValueSetsProvider { - private IFhirSystemDao systemDao; + private IFhirSystemDao systemDao; private IFhirResourceDao endpointDao; - public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { + public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { this.systemDao = systemDao; this.endpointDao = endpointDao; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index 44b013420..9d0acdab1 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -1,7 +1,6 @@ package org.opencds.cqf.dstu3.providers; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -9,14 +8,12 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.dstu3.builders.OperationOutcomeBuilder; import org.opencds.cqf.dstu3.builders.RandomIdBuilder; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java index c47a05adc..c5a1f1c70 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java @@ -38,7 +38,6 @@ import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.opencds.cqf.dstu3.helpers.LibraryHelper; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -48,13 +47,11 @@ public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResolutionProvider; - private FhirContext context; public CqlExecutionProvider(LibraryResolutionProvider libraryResolutionProvider, - EvaluationProviderFactory providerFactory, FhirContext context) { + EvaluationProviderFactory providerFactory) { this.providerFactory = providerFactory; this.libraryResolutionProvider = libraryResolutionProvider; - this.context = context; } private LibraryResolutionProvider getLibraryResourceProvider() { @@ -191,6 +188,7 @@ public Object evaluateInContext(DomainResource instance, String cql, String pati return context.resolveExpressionRef("Expression").evaluate(context); } + @SuppressWarnings("unchecked") @Operation(name = "$cql") public Bundle evaluate(@OperationParam(name = "code") String code, @OperationParam(name = "patientId") String patientId, @@ -319,12 +317,12 @@ public Bundle evaluate(@OperationParam(name = "code") String code, result.addParameter().setName("value").setValue(new StringType("null")); } else if (res instanceof List) { if (((List) res).size() > 0 && ((List) res).get(0) instanceof Resource) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } } else if (res instanceof Iterable) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } else if (res instanceof Resource) { result.addParameter().setName("value").setResource((Resource) res); } else { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java index cd4f52539..def152b0f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java @@ -75,7 +75,7 @@ public class DataRequirementsProvider { // 1. Find the Primary Library Resource // 2. Load the Primary Library as ELM. This will recursively load the dependent // libraries as ELM by Name - // 3. Load the Library Depedencies as Resources + // 3. Load the Library Dependencies as Resources // 4. Update the Data Requirements on the Resources accordingly // Since the Library Loader only exposes the loaded libraries as ELM, we // actually have to load them twice. @@ -144,7 +144,6 @@ private CqfMeasure createCqfMeasure(Measure measure, .entrySet()) { Library library = libraryEntry.getValue().getLeft(); org.hl7.fhir.dstu3.model.Library libraryResource = libraryEntry.getValue().getRight(); - Boolean isPrimaryLibrary = libraryResource != null && libraryResource.getId().equals(primaryLibraryId); String libraryNamespace = ""; if (primaryLibrary.getIncludes() != null) { for (IncludeDef include : primaryLibrary.getIncludes().getDef()) { @@ -394,7 +393,7 @@ public int compare(MeasureGroupPopulationComponent item, MeasureGroupPopulationC } } - // If there's only one group every critieria was shared. Kill the group. + // If there's only one group every criteria was shared. Kill the group. if (cqfMeasure.getGroup().size() == 1) { cqfMeasure.getGroup().clear(); } @@ -487,7 +486,7 @@ private String markdownToHtml(Parser parser, HtmlRenderer renderer, String markd } public org.hl7.fhir.dstu3.model.Library getDataRequirements(Measure measure, - LibraryResolutionProvider libraryResourceProvider) { + LibraryResolutionProvider libraryResourceProvider) { Map> libraryMap = this .createLibraryMap(measure, libraryResourceProvider); return this.getDataRequirements(measure, libraryMap.values().stream().map(x -> x.getRight()) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java index 3ccb7e0ec..66714f1e2 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java @@ -2,13 +2,10 @@ import java.io.File; import java.io.FileReader; -import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; -import java.io.StringReader; import java.io.StringWriter; import java.net.URI; -import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; @@ -19,16 +16,11 @@ import java.util.UUID; import java.util.stream.Collectors; -import javax.xml.XMLConstants; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; import com.jamesmurty.utils.XMLBuilder2; @@ -56,7 +48,6 @@ import org.opencds.cqf.measure.stu3.TerminologyRef; import org.opencds.cqf.measure.stu3.TerminologyRef.TerminologyRefType; import org.w3c.dom.Document; -import org.xml.sax.SAXException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; @@ -64,6 +55,7 @@ public class HQMFProvider { private static Map measureTypeValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; { put("PROCESS", "Process"); put("OUTCOME", "Outcome"); @@ -74,6 +66,7 @@ public class HQMFProvider { }; private static Map measureScoringValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; { put("PROPOR", "Proportion"); put("RATIO", "Ratio"); @@ -98,12 +91,14 @@ public CodeMapping(String code, String displayName, String criteriaName, String } public static Map measurePopulationValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; + { put("initial-population", new CodeMapping("IPOP", "Initial Population", "initialPopulationCriteria", "initialPopulation")); put("numerator", new CodeMapping("NUMER", "Numerator", "numeratorCriteria", "numerator")); put("numerator-exclusion", new CodeMapping("NUMEX", "Numerator Exclusion", "numeratorExclusionCriteria", "numeratorExclusions")); put("denominator", new CodeMapping("DENOM", "Denominator", "denominatorCriteria", "denominator")); - put("denominator-exclusion", new CodeMapping("DENEX", "Denominator Exclusion", "denominatorExclusionCritieria", "denominatorExclusions")); + put("denominator-exclusion", new CodeMapping("DENEX", "Denominator Exclusion", "denominatorExclusionCriteria", "denominatorExclusions")); put("denominator-exception", new CodeMapping("DENEXCEP", "Denominator Exception", "denominatorExceptionCriteria", "denominatorExceptions")); // TODO: Figure out what the codes for these are (MPOP, MPOPEX, MPOPEXCEP are guesses) put("measure-population", new CodeMapping("MPOP", "Measure Population", "measurePopulationCriteria", "measurePopulation")); @@ -325,7 +320,7 @@ private void addSubjectOfs(XMLBuilder2 xml, CqfMeasure m) { this.addMeasureAttributeWithCodeAndTextValue(xml, "RAT", codeSystem, "Rationale", "text/plain", m.hasRationale() ? m.getRationale() : "None"); - // Clinical Recomendation Statement + // Clinical Recommendation Statement this.addMeasureAttributeWithCodeAndTextValue(xml, "CRS", codeSystem, "Clinical Recommendation Statement", "text/plain", m.hasClinicalRecommendationStatement() ? m.getClinicalRecommendationStatement() : "None"); @@ -533,12 +528,12 @@ private void addPopulationCriteriaComponentSDE(XMLBuilder2 xml, String sdeIdRoot .elem("id").a("extension", criteriaReferenceIdExtension).a("root", criteriaReferenceIdRoot).up().up().up().up().up(); } - private XMLBuilder2 addPopulationCriteriaHeader(XMLBuilder2 xml, String crtieriaName, String criteriaRoot) { + private XMLBuilder2 addPopulationCriteriaHeader(XMLBuilder2 xml, String criteriaName, String criteriaRoot) { return xml.root().elem("component") .elem("populationCriteriaSection") .elem("templateId") .elem("item").a("extension","2017-08-01").a("root", "2.16.840.1.113883.10.20.28.2.7").up().up() - .elem("id").a("extension", crtieriaName).a("root", criteriaRoot).up() + .elem("id").a("extension", criteriaName).a("root", criteriaRoot).up() .elem("code").a("code","57026-7").a("codeSystem","2.16.840.1.113883.6.1").up() .elem("title").a("value", "Population Criteria Section").up() .elem("text").up(); @@ -606,34 +601,33 @@ private String writeDocument(Document d) { } } + // private boolean validateHQMF(String xml) { + // try { + // return this.validateXML(this.loadHQMFSchema(), xml); + // } + // catch (SAXException e) { + // return false; + // } + // } - private boolean validateHQMF(String xml) { - try { - return this.validateXML(this.loadHQMFSchema(), xml); - } - catch (SAXException e) { - return false; - } - } - - private boolean validateXML(Schema schema, String xml){ - try { - Validator validator = schema.newValidator(); - validator.validate(new StreamSource(new StringReader(xml))); - } catch (IOException | SAXException e) { - System.out.println("Exception: " + e.getMessage()); - return false; - } - return true; - } + // private boolean validateXML(Schema schema, String xml){ + // try { + // Validator validator = schema.newValidator(); + // validator.validate(new StreamSource(new StringReader(xml))); + // } catch (IOException | SAXException e) { + // System.out.println("Exception: " + e.getMessage()); + // return false; + // } + // return true; + // } - private Schema loadHQMFSchema() throws SAXException { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - URL hqmfSchema = ClassLoader.getSystemClassLoader().getResource("hqmf/schemas/EMeasure_N1.xsd"); - return factory.newSchema(hqmfSchema); - } + // private Schema loadHQMFSchema() throws SAXException { + // SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + // URL hqmfSchema = ClassLoader.getSystemClassLoader().getResource("hqmf/schemas/EMeasure_N1.xsd"); + // return factory.newSchema(hqmfSchema); + // } - // args[0] == relative path to json measure -> i.e. measure/mesure-demo.json (optional) + // args[0] == relative path to json measure -> i.e. measure/measure-demo.json (optional) // args[1] == path to resource output -> i.e. library/library-demo.json(optional) // args[2] == path to hqmf output -> i.e. hqmf.xml(optional) // args[3] == path to narrative output -> i.e. output.html(optional) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index 12e901d61..b11e269e2 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -24,7 +24,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java index f4fc41de7..0d690d25c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java @@ -41,13 +41,12 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -119,9 +118,14 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ } } } + + try { + Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); + theResource.setText(n.copy()); + } catch (Exception e) { + logger.info("Error generating narrative", e); + } - Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); - theResource.setText(n.copy()); // logger.info("Narrative: " + n.getDivAsString()); return this.measureResourceProvider.update(theRequest, theResource, theId, theRequestDetails.getConditionalUrl(RestOperationTypeEnum.UPDATE), theRequestDetails); @@ -172,7 +176,7 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, seed.setup(measure, periodStart, periodEnd, productLine, source, user, pass); // resolve report type - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + MeasureEvaluation evaluator = new MeasureEvaluation(this.registry, seed.getMeasurementPeriod()); if (reportType != null) { switch (reportType) { @@ -272,7 +276,7 @@ public Bundle careGapsReport(@OperationParam(name = "periodStart") String period MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); seed.setup(measure, periodStart, periodEnd, null, null, null, null); - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + MeasureEvaluation evaluator = new MeasureEvaluation(this.registry, seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); @@ -425,6 +429,7 @@ public org.hl7.fhir.dstu3.model.Library dataRequirements(@IdParam IdType theId, return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); } + @SuppressWarnings("unchecked") @Operation(name = "$submit-data", idempotent = true, type = Measure.class) public Resource submitData(RequestDetails details, @IdParam IdType theId, @OperationParam(name = "measure-report", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, @@ -453,7 +458,7 @@ public Resource submitData(RequestDetails details, @IdParam IdType theId, } } - return (Resource) this.registry.getSystemDao().transaction(details, transactionBundle); + return (Resource) ((IFhirSystemDao)this.registry.getSystemDao()).transaction(details, transactionBundle); } private Bundle createTransactionBundle(Bundle bundle) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java index 8ee8451b7..6ec03ca29 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java @@ -45,8 +45,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; public class PlanDefinitionApplyProvider { @@ -54,7 +52,7 @@ public class PlanDefinitionApplyProvider { private ModelResolver modelResolver; private ActivityDefinitionApplyProvider activityDefinitionApplyProvider; - private IFhirResourceDao planDefintionDao; + private IFhirResourceDao planDefinitionDao; private IFhirResourceDao activityDefinitionDao; private FhirContext fhirContext; @@ -62,19 +60,19 @@ public class PlanDefinitionApplyProvider { private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); public PlanDefinitionApplyProvider(FhirContext fhirContext, - ActivityDefinitionApplyProvider activitydefinitionApplyProvider, - IFhirResourceDao planDefintionDao, + ActivityDefinitionApplyProvider activityDefinitionApplyProvider, + IFhirResourceDao planDefinitionDao, IFhirResourceDao activityDefinitionDao, CqlExecutionProvider executionProvider) { this.executionProvider = executionProvider; this.modelResolver = new Dstu3FhirModelResolver(); - this.activityDefinitionApplyProvider = activitydefinitionApplyProvider; - this.planDefintionDao = planDefintionDao; + this.activityDefinitionApplyProvider = activityDefinitionApplyProvider; + this.planDefinitionDao = planDefinitionDao; this.activityDefinitionDao = activityDefinitionDao; this.fhirContext = fhirContext; } public IFhirResourceDao getDao() { - return this.planDefintionDao; + return this.planDefinitionDao; } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) @@ -88,7 +86,7 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name @OperationParam(name = "setting") String setting, @OperationParam(name = "settingContext") String settingContext) throws IOException, JAXBException, FHIRException { - PlanDefinition planDefinition = this.planDefintionDao.read(theId); + PlanDefinition planDefinition = this.planDefinitionDao.read(theId); if (planDefinition == null) { throw new IllegalArgumentException("Couldn't find PlanDefinition " + theId); @@ -145,11 +143,11 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct result = this.activityDefinitionApplyProvider.resolveActivityDefinition( (ActivityDefinition) resolveContained(session.getPlanDefinition(), action.getDefinition().getReferenceElement().getIdPart()), - session.getPatientId(), session.getPractionerId(), session.getOrganizationId()); + session.getPatientId(), session.getPractitionerId(), session.getOrganizationId()); } else { result = this.activityDefinitionApplyProvider.apply( new IdType(action.getDefinition().getReferenceElement().getIdPart()), - session.getPatientId(), session.getEncounterId(), session.getPractionerId(), + session.getPatientId(), session.getEncounterId(), session.getPractitionerId(), session.getOrganizationId(), null, session.getUserLanguage(), session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } @@ -182,7 +180,7 @@ private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitio else { - // TODO - likely need more date tranformations + // TODO - likely need more date transformations if (result instanceof DateTime) { result = new JavaDateBuilder().buildFromDateTime((DateTime) result).build(); } @@ -434,7 +432,7 @@ public Resource resolveContained(DomainResource resource, String id) { class Session { private final String patientId; private final PlanDefinition planDefinition; - private final String practionerId; + private final String practitionerId; private final String organizationId; private final String userType; private final String userLanguage; @@ -451,7 +449,7 @@ public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String pa this.planDefinition = planDefinition; this.carePlanBuilder = builder; this.encounterId = encounterId; - this.practionerId = practitionerId; + this.practitionerId = practitionerId; this.organizationId = organizationId; this.userType = userType; this.userLanguage = userLanguage; @@ -480,8 +478,8 @@ public String getEncounterId() { return this.encounterId; } - public String getPractionerId() { - return practionerId; + public String getPractitionerId() { + return practitionerId; } public String getOrganizationId() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index e34b9595d..5dc03c1b8 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -61,6 +61,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; public class BaseServlet extends RestfulServer { + private static final long serialVersionUID = 1L; DaoRegistry registry; FhirContext fhirContext; @@ -187,6 +188,7 @@ protected NarrativeProvider getNarrativeProvider() { // Since resource provider resolution not lazy, the providers here must be // resolved in the correct // order of dependencies. + @SuppressWarnings("unchecked") private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { NarrativeProvider narrativeProvider = this.getNarrativeProvider(); @@ -208,7 +210,7 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, this.registerProvider(libraryProvider); // CQL Execution - CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory, this.fhirContext); + CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory); this.registerProvider(cql); // Bundle processing @@ -245,6 +247,7 @@ protected IFhirResourceDao getDao(Class clazz) { return this.registry.getResourceDao(clazz); } + @SuppressWarnings("unchecked") protected BaseJpaResourceProvider getResourceProvider(Class clazz) { return (BaseJpaResourceProvider) this.getResourceProviders().stream() .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 099381bfb..5df0f3bd3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -38,8 +38,6 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; -import org.opencds.cqf.cql.engine.debug.DebugAction; -import org.opencds.cqf.cql.engine.debug.DebugLocator; import org.opencds.cqf.cql.engine.debug.DebugMap; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; @@ -57,6 +55,7 @@ @WebServlet(name = "cds-services") public class CdsHooksServlet extends HttpServlet { + private static final long serialVersionUID = 1L; private FhirVersionEnum version = FhirVersionEnum.DSTU3; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); @@ -153,7 +152,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) context.setContextValue("Patient", hook.getRequest().getContext().getPatientId().replace("Patient/", "")); context.setExpressionCaching(true); - EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, FhirContext.forDstu3().newRestfulGenericClient(baseUrl), + EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, FhirContext.forDstu3().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, planDefinition); this.setAccessControlHeaders(response); @@ -172,7 +171,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. response.getWriter().println("ERROR: Exception connecting to remote server."); this.printMessageAndCause(e, response); - this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); this.printStackTrack(e, response); } catch (DataProviderException e) { this.setAccessControlHeaders(response); @@ -180,7 +179,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println("ERROR: Exception in DataProvider."); this.printMessageAndCause(e, response); if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { - this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); } this.printStackTrack(e, response); @@ -189,7 +188,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } - private void handleServerResponseExecption(BaseServerResponseException e, HttpServletResponse response) + private void handleServerResponseException(BaseServerResponseException e, HttpServletResponse response) throws IOException { switch (e.getStatusCode()) { case 401: diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 1a70ddf92..3325bfd74 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -99,6 +99,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY); } + @SuppressWarnings("unchecked") private Iterable evaluateCriteria(Context context, Patient patient, Measure.MeasureGroupPopulationComponent pop) { if (!pop.hasCriteria()) { @@ -116,8 +117,7 @@ private Iterable evaluateCriteria(Context context, Patient patient, expressions.clear(); } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + logger.warn("Error resetting expression cache", e); } Object result = context.resolveExpressionRef(pop.getCriteria().getExpression()).evaluate(context); if (result == null) { @@ -132,7 +132,7 @@ private Iterable evaluateCriteria(Context context, Patient patient, } } - return (Iterable) result; + return (Iterable) result; } private boolean evaluatePopulationCriteria(Context context, Patient patient, @@ -318,6 +318,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } break; case MEASUREOBSERVATION: + measureObservation = new HashMap<>(); break; } } @@ -420,8 +421,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p // For each patient in the patient list for (Patient patient : patients) { - // Are they in the initial population? - boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, + evaluatePopulationCriteria(context, patient, initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, null); populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java index 2ef3511c3..344a52ade 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/FhirMeasureBundler.java @@ -9,12 +9,13 @@ */ public class FhirMeasureBundler { // Adds the resources returned from the given expressions to a bundle + @SuppressWarnings("unchecked") public Bundle bundle(Context context, String... expressionNames) { Bundle bundle = new Bundle(); bundle.setType(Bundle.BundleType.COLLECTION); for (String expressionName : expressionNames) { Object result = context.resolveExpressionRef(expressionName).evaluate(context); - for (Object element : (Iterable) result) { + for (Object element : (Iterable) result) { Bundle.BundleEntryComponent entry = new Bundle.BundleEntryComponent(); entry.setResource((Resource) element); // The null check for resourceType handles Lists, which don't have a resource diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java index 1605a891c..18325637e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java @@ -30,8 +30,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; /** @@ -78,8 +76,7 @@ public Resource resolveActivityDefinition(ActivityDefinition activityDefinition, String practitionerId, String organizationId) throws FHIRException { Resource result = null; try { - result = (Resource) Class.forName("org.hl7.fhir.r4.model." + activityDefinition.getKind().toCode()) - .newInstance(); + result = (Resource) Class.forName("org.hl7.fhir.r4.model." + activityDefinition.getKind().toCode()).getConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); throw new FHIRException("Could not find org.hl7.fhir.r4.model." + activityDefinition.getKind().toCode()); @@ -203,7 +200,7 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe } if (activityDefinition.hasBodySite()) { - throw new ActivityDefinitionApplyException("Bodysite does not map to " + activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("BodySite does not map to " + activityDefinition.getKind()); } if (activityDefinition.hasCode()) { @@ -217,12 +214,12 @@ private MedicationRequest resolveMedicationRequest(ActivityDefinition activityDe return medicationRequest; } - private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition, String practionerId, + private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition, String practitionerId, String organizationId) throws ActivityDefinitionApplyException { SupplyRequest supplyRequest = new SupplyRequest(); - if (practionerId != null) { - supplyRequest.setRequester(new Reference(practionerId)); + if (practitionerId != null) { + supplyRequest.setRequester(new Reference(practitionerId)); } if (organizationId != null) { @@ -250,7 +247,7 @@ private SupplyRequest resolveSupplyRequest(ActivityDefinition activityDefinition } if (activityDefinition.hasBodySite()) { - throw new ActivityDefinitionApplyException("Bodysite does not map to " + activityDefinition.getKind()); + throw new ActivityDefinitionApplyException("BodySite does not map to " + activityDefinition.getKind()); } return supplyRequest; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java index a15e66056..bbb9f49b9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java @@ -17,8 +17,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; @@ -28,10 +26,10 @@ public class CacheValueSetsProvider { - private IFhirSystemDao systemDao; + private IFhirSystemDao systemDao; private IFhirResourceDao endpointDao; - public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { + public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { this.systemDao = systemDao; this.endpointDao = endpointDao; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index 096fe6539..dd9117c55 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -8,9 +8,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java index c583a9a25..ac855087d 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java @@ -223,6 +223,7 @@ private Context setupContext(DomainResource instance, String patientId, LibraryL return context; } + @SuppressWarnings("unchecked") @Operation(name = "$cql") public Bundle evaluate(@OperationParam(name = "code") String code, @OperationParam(name = "patientId") String patientId, @@ -358,13 +359,13 @@ public Bundle evaluate(@OperationParam(name = "code") String code, .getResourceType() + "/" + ((Resource) ((List) res).get(0)).getIdElement().getIdPart())); } else { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } } else if (res instanceof Iterable) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } else if (res instanceof Resource) { if (executionResults != null && executionResults.equals("Summary")) { result.addParameter().setName("value") diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java index 0dd36db48..971ba12f4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java @@ -71,7 +71,7 @@ public class DataRequirementsProvider { // 1. Find the Primary Library Resource // 2. Load the Primary Library as ELM. This will recursively load the dependent // libraries as ELM by Name - // 3. Load the Library Depedencies as Resources + // 3. Load the Library Dependencies as Resources // 4. Update the Data Requirements on the Resources accordingly // Since the Library Loader only exposes the loaded libraries as ELM, we // actually have to load them twice. @@ -141,7 +141,6 @@ private CqfMeasure createCqfMeasure(Measure measure, .entrySet()) { Library library = libraryEntry.getValue().getLeft(); org.hl7.fhir.r4.model.Library libraryResource = libraryEntry.getValue().getRight(); - Boolean isPrimaryLibrary = libraryResource != null && libraryResource.getId().equals(primaryLibraryId); String libraryNamespace = ""; if (primaryLibrary.getIncludes() != null) { for (IncludeDef include : primaryLibrary.getIncludes().getDef()) { @@ -380,7 +379,7 @@ private CqfMeasure createCqfMeasure(Measure measure, } } - // If there's only one group every critieria was shared. Kill the group. + // If there's only one group every criteria was shared. Kill the group. if (cqfMeasure.getGroup().size() == 1) { cqfMeasure.getGroup().clear(); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java index f7a66ce3c..f137da2aa 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java @@ -2,13 +2,10 @@ import java.io.File; import java.io.FileReader; -import java.io.IOException; import java.io.PrintWriter; import java.io.Reader; -import java.io.StringReader; import java.io.StringWriter; import java.net.URI; -import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; @@ -19,16 +16,11 @@ import java.util.UUID; import java.util.stream.Collectors; -import javax.xml.XMLConstants; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; import com.jamesmurty.utils.XMLBuilder2; @@ -55,7 +47,6 @@ import org.opencds.cqf.measure.r4.TerminologyRef; import org.opencds.cqf.measure.r4.TerminologyRef.TerminologyRefType; import org.w3c.dom.Document; -import org.xml.sax.SAXException; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; @@ -63,6 +54,8 @@ public class HQMFProvider { private static Map measureTypeValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; + { put("PROCESS", "Process"); put("OUTCOME", "Outcome"); @@ -73,6 +66,8 @@ public class HQMFProvider { }; private static Map measureScoringValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; + { put("PROPOR", "Proportion"); put("RATIO", "Ratio"); @@ -97,6 +92,7 @@ public CodeMapping(String code, String displayName, String criteriaName, String } public static Map measurePopulationValueSetMap = new HashMap() { + private static final long serialVersionUID = 1L; { put("initial-population", new CodeMapping("IPOP", "Initial Population", "initialPopulationCriteria", "initialPopulation")); @@ -105,7 +101,7 @@ public CodeMapping(String code, String displayName, String criteriaName, String "numeratorExclusions")); put("denominator", new CodeMapping("DENOM", "Denominator", "denominatorCriteria", "denominator")); put("denominator-exclusion", new CodeMapping("DENEX", "Denominator Exclusion", - "denominatorExclusionCritieria", "denominatorExclusions")); + "denominatorExclusionCriteria", "denominatorExclusions")); put("denominator-exception", new CodeMapping("DENEXCEP", "Denominator Exception", "denominatorExceptionCriteria", "denominatorExceptions")); // TODO: Figure out what the codes for these are (MPOP, MPOPEX, MPOPEXCEP are @@ -326,7 +322,7 @@ private void addSubjectOfs(XMLBuilder2 xml, CqfMeasure m) { this.addMeasureAttributeWithCodeAndTextValue(xml, "RAT", codeSystem, "Rationale", "text/plain", m.hasRationale() ? m.getRationale() : "None"); - // Clinical Recomendation Statement + // Clinical Recommendation Statement this.addMeasureAttributeWithCodeAndTextValue(xml, "CRS", codeSystem, "Clinical Recommendation Statement", "text/plain", m.hasClinicalRecommendationStatement() ? m.getClinicalRecommendationStatement() : "None"); @@ -546,10 +542,10 @@ private void addPopulationCriteriaComponentSDE(XMLBuilder2 xml, String sdeIdRoot .up(); } - private XMLBuilder2 addPopulationCriteriaHeader(XMLBuilder2 xml, String crtieriaName, String criteriaRoot) { + private XMLBuilder2 addPopulationCriteriaHeader(XMLBuilder2 xml, String criteriaName, String criteriaRoot) { return xml.root().elem("component").elem("populationCriteriaSection").elem("templateId").elem("item") .a("extension", "2017-08-01").a("root", "2.16.840.1.113883.10.20.28.2.7").up().up().elem("id") - .a("extension", crtieriaName).a("root", criteriaRoot).up().elem("code").a("code", "57026-7") + .a("extension", criteriaName).a("root", criteriaRoot).up().elem("code").a("code", "57026-7") .a("codeSystem", "2.16.840.1.113883.6.1").up().elem("title").a("value", "Population Criteria Section") .up().elem("text").up(); } @@ -607,32 +603,32 @@ private String writeDocument(Document d) { } } - private boolean validateHQMF(String xml) { - try { - return this.validateXML(this.loadHQMFSchema(), xml); - } catch (SAXException e) { - return false; - } - } + // private boolean validateHQMF(String xml) { + // try { + // return this.validateXML(this.loadHQMFSchema(), xml); + // } catch (SAXException e) { + // return false; + // } + // } - private boolean validateXML(Schema schema, String xml) { - try { - Validator validator = schema.newValidator(); - validator.validate(new StreamSource(new StringReader(xml))); - } catch (IOException | SAXException e) { - System.out.println("Exception: " + e.getMessage()); - return false; - } - return true; - } + // private boolean validateXML(Schema schema, String xml) { + // try { + // Validator validator = schema.newValidator(); + // validator.validate(new StreamSource(new StringReader(xml))); + // } catch (IOException | SAXException e) { + // System.out.println("Exception: " + e.getMessage()); + // return false; + // } + // return true; + // } - private Schema loadHQMFSchema() throws SAXException { - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - URL hqmfSchema = ClassLoader.getSystemClassLoader().getResource("hqmf/schemas/EMeasure_N1.xsd"); - return factory.newSchema(hqmfSchema); - } + // private Schema loadHQMFSchema() throws SAXException { + // SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + // URL hqmfSchema = ClassLoader.getSystemClassLoader().getResource("hqmf/schemas/EMeasure_N1.xsd"); + // return factory.newSchema(hqmfSchema); + // } - // args[0] == relative path to json measure -> i.e. measure/mesure-demo.json + // args[0] == relative path to json measure -> i.e. measure/measure-demo.json // (optional) // args[1] == path to resource output -> i.e. // library/library-demo.json(optional) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 27efb278d..edec33eff 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -23,7 +23,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 2c980d697..d1448be1e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -40,13 +40,12 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -123,7 +122,7 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); theResource.setText(n.copy()); } catch (Exception e) { - // Ignore the exception so the resource still gets updated + logger.info("Error generating narrative", e); } return this.measureResourceProvider.update(theRequest, theResource, theId, @@ -430,6 +429,7 @@ public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId, return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider); } + @SuppressWarnings("unchecked") @Operation(name = "$submit-data", idempotent = true, type = Measure.class) public Resource submitData(RequestDetails details, @IdParam IdType theId, @OperationParam(name = "measurereport", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, @@ -458,7 +458,7 @@ public Resource submitData(RequestDetails details, @IdParam IdType theId, } } - return (Resource) this.registry.getSystemDao().transaction(details, transactionBundle); + return (Resource) ((IFhirSystemDao)this.registry.getSystemDao()).transaction(details, transactionBundle); } private Bundle createTransactionBundle(Bundle bundle) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index a337eb482..ead5599c9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -46,8 +46,6 @@ import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.OptionalParam; -import ca.uhn.fhir.rest.annotation.RequiredParam; public class PlanDefinitionApplyProvider { @@ -55,7 +53,7 @@ public class PlanDefinitionApplyProvider { private ModelResolver modelResolver; private ActivityDefinitionApplyProvider activityDefinitionApplyProvider; - private IFhirResourceDao planDefintionDao; + private IFhirResourceDao planDefinitionDao; private IFhirResourceDao activityDefinitionDao; private FhirContext fhirContext; @@ -63,19 +61,19 @@ public class PlanDefinitionApplyProvider { private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); public PlanDefinitionApplyProvider(FhirContext fhirContext, - ActivityDefinitionApplyProvider activitydefinitionApplyProvider, - IFhirResourceDao planDefintionDao, + ActivityDefinitionApplyProvider activityDefinitionApplyProvider, + IFhirResourceDao planDefinitionDao, IFhirResourceDao activityDefinitionDao, CqlExecutionProvider executionProvider) { this.executionProvider = executionProvider; this.modelResolver = new R4FhirModelResolver(); - this.activityDefinitionApplyProvider = activitydefinitionApplyProvider; - this.planDefintionDao = planDefintionDao; + this.activityDefinitionApplyProvider = activityDefinitionApplyProvider; + this.planDefinitionDao = planDefinitionDao; this.activityDefinitionDao = activityDefinitionDao; this.fhirContext = fhirContext; } public IFhirResourceDao getDao() { - return this.planDefintionDao; + return this.planDefinitionDao; } @Operation(name = "$apply", idempotent = true, type = PlanDefinition.class) @@ -89,7 +87,7 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name @OperationParam(name = "setting") String setting, @OperationParam(name = "settingContext") String settingContext) throws IOException, JAXBException, FHIRException { - PlanDefinition planDefinition = this.planDefintionDao.read(theId); + PlanDefinition planDefinition = this.planDefinitionDao.read(theId); if (planDefinition == null) { throw new IllegalArgumentException("Couldn't find PlanDefinition " + theId); @@ -146,11 +144,11 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct result = this.activityDefinitionApplyProvider.resolveActivityDefinition( (ActivityDefinition) resolveContained(session.getPlanDefinition(), action.getDefinitionCanonicalType().getValue()), - session.getPatientId(), session.getPractionerId(), session.getOrganizationId()); + session.getPatientId(), session.getPractitionerId(), session.getOrganizationId()); } else { result = this.activityDefinitionApplyProvider.apply( new IdType(CanonicalHelper.getId(action.getDefinitionCanonicalType())), - session.getPatientId(), session.getEncounterId(), session.getPractionerId(), + session.getPatientId(), session.getEncounterId(), session.getPractitionerId(), session.getOrganizationId(), null, session.getUserLanguage(), session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } @@ -183,7 +181,7 @@ private void resolveDynamicActions(Session session, PlanDefinition.PlanDefinitio else { - // TODO - likely need more date tranformations + // TODO - likely need more date transformations if (result instanceof DateTime) { result = new JavaDateBuilder().buildFromDateTime((DateTime) result).build(); } @@ -441,7 +439,7 @@ public Resource resolveContained(DomainResource resource, String id) { class Session { private final String patientId; private final PlanDefinition planDefinition; - private final String practionerId; + private final String practitionerId; private final String organizationId; private final String userType; private final String userLanguage; @@ -458,7 +456,7 @@ public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String pa this.planDefinition = planDefinition; this.carePlanBuilder = builder; this.encounterId = encounterId; - this.practionerId = practitionerId; + this.practitionerId = practitionerId; this.organizationId = organizationId; this.userType = userType; this.userLanguage = userLanguage; @@ -487,8 +485,8 @@ public String getEncounterId() { return this.encounterId; } - public String getPractionerId() { - return practionerId; + public String getPractitionerId() { + return practitionerId; } public String getOrganizationId() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 082e1d977..c7765f7be 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -61,6 +61,7 @@ import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; public class BaseServlet extends RestfulServer { + private static final long serialVersionUID = 1L; DaoRegistry registry; FhirContext fhirContext; @@ -190,6 +191,7 @@ protected NarrativeProvider getNarrativeProvider() { // Since resource provider resolution not lazy, the providers here must be // resolved in the correct // order of dependencies. + @SuppressWarnings("unchecked") private void resolveProviders(EvaluationProviderFactory providerFactory, JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { NarrativeProvider narrativeProvider = this.getNarrativeProvider(); @@ -246,6 +248,7 @@ protected IFhirResourceDao getDao(Class clazz) { return this.registry.getResourceDao(clazz); } + @SuppressWarnings("unchecked") protected BaseJpaResourceProvider getResourceProvider(Class clazz) { return (BaseJpaResourceProvider) this.getResourceProviders().stream() .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index c1710a5d6..e28549124 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -38,8 +38,6 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; -import org.opencds.cqf.cql.engine.debug.DebugAction; -import org.opencds.cqf.cql.engine.debug.DebugLocator; import org.opencds.cqf.cql.engine.debug.DebugMap; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; @@ -57,6 +55,7 @@ @WebServlet(name = "cds-services") public class CdsHooksServlet extends HttpServlet { + private static final long serialVersionUID = 1L; private FhirVersionEnum version = FhirVersionEnum.R4; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); @@ -157,7 +156,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) context.setContextValue("Patient", hook.getRequest().getContext().getPatientId().replace("Patient/", "")); context.setExpressionCaching(true); - EvaluationContext evaluationContext = new R4EvaluationContext(hook, version, + EvaluationContext evaluationContext = new R4EvaluationContext(hook, version, FhirContext.forR4().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, planDefinition); @@ -177,7 +176,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. response.getWriter().println("ERROR: Exception connecting to remote server."); this.printMessageAndCause(e, response); - this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); this.printStackTrack(e, response); } catch (DataProviderException e) { this.setAccessControlHeaders(response); @@ -185,7 +184,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.getWriter().println("ERROR: Exception in DataProvider."); this.printMessageAndCause(e, response); if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { - this.handleServerResponseExecption((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); } this.printStackTrack(e, response); @@ -194,7 +193,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } } - private void handleServerResponseExecption(BaseServerResponseException e, HttpServletResponse response) + private void handleServerResponseException(BaseServerResponseException e, HttpServletResponse response) throws IOException { switch (e.getStatusCode()) { case 401: From 45eafeabdc4e3ac6574fb4abdb8687e1c1e1f57c Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 11 Jun 2020 07:41:20 -0600 Subject: [PATCH 045/198] Adding OAuth pass thru functionality --- .../cqf/common/config/HapiProperties.java | 6 ++++- r4/pom.xml | 6 +++++ .../cqf/r4/providers/OAuthProvider.java | 22 +++++++++++++++++++ .../opencds/cqf/r4/servlet/BaseServlet.java | 5 +++++ r4/src/main/resources/hapi.properties | 4 ++++ 5 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 24275445b..4f57eb20c 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -48,6 +48,7 @@ public class HapiProperties { static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches"; static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params"; static final String EMAIL_FROM = "email.from"; + static final String OAUTH_ENABLED = "oauth.enabled"; private static Properties properties; @@ -352,5 +353,8 @@ public static String getObservationEndpoint() { public static String getObservationUserName(){return HapiProperties.getProperty("observation.username");}; public static String getObservationPassword(){return HapiProperties.getProperty("observation.password");}; - + //************************* OAuth ******************************************************* + public static Boolean getOAuthEnabled() { + return HapiProperties.getBooleanProperty(OAUTH_ENABLED, true); + } } diff --git a/r4/pom.xml b/r4/pom.xml index 2bb79b120..921cee573 100644 --- a/r4/pom.xml +++ b/r4/pom.xml @@ -17,5 +17,11 @@ common ${project.version} + + + ca.uhn.hapi.fhir + hapi-fhir-jpaserver-base + ${hapi.version} + diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java new file mode 100644 index 000000000..440142cef --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -0,0 +1,22 @@ +package org.opencds.cqf.r4.providers; + +import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.r4.model.CapabilityStatement; + +import javax.servlet.http.HttpServletRequest; + +public class OAuthProvider extends JpaConformanceProviderR4 { + + @Override + public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { + CapabilityStatement retVal; + + retVal = super.getServerConformance(theRequest, theRequestDetails); + + if(retVal != null){ + retVal.getImplementation().setDescription("cqf-ruler R4 server"); + } + return retVal; + } +} diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index f5605b7b8..1826b09f3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -223,6 +223,11 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); this.registerProvider(questionnaireProvider); + if(HapiProperties.getOAuthEnabled()) { + OAuthProvider oauthProvider = new OAuthProvider(); + this.registerProvider(oauthProvider); + } + CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); CdsHooksServlet.setLibraryResolutionProvider(libraryProvider); CdsHooksServlet.setSystemTerminologyProvider(localSystemTerminologyProvider); diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 010c9b4dd..9d40c5da7 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -154,3 +154,7 @@ email.password= # Enable Websocket Subscription Channel subscription.websocket.enabled=false +################################################## +# OAuth Settings +################################################## +oath.enabled=false From 7f378c6d79250b6fa75e7d04fc192553d3fc810e Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 11 Jun 2020 07:53:23 -0600 Subject: [PATCH 046/198] remove questionnaire code to keep this oauth only --- .../r4/providers/QuestionnaireProvider.java | 119 ------------------ .../opencds/cqf/r4/servlet/BaseServlet.java | 4 - 2 files changed, 123 deletions(-) delete mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java deleted file mode 100644 index b32fc0116..000000000 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.opencds.cqf.r4.providers; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.util.BundleUtil; -import com.github.dnault.xmlpatch.repackaged.org.jaxen.util.SingletonList; -import org.hl7.fhir.DateTime; -import org.hl7.fhir.r4.model.*; -import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.utilities.BundleUtils; - -import java.util.Date; -import java.util.List; - -import static org.opencds.cqf.common.helpers.ClientHelper.getClient; - -public class QuestionnaireProvider { - - private FhirContext fhirContext; - public QuestionnaireProvider(FhirContext fhirContext){ - this.fhirContext = fhirContext; - } - - @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) - public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; - } - - private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ - Bundle newBundle = new Bundle(); - - Identifier bundleId = new Identifier(); - bundleId.setValue(questionnaireResponse.getId()); - newBundle.setType(Bundle.BundleType.TRANSACTION); - newBundle.setIdentifier(bundleId); - - questionnaireResponse.getItem().stream().forEach(item ->{ - newBundle.addEntry(extractItem(item, questionnaireResponse)); - }); - return newBundle; - } - - //TODO - ids need work; add encounter; subject/patient; effectiveDateTime; value - // TODO - using value for questionnaire question - NOT THE RIGHT PLACE!! - private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, QuestionnaireResponse questionnaireResponse){ - // obs.setCode(); -// obs.setValue() = - - Observation obs = new Observation(); - obs.setStatus(Observation.ObservationStatus.FINAL); - obs.setId(item.getLinkId()); -// obs.setDerivedFrom(new SingletonList(questionnaireResponse.get); //create a reference to the QuestionnaireResponse ???? - obs.setValue(new StringType(item.getText() + "::" + item.getAnswer().get(0).getValueStringType().getValue())); - obs.setEffective(new DateTimeType( questionnaireResponse.getMeta().getLastUpdated())); - - Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); - berc.setMethod(Bundle.HTTPVerb.PUT); - berc.setUrl("Observation/" + item.getId() + "." + item.getLinkId()); - - Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); - bec.setResource(obs); - bec.setRequest(berc); - return bec; - } - - private Bundle sendObservationBundle(Bundle observationsBundle){ - String url = HapiProperties.getObservationEndpoint(); - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); - - IGenericClient client = getClient(fhirContext, url, user, password); - Bundle outcomeBundle = client.transaction() - .withBundle(observationsBundle) - .execute(); - return outcomeBundle; - } - - /** - * How do we store the QuestionnaireResponse Id?? - * - * var obxs = []; - * for(var i=0, iLen=values.length; i Date: Thu, 11 Jun 2020 08:48:24 -0600 Subject: [PATCH 047/198] typo fixed --- r4/src/main/resources/hapi.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 9d40c5da7..f34c62bb1 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -157,4 +157,4 @@ subscription.websocket.enabled=false ################################################## # OAuth Settings ################################################## -oath.enabled=false +oauth.enabled=false From 0ba343618811f68d25c3de994d053e5085817c52 Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 11 Jun 2020 10:50:43 -0600 Subject: [PATCH 048/198] progress in adding capability statements --- .../opencds/cqf/r4/providers/OAuthProvider.java | 17 ++++++++++++++++- .../org/opencds/cqf/r4/servlet/BaseServlet.java | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index 440142cef..b69267a24 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -1,16 +1,31 @@ package org.opencds.cqf.r4.providers; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; +import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider; import org.hl7.fhir.r4.model.CapabilityStatement; import javax.servlet.http.HttpServletRequest; -public class OAuthProvider extends JpaConformanceProviderR4 { +public class OAuthProvider extends ServerCapabilityStatementProvider {//JpaConformanceProviderR4 { + /** + * This class is NOT designed to be a real OAuth provider. + * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. + * It should only get instantiated if hapi.properties has oauth.enabled set to true. + */ + @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { CapabilityStatement retVal; +// setIncludeResourceCounts(false); +// setDaoConfig(new DaoConfig()); // we do NOT want a DAO inside the ruler +// setSearchParamRegistry(new SearchParamRegistryImpl()); + setPublisher("Alphora"); + retVal = super.getServerConformance(theRequest, theRequestDetails); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index a0d4852a5..651370eb1 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -222,6 +222,7 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, if(HapiProperties.getOAuthEnabled()) { OAuthProvider oauthProvider = new OAuthProvider(); this.registerProvider(oauthProvider); + this.setServerConformanceProvider(oauthProvider); } CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); From e0fb5f8f7c1d75ffbd21922c8ab2fa43dd7ed89f Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 12 Jun 2020 13:15:28 -0600 Subject: [PATCH 049/198] adding to capability statement for oauth; OAuthServlet is a placeholder only at this time; --- .../cqf/common/config/HapiProperties.java | 24 +++++++++-- .../cqf/r4/providers/OAuthProvider.java | 42 ++++++++++++++----- .../opencds/cqf/r4/servlet/BaseServlet.java | 21 +++++----- .../opencds/cqf/r4/servlet/OAuthServlet.java | 4 ++ r4/src/main/resources/hapi.properties | 12 +++++- 5 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 4f57eb20c..a7f12947a 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -49,6 +49,16 @@ public class HapiProperties { static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params"; static final String EMAIL_FROM = "email.from"; static final String OAUTH_ENABLED = "oauth.enabled"; + static final String OAUTH_SECURITY_CORS = "oauth.securityCors"; + static final String OAUTH_SECURITY_URL = "oauth.securityUrl"; + static final String OAUTH_SECURITY_EXT_AUTH_URL = "oauth.securityExtAuthUrl"; + static final String OAUTH_SECURITY_EXT_AUTH_VALUE_URI = "oauth.securityExtAuthValueUri"; + static final String OAUTH_SECURITY_EXT_TOKEN_URL = "oauth.securityExtTokenUrl"; + static final String OAUTH_SECURITY_EXT_TOKEN_VALUE_URI = "oauth.securityExtTokenValueUri"; + static final String OAUTH_SERVICE_SYSTEM = "oauth.serviceSystem"; + static final String OAUTH_SERVICE_CODE = "oauth.serviceCode"; + static final String OAUTH_SERVICE_DISPLAY = "oauth.serviceDisplay"; + static final String OAUTH_SERVICE_TEXT = "oauth.serviceText"; private static Properties properties; @@ -354,7 +364,15 @@ public static String getObservationEndpoint() { public static String getObservationPassword(){return HapiProperties.getProperty("observation.password");}; //************************* OAuth ******************************************************* - public static Boolean getOAuthEnabled() { - return HapiProperties.getBooleanProperty(OAUTH_ENABLED, true); - } + public static Boolean getOAuthEnabled(){return HapiProperties.getBooleanProperty(OAUTH_ENABLED, true);} + public static Boolean getOauthSecurityCors(){return HapiProperties.getBooleanProperty(OAUTH_SECURITY_CORS, true);} + public static String getOauthSecurityUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_URL, "");} + public static String getOauthSecurityExtAuthUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_URL, "");} + public static String getOauthSecurityExtAuthValueUri(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_VALUE_URI, "");} + public static String getOauthSecurityExtTokenUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_URL, "");} + public static String getOauthSecurityExtTokenValueUri(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_VALUE_URI, "");} + public static String getOauthServiceSystem(){return HapiProperties.getProperty(OAUTH_SERVICE_SYSTEM, "");} + public static String getOauthServiceCode(){return HapiProperties.getProperty(OAUTH_SERVICE_CODE, "");} + public static String getOauthServiceDisplay(){return HapiProperties.getProperty(OAUTH_SERVICE_DISPLAY, "");} + public static String getOauthServiceText(){return HapiProperties.getProperty(OAUTH_SERVICE_TEXT, "");} } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index b69267a24..8a888481d 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -1,37 +1,57 @@ package org.opencds.cqf.r4.providers; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider; -import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.*; +import org.opencds.cqf.common.config.HapiProperties; import javax.servlet.http.HttpServletRequest; -public class OAuthProvider extends ServerCapabilityStatementProvider {//JpaConformanceProviderR4 { +public class OAuthProvider extends JpaConformanceProviderR4 { /** * This class is NOT designed to be a real OAuth provider. * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. * It should only get instantiated if hapi.properties has oauth.enabled set to true. */ - @Metadata + public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { + super(theRestfulServer, theSystemDao, theDaoConfig); + } + + @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { CapabilityStatement retVal; -// setIncludeResourceCounts(false); -// setDaoConfig(new DaoConfig()); // we do NOT want a DAO inside the ruler -// setSearchParamRegistry(new SearchParamRegistryImpl()); - setPublisher("Alphora"); + retVal = super.getServerConformance(theRequest, theRequestDetails); + retVal.getRestFirstRep().getSecurity().setCors(HapiProperties.getOauthSecurityCors()); + Extension securityExtension = retVal.getRestFirstRep().getSecurity().addExtension(); + securityExtension.setUrl(HapiProperties.getOauthSecurityUrl()); + // security.extension.extension + Extension securityExtExt = securityExtension.addExtension(); + securityExtExt.setUrl(HapiProperties.getOauthSecurityExtAuthUrl()); + securityExtExt.setValue(new UriType(HapiProperties.getOauthSecurityExtAuthValueUri())); + Extension securityTokenExt = securityExtension.addExtension(); + securityTokenExt.setUrl(HapiProperties.getOauthSecurityExtTokenUrl()); + securityTokenExt.setValue(new UriType(HapiProperties.getOauthSecurityExtTokenValueUri())); - retVal = super.getServerConformance(theRequest, theRequestDetails); + // security.extension.service + Coding coding = new Coding(); + coding.setSystem(HapiProperties.getOauthServiceSystem()); + coding.setCode(HapiProperties.getOauthServiceCode()); + coding.setDisplay(HapiProperties.getOauthServiceDisplay()); + CodeableConcept codeConcept = new CodeableConcept(); + codeConcept.addCoding(coding); + retVal.getRestFirstRep().getSecurity().getService().add(codeConcept); + // retVal.getRestFirstRep().getSecurity().getService() //how do we handle "text" on the sample not part of getService - if(retVal != null){ - retVal.getImplementation().setDescription("cqf-ruler R4 server"); - } return retVal; } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 651370eb1..184b15deb 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -76,10 +76,17 @@ protected void initialize() throws ServletException { ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, - appCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); - setServerConformanceProvider(confProvider); + if(HapiProperties.getOAuthEnabled()) { + OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, + appCtx.getBean(DaoConfig.class)); + this.registerProvider(oauthProvider); + this.setServerConformanceProvider(oauthProvider); + }else { + JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, + appCtx.getBean(DaoConfig.class)); + confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); + setServerConformanceProvider(confProvider); + } JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( appCtx.getBean("terminologyService", ITermReadSvcR4.class), getFhirContext(), @@ -219,12 +226,6 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); this.registerProvider(planDefProvider); - if(HapiProperties.getOAuthEnabled()) { - OAuthProvider oauthProvider = new OAuthProvider(); - this.registerProvider(oauthProvider); - this.setServerConformanceProvider(oauthProvider); - } - CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); CdsHooksServlet.setLibraryResolutionProvider(libraryProvider); CdsHooksServlet.setSystemTerminologyProvider(localSystemTerminologyProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java new file mode 100644 index 000000000..6d46495a9 --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java @@ -0,0 +1,4 @@ +package org.opencds.cqf.r4.servlet; + +public class OAuthServlet { +} diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index f34c62bb1..d46801141 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -157,4 +157,14 @@ subscription.websocket.enabled=false ################################################## # OAuth Settings ################################################## -oauth.enabled=false +oauth.enabled=true +oauth.securityCors=true +oauth.securityUrl=http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris +oauth.securityExtAuthUrl=authorize +oauth.securityExtAuthValueUri=http://launch.smarthealthit.org/v/r4/auth/authorize +oauth.securityExtTokenUrl=token +oauth.securityExtTokenValueUri=http://launch.smarthealthit.org/v/r4/auth/token +oauth.serviceSystem=http://hl7.org/fhir/restful-security-service +oauth.serviceCode=SMART-on-FHIR +oauth.serviceDisplay=SMART-on-FHIR +oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) From 3bbf7d43551cacab895adaeb9f60ef5c5dd80654 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 12 Jun 2020 13:35:45 -0600 Subject: [PATCH 050/198] added oauth conformance statement to dstu3 --- .../cqf/dstu3/providers/OAuthProvider.java | 49 +++++++++++++++++++ .../cqf/dstu3/servlet/BaseServlet.java | 27 +++++----- dstu3/src/main/resources/hapi.properties | 15 ++++++ 3 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java new file mode 100644 index 000000000..02229014f --- /dev/null +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java @@ -0,0 +1,49 @@ +package org.opencds.cqf.dstu3.providers; + +import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.dstu3.model.*; +import org.opencds.cqf.common.config.HapiProperties; + +import javax.servlet.http.HttpServletRequest; + +public class OAuthProvider extends JpaConformanceProviderDstu3 { + public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { + super(theRestfulServer, theSystemDao, theDaoConfig); + } + + @Metadata + @Override + public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { + CapabilityStatement retVal; + retVal = super.getServerConformance(theRequest, theRequestDetails); + + retVal.getRestFirstRep().getSecurity().setCors(HapiProperties.getOauthSecurityCors()); + Extension securityExtension = retVal.getRestFirstRep().getSecurity().addExtension(); + securityExtension.setUrl(HapiProperties.getOauthSecurityUrl()); + // security.extension.extension + Extension securityExtExt = securityExtension.addExtension(); + securityExtExt.setUrl(HapiProperties.getOauthSecurityExtAuthUrl()); + securityExtExt.setValue(new UriType(HapiProperties.getOauthSecurityExtAuthValueUri())); + Extension securityTokenExt = securityExtension.addExtension(); + securityTokenExt.setUrl(HapiProperties.getOauthSecurityExtTokenUrl()); + securityTokenExt.setValue(new UriType(HapiProperties.getOauthSecurityExtTokenValueUri())); + + // security.extension.service + Coding coding = new Coding(); + coding.setSystem(HapiProperties.getOauthServiceSystem()); + coding.setCode(HapiProperties.getOauthServiceCode()); + coding.setDisplay(HapiProperties.getOauthServiceDisplay()); + CodeableConcept codeConcept = new CodeableConcept(); + codeConcept.addCoding(coding); + retVal.getRestFirstRep().getSecurity().getService().add(codeConcept); + // retVal.getRestFirstRep().getSecurity().getService() //how do we handle "text" on the sample not part of getService + + return retVal; + } + +} diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index e34b9595d..45b7fd9a2 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -19,16 +19,7 @@ import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.dstu3.evaluation.ProviderFactory; -import org.opencds.cqf.dstu3.providers.ActivityDefinitionApplyProvider; -import org.opencds.cqf.dstu3.providers.ApplyCqlOperationProvider; -import org.opencds.cqf.dstu3.providers.CacheValueSetsProvider; -import org.opencds.cqf.dstu3.providers.CodeSystemUpdateProvider; -import org.opencds.cqf.dstu3.providers.CqlExecutionProvider; -import org.opencds.cqf.dstu3.providers.HQMFProvider; -import org.opencds.cqf.dstu3.providers.JpaTerminologyProvider; -import org.opencds.cqf.dstu3.providers.LibraryOperationsProvider; -import org.opencds.cqf.dstu3.providers.MeasureOperationsProvider; -import org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider; +import org.opencds.cqf.dstu3.providers.*; import org.opencds.cqf.library.stu3.NarrativeProvider; import org.opencds.cqf.measure.stu3.CodeTerminologyRef; import org.opencds.cqf.measure.stu3.CqfMeasure; @@ -94,10 +85,18 @@ protected void initialize() throws ServletException { ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - appCtx.getBean(DaoConfig.class)); - confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); - setServerConformanceProvider(confProvider); + if(HapiProperties.getOAuthEnabled()) { + OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, + appCtx.getBean(DaoConfig.class)); + this.registerProvider(oauthProvider); + this.setServerConformanceProvider(oauthProvider); + }else { + + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, + appCtx.getBean(DaoConfig.class)); + confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); + setServerConformanceProvider(confProvider); + } JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( appCtx.getBean("terminologyService", ITermReadSvcDstu3.class), getFhirContext(), diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index d7e10f5ce..137d5b034 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -150,3 +150,18 @@ email.password= # Enable Websocket Subscription Channel subscription.websocket.enabled=false +################################################## +# OAuth Settings +################################################## +oauth.enabled=true +oauth.securityCors=true +oauth.securityUrl=http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris +oauth.securityExtAuthUrl=authorize +oauth.securityExtAuthValueUri=http://launch.smarthealthit.org/v/r4/auth/authorize +oauth.securityExtTokenUrl=token +oauth.securityExtTokenValueUri=http://launch.smarthealthit.org/v/r4/auth/token +oauth.serviceSystem=http://hl7.org/fhir/restful-security-service +oauth.serviceCode=SMART-on-FHIR +oauth.serviceDisplay=SMART-on-FHIR +oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) + From e24287af6c430cbd90a3e5572213151800a712ea Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 12 Jun 2020 13:37:14 -0600 Subject: [PATCH 051/198] adding code copied from cdshooks servlet; Needs to be gone through carefully --- .../cqf/r4/providers/OAuthProvider.java | 2 +- .../opencds/cqf/r4/servlet/OAuthServlet.java | 51 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index 8a888481d..d270ff154 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -25,7 +25,7 @@ public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao Date: Tue, 16 Jun 2020 17:08:28 -0600 Subject: [PATCH 052/198] Revert to Java 8 --- .../cqf/common/retrieve/JpaFhirRetrieveProvider.java | 12 +++++++----- .../dstu3/providers/CodeSystemUpdateProvider.java | 12 ++++++------ pom.xml | 8 ++++---- .../cqf/r4/providers/CodeSystemUpdateProvider.java | 12 ++++++------ 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index 24c383c17..6d6ce96af 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -16,6 +17,7 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.IBundleProvider; public class JpaFhirRetrieveProvider extends SearchParamFhirRetrieveProvider { @@ -46,16 +48,16 @@ protected Iterable executeQueries(String dataType, List executeQuery(String dataType, SearchParameterMap map) { // TODO: Once HAPI breaks this out from the server dependencies // we can include it on its own. - var hapiMap = new ca.uhn.fhir.jpa.searchparam.SearchParameterMap(); + ca.uhn.fhir.jpa.searchparam.SearchParameterMap hapiMap = new ca.uhn.fhir.jpa.searchparam.SearchParameterMap(); try { - var methods = hapiMap.getClass().getDeclaredMethods(); - var methodList = List.of(methods); + Method[] methods = hapiMap.getClass().getDeclaredMethods(); + List methodList = List.of(methods); List puts = methodList.stream().filter(x -> x.getName().equals("put")).collect(Collectors.toList()); - var method = puts.get(0); + Method method = puts.get(0); method.setAccessible(true); - for (var entry : map.entrySet()) { + for (Map.Entry>> entry : map.entrySet()) { method.invoke(hapiMap, entry.getKey(), entry.getValue()); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index 9d0acdab1..f2a007aab 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -88,7 +88,7 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { // Possible for this to run out of memory with really large ValueSets and CodeSystems. Map> codesBySystem = new HashMap<>(); - for (var vs : valueSets){ + for (ValueSet vs : valueSets){ if (vs.hasCompose() && vs.getCompose().hasInclude()) { for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { @@ -96,12 +96,12 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { continue; } - var system = csc.getSystem(); + String system = csc.getSystem(); if (!codesBySystem.containsKey(system)){ codesBySystem.put(system, new HashSet<>()); } - var codes = codesBySystem.get(system); + Set codes = codesBySystem.get(system); codes.addAll(csc.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) .collect(Collectors.toList())); @@ -110,9 +110,9 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { } - for(var entry : codesBySystem.entrySet()) { - var system = entry.getKey(); - var codeSystem = getCodeSystemByUrl(system); + for(Map.Entry> entry : codesBySystem.entrySet()) { + String system = entry.getKey(); + CodeSystem codeSystem = getCodeSystemByUrl(system); updateCodeSystem(codeSystem.setUrl(system), getUnionDistinctCodes(entry.getValue(), codeSystem)); codeSystems.add(codeSystem.getUrl()); diff --git a/pom.xml b/pom.xml index 75d5eb887..ad835eff2 100644 --- a/pom.xml +++ b/pom.xml @@ -343,8 +343,8 @@ maven-compiler-plugin 3.8.0 - 11 - 11 + 1.8 + 1.8 @@ -355,9 +355,9 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.1.1 + 3.2.0 - 11 + 8 diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index dd9117c55..7f88553f2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -88,7 +88,7 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { // Possible for this to run out of memory with really large ValueSets and CodeSystems. Map> codesBySystem = new HashMap<>(); - for (var vs : valueSets){ + for (ValueSet vs : valueSets){ if (vs.hasCompose() && vs.getCompose().hasInclude()) { for (ValueSet.ConceptSetComponent csc : vs.getCompose().getInclude()) { @@ -96,12 +96,12 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { continue; } - var system = csc.getSystem(); + String system = csc.getSystem(); if (!codesBySystem.containsKey(system)){ codesBySystem.put(system, new HashSet<>()); } - var codes = codesBySystem.get(system); + Set codes = codesBySystem.get(system); codes.addAll(csc.getConcept().stream().map(ValueSet.ConceptReferenceComponent::getCode) .collect(Collectors.toList())); @@ -110,9 +110,9 @@ public OperationOutcome performCodeSystemUpdate(List valueSets) { } - for(var entry : codesBySystem.entrySet()) { - var system = entry.getKey(); - var codeSystem = getCodeSystemByUrl(system); + for(Map.Entry> entry : codesBySystem.entrySet()) { + String system = entry.getKey(); + CodeSystem codeSystem = getCodeSystemByUrl(system); updateCodeSystem(codeSystem.setUrl(system), getUnionDistinctCodes(entry.getValue(), codeSystem)); codeSystems.add(codeSystem.getUrl()); From 84328e462ff36cb9370809e0649aac841b47da6d Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 19 Jun 2020 10:12:41 -0600 Subject: [PATCH 053/198] stopping place to update oauth branch --- .../r4/providers/QuestionnaireProvider.java | 119 ++++++++++++++++++ .../opencds/cqf/r4/servlet/BaseServlet.java | 16 +-- 2 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java new file mode 100644 index 000000000..b32fc0116 --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -0,0 +1,119 @@ +package org.opencds.cqf.r4.providers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.util.BundleUtil; +import com.github.dnault.xmlpatch.repackaged.org.jaxen.util.SingletonList; +import org.hl7.fhir.DateTime; +import org.hl7.fhir.r4.model.*; +import org.opencds.cqf.common.config.HapiProperties; +import org.opencds.cqf.utilities.BundleUtils; + +import java.util.Date; +import java.util.List; + +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; + +public class QuestionnaireProvider { + + private FhirContext fhirContext; + public QuestionnaireProvider(FhirContext fhirContext){ + this.fhirContext = fhirContext; + } + + @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) + public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; + } + + private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ + Bundle newBundle = new Bundle(); + + Identifier bundleId = new Identifier(); + bundleId.setValue(questionnaireResponse.getId()); + newBundle.setType(Bundle.BundleType.TRANSACTION); + newBundle.setIdentifier(bundleId); + + questionnaireResponse.getItem().stream().forEach(item ->{ + newBundle.addEntry(extractItem(item, questionnaireResponse)); + }); + return newBundle; + } + + //TODO - ids need work; add encounter; subject/patient; effectiveDateTime; value + // TODO - using value for questionnaire question - NOT THE RIGHT PLACE!! + private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, QuestionnaireResponse questionnaireResponse){ + // obs.setCode(); +// obs.setValue() = + + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(item.getLinkId()); +// obs.setDerivedFrom(new SingletonList(questionnaireResponse.get); //create a reference to the QuestionnaireResponse ???? + obs.setValue(new StringType(item.getText() + "::" + item.getAnswer().get(0).getValueStringType().getValue())); + obs.setEffective(new DateTimeType( questionnaireResponse.getMeta().getLastUpdated())); + + Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); + berc.setMethod(Bundle.HTTPVerb.PUT); + berc.setUrl("Observation/" + item.getId() + "." + item.getLinkId()); + + Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); + bec.setResource(obs); + bec.setRequest(berc); + return bec; + } + + private Bundle sendObservationBundle(Bundle observationsBundle){ + String url = HapiProperties.getObservationEndpoint(); + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; + } + + /** + * How do we store the QuestionnaireResponse Id?? + * + * var obxs = []; + * for(var i=0, iLen=values.length; i IFhirResourceDao getDao(Class clazz) { From 1ed20d05fbe261cb9e2aac5e7eab93c2e153a220 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 19 Jun 2020 10:17:03 -0600 Subject: [PATCH 054/198] suggested changes made --- .../opencds/cqf/r4/servlet/OAuthServlet.java | 53 ------------------- r4/src/main/resources/hapi.properties | 1 - 2 files changed, 54 deletions(-) delete mode 100644 r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java deleted file mode 100644 index c91d5c70f..000000000 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/OAuthServlet.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.opencds.cqf.r4.servlet; - -import ca.uhn.fhir.context.FhirContext; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import org.apache.http.entity.ContentType; -import org.opencds.cqf.cds.discovery.DiscoveryResolutionR4; -import org.opencds.cqf.common.config.HapiProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Arrays; - -public class OAuthServlet extends HttpServlet { - private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); - - @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - logger.info(request.getRequestURI()); - if (request.getRequestURL().toString().endsWith(".well-known/smart-configuration")) { -// set up json response - - this.setAccessControlHeaders(response); - response.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); - response.getWriter().println(new GsonBuilder().setPrettyPrinting().create().toJson(getServices())); - } - } - - private JsonObject getServices() { - return new DiscoveryResolutionR4(FhirContext.forR4().newRestfulGenericClient(HapiProperties.getServerAddress())) - .resolve().getAsJson(); - } - - private void setAccessControlHeaders(HttpServletResponse resp) { - if (HapiProperties.getCorsEnabled()) { - resp.setHeader("Access-Control-Allow-Origin", HapiProperties.getCorsAllowedOrigin()); - resp.setHeader("Access-Control-Allow-Methods", - String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); - resp.setHeader("Access-Control-Allow-Headers", String.join(", ", Arrays.asList("x-fhir-starter", "Origin", - "Accept", "X-Requested-With", "Content-Type", "Authorization", "Cache-Control"))); - resp.setHeader("Access-Control-Expose-Headers", - String.join(", ", Arrays.asList("Location", "Content-Location"))); - resp.setHeader("Access-Control-Max-Age", "86400"); - } - } - -} diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index db98069cd..3f8b4d389 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -27,7 +27,6 @@ allow_override_default_search_params=true allow_contains_searches=true allow_multiple_delete=true allow_external_references=true -allow_cascading_deletes=true allow_placeholder_references=true expunge_enabled=true persistence_unit_name=HAPI_PU From 4454d91165fccd808b6459fa5159f88438a784c6 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Mon, 22 Jun 2020 10:16:10 -0600 Subject: [PATCH 055/198] WIP outstanding changes. need to double check. --- .../providers/MeasureOperationsProvider.java | 131 +++++++++++------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index d066b72ad..11d0cfee3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -29,6 +29,7 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueEvidenceComponent; import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueStatus; +import org.hl7.fhir.ParametersParameter; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; @@ -75,11 +76,12 @@ public class MeasureOperationsProvider { private DaoRegistry registry; private EvaluationProviderFactory factory; - private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class); - public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, LibraryResolutionProvider libraryResolutionProvider, - MeasureResourceProvider measureResourceProvider) { + public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, + NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, + LibraryResolutionProvider libraryResolutionProvider, + MeasureResourceProvider measureResourceProvider) { this.registry = registry; this.factory = factory; @@ -104,9 +106,11 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - theResource.getRelatedArtifact().removeIf(relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); + theResource.getRelatedArtifact().removeIf( + relatedArtifact -> relatedArtifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); // Ensure All Related Artifacts for all referenced Libraries if (!cqfMeasure.getRelatedArtifact().isEmpty()) { @@ -126,12 +130,12 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ } } - try { - Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); - theResource.setText(n.copy()); - } catch (Exception e) { - //Ignore the exception so the resource still gets updated - } + try { + Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); + theResource.setText(n.copy()); + } catch (Exception e) { + // Ignore the exception so the resource still gets updated + } return this.measureResourceProvider.update(theRequest, theResource, theId, theRequestDetails.getConditionalUrl(RestOperationTypeEnum.UPDATE), theRequestDetails); @@ -140,7 +144,8 @@ public MethodOutcome refreshGeneratedContent(HttpServletRequest theRequest, Requ @Operation(name = "$get-narrative", idempotent = true, type = Measure.class) public Parameters getNarrative(@IdParam IdType theId) { Measure theResource = this.measureResourceProvider.getDao().read(theId); - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); Narrative n = this.narrativeProvider.getNarrative(this.measureResourceProvider.getContext(), cqfMeasure); Parameters p = new Parameters(); p.addParameter().setValue(new StringType(n.getDivAsString())); @@ -148,7 +153,8 @@ public Parameters getNarrative(@IdParam IdType theId) { } private String generateHQMF(Measure theResource) { - CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, this.libraryResolutionProvider); + CqfMeasure cqfMeasure = this.dataRequirementsProvider.createCqfMeasure(theResource, + this.libraryResolutionProvider); return this.hqmfProvider.generateHQMF(cqfMeasure); } @@ -168,7 +174,8 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name @OptionalParam(name = "source") String source, @OptionalParam(name = "user") String user, @OptionalParam(name = "pass") String pass) throws InternalErrorException, FHIRException { LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResolutionProvider); + MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, + this.libraryResolutionProvider); Measure measure = this.measureResourceProvider.getDao().read(theId); if (measure == null) { @@ -178,24 +185,24 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name seed.setup(measure, periodStart, periodEnd, productLine, source, user, pass); // resolve report type - MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, seed.getMeasurementPeriod()); + MeasureEvaluation evaluator = new MeasureEvaluation(seed.getDataProvider(), this.registry, + seed.getMeasurementPeriod()); if (reportType != null) { switch (reportType) { - case "patient": - return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - case "patient-list": - return evaluator.evaluateSubjectListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); - case "population": - return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); - default: - throw new IllegalArgumentException("Invalid report type: " + reportType); + case "patient": + return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); + case "patient-list": + return evaluator.evaluateSubjectListMeasure(seed.getMeasure(), seed.getContext(), practitionerRef); + case "population": + return evaluator.evaluatePopulationMeasure(seed.getMeasure(), seed.getContext()); + default: + throw new IllegalArgumentException("Invalid report type: " + reportType); } } // default report type is patient MeasureReport report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); - if (productLine != null) - { + if (productLine != null) { Extension ext = new Extension(); ext.setUrl("http://hl7.org/fhir/us/cqframework/cqfmeasures/StructureDefinition/cqfm-productLine"); ext.setValue(new StringType(productLine)); @@ -207,35 +214,59 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @RequiredParam(name // @Operation(name = "$evaluate-measure-with-source", idempotent = true) // public MeasureReport evaluateMeasure(@IdParam IdType theId, - // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) Bundle sourceData, - // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, - // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { - // if (periodStart == null || periodEnd == null) { - // throw new IllegalArgumentException("periodStart and periodEnd are required for measure evaluation"); - // } - // LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResourceProvider); - // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, this.libraryResourceProvider); - // Measure measure = this.getDao().read(theId); - - // if (measure == null) { - // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); - // } - - // seed.setup(measure, periodStart, periodEnd, null, null, null, null); - // BundleDataProviderStu3 bundleProvider = new BundleDataProviderStu3(sourceData); - // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); - // seed.getContext().registerDataProvider("http://hl7.org/fhir", bundleProvider); - // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, seed.getMeasurementPeriod()); - // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), ""); + // @OperationParam(name = "sourceData", min = 1, max = 1, type = Bundle.class) + // Bundle sourceData, + // @OperationParam(name = "periodStart", min = 1, max = 1) String periodStart, + // @OperationParam(name = "periodEnd", min = 1, max = 1) String periodEnd) { + // if (periodStart == null || periodEnd == null) { + // throw new IllegalArgumentException("periodStart and periodEnd are required + // for measure evaluation"); + // } + // LibraryLoader libraryLoader = + // LibraryHelper.createLibraryLoader(this.libraryResourceProvider); + // MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, + // libraryLoader, this.libraryResourceProvider); + // Measure measure = this.getDao().read(theId); + + // if (measure == null) { + // throw new RuntimeException("Could not find Measure/" + theId.getIdPart()); + // } + + // seed.setup(measure, periodStart, periodEnd, null, null, null, null); + // BundleDataProviderStu3 bundleProvider = new + // BundleDataProviderStu3(sourceData); + // bundleProvider.setTerminologyProvider(provider.getTerminologyProvider()); + // seed.getContext().registerDataProvider("http://hl7.org/fhir", + // bundleProvider); + // MeasureEvaluation evaluator = new MeasureEvaluation(bundleProvider, + // seed.getMeasurementPeriod()); + // return evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), + // ""); // } @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) - public Bundle careGapsReport(@RequiredParam(name = "periodStart") String periodStart, - @RequiredParam(name = "periodEnd") String periodEnd, - @RequiredParam(name = "subject") String subject, @OptionalParam(name = "topic") String topic) { - - //TODO: topic should allow many + public Parameters careGapsReport(@RequiredParam(name = "periodStart") String periodStart, + @RequiredParam(name = "periodEnd") String periodEnd, @OptionalParam(name = "subject") String subject, + @OptionalParam(name = "subjectGroup") String subjectGroup, @OptionalParam(name = "topic") String topic, + @OptionalParam(name = "practitioner") String practitionerRef) { + + // TODO: topic should allow many + + if (practitionerRef == null || practitionerRef.equals("")) { + return new Parameters().addParameter( + new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + subject) + .setResource(patientCareGap(periodStart, periodEnd, subject, topic))); + } + + + Parameters parameters = new Parameters(); + + return parameters; + } + + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic) { if (subject == null || subject.equals("")) { throw new IllegalArgumentException("Subject is required."); } From a3ac9fb9e13e20688f7df00fca496cc84476cf88 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 22 Jun 2020 18:00:39 -0600 Subject: [PATCH 056/198] Delombokify, add support for library lookup by Url --- .../InMemoryLibraryResourceProvider.java | 6 ++++ .../providers/LibraryResolutionProvider.java | 2 ++ .../evaluation/MeasureEvaluationSeed.java | 19 ++++++++++-- .../cqf/dstu3/helpers/LibraryHelper.java | 16 +++++++--- .../providers/LibraryOperationsProvider.java | 29 +++++++++++++++++++ pom.xml | 4 +-- .../r4/evaluation/MeasureEvaluationSeed.java | 19 ++++++++++-- .../opencds/cqf/r4/helpers/LibraryHelper.java | 16 +++++++--- .../providers/LibraryOperationsProvider.java | 29 +++++++++++++++++++ 9 files changed, 124 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java index bca9d169f..e788d38d0 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/InMemoryLibraryResourceProvider.java @@ -57,4 +57,10 @@ public void update(LibraryType library) { this.libraries.put(this.getId.apply(library), library); } + @Override + public LibraryType resolveLibraryByCanonicalUrl(String libraryUrl) { + // TODO Auto-generated method stub + return null; + } + } \ No newline at end of file diff --git a/common/src/main/java/org/opencds/cqf/common/providers/LibraryResolutionProvider.java b/common/src/main/java/org/opencds/cqf/common/providers/LibraryResolutionProvider.java index 3866feb0e..e278047f2 100644 --- a/common/src/main/java/org/opencds/cqf/common/providers/LibraryResolutionProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/providers/LibraryResolutionProvider.java @@ -8,6 +8,8 @@ public interface LibraryResolutionProvider { public LibraryType resolveLibraryByName(String libraryName, String libraryVersion); + public LibraryType resolveLibraryByCanonicalUrl(String libraryUrl); + // Hmmm... Probably need to think through this use case a bit more. // Should we throw an exception? Should this be a different interface? diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java index 94e6ee995..48bb239da 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluationSeed.java @@ -18,9 +18,6 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.dstu3.helpers.LibraryHelper; -import lombok.Data; - -@Data public class MeasureEvaluationSeed { private Measure measure; private Context context; @@ -37,6 +34,22 @@ public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryL this.libraryResourceProvider = libraryResourceProvider; } + public Measure getMeasure() { + return this.measure; + } + + public Context getContext() { + return this.context; + } + + public Interval getMeasurementPeriod() { + return this.measurementPeriod; + } + + public DataProvider getDataProvider() { + return this.dataProvider; + } + public void setup(Measure measure, String periodStart, String periodEnd, String productLine, String source, String user, String pass) { this.measure = measure; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 203087535..7d05061d0 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; @@ -67,10 +68,17 @@ public static List loadLibraries(Meas org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) { - org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + if (artifact.getResource().getReferenceElement().getResourceType().equals("Library")) { + org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); + + if (library == null) { + throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource().getReference())); + } + + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); + } } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index b11e269e2..4bef02e1f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -28,6 +29,8 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriParam; public class LibraryOperationsProvider implements org.opencds.cqf.common.providers.LibraryResolutionProvider { @@ -141,6 +144,32 @@ public Library resolveLibraryById(String libraryId) { } } + @Override + public Library resolveLibraryByCanonicalUrl(String url) { + Objects.requireNonNull(url, "url must not be null"); + + String[] parts = url.split("|"); + String resourceUrl = parts[0]; + String version = null; + if (parts.length > 1) { + version = parts[1]; + } + + SearchParameterMap map = new SearchParameterMap(); + map.add("url", new UriParam(resourceUrl)); + if (version != null) { + map.add("version", new TokenParam(version)); + } + + ca.uhn.fhir.rest.api.server.IBundleProvider bundleProvider = this.libraryResourceProvider.getDao().search(map); + + if (bundleProvider.size() == 0) { + return null; + } + List resourceList = bundleProvider.getResources(0, bundleProvider.size()); + return LibraryResolutionProvider.selectFromList(resolveLibraries(resourceList), version, x -> x.getVersion()); + } + @Override public Library resolveLibraryByName(String libraryName, String libraryVersion) { Iterable libraries = getLibrariesByName(libraryName); diff --git a/pom.xml b/pom.xml index ad835eff2..2f9f19f12 100644 --- a/pom.xml +++ b/pom.xml @@ -259,12 +259,12 @@ - + org.eclipse.jetty diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java index 70d21c228..d79fe3ae0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java @@ -18,9 +18,6 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.r4.helpers.LibraryHelper; -import lombok.Data; - -@Data public class MeasureEvaluationSeed { private Measure measure; private Context context; @@ -37,6 +34,22 @@ public MeasureEvaluationSeed(EvaluationProviderFactory providerFactory, LibraryL this.libraryResourceProvider = libraryResourceProvider; } + public Measure getMeasure() { + return this.measure; + } + + public Context getContext() { + return this.context; + } + + public Interval getMeasurementPeriod() { + return this.measurementPeriod; + } + + public DataProvider getDataProvider() { + return this.dataProvider; + } + public void setup(Measure measure, String periodStart, String periodEnd, String productLine, String source, String user, String pass) { this.measure = measure; diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 0bd5ea646..762912c1c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.List; +import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; @@ -64,10 +65,17 @@ public static List loadLibraries(Meas org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.hasResource()) { - org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource()); - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + if (artifact.getResource().contains(("/Library/"))) { + org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryByCanonicalUrl(artifact.getResource()); + + if (library == null) { + throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource())); + } + + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); + } } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index edec33eff..b4a50a489 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.servlet.http.HttpServletRequest; @@ -27,6 +28,8 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.UriParam; public class LibraryOperationsProvider implements LibraryResolutionProvider { @@ -157,6 +160,32 @@ public Library resolveLibraryByName(String libraryName, String libraryVersion) { return library; } + @Override + public Library resolveLibraryByCanonicalUrl(String url) { + Objects.requireNonNull(url, "url must not be null"); + + String[] parts = url.split("|"); + String resourceUrl = parts[0]; + String version = null; + if (parts.length > 1) { + version = parts[1]; + } + + SearchParameterMap map = new SearchParameterMap(); + map.add("url", new UriParam(resourceUrl)); + if (version != null) { + map.add("version", new TokenParam(version)); + } + + ca.uhn.fhir.rest.api.server.IBundleProvider bundleProvider = this.libraryResourceProvider.getDao().search(map); + + if (bundleProvider.size() == 0) { + return null; + } + List resourceList = bundleProvider.getResources(0, bundleProvider.size()); + return LibraryResolutionProvider.selectFromList(resolveLibraries(resourceList), version, x -> x.getVersion()); + } + private Iterable getLibrariesByName(String name) { // Search for libraries by name SearchParameterMap map = new SearchParameterMap(); From 75a9c7917e232969ab2b9bfd53f1210bc3472186 Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 23 Jun 2020 09:08:02 -0600 Subject: [PATCH 057/198] convert questionnaireResponse to Observation --- .../r4/providers/QuestionnaireProvider.java | 86 ++++++------------- r4/src/main/resources/hapi.properties | 4 + 2 files changed, 30 insertions(+), 60 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index b32fc0116..c79367923 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -1,19 +1,13 @@ package org.opencds.cqf.r4.providers; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.util.BundleUtil; -import com.github.dnault.xmlpatch.repackaged.org.jaxen.util.SingletonList; -import org.hl7.fhir.DateTime; import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.utilities.BundleUtils; import java.util.Date; -import java.util.List; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -26,41 +20,49 @@ public QuestionnaireProvider(FhirContext fhirContext){ @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; + if(questionnaireResponse != null) { + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; + } + return null; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ Bundle newBundle = new Bundle(); + Date authored = questionnaireResponse.getAuthored(); Identifier bundleId = new Identifier(); - bundleId.setValue(questionnaireResponse.getId()); + bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); questionnaireResponse.getItem().stream().forEach(item ->{ - newBundle.addEntry(extractItem(item, questionnaireResponse)); + newBundle.addEntry(extractItem(item, authored, questionnaireResponse)); }); return newBundle; } - //TODO - ids need work; add encounter; subject/patient; effectiveDateTime; value - // TODO - using value for questionnaire question - NOT THE RIGHT PLACE!! - private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, QuestionnaireResponse questionnaireResponse){ - // obs.setCode(); -// obs.setValue() = - + private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, QuestionnaireResponse questionnaireResponse){ Observation obs = new Observation(); + obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); - obs.setId(item.getLinkId()); -// obs.setDerivedFrom(new SingletonList(questionnaireResponse.get); //create a reference to the QuestionnaireResponse ???? - obs.setValue(new StringType(item.getText() + "::" + item.getAnswer().get(0).getValueStringType().getValue())); - obs.setEffective(new DateTimeType( questionnaireResponse.getMeta().getLastUpdated())); - + Coding qrCoding = new Coding(); + qrCoding.setCode("74465-6"); + qrCoding.setDisplay(" Questionnaire response Document"); + obs.setCode(new CodeableConcept().addCoding(qrCoding)); + obs.setId(questionnaireResponse.getIdElement().getIdPart() + "." + item.getLinkId()); + switch(item.getAnswer().get(0).getValue().fhirType()){ + case "string": + obs.setValue(new StringType(item.getAnswer().get(0).getValueStringType().getValue())); + break; + case "Coding": + obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); + break; + } Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); - berc.setUrl("Observation/" + item.getId() + "." + item.getLinkId()); + berc.setUrl("Observation/" + obs.getId()); Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); bec.setResource(obs); @@ -73,47 +75,11 @@ private Bundle sendObservationBundle(Bundle observationsBundle){ String user = HapiProperties.getObservationUserName(); String password = HapiProperties.getObservationPassword(); +System.out.println(fhirContext.newJsonParser().encodeResourceToString(observationsBundle)); IGenericClient client = getClient(fhirContext, url, user, password); Bundle outcomeBundle = client.transaction() .withBundle(observationsBundle) .execute(); return outcomeBundle; } - - /** - * How do we store the QuestionnaireResponse Id?? - * - * var obxs = []; - * for(var i=0, iLen=values.length; i Date: Tue, 23 Jun 2020 11:43:37 -0600 Subject: [PATCH 058/198] added subject to Observation --- .../org/opencds/cqf/r4/providers/QuestionnaireProvider.java | 2 +- r4/src/main/resources/hapi.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index c79367923..95128cbf5 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -47,6 +47,7 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setSubject(questionnaireResponse.getSubject()); Coding qrCoding = new Coding(); qrCoding.setCode("74465-6"); qrCoding.setDisplay(" Questionnaire response Document"); @@ -75,7 +76,6 @@ private Bundle sendObservationBundle(Bundle observationsBundle){ String user = HapiProperties.getObservationUserName(); String password = HapiProperties.getObservationPassword(); -System.out.println(fhirContext.newJsonParser().encodeResourceToString(observationsBundle)); IGenericClient client = getClient(fhirContext, url, user, password); Bundle outcomeBundle = client.transaction() .withBundle(observationsBundle) diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 30c8ad26d..7103a8a2c 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -165,6 +165,6 @@ oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) -observation.endpoint=http://localhost:8080/cqf-ruler-r4/fhir +observation.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir observation.username= observation.password= \ No newline at end of file From a439a2a72bb7446a11dd911556babcbd54a3028c Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 23 Jun 2020 13:30:33 -0600 Subject: [PATCH 059/198] Clean up warnings, fix url lookup --- .../retrieve/JpaFhirRetrieveProvider.java | 3 ++- .../cqf/dstu3/helpers/LibraryHelper.java | 1 - .../providers/CodeSystemUpdateProvider.java | 2 +- .../providers/LibraryOperationsProvider.java | 2 +- .../opencds/cqf/r4/helpers/LibraryHelper.java | 24 ++++++++++++------- .../providers/CodeSystemUpdateProvider.java | 2 +- .../providers/LibraryOperationsProvider.java | 2 +- .../cqf/r4/providers/OAuthProvider.java | 18 ++++++++------ 8 files changed, 32 insertions(+), 22 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index 6d6ce96af..0bcefe741 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -2,6 +2,7 @@ import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -52,7 +53,7 @@ protected Collection executeQuery(String dataType, SearchParameterMap ma try { Method[] methods = hapiMap.getClass().getDeclaredMethods(); - List methodList = List.of(methods); + List methodList = Arrays.asList(methods); List puts = methodList.stream().filter(x -> x.getName().equals("put")).collect(Collectors.toList()); Method method = puts.get(0); method.setAccessible(true); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 7d05061d0..5e187525a 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.poi.openxml4j.exceptions.InvalidOperationException; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index f2a007aab..1e6c2af75 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -160,7 +160,7 @@ private Set getUnionDistinctCodes(Set valueSetCodes, CodeSystem } valueSetCodes.addAll(codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) - .collect(Collectors.toUnmodifiableSet())); + .collect(Collectors.toSet())); return valueSetCodes; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index 4bef02e1f..342c5717b 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -148,7 +148,7 @@ public Library resolveLibraryById(String libraryId) { public Library resolveLibraryByCanonicalUrl(String url) { Objects.requireNonNull(url, "url must not be null"); - String[] parts = url.split("|"); + String[] parts = url.split("\\|"); String resourceUrl = parts[0]; String version = null; if (parts.length > 1) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 762912c1c..6a6fe6520 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -65,17 +65,23 @@ public static List loadLibraries(Meas org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.hasResource()) { - if (artifact.getResource().contains(("/Library/"))) { - org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryByCanonicalUrl(artifact.getResource()); + org.hl7.fhir.r4.model.Library library = null; + // Raw references to Library/libraryId or libraryId + if (artifact.getResource().startsWith("Library/") || ! artifact.getResource().contains("/")) { + library = libraryResourceProvider.resolveLibraryById(artifact.getResource().replace("Library/", "")); + } + // Full url (e.g. http://hl7.org/fhir/us/Library/FHIRHelpers) + else if (artifact.getResource().contains(("/Library/"))) { + library = libraryResourceProvider.resolveLibraryByCanonicalUrl(artifact.getResource()); + } - if (library == null) { - throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource())); - } - - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + if (library == null) { + throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource())); } + + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index 7f88553f2..cb60b446a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -160,7 +160,7 @@ private Set getUnionDistinctCodes(Set valueSetCodes, CodeSystem } valueSetCodes.addAll(codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) - .collect(Collectors.toUnmodifiableSet())); + .collect(Collectors.toSet())); return valueSetCodes; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index b4a50a489..8cba9ab53 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -164,7 +164,7 @@ public Library resolveLibraryByName(String libraryName, String libraryVersion) { public Library resolveLibraryByCanonicalUrl(String url) { Objects.requireNonNull(url, "url must not be null"); - String[] parts = url.split("|"); + String[] parts = url.split("\\|"); String resourceUrl = parts[0]; String version = null; if (parts.length > 1) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index d270ff154..e114560c0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -1,18 +1,22 @@ package org.opencds.cqf.r4.providers; +import javax.servlet.http.HttpServletRequest; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.UriType; +import org.opencds.cqf.common.config.HapiProperties; + import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; -import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; -import org.hl7.fhir.dstu3.model.StringType; -import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider; -import org.hl7.fhir.r4.model.*; -import org.opencds.cqf.common.config.HapiProperties; - -import javax.servlet.http.HttpServletRequest; public class OAuthProvider extends JpaConformanceProviderR4 { /** From 01ce6a4c3976e7a746ef3bbede90fd247ef726aa Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 10:33:26 -0600 Subject: [PATCH 060/198] fixed extra white space and added error bundle --- .../r4/providers/QuestionnaireProvider.java | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 95128cbf5..3663d123c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -25,7 +25,16 @@ public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); return returnBundle; } - return null; + return createErrorBundle("The QuestionnaireResponse was null."); + } + + private Bundle createErrorBundle(String errorMessage){ + Bundle errorBundle = new Bundle(); + Identifier bundleId = new Identifier(); + bundleId.setValue("Error in QuestionnaireResponse/$extract " + errorMessage); + errorBundle.setType(Bundle.BundleType.MESSAGE); + errorBundle.setIdentifier(bundleId); + return errorBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ @@ -50,7 +59,7 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna obs.setSubject(questionnaireResponse.getSubject()); Coding qrCoding = new Coding(); qrCoding.setCode("74465-6"); - qrCoding.setDisplay(" Questionnaire response Document"); + qrCoding.setDisplay("Questionnaire response Document"); obs.setCode(new CodeableConcept().addCoding(qrCoding)); obs.setId(questionnaireResponse.getIdElement().getIdPart() + "." + item.getLinkId()); switch(item.getAnswer().get(0).getValue().fhirType()){ @@ -73,13 +82,15 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna private Bundle sendObservationBundle(Bundle observationsBundle){ String url = HapiProperties.getObservationEndpoint(); - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); + if(null != url && url.length() > 0) { + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); - IGenericClient client = getClient(fhirContext, url, user, password); - Bundle outcomeBundle = client.transaction() - .withBundle(observationsBundle) - .execute(); - return outcomeBundle; - } + IGenericClient client = getClient(fhirContext, url, user, password); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; + } + return createErrorBundle("The observation.endpoint in hapi.properties was not set."); } } From d056678e070383d4af659268fe36451e65332745 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 10:36:48 -0600 Subject: [PATCH 061/198] added ObservationProvider/$extract functionality to dstu3 --- .../providers/QuestionnaireProvider.java | 96 +++++++++++++++++++ .../cqf/dstu3/servlet/BaseServlet.java | 16 +--- dstu3/src/main/resources/hapi.properties | 4 + 3 files changed, 105 insertions(+), 11 deletions(-) create mode 100644 dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java new file mode 100644 index 000000000..87e67bf28 --- /dev/null +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -0,0 +1,96 @@ +package org.opencds.cqf.dstu3.providers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import org.hl7.fhir.r4.model.*; +import org.opencds.cqf.common.config.HapiProperties; + +import java.util.Date; + +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; + +public class QuestionnaireProvider { + + private FhirContext fhirContext; + public QuestionnaireProvider(FhirContext fhirContext){ + this.fhirContext = fhirContext; + } + + @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) + public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { + if(questionnaireResponse != null) { + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; + } + return createErrorBundle("The QuestionnaireResponse was null."); + } + + private Bundle createErrorBundle(String errorMessage){ + Bundle errorBundle = new Bundle(); + Identifier bundleId = new Identifier(); + bundleId.setValue("Error in QuestionnaireResponse/$extract " + errorMessage); + errorBundle.setType(Bundle.BundleType.MESSAGE); + errorBundle.setIdentifier(bundleId); + return errorBundle; + } + + private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ + Bundle newBundle = new Bundle(); + Date authored = questionnaireResponse.getAuthored(); + + Identifier bundleId = new Identifier(); + bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); + newBundle.setType(Bundle.BundleType.TRANSACTION); + newBundle.setIdentifier(bundleId); + + questionnaireResponse.getItem().stream().forEach(item ->{ + newBundle.addEntry(extractItem(item, authored, questionnaireResponse)); + }); + return newBundle; + } + + private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, QuestionnaireResponse questionnaireResponse){ + Observation obs = new Observation(); + obs.setEffective(new DateTimeType(authored)); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setSubject(questionnaireResponse.getSubject()); + Coding qrCoding = new Coding(); + qrCoding.setCode("74465-6"); + qrCoding.setDisplay("Questionnaire response Document"); + obs.setCode(new CodeableConcept().addCoding(qrCoding)); + obs.setId(questionnaireResponse.getIdElement().getIdPart() + "." + item.getLinkId()); + switch(item.getAnswer().get(0).getValue().fhirType()){ + case "string": + obs.setValue(new StringType(item.getAnswer().get(0).getValueStringType().getValue())); + break; + case "Coding": + obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); + break; + } + Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); + berc.setMethod(Bundle.HTTPVerb.PUT); + berc.setUrl("Observation/" + obs.getId()); + + Bundle.BundleEntryComponent bec = new Bundle.BundleEntryComponent(); + bec.setResource(obs); + bec.setRequest(berc); + return bec; + } + + private Bundle sendObservationBundle(Bundle observationsBundle){ + String url = HapiProperties.getObservationEndpoint(); + if(null != url && url.length() > 0) { + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; + } + return createErrorBundle("The observation.endpoint in hapi.properties was not set."); } +} diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index 0fe5365c5..d61fc1384 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -19,17 +19,7 @@ import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.dstu3.evaluation.ProviderFactory; -import org.opencds.cqf.dstu3.providers.ActivityDefinitionApplyProvider; -import org.opencds.cqf.dstu3.providers.ApplyCqlOperationProvider; -import org.opencds.cqf.dstu3.providers.CacheValueSetsProvider; -import org.opencds.cqf.dstu3.providers.CodeSystemUpdateProvider; -import org.opencds.cqf.dstu3.providers.CqlExecutionProvider; -import org.opencds.cqf.dstu3.providers.HQMFProvider; -import org.opencds.cqf.dstu3.providers.JpaTerminologyProvider; -import org.opencds.cqf.dstu3.providers.LibraryOperationsProvider; -import org.opencds.cqf.dstu3.providers.MeasureOperationsProvider; -import org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider; -import org.opencds.cqf.dstu3.providers.OAuthProvider; +import org.opencds.cqf.dstu3.providers.*; import org.opencds.cqf.library.stu3.NarrativeProvider; import org.opencds.cqf.measure.stu3.CodeTerminologyRef; import org.opencds.cqf.measure.stu3.CqfMeasure; @@ -250,6 +240,10 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, CdsHooksServlet.setLibraryResolutionProvider(libraryProvider); CdsHooksServlet.setSystemTerminologyProvider(localSystemTerminologyProvider); CdsHooksServlet.setSystemRetrieveProvider(localSystemRetrieveProvider); + + // QuestionnaireResponse processing + QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); + this.registerProvider(questionnaireProvider); } protected IFhirResourceDao getDao(Class clazz) { diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index 988091cf7..65e46c699 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -164,3 +164,7 @@ oauth.serviceSystem=http://hl7.org/fhir/restful-security-service oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) + +observation.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +observation.username= +observation.password= \ No newline at end of file From 622ace2834535da13d87c71438e816f7ffe57bee Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 10:43:11 -0600 Subject: [PATCH 062/198] fixed fhir version error in QuestionnaireProvider --- .../org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index 87e67bf28..39fa4479e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -4,7 +4,7 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.client.api.IGenericClient; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.dstu3.model.*; import org.opencds.cqf.common.config.HapiProperties; import java.util.Date; From 3669ad5635010077a523600603c1d4f9531a7852 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 12:27:05 -0600 Subject: [PATCH 063/198] changed error handling to exceptions --- .../providers/QuestionnaireProvider.java | 42 ++++++++----------- .../r4/providers/QuestionnaireProvider.java | 42 ++++++++----------- 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index 39fa4479e..86433221f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -20,21 +20,12 @@ public QuestionnaireProvider(FhirContext fhirContext){ @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - if(questionnaireResponse != null) { - Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; + if(questionnaireResponse == null) { + throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } - return createErrorBundle("The QuestionnaireResponse was null."); - } - - private Bundle createErrorBundle(String errorMessage){ - Bundle errorBundle = new Bundle(); - Identifier bundleId = new Identifier(); - bundleId.setValue("Error in QuestionnaireResponse/$extract " + errorMessage); - errorBundle.setType(Bundle.BundleType.MESSAGE); - errorBundle.setIdentifier(bundleId); - return errorBundle; + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ @@ -80,17 +71,18 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna return bec; } - private Bundle sendObservationBundle(Bundle observationsBundle){ + private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException{ String url = HapiProperties.getObservationEndpoint(); - if(null != url && url.length() > 0) { - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); - - IGenericClient client = getClient(fhirContext, url, user, password); - Bundle outcomeBundle = client.transaction() - .withBundle(observationsBundle) - .execute(); - return outcomeBundle; + if (null == url || url.length() < 1) { + throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); } - return createErrorBundle("The observation.endpoint in hapi.properties was not set."); } + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; + } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 3663d123c..21f8154de 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -20,21 +20,12 @@ public QuestionnaireProvider(FhirContext fhirContext){ @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - if(questionnaireResponse != null) { - Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; + if(questionnaireResponse == null) { + throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } - return createErrorBundle("The QuestionnaireResponse was null."); - } - - private Bundle createErrorBundle(String errorMessage){ - Bundle errorBundle = new Bundle(); - Identifier bundleId = new Identifier(); - bundleId.setValue("Error in QuestionnaireResponse/$extract " + errorMessage); - errorBundle.setType(Bundle.BundleType.MESSAGE); - errorBundle.setIdentifier(bundleId); - return errorBundle; + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ @@ -80,17 +71,18 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna return bec; } - private Bundle sendObservationBundle(Bundle observationsBundle){ + private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException{ String url = HapiProperties.getObservationEndpoint(); - if(null != url && url.length() > 0) { - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); - - IGenericClient client = getClient(fhirContext, url, user, password); - Bundle outcomeBundle = client.transaction() - .withBundle(observationsBundle) - .execute(); - return outcomeBundle; + if (null == url || url.length() < 1) { + throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); } - return createErrorBundle("The observation.endpoint in hapi.properties was not set."); } + String user = HapiProperties.getObservationUserName(); + String password = HapiProperties.getObservationPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Bundle outcomeBundle = client.transaction() + .withBundle(observationsBundle) + .execute(); + return outcomeBundle; + } } From c2106aa44c1ce25e1084fbad6f7f94140824d69e Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 14:19:58 -0600 Subject: [PATCH 064/198] initial concept map code system transformation --- .../cqf/r4/providers/ObservationProvider.java | 42 +++++++++++++++++++ .../opencds/cqf/r4/servlet/BaseServlet.java | 4 ++ 2 files changed, 46 insertions(+) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java new file mode 100644 index 000000000..5aeda0ed5 --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -0,0 +1,42 @@ +package org.opencds.cqf.r4.providers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.ConceptMap; +import org.hl7.fhir.r4.model.Enumerations; +import org.hl7.fhir.r4.model.Observation; + +public class ObservationProvider { + + private FhirContext fhirContext; + + public ObservationProvider(FhirContext fhirContext){ + this.fhirContext = fhirContext; + } + + + @Operation(name = "$transform", idempotent = false, type = Observation.class) + public Bundle transformObservations(@OperationParam(name = "observations") Bundle observations, + @OperationParam(name = "inputCodeSystem") String inputCodeSystem, + @OperationParam(name = "outputCodeSystem")String outputCodeSystem) { + if(null == observations || null == inputCodeSystem || null == outputCodeSystem) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. One of the parameters was null"); + } + ConceptMap conceptMapIn = new ConceptMap(); + conceptMapIn.setUrl(inputCodeSystem); + conceptMapIn.setStatus(Enumerations.PublicationStatus.ACTIVE); + + ConceptMap conceptMapOut = new ConceptMap(); + conceptMapOut.setUrl(outputCodeSystem); + conceptMapOut.setStatus(Enumerations.PublicationStatus.ACTIVE); + + + Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; + } + + private +} diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 9fa103ada..49eb530a9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -246,6 +246,10 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, // QuestionnaireResponse processing QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); this.registerProvider(questionnaireProvider); + + // QuestionnaireResponse processing + ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); + this.registerProvider(observationProvider); } protected IFhirResourceDao getDao(Class clazz) { From 1c3a797ad6ecff2355fd659cc8945d10f7246455 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 14:20:34 -0600 Subject: [PATCH 065/198] initial concept map code system transformation --- r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 49eb530a9..4db5bd8a8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -247,7 +247,7 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); this.registerProvider(questionnaireProvider); - // QuestionnaireResponse processing + // Observation processing ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); this.registerProvider(observationProvider); } From 7cecb7b8d26f896d913fe493c7442d34b583c06c Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 14:36:49 -0600 Subject: [PATCH 066/198] added and turned off enabled flags for OAuth and QuestionnaireResponse extract --- .../cqf/common/config/HapiProperties.java | 17 ++++++++++------- .../dstu3/providers/QuestionnaireProvider.java | 6 +++--- .../opencds/cqf/dstu3/servlet/BaseServlet.java | 6 ++++-- dstu3/src/main/resources/hapi.properties | 9 +++++---- .../cqf/r4/providers/QuestionnaireProvider.java | 6 +++--- .../org/opencds/cqf/r4/servlet/BaseServlet.java | 6 ++++-- r4/src/main/resources/hapi.properties | 9 +++++---- 7 files changed, 34 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index a7f12947a..5b1707dca 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -59,6 +59,10 @@ public class HapiProperties { static final String OAUTH_SERVICE_CODE = "oauth.serviceCode"; static final String OAUTH_SERVICE_DISPLAY = "oauth.serviceDisplay"; static final String OAUTH_SERVICE_TEXT = "oauth.serviceText"; + static final String QUESTIONNAIRE_RESPONSE_ENABLED = "questionnaireResponseExtract.enabled"; + static final String QUESTIONNAIRE_RESPONSE_ENDPOINT = "questionnaireResponseExtract.endpoint"; + static final String QUESTIONNAIRE_RESPONSE_USERNAME = "questionnaireResponseExtract.username"; + static final String QUESTIONNAIRE_RESPONSE_PASSWORD = "questionnaireResponseExtract.password"; private static Properties properties; @@ -357,14 +361,8 @@ public static Long getReuseCachedSearchResultsMillis() { return Long.valueOf(value); } - public static String getObservationEndpoint() { - return HapiProperties.getProperty("observation.endpoint"); - } - public static String getObservationUserName(){return HapiProperties.getProperty("observation.username");}; - public static String getObservationPassword(){return HapiProperties.getProperty("observation.password");}; - //************************* OAuth ******************************************************* - public static Boolean getOAuthEnabled(){return HapiProperties.getBooleanProperty(OAUTH_ENABLED, true);} + public static Boolean getOAuthEnabled(){return HapiProperties.getBooleanProperty(OAUTH_ENABLED, false);} public static Boolean getOauthSecurityCors(){return HapiProperties.getBooleanProperty(OAUTH_SECURITY_CORS, true);} public static String getOauthSecurityUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_URL, "");} public static String getOauthSecurityExtAuthUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_URL, "");} @@ -375,4 +373,9 @@ public static String getObservationEndpoint() { public static String getOauthServiceCode(){return HapiProperties.getProperty(OAUTH_SERVICE_CODE, "");} public static String getOauthServiceDisplay(){return HapiProperties.getProperty(OAUTH_SERVICE_DISPLAY, "");} public static String getOauthServiceText(){return HapiProperties.getProperty(OAUTH_SERVICE_TEXT, "");} + + public static Boolean getQuestionnaireResponseExtractEnabled(){return HapiProperties.getBooleanProperty(QUESTIONNAIRE_RESPONSE_ENABLED, false);} + public static String getQuestionnaireResponseExtractEndpoint() {return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_ENDPOINT);} + public static String getQuestionnaireResponseExtractUserName(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_USERNAME);}; + public static String getQuestionnaireResponseExtractPassword(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD);}; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index 86433221f..e6bd00f2d 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -72,12 +72,12 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna } private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException{ - String url = HapiProperties.getObservationEndpoint(); + String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); } - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); + String user = HapiProperties.getQuestionnaireResponseExtractUserName(); + String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); Bundle outcomeBundle = client.transaction() diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index d61fc1384..bed3f54b9 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -242,8 +242,10 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, CdsHooksServlet.setSystemRetrieveProvider(localSystemRetrieveProvider); // QuestionnaireResponse processing - QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); - this.registerProvider(questionnaireProvider); + if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { + QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); + this.registerProvider(questionnaireProvider); + } } protected IFhirResourceDao getDao(Class clazz) { diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index 65e46c699..afa53b1d5 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -153,7 +153,7 @@ subscription.websocket.enabled=false ################################################## # OAuth Settings ################################################## -oauth.enabled=true +oauth.enabled=false oauth.securityCors=true oauth.securityUrl=http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris oauth.securityExtAuthUrl=authorize @@ -165,6 +165,7 @@ oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) -observation.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir -observation.username= -observation.password= \ No newline at end of file +questionnaireResponseExtract.enabled=false +questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +questionnaireResponseExtract.username= +questionnaireResponseExtract.password= \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 21f8154de..767d30cdd 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -72,12 +72,12 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna } private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException{ - String url = HapiProperties.getObservationEndpoint(); + String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); } - String user = HapiProperties.getObservationUserName(); - String password = HapiProperties.getObservationPassword(); + String user = HapiProperties.getQuestionnaireResponseExtractUserName(); + String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); Bundle outcomeBundle = client.transaction() diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 9fa103ada..0989718ad 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -244,8 +244,10 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, CdsHooksServlet.setSystemRetrieveProvider(localSystemRetrieveProvider); // QuestionnaireResponse processing - QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); - this.registerProvider(questionnaireProvider); + if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { + QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); + this.registerProvider(questionnaireProvider); + } } protected IFhirResourceDao getDao(Class clazz) { diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 7103a8a2c..5338239e4 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -153,7 +153,7 @@ subscription.websocket.enabled=false ################################################## # OAuth Settings ################################################## -oauth.enabled=true +oauth.enabled=false oauth.securityCors=true oauth.securityUrl=http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris oauth.securityExtAuthUrl=authorize @@ -165,6 +165,7 @@ oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) -observation.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir -observation.username= -observation.password= \ No newline at end of file +questionnaireResponseExtract.enabled=false +questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +questionnaireResponseExtract.username= +questionnaireResponseExtract.password= \ No newline at end of file From 39e5f5650ffad2b5238ad59a5688c2102dfab616 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 24 Jun 2020 14:52:44 -0600 Subject: [PATCH 067/198] added flags for enabled --- .../java/org/opencds/cqf/common/config/HapiProperties.java | 3 +++ dstu3/src/main/resources/hapi.properties | 4 +++- .../org/opencds/cqf/r4/providers/ObservationProvider.java | 6 ++---- .../main/java/org/opencds/cqf/r4/servlet/BaseServlet.java | 6 ++++-- r4/src/main/resources/hapi.properties | 4 +++- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 5b1707dca..62b44628e 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -63,6 +63,7 @@ public class HapiProperties { static final String QUESTIONNAIRE_RESPONSE_ENDPOINT = "questionnaireResponseExtract.endpoint"; static final String QUESTIONNAIRE_RESPONSE_USERNAME = "questionnaireResponseExtract.username"; static final String QUESTIONNAIRE_RESPONSE_PASSWORD = "questionnaireResponseExtract.password"; + static final String OBSERVATION_TRANSFORM_ENABLED = "observationTransform.enabled"; private static Properties properties; @@ -378,4 +379,6 @@ public static Long getReuseCachedSearchResultsMillis() { public static String getQuestionnaireResponseExtractEndpoint() {return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_ENDPOINT);} public static String getQuestionnaireResponseExtractUserName(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_USERNAME);}; public static String getQuestionnaireResponseExtractPassword(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD);}; + + public static Boolean getObservationTransformEnabled(){return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_ENABLED, false);} } diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index afa53b1d5..86fc3bcd1 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -168,4 +168,6 @@ oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealt questionnaireResponseExtract.enabled=false questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= -questionnaireResponseExtract.password= \ No newline at end of file +questionnaireResponseExtract.password= + +observationTransform.enabled=true diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java index 5aeda0ed5..8bbbab239 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -33,10 +33,8 @@ public Bundle transformObservations(@OperationParam(name = "observations") Bundl conceptMapOut.setStatus(Enumerations.PublicationStatus.ACTIVE); - Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); +// Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); + Bundle returnBundle = new Bundle();//sendObservationBundle(observationsFromQuestionnaireResponse); return returnBundle; } - - private } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 9f1882645..016315779 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -249,8 +249,10 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, this.registerProvider(questionnaireProvider); } // Observation processing - ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); - this.registerProvider(observationProvider); + if(HapiProperties.getObservationTransformEnabled()) { + ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); + this.registerProvider(observationProvider); + } } diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 5338239e4..931e83688 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -168,4 +168,6 @@ oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealt questionnaireResponseExtract.enabled=false questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= -questionnaireResponseExtract.password= \ No newline at end of file +questionnaireResponseExtract.password= + +observationTransform.enabled=true \ No newline at end of file From 9f01369a70b3b32eac3b6bb2ac71597f928be4fd Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 29 Jun 2020 14:58:23 -0600 Subject: [PATCH 068/198] added replaceCode seting to use with replace code or add code. Default false so add code. --- .../cqf/common/config/HapiProperties.java | 7 ++- .../cqf/r4/providers/ObservationProvider.java | 58 +++++++++++++------ r4/src/main/resources/hapi.properties | 5 +- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 62b44628e..688b3665f 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -64,7 +64,9 @@ public class HapiProperties { static final String QUESTIONNAIRE_RESPONSE_USERNAME = "questionnaireResponseExtract.username"; static final String QUESTIONNAIRE_RESPONSE_PASSWORD = "questionnaireResponseExtract.password"; static final String OBSERVATION_TRANSFORM_ENABLED = "observationTransform.enabled"; - + static final String OBSERVATION_TRANSFORM_USERNAME = "observationTransform.username"; + static final String OBSERVATION_TRANSFORM_PASSWORD = "observationTransform.password"; + static final String OBSERVATION_TRANSFORM_REPLACE_CODE = "observationTransform.replaceCode"; private static Properties properties; /* @@ -381,4 +383,7 @@ public static Long getReuseCachedSearchResultsMillis() { public static String getQuestionnaireResponseExtractPassword(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD);}; public static Boolean getObservationTransformEnabled(){return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_ENABLED, false);} + public static String getObservationTransformUsername(){return HapiProperties.getProperty(OBSERVATION_TRANSFORM_USERNAME);} + public static String getObservationTransformPassword(){return HapiProperties.getProperty(OBSERVATION_TRANSFORM_PASSWORD);} + public static Boolean getObservationTransformReplaceCode(){return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_REPLACE_CODE, false);} } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java index 8bbbab239..e9dcd7a91 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -3,10 +3,20 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.util.BundleUtil; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.ConceptMap; -import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Observation; +import org.opencds.cqf.common.config.HapiProperties; + +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +import static org.opencds.cqf.common.config.HapiProperties.getObservationTransformReplaceCode; +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; public class ObservationProvider { @@ -16,25 +26,39 @@ public ObservationProvider(FhirContext fhirContext){ this.fhirContext = fhirContext; } - @Operation(name = "$transform", idempotent = false, type = Observation.class) - public Bundle transformObservations(@OperationParam(name = "observations") Bundle observations, - @OperationParam(name = "inputCodeSystem") String inputCodeSystem, - @OperationParam(name = "outputCodeSystem")String outputCodeSystem) { - if(null == observations || null == inputCodeSystem || null == outputCodeSystem) { - throw new IllegalArgumentException("Unable to perform operation Observation$transform. One of the parameters was null"); + public Bundle transformObservations( + @OperationParam(name = "observations") Bundle observationsBundle, + @OperationParam(name = "conceptMapURL") String conceptMapURL + ) { + if(null == observationsBundle) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. No Observation bundle passed in."); } - ConceptMap conceptMapIn = new ConceptMap(); - conceptMapIn.setUrl(inputCodeSystem); - conceptMapIn.setStatus(Enumerations.PublicationStatus.ACTIVE); - - ConceptMap conceptMapOut = new ConceptMap(); - conceptMapOut.setUrl(outputCodeSystem); - conceptMapOut.setStatus(Enumerations.PublicationStatus.ACTIVE); + if(null == conceptMapURL) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. No concept map url specified."); + } + IGenericClient client = getClient(fhirContext, conceptMapURL, HapiProperties.getObservationTransformUsername(), HapiProperties.getObservationTransformPassword() ); + ConceptMap transformConceptMap = client.read().resource(ConceptMap.class).withUrl (conceptMapURL).execute(); + if(null == transformConceptMap) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. Unable to get concept map."); + } + HashMap codeMappings = new HashMap<>(); + transformConceptMap.getGroup().forEach(group -> group.getElement().forEach(codeElement -> codeMappings.put(codeElement.getCode(), codeElement.getTarget().get(0).getCode()))); + List observations = (List) BundleUtil.toListOfResources(fhirContext, observationsBundle).stream() + .filter(resource -> resource instanceof Observation) + .map(Observation.class::cast) + .collect(Collectors.toList()); + observations.forEach(observation -> { + if (codeMappings.get(observation.getCode().getCoding().get(0).getCode()) != null) { + if(HapiProperties.getObservationTransformReplaceCode()){ + observation.getCode().getCoding().get(0).setCode(codeMappings.get(observation.getCode().getCoding().get(0).getCode())); + }else{ + observation.getCode().getCoding().add(new Coding("", codeMappings.get(observation.getCode().getCoding().get(0).getCode()), observation.getCode().getCoding().get(0).getDisplay())); + } + } + }); -// Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = new Bundle();//sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; + return observationsBundle; } } diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 931e83688..ad7d4373b 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -170,4 +170,7 @@ questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.co questionnaireResponseExtract.username= questionnaireResponseExtract.password= -observationTransform.enabled=true \ No newline at end of file +observationTransform.enabled=true +observationTransform.username= +observationTransform.password= +observationTransform.replaceCode=false From 5d5aaf9bf3523d62d98378243ab6f5a514ec2894 Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 2 Jul 2020 09:29:43 -0600 Subject: [PATCH 069/198] refactored transform functionality. --- .../cqf/r4/providers/ObservationProvider.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java index e9dcd7a91..1bcc43cf7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -42,23 +42,40 @@ public Bundle transformObservations( if(null == transformConceptMap) { throw new IllegalArgumentException("Unable to perform operation Observation$transform. Unable to get concept map."); } - HashMap codeMappings = new HashMap<>(); - transformConceptMap.getGroup().forEach(group -> group.getElement().forEach(codeElement -> codeMappings.put(codeElement.getCode(), codeElement.getTarget().get(0).getCode()))); - - List observations = (List) BundleUtil.toListOfResources(fhirContext, observationsBundle).stream() + List observations = BundleUtil.toListOfResources(fhirContext, observationsBundle).stream() .filter(resource -> resource instanceof Observation) .map(Observation.class::cast) .collect(Collectors.toList()); - observations.forEach(observation -> { - if (codeMappings.get(observation.getCode().getCoding().get(0).getCode()) != null) { - if(HapiProperties.getObservationTransformReplaceCode()){ - observation.getCode().getCoding().get(0).setCode(codeMappings.get(observation.getCode().getCoding().get(0).getCode())); - }else{ - observation.getCode().getCoding().add(new Coding("", codeMappings.get(observation.getCode().getCoding().get(0).getCode()), observation.getCode().getCoding().get(0).getDisplay())); + /** + * TODO - There must be a more efficient way to loop through this, but so far I have not come up with it. + */ + transformConceptMap.getGroup().forEach(group -> { + HashMap codeMappings = new HashMap<>(); + String targetSystem = group.getTarget(); + group.getElement().forEach(codeElement -> { + codeMappings.put(codeElement.getCode(), codeElement.getTarget().get(0)); + }); + observations.forEach(observation -> { + if(observation.getValue().fhirType().equalsIgnoreCase("codeableconcept")){ + if (codeMappings.get(observation.getValueCodeableConcept().getCoding().get(0).getCode()) != null) { + if(HapiProperties.getObservationTransformReplaceCode()){ + String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); + observation.getValueCodeableConcept().getCoding().get(0).setCode(codeMappings.get(obsValueCode).getCode()); + observation.getValueCodeableConcept().getCoding().get(0).setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation.getValueCodeableConcept().getCoding().get(0).setSystem(targetSystem); + }else{ + String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); + Coding newCoding = new Coding(); + newCoding.setSystem(targetSystem); + newCoding.setCode(codeMappings.get(obsValueCode).getCode()); + newCoding.setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation. getValueCodeableConcept().getCoding().add(newCoding); + } + } + } - } + }); }); - return observationsBundle; } } From ddd9ed216ef885eb57b73e87551c2f225f15bb48 Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 6 Jul 2020 12:03:16 -0600 Subject: [PATCH 070/198] minor edits fixing loop --- .../cqf/r4/providers/ObservationProvider.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java index 1bcc43cf7..c636cec94 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -15,7 +15,6 @@ import java.util.List; import java.util.stream.Collectors; -import static org.opencds.cqf.common.config.HapiProperties.getObservationTransformReplaceCode; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; public class ObservationProvider { @@ -57,22 +56,22 @@ public Bundle transformObservations( }); observations.forEach(observation -> { if(observation.getValue().fhirType().equalsIgnoreCase("codeableconcept")){ - if (codeMappings.get(observation.getValueCodeableConcept().getCoding().get(0).getCode()) != null) { - if(HapiProperties.getObservationTransformReplaceCode()){ - String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); - observation.getValueCodeableConcept().getCoding().get(0).setCode(codeMappings.get(obsValueCode).getCode()); - observation.getValueCodeableConcept().getCoding().get(0).setDisplay(codeMappings.get(obsValueCode).getDisplay()); - observation.getValueCodeableConcept().getCoding().get(0).setSystem(targetSystem); - }else{ - String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); - Coding newCoding = new Coding(); - newCoding.setSystem(targetSystem); - newCoding.setCode(codeMappings.get(obsValueCode).getCode()); - newCoding.setDisplay(codeMappings.get(obsValueCode).getDisplay()); - observation. getValueCodeableConcept().getCoding().add(newCoding); + String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); + if(obsValueCode != null) { + if (codeMappings.get(observation.getValueCodeableConcept().getCoding().get(0).getCode()) != null) { + if (HapiProperties.getObservationTransformReplaceCode()) { + observation.getValueCodeableConcept().getCoding().get(0).setCode(codeMappings.get(obsValueCode).getCode()); + observation.getValueCodeableConcept().getCoding().get(0).setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation.getValueCodeableConcept().getCoding().get(0).setSystem(targetSystem); + } else { + Coding newCoding = new Coding(); + newCoding.setSystem(targetSystem); + newCoding.setCode(codeMappings.get(obsValueCode).getCode()); + newCoding.setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation.getValueCodeableConcept().getCoding().add(newCoding); + } } } - } }); }); From 2a29afbab7dfd2a22a36084eaf15311049ec0a7f Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 6 Jul 2020 13:14:22 -0600 Subject: [PATCH 071/198] added dstu3 $transform operation --- .../dstu3/providers/ObservationProvider.java | 80 +++++++++++++++++++ .../cqf/dstu3/servlet/BaseServlet.java | 5 ++ 2 files changed, 85 insertions(+) create mode 100644 dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java new file mode 100644 index 000000000..395ef38b0 --- /dev/null +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java @@ -0,0 +1,80 @@ +package org.opencds.cqf.dstu3.providers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.util.BundleUtil; +import org.hl7.fhir.dstu3.model.Bundle; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.ConceptMap; +import org.hl7.fhir.dstu3.model.Observation; +import org.opencds.cqf.common.config.HapiProperties; + +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; + +public class ObservationProvider { + + private FhirContext fhirContext; + + public ObservationProvider(FhirContext fhirContext){ + this.fhirContext = fhirContext; + } + + @Operation(name = "$transform", idempotent = false, type = Observation.class) + public Bundle transformObservations( + @OperationParam(name = "observations") Bundle observationsBundle, + @OperationParam(name = "conceptMapURL") String conceptMapURL + ) { + if(null == observationsBundle) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. No Observation bundle passed in."); + } + if(null == conceptMapURL) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. No concept map url specified."); + } + IGenericClient client = getClient(fhirContext, conceptMapURL, HapiProperties.getObservationTransformUsername(), HapiProperties.getObservationTransformPassword() ); + ConceptMap transformConceptMap = client.read().resource(ConceptMap.class).withUrl (conceptMapURL).execute(); + if(null == transformConceptMap) { + throw new IllegalArgumentException("Unable to perform operation Observation$transform. Unable to get concept map."); + } + List observations = BundleUtil.toListOfResources(fhirContext, observationsBundle).stream() + .filter(resource -> resource instanceof Observation) + .map(Observation.class::cast) + .collect(Collectors.toList()); + /** + * TODO - There must be a more efficient way to loop through this, but so far I have not come up with it. + */ + transformConceptMap.getGroup().forEach(group -> { + HashMap codeMappings = new HashMap<>(); + String targetSystem = group.getTarget(); + group.getElement().forEach(codeElement -> { + codeMappings.put(codeElement.getCode(), codeElement.getTarget().get(0)); + }); + observations.forEach(observation -> { + if(observation.getValue().fhirType().equalsIgnoreCase("codeableconcept")){ + String obsValueCode = observation.getValueCodeableConcept().getCoding().get(0).getCode(); + if(obsValueCode != null) { + if (codeMappings.get(observation.getValueCodeableConcept().getCoding().get(0).getCode()) != null) { + if (HapiProperties.getObservationTransformReplaceCode()) { + observation.getValueCodeableConcept().getCoding().get(0).setCode(codeMappings.get(obsValueCode).getCode()); + observation.getValueCodeableConcept().getCoding().get(0).setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation.getValueCodeableConcept().getCoding().get(0).setSystem(targetSystem); + } else { + Coding newCoding = new Coding(); + newCoding.setSystem(targetSystem); + newCoding.setCode(codeMappings.get(obsValueCode).getCode()); + newCoding.setDisplay(codeMappings.get(obsValueCode).getDisplay()); + observation.getValueCodeableConcept().getCoding().add(newCoding); + } + } + } + } + }); + }); + return observationsBundle; + } +} diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index bed3f54b9..b994abc55 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -246,6 +246,11 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); this.registerProvider(questionnaireProvider); } + // Observation processing + if(HapiProperties.getObservationTransformEnabled()) { + ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); + this.registerProvider(observationProvider); + } } protected IFhirResourceDao getDao(Class clazz) { From 43485bce572ff810f3867072c1750897ab43b839 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 6 Jul 2020 22:16:31 -0600 Subject: [PATCH 072/198] Update to release versions of major dependencies --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 2f9f19f12..7ca321b34 100644 --- a/pom.xml +++ b/pom.xml @@ -55,10 +55,10 @@ 9.4.28.v20200408 2.10.1 4.2.0 - 1.1.0-SNAPSHOT - 1.4.0-SNAPSHOT - 1.4.9-SNAPSHOT - 1.1.0-SNAPSHOT + 1.1.1 + 1.4.0 + 1.4.9 + 1.1.0 1.7.30 From ea5ebca69d633dcf990ceff2f69637063d62e4b5 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Tue, 7 Jul 2020 18:59:02 -0600 Subject: [PATCH 073/198] Update pom.xml --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7ca321b34..dbb8c2030 100644 --- a/pom.xml +++ b/pom.xml @@ -44,8 +44,8 @@ 0 - 1 - 15 + 3 + 0 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT From d78af492185dd626a580550ebbfac7dad5ed8304 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Tue, 7 Jul 2020 21:51:48 -0600 Subject: [PATCH 074/198] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dbb8c2030..02619b658 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 0 - 3 + 4 0 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT From ee443d33889f03c8c5ccb83a7f9dd7aa9f1335d6 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 8 Jul 2020 15:19:43 -0600 Subject: [PATCH 075/198] Fix 404 exception bug, Add handling of CqlException --- .../cqf/dstu3/servlet/CdsHooksServlet.java | 14 +++++++++++++- pom.xml | 2 +- .../opencds/cqf/r4/servlet/CdsHooksServlet.java | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 5df0f3bd3..42e95b516 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -39,6 +39,7 @@ import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; import org.opencds.cqf.cql.engine.debug.DebugMap; +import org.opencds.cqf.cql.engine.exception.CqlException; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; @@ -171,7 +172,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. response.getWriter().println("ERROR: Exception connecting to remote server."); this.printMessageAndCause(e, response); - this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException(e, response); this.printStackTrack(e, response); } catch (DataProviderException e) { this.setAccessControlHeaders(response); @@ -182,6 +183,17 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); } + this.printStackTrack(e, response); + } + catch (CqlException e) { + this.setAccessControlHeaders(response); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception in CQL Execution."); + this.printMessageAndCause(e, response); + if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); + } + this.printStackTrack(e, response); } catch (Exception e) { throw new ServletException("ERROR: Exception in cds-hooks processing.", e); diff --git a/pom.xml b/pom.xml index 02619b658..7f3fd4a00 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 1.1.1 1.4.0 1.4.9 - 1.1.0 + 1.3.0-SNAPSHOT 1.7.30 diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index e28549124..478b23776 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -39,6 +39,7 @@ import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.data.CompositeDataProvider; import org.opencds.cqf.cql.engine.debug.DebugMap; +import org.opencds.cqf.cql.engine.exception.CqlException; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.fhir.exception.DataProviderException; @@ -176,7 +177,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. response.getWriter().println("ERROR: Exception connecting to remote server."); this.printMessageAndCause(e, response); - this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); + this.handleServerResponseException(e, response); this.printStackTrack(e, response); } catch (DataProviderException e) { this.setAccessControlHeaders(response); @@ -188,7 +189,19 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } this.printStackTrack(e, response); - } catch (Exception e) { + } + catch (CqlException e) { + this.setAccessControlHeaders(response); + response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. + response.getWriter().println("ERROR: Exception in CQL Execution."); + this.printMessageAndCause(e, response); + if (e.getCause() != null && (e.getCause() instanceof BaseServerResponseException)) { + this.handleServerResponseException((BaseServerResponseException) e.getCause(), response); + } + + this.printStackTrack(e, response); + } + catch (Exception e) { throw new ServletException("ERROR: Exception in cds-hooks processing.", e); } } From c74455789c32aafe7a604a10343a733ced507fce Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 17 Jul 2020 17:36:03 -0600 Subject: [PATCH 076/198] Configurable cds-hooks options --- .../cqf/common/config/HapiProperties.java | 18 ++++++++++++++++++ .../cqf/dstu3/servlet/CdsHooksServlet.java | 17 ++++++++++++++++- dstu3/src/main/resources/hapi.properties | 9 ++++++++- .../cqf/r4/servlet/CdsHooksServlet.java | 16 +++++++++++++++- r4/src/main/resources/hapi.properties | 9 ++++++++- 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 5b1707dca..251a4a97b 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -9,6 +9,7 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.api.SearchStyleEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; public class HapiProperties { @@ -64,6 +65,10 @@ public class HapiProperties { static final String QUESTIONNAIRE_RESPONSE_USERNAME = "questionnaireResponseExtract.username"; static final String QUESTIONNAIRE_RESPONSE_PASSWORD = "questionnaireResponseExtract.password"; + static final String CDSHOOKS_FHIRSERVER_MAXCODESPERQUERY = "cds_hooks.fhirServer.maxCodesPerQuery"; + static final String CDSHOOKS_FHIRSERVER_EXPANDVALUESETS = "cds_hooks.fhirServer.expandValueSets"; + static final String CDSHOOKS_FHIRSERVER_SEARCHSTYLE= "cds_hooks.fhirServer.searchStyle"; + private static Properties properties; /* @@ -378,4 +383,17 @@ public static Long getReuseCachedSearchResultsMillis() { public static String getQuestionnaireResponseExtractEndpoint() {return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_ENDPOINT);} public static String getQuestionnaireResponseExtractUserName(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_USERNAME);}; public static String getQuestionnaireResponseExtractPassword(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD);}; + + //************************* CDS_HOOKS **************** + public static Integer getCdsHooksFhirServerMaxCodesPerQuery() { return HapiProperties.getIntegerProperty(CDSHOOKS_FHIRSERVER_MAXCODESPERQUERY, 64);} + public static Boolean getCdsHooksFhirServerExpandValueSets() { return HapiProperties.getBooleanProperty(CDSHOOKS_FHIRSERVER_EXPANDVALUESETS, true);} + public static SearchStyleEnum getCdsHooksFhirServerSearchStyleEnum() { + String searchStyleEnumString = HapiProperties.getProperty(CDSHOOKS_FHIRSERVER_SEARCHSTYLE); + + if (searchStyleEnumString != null && searchStyleEnumString.length() > 0) { + return SearchStyleEnum.valueOf(searchStyleEnumString); + } + + return SearchStyleEnum.GET; + } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 42e95b516..a86e47a5e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -30,6 +30,7 @@ import org.opencds.cqf.cds.hooks.Hook; import org.opencds.cqf.cds.hooks.HookFactory; import org.opencds.cqf.cds.hooks.Stu3HookEvaluator; +import org.opencds.cqf.cds.providers.ProviderConfiguration; import org.opencds.cqf.cds.request.JsonHelper; import org.opencds.cqf.cds.request.Request; import org.opencds.cqf.cds.response.CdsCard; @@ -68,6 +69,19 @@ public class CdsHooksServlet extends HttpServlet { private static JpaTerminologyProvider jpaTerminologyProvider; + private ProviderConfiguration providerConfiguration; + + public ProviderConfiguration getProviderConfiguration() { + if (providerConfiguration == null) { + providerConfiguration = new ProviderConfiguration( + HapiProperties.getCdsHooksFhirServerExpandValueSets(), + HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), + HapiProperties.getCdsHooksFhirServerSearchStyleEnum()); + } + + return providerConfiguration; + } + // TODO: There's probably a way to wire this all up using Spring public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; @@ -154,7 +168,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) context.setExpressionCaching(true); EvaluationContext evaluationContext = new Stu3EvaluationContext(hook, version, FhirContext.forDstu3().newRestfulGenericClient(baseUrl), - jpaTerminologyProvider, context, library, planDefinition); + jpaTerminologyProvider, context, library, + planDefinition, this.getProviderConfiguration()); this.setAccessControlHeaders(response); diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index afa53b1d5..5a8938812 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -168,4 +168,11 @@ oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealt questionnaireResponseExtract.enabled=false questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= -questionnaireResponseExtract.password= \ No newline at end of file +questionnaireResponseExtract.password= + +################################################## +# CDS-Hooks Settings +################################################## +cds_hooks.fhirServer.maxCodesPerQuery= +cds_hooks.fhirServer.expandValueSets= +cds_hooks.fhirServer.searchStyle= \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 478b23776..3a1bef603 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -30,6 +30,7 @@ import org.opencds.cqf.cds.hooks.Hook; import org.opencds.cqf.cds.hooks.HookFactory; import org.opencds.cqf.cds.hooks.R4HookEvaluator; +import org.opencds.cqf.cds.providers.ProviderConfiguration; import org.opencds.cqf.cds.request.JsonHelper; import org.opencds.cqf.cds.request.Request; import org.opencds.cqf.cds.response.CdsCard; @@ -68,6 +69,19 @@ public class CdsHooksServlet extends HttpServlet { private static JpaTerminologyProvider jpaTerminologyProvider; + private ProviderConfiguration providerConfiguration; + + public ProviderConfiguration getProviderConfiguration() { + if (providerConfiguration == null) { + providerConfiguration = new ProviderConfiguration( + HapiProperties.getCdsHooksFhirServerExpandValueSets() , + HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), + HapiProperties.getCdsHooksFhirServerSearchStyleEnum()); + } + + return providerConfiguration; + } + // TODO: There's probably a way to wire this all up using Spring public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; @@ -159,7 +173,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) EvaluationContext evaluationContext = new R4EvaluationContext(hook, version, FhirContext.forR4().newRestfulGenericClient(baseUrl), jpaTerminologyProvider, context, library, - planDefinition); + planDefinition, this.getProviderConfiguration()); this.setAccessControlHeaders(response); diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 5338239e4..5e64ed20b 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -168,4 +168,11 @@ oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealt questionnaireResponseExtract.enabled=false questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= -questionnaireResponseExtract.password= \ No newline at end of file +questionnaireResponseExtract.password= + +################################################## +# CDS-Hooks Settings +################################################## +cds_hooks.fhirServer.maxCodesPerQuery= +cds_hooks.fhirServer.expandValueSets= +cds_hooks.fhirServer.searchStyle= \ No newline at end of file From 4646d71c4c9999a997a5697033f7199c28a7bc87 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 20 Jul 2020 21:41:56 -0600 Subject: [PATCH 077/198] Rev to engine and translator 1.5.0-SNAPSHOT --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7f3fd4a00..46a7b9d8d 100644 --- a/pom.xml +++ b/pom.xml @@ -56,8 +56,8 @@ 2.10.1 4.2.0 1.1.1 - 1.4.0 - 1.4.9 + 1.5.0-SNAPSHOT + 1.5.0-SNAPSHOT 1.3.0-SNAPSHOT 1.7.30 From 5bbeffedae5365dfd755b56751e5a58258062327 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 20 Jul 2020 21:52:38 -0600 Subject: [PATCH 078/198] Revert to translator 1.4.9 due to breakage --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 46a7b9d8d..c6bfbb9b3 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 4.2.0 1.1.1 1.5.0-SNAPSHOT - 1.5.0-SNAPSHOT + 1.4.9 1.3.0-SNAPSHOT 1.7.30 From 7c9de3e17ff448e752b7d31843aa7ea3b5fa368c Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 23 Jul 2020 12:11:04 -0600 Subject: [PATCH 079/198] changes to Observation to facilitate use in PainManager --- .../r4/providers/QuestionnaireProvider.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 767d30cdd..6a7bf5491 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -7,6 +7,7 @@ import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; +import java.util.Collections; import java.util.Date; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -48,6 +49,10 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setSubject(questionnaireResponse.getSubject()); + Coding qrCategoryCoding = new Coding(); + qrCategoryCoding.setCode("survey"); + qrCategoryCoding.setSystem("http://hl7.org/fhir/observation-category"); + obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); Coding qrCoding = new Coding(); qrCoding.setCode("74465-6"); qrCoding.setDisplay("Questionnaire response Document"); @@ -60,7 +65,24 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna case "Coding": obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); break; + case "boolean": + obs.setValue(new BooleanType(item.getAnswer().get(0).getValueBooleanType().booleanValue())); + break; } + Reference questionnaireResponseReference = new Reference(); + questionnaireResponseReference.setReference("QuestionnaireResponse" + "/" + questionnaireResponse.getIdElement().getIdPart()); + obs.setDerivedFrom(Collections.singletonList(questionnaireResponseReference)); + Extension linkIdExtension = new Extension(); + linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); + linkIdExtension.setValue(new StringType(item.getLinkId())); +// Extension refExtension = new Extension(); +// refExtension.setUrl(ExtensionEnum.GRCONTAINED.getUrl()); +// if(!mReport.hasId()){ +// mReport.setId(UUID.randomUUID().toString()); +// } +// refExtension.setValue(new Reference("#" + mReport.getId())); +// newGuidanceResponse.addExtension(refExtension); obs. + obs.addExtension(linkIdExtension); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); berc.setUrl("Observation/" + obs.getId()); From e99d73544a9ddd896c0a674e4ec9d8fefb7eb7ab Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 30 Jul 2020 09:29:30 -0600 Subject: [PATCH 080/198] Add notifications to travis build --- .travis.yml | 73 ++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/.travis.yml b/.travis.yml index 89cf7ff3e..705803b2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,53 +2,46 @@ language: java jdk: openjdk11 os: linux dist: xenial - services: - - docker - +- docker cache: timeout: 180 directories: - "$HOME/.m2/repository" - install: - - mvn install -U -DskipTests=true -Dmaven.javadoc.skip=true -B -V - +- mvn install -U -DskipTests=true -Dmaven.javadoc.skip=true -B -V script: - # master or PRs into master, use the release profile - - 'if [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B -P release; fi' - - 'if ! [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B; fi' - - -## export GPG details +- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B -P release; fi +- if ! [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B; fi before_deploy: - - 'if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import; fi' - - 'if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust; fi' - - 'echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin' - -# NOTE: tests were already run as part of the script phase +- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_SECRET_KEYS | base64 --decode + | $GPG_EXECUTABLE --import; fi +- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_OWNERTRUST | base64 --decode + | $GPG_EXECUTABLE --import-ownertrust; fi +- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin deploy: - # deploy develop as a snapshot - - provider: script - script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true && docker build . -t contentgroup/cqf-ruler:develop && docker push contentgroup/cqf-ruler:develop" - cleanup: false - skip_cleanup: true # This is the current correct option, but is soon to be deprecated by the above. - on: - branch: develop - # deploy master to production - - provider: script - script: "cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -P release -DskipTests=true && docker build . -t contentgroup/cqf-ruler && docker push contentgroup/cqf-ruler" - cleanup: false - skip_cleanup: true # This is the current correct option, but is soon to be deprecated by the above. - on: - branch: master - -# Remove the binaries generated by this build so that the cache isn't invalidated. -# Probably a better way to do this. +- provider: script + script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true + && docker build . -t contentgroup/cqf-ruler:develop && docker push contentgroup/cqf-ruler:develop + cleanup: false + skip_cleanup: true + on: + branch: develop +- provider: script + script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -P release + -DskipTests=true && docker build . -t contentgroup/cqf-ruler && docker push contentgroup/cqf-ruler + cleanup: false + skip_cleanup: true + on: + branch: master before_cache: - - rm -rf $HOME/.m2/repository/org/opencds/cqf/parent - - rm -rf $HOME/.m2/repository/org/opencds/cqf/common - - rm -rf $HOME/.m2/repository/org/opencds/cqf/dstu3 - - rm -rf $HOME/.m2/repository/org/opencds/cqf/r4 - - rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-dstu3 - - rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-r4 +- rm -rf $HOME/.m2/repository/org/opencds/cqf/parent +- rm -rf $HOME/.m2/repository/org/opencds/cqf/common +- rm -rf $HOME/.m2/repository/org/opencds/cqf/dstu3 +- rm -rf $HOME/.m2/repository/org/opencds/cqf/r4 +- rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-dstu3 +- rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-r4 +notifications: + slack: + rooms: + secure: PDB1pyN2TuKQ3Leafmyp1+DeHGJjh3oGSo+FVvoJELaMuwz+Ex4wEvFCmCgXwa6yA/AOl+BTAIByKgSHl3BeQ/5bdmy09nUJfW2sThqqOpqf9wMqdQyPIPs1W3rp9UDglVBMdPnwaCjab2amDfy//CyDGXt4E5KlIvxSpsF7Nb3Qm5rBWWXTkPmdM6tSxTybY2pAV0cHOnYoXQnDpEE19RDWcpihM0Z0fRmkF3HUzpnPJ4GqSUu8NDKVOEYZxrV7f+osnBeWLLbcUxY5Aw3om5C96ZbHn2YsxI5+jo9BfAlHsyuHkqoUjeF1O0Yd1J/ru8fgX2tN32BS3XSiqimWoXVz8p6fu44Usg92leFgLbfNN2XEdbjFI470435B6oRXCuAFS6HCA2j6Ovi3l8hxtWmBDRxLxcgF6Q2BvzzM/6y5W2aDTQ0CuhSV4n4mKSPAGg3hm1U1oyJBIdKzntcrE7tCcTzLeC1rCRnLGEFgz45UsRUwFOXlIZy0n8WyqYUv8deEHqa+IM17YbTgRericBFim4X7H6GdOsWn1dUTuzmX/MOWjEY5LDFPUDWE3CbjGbo6e8R/VRhAM5pIilFbaa7pr1h/P7hdEV3WiW1L2XryttEkVxEwS4EEGwaxpDOK8u2IYiWhu4pj/j52xIdMtBhvPc3qZQXql/W8eQNtI1A= From 4bd6c47159ed2bae56526e6fa47611a0620c0f21 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 30 Jul 2020 10:16:52 -0600 Subject: [PATCH 081/198] Update travis token --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 705803b2b..f0add6833 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,4 +44,4 @@ before_cache: notifications: slack: rooms: - secure: PDB1pyN2TuKQ3Leafmyp1+DeHGJjh3oGSo+FVvoJELaMuwz+Ex4wEvFCmCgXwa6yA/AOl+BTAIByKgSHl3BeQ/5bdmy09nUJfW2sThqqOpqf9wMqdQyPIPs1W3rp9UDglVBMdPnwaCjab2amDfy//CyDGXt4E5KlIvxSpsF7Nb3Qm5rBWWXTkPmdM6tSxTybY2pAV0cHOnYoXQnDpEE19RDWcpihM0Z0fRmkF3HUzpnPJ4GqSUu8NDKVOEYZxrV7f+osnBeWLLbcUxY5Aw3om5C96ZbHn2YsxI5+jo9BfAlHsyuHkqoUjeF1O0Yd1J/ru8fgX2tN32BS3XSiqimWoXVz8p6fu44Usg92leFgLbfNN2XEdbjFI470435B6oRXCuAFS6HCA2j6Ovi3l8hxtWmBDRxLxcgF6Q2BvzzM/6y5W2aDTQ0CuhSV4n4mKSPAGg3hm1U1oyJBIdKzntcrE7tCcTzLeC1rCRnLGEFgz45UsRUwFOXlIZy0n8WyqYUv8deEHqa+IM17YbTgRericBFim4X7H6GdOsWn1dUTuzmX/MOWjEY5LDFPUDWE3CbjGbo6e8R/VRhAM5pIilFbaa7pr1h/P7hdEV3WiW1L2XryttEkVxEwS4EEGwaxpDOK8u2IYiWhu4pj/j52xIdMtBhvPc3qZQXql/W8eQNtI1A= + secure: UcwCk/9xDrkLXtaHPXJAsJuyZN7MIzRunndBpxMk/fwiAF56ZscPMu5IlPN72x2TqdkDPDAykGnczQ/w76txFcIzyH9/06fmXh//8XL/YWpERsfCW9q1XOn+xTTXwrxF5f6eaAMqYlNDe5tXLCOHCEnuHpdFNnFoNasZ2dTqaDNP+jjPLlSO0zhVv5zW4d75lpklg2+SQra5smgKW4eXJO6z4mXFJtkhN/lKwlgKCKbC+G8o3QO+zSJouyeCmr6xdPkTob1va8jNuR1SRIlk943btmZAUlfLh9pUMO2Hhay1BJhXpFrO5ZE3eCwU9jaoCyoTTPcVBytOKKNbGr1miRM/fDdxDYUMGQbqmkKDMqTZzkeLSbQPH92bs2lUILZf0xr2wbi8mZICdz0HZwRkaLzF6okIeQ69/b7/QNbDXAxKlT0DaShtidVbUfwNb/mUOXc3fkjn8vShPn7n9xtdLQvrGR9bJVHHBIDYS+dge2EtdPzsrmHsEhGS153uytPgF+zcYwRN83Xpyjk78+AMQFmzorErz6I+74ZQyXFzN4Z2Z1SfeGGpBp7uUK7dDD1WYVk370hf5oU19euWdSWmzBDajrLlalH25VKt140lQBMU6QyDmdraWM0f0iut034QbTUisOo+O2Km0AhZdTVTVmZSyLOJ6Ec+lusU2AHtayM= From 53557703fbb60e09ce23caa1e6fbcd9eba0ff716 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 8 May 2020 16:41:24 -0600 Subject: [PATCH 082/198] Add param to library --- .../cqf/common/helpers/ClientHelperDos.java | 74 ++++++ .../providers/LibraryOperationsProvider.java | 213 +++++++++++++++++- .../opencds/cqf/r4/servlet/BaseServlet.java | 26 +-- 3 files changed, 287 insertions(+), 26 deletions(-) create mode 100644 common/src/main/java/org/opencds/cqf/common/helpers/ClientHelperDos.java diff --git a/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelperDos.java b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelperDos.java new file mode 100644 index 000000000..ce863fe8e --- /dev/null +++ b/common/src/main/java/org/opencds/cqf/common/helpers/ClientHelperDos.java @@ -0,0 +1,74 @@ +package org.opencds.cqf.common.helpers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.api.IClientInterceptor; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +// NOTICE: This is trash code. It's for a one-off. Don't fix it. +public class ClientHelperDos { + + public static IGenericClient getClient(FhirContext fhirContext, String url) { + fhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + return fhirContext.newRestfulGenericClient(url); + } + + // Overload in case you need to specify a specific version of the context + public static IGenericClient getClient(FhirContext fhirContext, org.hl7.fhir.dstu3.model.Endpoint endpoint) { + IGenericClient client = getClient(fhirContext, endpoint.getAddress()); + if (endpoint.hasHeader()) { + List headerList = endpoint.getHeader().stream().map(headerString -> headerString.asStringValue()) + .collect(Collectors.toList()); + registerAuth(client, headerList); + } + return client; + } + + public static IGenericClient getClient(FhirContext fhirContext, org.hl7.fhir.r4.model.Endpoint endpoint) { + IGenericClient client = getClient(fhirContext, endpoint.getAddress()); + if (endpoint.hasHeader()) { + List headerList = endpoint.getHeader().stream().map(headerString -> headerString.asStringValue()) + .collect(Collectors.toList()); + registerAuth(client, headerList); + } + return client; + } + + private static void registerAuth(IGenericClient client, List headerList) { + Map headerMap = setupHeaderMap(headerList); + for (Map.Entry entry : headerMap.entrySet()) { + IClientInterceptor headInterceptor = new org.opencds.cqf.cql.engine.fhir.terminology.HeaderInjectionInterceptor( + entry.getKey(), entry.getValue()); + client.registerInterceptor(headInterceptor); + } + } + + private static Map setupHeaderMap(List headerList) { + Map headerMap = new HashMap(); + String leftAuth = null; + String rightAuth = null; + if (headerList.size() < 1 || headerList.isEmpty()) { + leftAuth = null; + rightAuth = null; + headerMap.put(leftAuth, rightAuth); + } else { + for (String header : headerList) { + if (!header.contains(":")) { + throw new RuntimeException("Endpoint header must contain \":\" ."); + } + String[] authSplit = header.split(":"); + leftAuth = authSplit[0]; + rightAuth = authSplit[1]; + headerMap.put(leftAuth, rightAuth); + } + + } + return headerMap; + } + +} diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 8cba9ab53..6faf26e8a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -1,24 +1,61 @@ package org.opencds.cqf.r4.providers; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.Map; import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; +import org.cqframework.cql.cql2elm.CqlTranslatorException; import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; +import org.cqframework.cql.elm.execution.VersionedIdentifier; +import org.cqframework.cql.elm.tracking.TrackBack; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Narrative; import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.opencds.cqf.common.evaluation.LibraryLoader; +import org.opencds.cqf.common.helpers.ClientHelperDos; +import org.opencds.cqf.common.helpers.DateHelper; +import org.opencds.cqf.common.helpers.TranslatorHelper; +import org.opencds.cqf.common.helpers.UsingHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.providers.LibrarySourceProvider; +import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; +import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.data.CompositeDataProvider; +import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.CqlEngine; +import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.fhir.model.FhirModelResolver; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; +import org.opencds.cqf.cql.engine.fhir.retrieve.RestFhirRetrieveProvider; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; +import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; +import org.opencds.cqf.cql.engine.retrieve.RetrieveProvider; +import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.library.r4.NarrativeProvider; +import org.opencds.cqf.r4.helpers.FhirMeasureBundler; +import org.opencds.cqf.r4.helpers.LibraryHelper; +import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; @@ -27,6 +64,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; @@ -36,12 +74,16 @@ public class LibraryOperationsProvider implements LibraryResolutionProvider resolvedParameters = new HashMap<>(); + + if (parameters != null) { + for (Parameters.ParametersParameterComponent pc : parameters.getParameter()) { + resolvedParameters.put(pc.getName(), pc.getValue()); + } + } + + if (periodStart != null && periodEnd != null) { + // resolve the measurement period + Interval measurementPeriod = new Interval(DateHelper.resolveRequestDate(periodStart, true), true, + DateHelper.resolveRequestDate(periodEnd, false), true); + + resolvedParameters.put("Measurement Period", + new Interval(DateTime.fromJavaDate((Date) measurementPeriod.getStart()), true, + DateTime.fromJavaDate((Date) measurementPeriod.getEnd()), true)); + } + + if (productLine != null) { + resolvedParameters.put("Product Line", productLine); + } + + EvaluationResult evalResult = engine.evaluate(libraryIdentifier, + Pair.of(contextParam != null ? contextParam : "Unspecified", patientId == null ? "null" : patientId), + Collections.singletonMap(null, null)); + + List results = new ArrayList<>(); + FhirMeasureBundler bundler = new FhirMeasureBundler(); + + if (evalResult != null && evalResult.expressionResults != null) { + for (Map.Entry def : evalResult.expressionResults.entrySet()) { + + Parameters result = new Parameters(); + + try { + result.setId(def.getKey()); + Object res = def.getValue(); + // String location = String.format("[%d:%d]", + // locations.get(def.getName()).get(0), + // locations.get(def.getName()).get(1)); + // result.addParameter().setName("location").setValue(new StringType(location)); + + // Object res = def instanceof org.cqframework.cql.elm.execution.FunctionDef + // ? "Definition successfully validated" + // : def.getExpression().evaluate(context); + + if (res == null) { + result.addParameter().setName("value").setValue(new StringType("null")); + } else if (res instanceof List) { + if (((List) res).size() > 0 && ((List) res).get(0) instanceof Resource) { + if (executionResults != null && executionResults.equals("Summary")) { + result.addParameter().setName("value") + .setValue(new StringType(((Resource) ((List) res).get(0)).getIdElement() + .getResourceType() + "/" + + ((Resource) ((List) res).get(0)).getIdElement().getIdPart())); + } else { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } + } else { + result.addParameter().setName("value").setValue(new StringType(res.toString())); + } + } else if (res instanceof Iterable) { + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + } else if (res instanceof Resource) { + if (executionResults != null && executionResults.equals("Summary")) { + result.addParameter().setName("value") + .setValue(new StringType(((Resource) res).getIdElement().getResourceType() + "/" + + ((Resource) res).getIdElement().getIdPart())); + } else { + result.addParameter().setName("value").setResource((Resource) res); + } + } else { + result.addParameter().setName("value").setValue(new StringType(res.toString())); + } + + result.addParameter().setName("resultType").setValue(new StringType(resolveType(res))); + } catch (RuntimeException re) { + re.printStackTrace(); + + String message = re.getMessage() != null ? re.getMessage() : re.getClass().getName(); + result.addParameter().setName("error").setValue(new StringType(message)); + } + results.add(result); + } + } + + return bundler.bundle(results); + } + // TODO: Figure out if we should throw an exception or something here. @Override public void update(Library library) { @@ -207,4 +389,33 @@ private Iterable resolveLibraries(List> getLocations(org.hl7.elm.r1.Library library) { + Map> locations = new HashMap<>(); + + if (library.getStatements() == null) + return locations; + + for (org.hl7.elm.r1.ExpressionDef def : library.getStatements().getDef()) { + int startLine = def.getTrackbacks().isEmpty() ? 0 : def.getTrackbacks().get(0).getStartLine(); + int startChar = def.getTrackbacks().isEmpty() ? 0 : def.getTrackbacks().get(0).getStartChar(); + List loc = Arrays.asList(startLine, startChar); + locations.put(def.getName(), loc); + } + + return locations; + } + + private String resolveType(Object result) { + String type = result == null ? "Null" : result.getClass().getSimpleName(); + switch (type) { + case "BigDecimal": + return "Decimal"; + case "ArrayList": + return "List"; + case "FhirBundleCursor": + return "Retrieve"; + } + return type; + } } \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 016315779..ed92ee933 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -159,29 +159,6 @@ protected void initialize() throws ServletException { if (serverAddress != null && serverAddress.length() > 0) { setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); } - - registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); - - if (HapiProperties.getCorsEnabled()) { - CorsConfiguration config = new CorsConfiguration(); - config.addAllowedHeader("x-fhir-starter"); - config.addAllowedHeader("Origin"); - config.addAllowedHeader("Accept"); - config.addAllowedHeader("X-Requested-With"); - config.addAllowedHeader("Content-Type"); - config.addAllowedHeader("Authorization"); - config.addAllowedHeader("Cache-Control"); - - config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin()); - - config.addExposedHeader("Location"); - config.addExposedHeader("Content-Location"); - config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); - - // Create the interceptor and register it - CorsInterceptor interceptor = new CorsInterceptor(config); - registerInterceptor(interceptor); - } } protected NarrativeProvider getNarrativeProvider() { @@ -207,7 +184,7 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, // Library processing LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider( - (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider); + (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider, registry, localSystemTerminologyProvider); this.registerProvider(libraryProvider); // CQL Execution @@ -253,7 +230,6 @@ private void resolveProviders(EvaluationProviderFactory providerFactory, ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); this.registerProvider(observationProvider); } - } protected IFhirResourceDao getDao(Class clazz) { From 76bd765509d332cb6184a73928ada0aa6972f792 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sun, 10 May 2020 13:05:01 -0600 Subject: [PATCH 083/198] Fix bug with context parameters --- .../org/opencds/cqf/r4/providers/LibraryOperationsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 6faf26e8a..a6af23e0e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -249,7 +249,7 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" EvaluationResult evalResult = engine.evaluate(libraryIdentifier, Pair.of(contextParam != null ? contextParam : "Unspecified", patientId == null ? "null" : patientId), - Collections.singletonMap(null, null)); + resolvedParameters); List results = new ArrayList<>(); FhirMeasureBundler bundler = new FhirMeasureBundler(); From 54d4da4a82eb33e627e91cd9eb918f38b7878590 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 18 May 2020 13:49:54 -0600 Subject: [PATCH 084/198] Fixes for remote terminology --- .../cqf/r4/providers/LibraryOperationsProvider.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index a6af23e0e..8f62c53e7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -210,14 +210,19 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" terminologyProvider = this.defaultTerminologyProvider; } + // TODO: If the terminology server and the data server are the same and the data server supports fast "in" + // you don't need to expand. Otherwise, do. DataProvider dataProvider; if (dataEndpoint != null) { IGenericClient client = ClientHelperDos.getClient(resolver.getFhirContext(), dataEndpoint); - dataProvider = new CompositeDataProvider(resolver, - new RestFhirRetrieveProvider(new SearchParameterResolver(resolver.getFhirContext()), client)); + RestFhirRetrieveProvider retriever = new RestFhirRetrieveProvider(new SearchParameterResolver(resolver.getFhirContext()), client); + retriever.setTerminologyProvider(terminologyProvider); + retriever.setExpandValueSets(true); + dataProvider = new CompositeDataProvider(resolver, retriever); } else { RetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(resolver.getFhirContext())); + dataProvider = new CompositeDataProvider(resolver, retriever); } From fd152dc606a3da862a94b24a3ef5bafe53a75259 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 18 May 2020 13:58:22 -0600 Subject: [PATCH 085/198] Add optimization for local vs remote terminology --- .../r4/providers/LibraryOperationsProvider.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 8f62c53e7..5335cad76 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -209,19 +209,25 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { terminologyProvider = this.defaultTerminologyProvider; } - - // TODO: If the terminology server and the data server are the same and the data server supports fast "in" - // you don't need to expand. Otherwise, do. + DataProvider dataProvider; if (dataEndpoint != null) { IGenericClient client = ClientHelperDos.getClient(resolver.getFhirContext(), dataEndpoint); RestFhirRetrieveProvider retriever = new RestFhirRetrieveProvider(new SearchParameterResolver(resolver.getFhirContext()), client); retriever.setTerminologyProvider(terminologyProvider); - retriever.setExpandValueSets(true); + if (terminologyEndpoint != null && !terminologyEndpoint.getAddress().equals(dataEndpoint.getAddress())) { + retriever.setExpandValueSets(true); + } + dataProvider = new CompositeDataProvider(resolver, retriever); } else { RetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(resolver.getFhirContext())); + retriever.setTerminologyProvider(terminologyProvider); + // Assume it's a different server, therefore need to expand. + if (terminologyEndpoint != null) { + retriever.setExpandValueSets(true); + } dataProvider = new CompositeDataProvider(resolver, retriever); } From 0c0afceb71f193e1b0266f47ddb9777527f3a47a Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 31 Jul 2020 23:33:45 -0600 Subject: [PATCH 086/198] Fix error with merge --- .../org/opencds/cqf/r4/providers/LibraryOperationsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 5335cad76..046b9d5b8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -221,7 +221,7 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" dataProvider = new CompositeDataProvider(resolver, retriever); } else { - RetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, + JpaFhirRetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(resolver.getFhirContext())); retriever.setTerminologyProvider(terminologyProvider); // Assume it's a different server, therefore need to expand. From 21936c12c11afe8d87419ce86d40169342c88acc Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 31 Jul 2020 23:40:46 -0600 Subject: [PATCH 087/198] Fixed missing CORS options --- .../opencds/cqf/r4/servlet/BaseServlet.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index ed92ee933..b09b91d6a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -159,6 +159,29 @@ protected void initialize() throws ServletException { if (serverAddress != null && serverAddress.length() > 0) { setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); } + + registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); + + if (HapiProperties.getCorsEnabled()) { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedHeader("x-fhir-starter"); + config.addAllowedHeader("Origin"); + config.addAllowedHeader("Accept"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedHeader("Content-Type"); + config.addAllowedHeader("Authorization"); + config.addAllowedHeader("Cache-Control"); + + config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin()); + + config.addExposedHeader("Location"); + config.addExposedHeader("Content-Location"); + config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")); + + // Create the interceptor and register it + CorsInterceptor interceptor = new CorsInterceptor(config); + registerInterceptor(interceptor); + } } protected NarrativeProvider getNarrativeProvider() { From d592e1e161ec67929421d4beeb6216eba833c402 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 3 Aug 2020 11:56:51 -0600 Subject: [PATCH 088/198] Fix not using local terminology provider --- .../org/opencds/cqf/r4/providers/LibraryOperationsProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 046b9d5b8..4ae6c34dc 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -215,7 +215,7 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" IGenericClient client = ClientHelperDos.getClient(resolver.getFhirContext(), dataEndpoint); RestFhirRetrieveProvider retriever = new RestFhirRetrieveProvider(new SearchParameterResolver(resolver.getFhirContext()), client); retriever.setTerminologyProvider(terminologyProvider); - if (terminologyEndpoint != null && !terminologyEndpoint.getAddress().equals(dataEndpoint.getAddress())) { + if (terminologyEndpoint == null ||(terminologyEndpoint != null && !terminologyEndpoint.getAddress().equals(dataEndpoint.getAddress()))) { retriever.setExpandValueSets(true); } From 967d952a84af15fac9a2566a54af8197420e57e8 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 5 Aug 2020 20:39:44 -0600 Subject: [PATCH 089/198] enabled EnableDateRangeOptimization Translator option by default --- .../org/opencds/cqf/common/evaluation/LibraryLoader.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java index f21eee88b..a5ede19aa 100644 --- a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java +++ b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java @@ -80,8 +80,9 @@ private Library loadLibrary(VersionedIdentifier libraryIdentifier) { .withVersion(libraryIdentifier.getVersion()); ArrayList errors = new ArrayList<>(); - org.hl7.elm.r1.Library translatedLibrary = libraryManager - .resolveLibrary(identifier, CqlTranslatorOptions.defaultOptions(), errors).getLibrary(); + CqlTranslatorOptions translatorOptions = CqlTranslatorOptions.defaultOptions(); + translatorOptions.getOptions().add(CqlTranslator.Options.EnableDateRangeOptimization); + org.hl7.elm.r1.Library translatedLibrary = libraryManager.resolveLibrary(identifier, translatorOptions, errors).getLibrary(); if (CqlTranslatorException.HasErrors(errors)) { throw new IllegalArgumentException(errorsToString(errors)); From 381bd099d116d07c300760a674632073176372a3 Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 10 Aug 2020 07:33:49 -0600 Subject: [PATCH 090/198] pre-new gic branch --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 11d0cfee3..a956f70ca 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -252,6 +252,10 @@ public Parameters careGapsReport(@RequiredParam(name = "periodStart") String per // TODO: topic should allow many + if(null != subjectGroup && subjectGroup.length() > 0){ + + } + if (practitionerRef == null || practitionerRef.equals("")) { return new Parameters().addParameter( new Parameters.ParametersParameterComponent() From bcddefa888fb41a068d7d1e4900349fefc76f577 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 10 Aug 2020 10:37:30 -0600 Subject: [PATCH 091/198] Add addtional data parameter to evaluate --- pom.xml | 6 ++++++ .../r4/providers/LibraryOperationsProvider.java | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index c6bfbb9b3..e07cbb09b 100644 --- a/pom.xml +++ b/pom.xml @@ -57,6 +57,7 @@ 4.2.0 1.1.1 1.5.0-SNAPSHOT + 1.0.0-SNAPSHOT 1.4.9 1.3.0-SNAPSHOT 1.7.30 @@ -92,6 +93,11 @@ + + org.opencds.cqf.cql + evaluator.execution + ${cql-evaluator.version} + org.opencds.cqf tooling diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 4ae6c34dc..fcb015259 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -28,6 +28,7 @@ import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.opencds.cqf.cds.providers.PriorityRetrieveProvider; import org.opencds.cqf.common.evaluation.LibraryLoader; import org.opencds.cqf.common.helpers.ClientHelperDos; import org.opencds.cqf.common.helpers.DateHelper; @@ -51,6 +52,7 @@ import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.opencds.cqf.cql.evaluator.execution.provider.BundleRetrieveProvider; import org.opencds.cqf.library.r4.NarrativeProvider; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; @@ -185,7 +187,8 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" @OperationParam(name = "dataEndpoint") Endpoint dataEndpoint, @OperationParam(name = "context") String contextParam, @OperationParam(name = "executionResults") String executionResults, - @OperationParam(name = "parameters") Parameters parameters) { + @OperationParam(name = "parameters") Parameters parameters, + @OperationParam(name = "additionalData") Bundle additionalData) { if (patientId == null && contextParam != null && contextParam.equals("Patient")) { throw new IllegalArgumentException("Must specify a patientId when executing in Patient context."); @@ -209,6 +212,8 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { terminologyProvider = this.defaultTerminologyProvider; } + + BundleRetrieveProvider bundleProvider = new BundleRetrieveProvider(resolver, additionalData); DataProvider dataProvider; if (dataEndpoint != null) { @@ -219,7 +224,9 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" retriever.setExpandValueSets(true); } - dataProvider = new CompositeDataProvider(resolver, retriever); + PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); + + dataProvider = new CompositeDataProvider(resolver, priorityProvider); } else { JpaFhirRetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(resolver.getFhirContext())); @@ -229,7 +236,9 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" retriever.setExpandValueSets(true); } - dataProvider = new CompositeDataProvider(resolver, retriever); + PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); + + dataProvider = new CompositeDataProvider(resolver, priorityProvider); } LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); From ba7477bb6a8c0b8f58d5d0f44cb4fe5557ccbce0 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 12 Aug 2020 11:15:10 -0600 Subject: [PATCH 092/198] Fix terminology provider and endpoint null pointer exceptions in ruler --- .../providers/LibraryOperationsProvider.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index fcb015259..1419cdc68 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -212,8 +212,6 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { terminologyProvider = this.defaultTerminologyProvider; } - - BundleRetrieveProvider bundleProvider = new BundleRetrieveProvider(resolver, additionalData); DataProvider dataProvider; if (dataEndpoint != null) { @@ -224,9 +222,17 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" retriever.setExpandValueSets(true); } - PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); + if (additionalData != null) { + BundleRetrieveProvider bundleProvider = new BundleRetrieveProvider(resolver, additionalData, terminologyProvider); + PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); + dataProvider = new CompositeDataProvider(resolver, priorityProvider); + } + else + { + dataProvider = new CompositeDataProvider(resolver, retriever); + } - dataProvider = new CompositeDataProvider(resolver, priorityProvider); + } else { JpaFhirRetrieveProvider retriever = new JpaFhirRetrieveProvider(this.registry, new SearchParameterResolver(resolver.getFhirContext())); @@ -236,9 +242,15 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" retriever.setExpandValueSets(true); } - PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); - - dataProvider = new CompositeDataProvider(resolver, priorityProvider); + if (additionalData != null) { + BundleRetrieveProvider bundleProvider = new BundleRetrieveProvider(resolver, additionalData, terminologyProvider); + PriorityRetrieveProvider priorityProvider = new PriorityRetrieveProvider(bundleProvider, retriever); + dataProvider = new CompositeDataProvider(resolver, priorityProvider); + } + else + { + dataProvider = new CompositeDataProvider(resolver, retriever); + } } LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); From 1f7417785207e8eebc877031347b943d40a7d1ff Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 12 Aug 2020 16:09:51 -0600 Subject: [PATCH 093/198] Turned off enableDateRangeOptimization --- .../java/org/opencds/cqf/common/evaluation/LibraryLoader.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java index a5ede19aa..f8004cb07 100644 --- a/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java +++ b/common/src/main/java/org/opencds/cqf/common/evaluation/LibraryLoader.java @@ -80,9 +80,7 @@ private Library loadLibrary(VersionedIdentifier libraryIdentifier) { .withVersion(libraryIdentifier.getVersion()); ArrayList errors = new ArrayList<>(); - CqlTranslatorOptions translatorOptions = CqlTranslatorOptions.defaultOptions(); - translatorOptions.getOptions().add(CqlTranslator.Options.EnableDateRangeOptimization); - org.hl7.elm.r1.Library translatedLibrary = libraryManager.resolveLibrary(identifier, translatorOptions, errors).getLibrary(); + org.hl7.elm.r1.Library translatedLibrary = libraryManager.resolveLibrary(identifier, CqlTranslatorOptions.defaultOptions(), errors).getLibrary(); if (CqlTranslatorException.HasErrors(errors)) { throw new IllegalArgumentException(errorsToString(errors)); From 97019b8840c0d5e133b1685e1dc14244486d29ca Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Thu, 13 Aug 2020 15:27:14 -0600 Subject: [PATCH 094/198] #240: Fixed library loader attempting to load non-logic-libraries --- .../cqf/dstu3/helpers/LibraryHelper.java | 47 +++++++++++------ .../opencds/cqf/r4/helpers/LibraryHelper.java | 50 +++++++++++++------ 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 5e187525a..f59466d00 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -7,12 +7,8 @@ import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; import org.cqframework.cql.elm.execution.VersionedIdentifier; -import org.hl7.fhir.dstu3.model.Measure; -import org.hl7.fhir.dstu3.model.PlanDefinition; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.RelatedArtifact; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.RelatedArtifact.RelatedArtifactType; -import org.hl7.fhir.dstu3.model.Resource; import org.opencds.cqf.common.evaluation.LibraryLoader; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.providers.LibrarySourceProvider; @@ -59,8 +55,10 @@ public static List loadLibraries(Meas } org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(id); - libraries.add(libraryLoader - .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))); + if (library != null && isLogicLibrary(library)) { + libraries.add(libraryLoader + .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))); + } } VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); @@ -69,14 +67,12 @@ public static List loadLibraries(Meas if (artifact.hasType() && artifact.getType().equals(RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) { if (artifact.getResource().getReferenceElement().getResourceType().equals("Library")) { org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); - - if (library == null) { - throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource().getReference())); - } - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + if (library != null && isLogicLibrary(library)) { + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); + } } } } @@ -89,6 +85,29 @@ public static List loadLibraries(Meas return libraries; } + private static boolean isLogicLibrary(org.hl7.fhir.dstu3.model.Library library) { + if (library == null) { + return false; + } + + if (!library.hasType()) { + return false; + } + + if (!library.getType().hasCoding()) { + return false; + } + + for (Coding c : library.getType().getCoding()) { + if (c.hasSystem() && c.getSystem().equals("http://hl7.org/fhir/library-type") + && c.hasCode() && c.getCode().equals("logic-library")) { + return true; + } + } + + return false; + } + public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 6a6fe6520..3779ab74c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -8,11 +8,7 @@ import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; import org.cqframework.cql.elm.execution.VersionedIdentifier; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.Measure; -import org.hl7.fhir.r4.model.PlanDefinition; -import org.hl7.fhir.r4.model.RelatedArtifact; -import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.evaluation.LibraryLoader; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.providers.LibrarySourceProvider; @@ -55,16 +51,17 @@ public static List loadLibraries(Meas // We just loaded it into the server so we can access it by Id org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(id); - - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); + if (library != null && isLogicLibrary(library)) { + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); + } } VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { - if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.hasResource()) { + if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource()) { org.hl7.fhir.r4.model.Library library = null; // Raw references to Library/libraryId or libraryId if (artifact.getResource().startsWith("Library/") || ! artifact.getResource().contains("/")) { @@ -75,13 +72,11 @@ else if (artifact.getResource().contains(("/Library/"))) { library = libraryResourceProvider.resolveLibraryByCanonicalUrl(artifact.getResource()); } - if (library == null) { - throw new IllegalArgumentException(String.format("Unable to resolve library reference: %s", artifact.getResource())); + if (library != null && isLogicLibrary(library)) { + libraries.add( + libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) + ); } - - libraries.add( - libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) - ); } } @@ -93,6 +88,29 @@ else if (artifact.getResource().contains(("/Library/"))) { return libraries; } + private static boolean isLogicLibrary(org.hl7.fhir.r4.model.Library library) { + if (library == null) { + return false; + } + + if (!library.hasType()) { + return false; + } + + if (!library.getType().hasCoding()) { + return false; + } + + for (Coding c : library.getType().getCoding()) { + if (c.hasSystem() && c.getSystem().equals("http://terminology.hl7.org/CodeSystem/library-type") + && c.hasCode() && c.getCode().equals("logic-library")) { + return true; + } + } + + return false; + } + public static Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { From 90ae165f2e82b7d37197cab3bc2061eb27c52536 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 13 Aug 2020 15:31:03 -0600 Subject: [PATCH 095/198] Update to latest GIC implementation --- .../providers/MeasureOperationsProvider.java | 195 ++++++++++-------- 1 file changed, 110 insertions(+), 85 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 5bc76f871..50f4ce1fe 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,41 +1,21 @@ package org.opencds.cqf.r4.providers; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import javax.servlet.http.HttpServletRequest; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.*; +import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Composition; -import org.hl7.fhir.r4.model.DetectedIssue; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.ListResource; -import org.hl7.fhir.r4.model.Measure; -import org.hl7.fhir.r4.model.MeasureReport; -import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Narrative; -import org.hl7.fhir.r4.model.Organization; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.RelatedArtifact; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueEvidenceComponent; -import org.hl7.fhir.r4.model.DetectedIssue.DetectedIssueStatus; -import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.library.r4.NarrativeProvider; import org.opencds.cqf.measure.r4.CqfMeasure; @@ -47,22 +27,17 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.rest.annotation.IdParam; -import ca.uhn.fhir.rest.annotation.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; -import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import static org.opencds.cqf.common.helpers.ClientHelper.getClient; + public class MeasureOperationsProvider { private NarrativeProvider narrativeProvider; @@ -245,40 +220,71 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Parameters careGapsReport(@OperationParam(name = "periodStart") String periodStart, - @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, - @OperationParam(name = "subjectGroup") String subjectGroup, @OperationParam(name = "topic") String topic, - @OperationParam(name = "practitioner") String practitionerRef) { - - // TODO: topic should allow many - - if(null != subjectGroup && subjectGroup.length() > 0){ - + @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, + @OperationParam(name = "topic") String topic,@OperationParam(name = "practitioner") String practitioner, + @OperationParam(name = "measure") String measure){ + //TODO: status - optional if null all gaps - if closed-gap code only those gaps that are closed if open-gap code only those that are open + //TODO: topic should allow many and be a union of them + //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." + Parameters returnParams = new Parameters(); + if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure)) { + if(subject.startsWith("Patient/")){ + returnParams.addParameter(new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + subject) + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure))); + return returnParams; + }else if(subject.startsWith("Group/")) { + (getPatientListFromGroup(subject)) + .forEach(groupSubject -> returnParams.addParameter(new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + groupSubject) + .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure)))); + } + return returnParams; } - - if (practitionerRef == null || practitionerRef.equals("")) { + if (practitioner == null || practitioner.equals("")) { return new Parameters().addParameter( - new Parameters.ParametersParameterComponent() - .setName("Gaps in Care Report - " + subject) - .setResource(patientCareGap(periodStart, periodEnd, subject, topic))); + new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + subject) + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure))); } + return returnParams; + } - - - Parameters parameters = new Parameters(); + private List getPatientListFromGroup(String subjectGroupRef){ + List patientList = new ArrayList<>(); - return parameters; + DataProvider dataProvider = this.factory.createDataProvider("FHIR", "4"); + Iterable groupRetrieve = dataProvider.retrieve("Group", "id", subjectGroupRef, "Group", null, null, null, + null, null, null, null, null); + Group group; + if (groupRetrieve.iterator().hasNext()) { + group = (Group) groupRetrieve.iterator().next(); + group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); + } + return patientList; } - private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic) { + private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, + String practitioner, String measure){ + if(periodStart == null || periodStart.equals("") || + periodEnd == null || periodEnd.equals("")){ + throw new IllegalArgumentException("periodStart and periodEnd are required."); + } if (subject == null || subject.equals("")) { throw new IllegalArgumentException("Subject is required."); } + if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { + throw new IllegalArgumentException("Subject must follow the format of either Patient/ID OR Group/ID."); + } + return true; + } + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure) { //TODO: this is an org hack. Need to figure out what the right thing is. IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); - List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); - - SearchParameterMap theParams = new SearchParameterMap(); + var org = orgDao.search(new SearchParameterMap()).getResources(0, 1); + + SearchParameterMap theParams = new SearchParameterMap(); // if (theId != null) { // var measureParam = new StringParam(theId.getIdPart()); @@ -286,48 +292,47 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // } if (topic != null && !topic.equals("")) { - TokenParam topicParam = new TokenParam(topic); + var topicParam = new TokenParam(topic); theParams.add("topic", topicParam); - } - - List measures = this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); - + } + List measures = getMeasureList(theParams, measure); + Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); - Composition composition = new Composition(); + Composition composition = new Composition(); composition.setStatus(Composition.CompositionStatus.FINAL) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) .setTitle("Care Gap Report"); List reports = new ArrayList<>(); - List detectedIssues = new ArrayList(); + List detectedIssues = new ArrayList(); MeasureReport report = null; - + for (IBaseResource resource : measures) { - - Measure measure = (Measure) resource; + + Measure measureResource = (Measure) resource; Composition.SectionComponent section = new Composition.SectionComponent(); - if (measure.hasTitle()) { - section.setTitle(measure.getTitle()); + if (measureResource.hasTitle()) { + section.setTitle(measureResource.getTitle()); } - + // TODO - this is configured for patient-level evaluation only - report = evaluateMeasure(measure.getIdElement(), periodStart, periodEnd, null, null, subject, null, + report = evaluateMeasure(measureResource.getIdElement(), periodStart, periodEnd, null, "patient", subject, null, null, null, null, null, null); - + report.setId(UUID.randomUUID().toString()); report.setDate(new Date()); - report.setImprovementNotation(measure.getImprovementNotation()); - //TODO: this is an org hack + report.setImprovementNotation(measureResource.getImprovementNotation()); + //TODO: this is an org hack && requires an Organization to be in the ruler report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); report.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/indv-measurereport-deqm")); section.setFocus(new Reference("MeasureReport/" + report.getId())); //TODO: DetectedIssue //section.addEntry(new Reference("MeasureReport/" + report.getId())); - if (report.hasGroup() && measure.hasScoring()) { + if (report.hasGroup() && measureResource.hasScoring()) { int numerator = 0; int denominator = 0; for (MeasureReport.MeasureReportGroupComponent group : report.getGroup()) { @@ -356,8 +361,8 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje //TODO: implement this per the spec //Holding off on implementiation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) double proportion = 0.0; - if (measure.getScoring().hasCoding() && denominator != 0) { - for (Coding coding : measure.getScoring().getCoding()) { + if (measureResource.getScoring().hasCoding() && denominator != 0) { + for (Coding coding : measureResource.getScoring().getCoding()) { if (coding.hasCode() && coding.getCode().equals("proportion")) { if (denominator != 0.0 ) { proportion = numerator / denominator; @@ -368,27 +373,27 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // TODO - this is super hacky ... change once improvementNotation is specified // as a code - String improvementNotation = measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); + String improvementNotation = measureResource.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); if ( ((improvementNotation.equals("increase")) && (proportion < 1.0)) || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { DetectedIssue detectedIssue = new DetectedIssue(); detectedIssue.setId(UUID.randomUUID().toString()); - detectedIssue.setStatus(DetectedIssueStatus.FINAL); + detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); - detectedIssue.getEvidence().add(new DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); + detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); CodeableConcept code = new CodeableConcept() .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); detectedIssue.setCode(code); - + section.addEntry( - new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); - composition.addSection(section); - - detectedIssues.add(detectedIssue); - reports.add(report); - } + new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); + composition.addSection(section); + + detectedIssues.add(detectedIssue); + reports.add(report); + } // TODO - add other types of improvement notation cases } @@ -407,6 +412,26 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje return careGapReport; } + private List getMeasureList(SearchParameterMap theParams, String measure){ + if(null != measure && measure.length() > 0){ + List finalMeasureList = new ArrayList<>(); + List allMeasures = this.measureResourceProvider + .getDao() + .search(theParams) + .getResources(0, 1000); + for(String singleName: measure.split(",")){ + allMeasures.forEach(measureResource -> { + if(((Measure)measureResource).getName().equalsIgnoreCase(singleName.trim())) { + finalMeasureList.add(measureResource); + } + }); + } + return finalMeasureList; + }else { + return this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); + } + } + @Operation(name = "$collect-data", idempotent = true, type = Measure.class) public Parameters collectData(@IdParam IdType theId, @OperationParam(name = "periodStart") String periodStart, @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "patient") String patientRef, From 3f7cb1e6c52544de7b715cab5f18fae7943c503f Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 13 Aug 2020 15:59:19 -0600 Subject: [PATCH 096/198] Update to remove var keyword --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 50f4ce1fe..e433bd7c9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -282,7 +282,7 @@ private Boolean careGapParameterValidation(String periodStart, String periodEnd, private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure) { //TODO: this is an org hack. Need to figure out what the right thing is. IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); - var org = orgDao.search(new SearchParameterMap()).getResources(0, 1); + List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); SearchParameterMap theParams = new SearchParameterMap(); @@ -292,7 +292,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // } if (topic != null && !topic.equals("")) { - var topicParam = new TokenParam(topic); + TokenParam topicParam = new TokenParam(topic); theParams.add("topic", topicParam); } List measures = getMeasureList(theParams, measure); From 820cefa92fcba039a784f81e1377f99dc62f6d6f Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 13 Aug 2020 18:10:30 -0600 Subject: [PATCH 097/198] Protect against empty Org and historical measures --- .../providers/MeasureOperationsProvider.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index e433bd7c9..9611ae9d9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,6 +1,7 @@ package org.opencds.cqf.r4.providers; import java.util.*; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; @@ -310,8 +311,8 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje MeasureReport report = null; for (IBaseResource resource : measures) { - Measure measureResource = (Measure) resource; + Composition.SectionComponent section = new Composition.SectionComponent(); if (measureResource.hasTitle()) { @@ -326,7 +327,9 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje report.setDate(new Date()); report.setImprovementNotation(measureResource.getImprovementNotation()); //TODO: this is an org hack && requires an Organization to be in the ruler - report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); + if (org != null && org.size() > 0) { + report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); + } report.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/indv-measurereport-deqm")); section.setFocus(new Reference("MeasureReport/" + report.getId())); //TODO: DetectedIssue @@ -413,22 +416,32 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } private List getMeasureList(SearchParameterMap theParams, String measure){ - if(null != measure && measure.length() > 0){ + if(measure != null && measure.length() > 0){ List finalMeasureList = new ArrayList<>(); List allMeasures = this.measureResourceProvider .getDao() .search(theParams) .getResources(0, 1000); for(String singleName: measure.split(",")){ + if (singleName.equals("")) { + continue; + } allMeasures.forEach(measureResource -> { if(((Measure)measureResource).getName().equalsIgnoreCase(singleName.trim())) { - finalMeasureList.add(measureResource); + if (measureResource != null) { + finalMeasureList.add(measureResource); + } } }); } return finalMeasureList; }else { - return this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000); + return + //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. + this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) + .stream() + .filter(resource -> ((Measure)resource).getUrl() != null && !((Measure)resource).getUrl().equals("")) + .collect(Collectors.toList()); } } From aa86bce806866741aefc79720d7e129c5ca7a553 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 13 Aug 2020 18:31:45 -0600 Subject: [PATCH 098/198] Just a comment --- r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 3779ab74c..eca9a3bb7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -36,6 +36,7 @@ public static List loadLibraries(Meas List libraries = new ArrayList(); // load libraries + //TODO: if there's a bad measure argument, this blows up for an obscure error for (CanonicalType ref : measure.getLibrary()) { // if library is contained in measure, load it into server String id = CanonicalHelper.getId(ref); From 20ac4c91eca6d682efad2ef462d4d0c0c85b482b Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 14 Aug 2020 08:43:49 -0600 Subject: [PATCH 099/198] additional parameter validation to meet spec --- .../providers/MeasureOperationsProvider.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 9611ae9d9..b739624ba 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -5,10 +5,8 @@ import javax.servlet.http.HttpServletRequest; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; @@ -37,8 +35,6 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import static org.opencds.cqf.common.helpers.ClientHelper.getClient; - public class MeasureOperationsProvider { private NarrativeProvider narrativeProvider; @@ -223,22 +219,23 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, public Parameters careGapsReport(@OperationParam(name = "periodStart") String periodStart, @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, @OperationParam(name = "topic") String topic,@OperationParam(name = "practitioner") String practitioner, - @OperationParam(name = "measure") String measure){ + @OperationParam(name = "measure") String measure, @OperationParam(name="status")String status, + @OperationParam(name = "organization") String organization){ //TODO: status - optional if null all gaps - if closed-gap code only those gaps that are closed if open-gap code only those that are open //TODO: topic should allow many and be a union of them //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." Parameters returnParams = new Parameters(); - if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure)) { + if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { if(subject.startsWith("Patient/")){ returnParams.addParameter(new Parameters.ParametersParameterComponent() .setName("Gaps in Care Report - " + subject) - .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure))); + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); return returnParams; }else if(subject.startsWith("Group/")) { (getPatientListFromGroup(subject)) .forEach(groupSubject -> returnParams.addParameter(new Parameters.ParametersParameterComponent() .setName("Gaps in Care Report - " + groupSubject) - .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure)))); + .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status)))); } return returnParams; } @@ -246,7 +243,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe return new Parameters().addParameter( new Parameters.ParametersParameterComponent() .setName("Gaps in Care Report - " + subject) - .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure))); + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure,status))); } return returnParams; } @@ -266,21 +263,30 @@ private List getPatientListFromGroup(String subjectGroupRef){ } private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, - String practitioner, String measure){ + String practitioner, String measure, String status, String organization){ if(periodStart == null || periodStart.equals("") || periodEnd == null || periodEnd.equals("")){ throw new IllegalArgumentException("periodStart and periodEnd are required."); } + //TODO - remove this - covered in check of subject/practitioner/organization - left in for now 'cause we need a subject to develop if (subject == null || subject.equals("")) { throw new IllegalArgumentException("Subject is required."); } - if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { - throw new IllegalArgumentException("Subject must follow the format of either Patient/ID OR Group/ID."); + if(null != subject) { + if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { + throw new IllegalArgumentException("Subject must follow the format of either Patient/ID OR Group/ID."); + } + } + if(null != practitioner && null == organization){ + throw new IllegalArgumentException("If a practitioner is specified then an organization must also be specified."); + } + if(null == subject && null == practitioner && null == organization){ + throw new IllegalArgumentException("periodStart AND periodEnd AND (subject OR organization OR (practitioner AND organization)) MUST be provided"); } return true; } - private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure) { + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure, String status) { //TODO: this is an org hack. Need to figure out what the right thing is. IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); From 32f2fecb2e76286f5d0341191a78ddf09a3bbe59 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 14 Aug 2020 11:20:02 -0600 Subject: [PATCH 100/198] status functionality in place. Refactor for cleanup needed --- .../providers/MeasureOperationsProvider.java | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index b739624ba..5b18047e3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -233,9 +233,17 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe return returnParams; }else if(subject.startsWith("Group/")) { (getPatientListFromGroup(subject)) - .forEach(groupSubject -> returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("Gaps in Care Report - " + groupSubject) - .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status)))); + .forEach(groupSubject ->{ + Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); + if(null != patientGapBundle){ + returnParams.addParameter(new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + groupSubject) + .setResource(patientGapBundle)); + } + }); +// groupSubject -> returnParams.addParameter(new Parameters.ParametersParameterComponent() +// .setName("Gaps in Care Report - " + groupSubject) +// .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status)))); } return returnParams; } @@ -274,9 +282,12 @@ private Boolean careGapParameterValidation(String periodStart, String periodEnd, } if(null != subject) { if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { - throw new IllegalArgumentException("Subject must follow the format of either Patient/ID OR Group/ID."); + throw new IllegalArgumentException("Subject must follow the format of either 'Patient/ID' OR 'Group/ID'."); } } + if(null != status && (!status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap"))){ + throw new IllegalArgumentException("If status is present, it must be either 'open-gap' or 'closed-gap'."); + } if(null != practitioner && null == organization){ throw new IllegalArgumentException("If a practitioner is specified then an organization must also be specified."); } @@ -315,6 +326,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje List reports = new ArrayList<>(); List detectedIssues = new ArrayList(); MeasureReport report = null; + boolean hasIssue = false; for (IBaseResource resource : measures) { Measure measureResource = (Measure) resource; @@ -383,10 +395,10 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // TODO - this is super hacky ... change once improvementNotation is specified // as a code String improvementNotation = measureResource.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); - if ( - ((improvementNotation.equals("increase")) && (proportion < 1.0)) - || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { - + if (((improvementNotation.equals("increase")) && (proportion < 1.0)) + || ((improvementNotation.equals("decrease")) && (proportion > 0.0)) + && (null == status || "".equalsIgnoreCase(status) || "open-gap".equalsIgnoreCase(status))) { + hasIssue = true; DetectedIssue detectedIssue = new DetectedIssue(); detectedIssue.setId(UUID.randomUUID().toString()); detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); @@ -407,17 +419,24 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // TODO - add other types of improvement notation cases } } - - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); - - for (MeasureReport rep : reports) { - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + if((null == status || status == "") + || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) + ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); } + if((null == status || status == "") + || (hasIssue && !"closed-gap".equalsIgnoreCase(status))) { + for (MeasureReport rep : reports) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + } - for (DetectedIssue detectedIssue: detectedIssues) { - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); + for (DetectedIssue detectedIssue : detectedIssues) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); + } + } + if(careGapReport.getEntry().isEmpty()){ + return null; } - return careGapReport; } From f2976135e0078b7a64a1deeeda2d1b4f21301798 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 14 Aug 2020 12:52:20 -0600 Subject: [PATCH 101/198] status more complete. Added Parameters ID similar to the example. --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 5b18047e3..26c3f7d97 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -232,6 +232,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); return returnParams; }else if(subject.startsWith("Group/")) { + returnParams.setId((status==null?"all-gaps": status) + "-" + subject.replace("/","_") + "-report"); (getPatientListFromGroup(subject)) .forEach(groupSubject ->{ Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); @@ -241,9 +242,6 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe .setResource(patientGapBundle)); } }); -// groupSubject -> returnParams.addParameter(new Parameters.ParametersParameterComponent() -// .setName("Gaps in Care Report - " + groupSubject) -// .setResource(patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status)))); } return returnParams; } From 7e37dae52a432c5bfc9cf26a8b5c58323dcdb5ab Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 14 Aug 2020 14:14:37 -0600 Subject: [PATCH 102/198] status added to produce all, closed-gap, and open-gap reports --- .../cqf/r4/providers/MeasureOperationsProvider.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 26c3f7d97..976ea3890 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -411,19 +411,16 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje composition.addSection(section); detectedIssues.add(detectedIssue); - reports.add(report); } + reports.add(report); // TODO - add other types of improvement notation cases } } - if((null == status || status == "") - || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) - ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ + if((null == status || status == "") //everything + || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) //filter out closed-gap that has issues for OPEN-GAP + ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filet out open-gap without issues for CLOSE-GAP careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); - } - if((null == status || status == "") - || (hasIssue && !"closed-gap".equalsIgnoreCase(status))) { for (MeasureReport rep : reports) { careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); } From 7e6e4b28b83d95451dc880dd27ba8c6fb7fe2bfe Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Tue, 18 Aug 2020 08:49:23 -0600 Subject: [PATCH 103/198] Added error logging in exception handing of cds servlet doPost --- .../java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 4 ++++ .../main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index a86e47a5e..07bf77fd6 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -189,6 +189,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) this.printMessageAndCause(e, response); this.handleServerResponseException(e, response); this.printStackTrack(e, response); + logger.error(e.toString()); } catch (DataProviderException e) { this.setAccessControlHeaders(response); response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. @@ -199,6 +200,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } this.printStackTrack(e, response); + logger.error(e.toString()); } catch (CqlException e) { this.setAccessControlHeaders(response); @@ -210,7 +212,9 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } this.printStackTrack(e, response); + logger.error(e.toString()); } catch (Exception e) { + logger.error(e.toString()); throw new ServletException("ERROR: Exception in cds-hooks processing.", e); } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 3a1bef603..27cece5a4 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -193,6 +193,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) this.printMessageAndCause(e, response); this.handleServerResponseException(e, response); this.printStackTrack(e, response); + logger.error(e.toString()); } catch (DataProviderException e) { this.setAccessControlHeaders(response); response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. @@ -203,6 +204,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } this.printStackTrack(e, response); + logger.error(e.toString()); } catch (CqlException e) { this.setAccessControlHeaders(response); @@ -214,8 +216,10 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) } this.printStackTrack(e, response); + logger.error(e.toString()); } catch (Exception e) { + logger.error(e.toString()); throw new ServletException("ERROR: Exception in cds-hooks processing.", e); } } From e76a72a7b2c969e9dee43059a3a9ba564c9aca21 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 19 Aug 2020 14:09:16 -0600 Subject: [PATCH 104/198] Added missing elements to composition and gap report; --- .../cqf/r4/providers/MeasureOperationsProvider.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 976ea3890..c69217a51 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,5 +1,6 @@ package org.opencds.cqf.r4.providers; +import java.sql.Timestamp; import java.util.*; import java.util.stream.Collectors; @@ -315,11 +316,18 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); + careGapReport.setTimestamp(new Date()); Composition composition = new Composition(); composition.setStatus(Composition.CompositionStatus.FINAL) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) - .setTitle("Care Gap Report"); + .setTitle("Care Gap Report for " + subject) + .setDate(new Date()) + .setType(new CodeableConcept() + .addCoding(new Coding() + .setCode("gaps-doc") + .setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/gaps-doc-type") + .setDisplay("Gaps in Care Report"))); List reports = new ArrayList<>(); List detectedIssues = new ArrayList(); From af4502a1ee0d4655b3b50c3226819de8a3048a0e Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 20 Aug 2020 14:28:50 -0600 Subject: [PATCH 105/198] Created new EvaluatedResource inside MeasureReport. --- .../providers/MeasureOperationsProvider.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index c69217a51..05993372d 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,6 +1,5 @@ package org.opencds.cqf.r4.providers; -import java.sql.Timestamp; import java.util.*; import java.util.stream.Collectors; @@ -425,14 +424,35 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // TODO - add other types of improvement notation cases } } + Parameters parameters = new Parameters(); if((null == status || status == "") //everything || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) //filter out closed-gap that has issues for OPEN-GAP ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filet out open-gap without issues for CLOSE-GAP careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); for (MeasureReport rep : reports) { careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + if (report.hasContained()) { + for (Resource contained : report.getContained()) { + if (contained instanceof Bundle) { + addEvaluatedResourcesToParameters((Bundle) contained, parameters); + if(null != parameters && !parameters.isEmpty()) { + List evaluatedResource = new ArrayList<>(); + parameters.getParameter().forEach(parameter -> { + Reference newEvaluatedResourceItem = new Reference(); + newEvaluatedResourceItem.setReference(parameter.getResource().getId()); + List evalResourceExt = new ArrayList<>(); + evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-ppopulationReference", + new CodeableConcept() + .addCoding(new Coding("http://teminology.hl7.org/CodeSystem/measure-population", "initial-population", "initial-population")))); + newEvaluatedResourceItem.setExtension(evalResourceExt); + evaluatedResource.add(newEvaluatedResourceItem); + }); + report.setEvaluatedResource(evaluatedResource); + } + } + } + } } - for (DetectedIssue detectedIssue : detectedIssues) { careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); } From 566e58cc4a7920d0c4619abac380e3aeff506ce5 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Sun, 23 Aug 2020 17:25:36 -0600 Subject: [PATCH 106/198] Initial implementation --- .../opencds/cqf/r4/helpers/LibraryHelper.java | 10 ++++ .../providers/LibraryOperationsProvider.java | 26 +++++++++- .../R4BundleLibrarySourceProvider.java | 48 +++++++++++++++++++ ...VersionComparingLibrarySourceProvider.java | 46 ++++++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/VersionComparingLibrarySourceProvider.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 3779ab74c..b1e1d98d6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -30,6 +30,16 @@ public static LibraryLoader createLibraryLoader(LibraryResolutionProvider loadLibraries(Measure measure, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 1419cdc68..3d15dcb09 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -28,6 +28,7 @@ import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.opencds.cqf.cds.providers.PriorityRetrieveProvider; import org.opencds.cqf.common.evaluation.LibraryLoader; import org.opencds.cqf.common.helpers.ClientHelperDos; @@ -70,6 +71,7 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; +import ca.uhn.fhir.util.BundleUtil; public class LibraryOperationsProvider implements LibraryResolutionProvider { @@ -194,7 +196,22 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" throw new IllegalArgumentException("Must specify a patientId when executing in Patient context."); } - Library theResource = this.libraryResourceProvider.getDao().read(theId); + Bundle libraryBundle = new Bundle(); + Library theResource = null; + if (additionalData != null) { + for (BundleEntryComponent entry : additionalData.getEntry()) { + if (entry.getResource().fhirType().equals("Library")) { + libraryBundle.addEntry(entry); + if (entry.getResource().getIdElement().equals(theId)) { + theResource = (Library) entry.getResource(); + } + } + } + } + + if (theResource == null) { + theResource = this.libraryResourceProvider.getDao().read(theId); + } VersionedIdentifier libraryIdentifier = new VersionedIdentifier().withId(theResource.getName()) .withVersion(theResource.getVersion()); @@ -253,7 +270,12 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } } - LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.getLibraryResourceProvider()); + org.cqframework.cql.cql2elm.LibrarySourceProvider bundleLibraryProvider = new R4BundleLibrarySourceProvider(libraryBundle); + LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(bundleLibraryProvider); + LibraryResolutionProvider provider = this.getLibraryResourceProvider(); + libraryLoader.getLibraryManager().getLibrarySourceLoader().registerProvider( + new LibrarySourceProvider(provider, + x -> x.getContent(), x -> x.getContentType(), x -> x.getData())); CqlEngine engine = new CqlEngine(libraryLoader, Collections.singletonMap("http://hl7.org/fhir", dataProvider), terminologyProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java new file mode 100644 index 000000000..39f6b2cb6 --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java @@ -0,0 +1,48 @@ +package org.opencds.cqf.r4.providers; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Objects; + +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.r4.model.*; + +public class R4BundleLibrarySourceProvider extends VersionComparingLibrarySourceProvider { + + Bundle bundle; + public R4BundleLibrarySourceProvider(Bundle bundle) { + this.bundle = bundle; + } + + @Override + public InputStream getLibrarySource(VersionedIdentifier versionedIdentifier) { + Objects.requireNonNull(versionedIdentifier, "versionedIdentifier can not be null."); + + Library library = this.getLibrary(versionedIdentifier.getId(), versionedIdentifier.getVersion()); + if (library == null ){ + return null; + } + + return this.getCqlStream(library); + } + + public Library getLibrary(String name, String version) { + // TODO: Check the bundle + + return null; + } + + private InputStream getCqlStream(Library library) { + if (library.hasContent()) { + for (Attachment content : library.getContent()) { + // TODO: Could use this for any content type, would require a mapping from content type to LanguageServer LanguageId + if (content.getContentType().equals("text/cql")) { + return new ByteArrayInputStream(content.getData()); + } + // TODO: Decompile ELM if no CQL is available? + } + } + + return null; + } +} \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/VersionComparingLibrarySourceProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/VersionComparingLibrarySourceProvider.java new file mode 100644 index 000000000..07bfb901c --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/VersionComparingLibrarySourceProvider.java @@ -0,0 +1,46 @@ +package org.opencds.cqf.r4.providers; + +import org.cqframework.cql.cql2elm.LibrarySourceProvider; + +public abstract class VersionComparingLibrarySourceProvider implements LibrarySourceProvider { + public static int compareVersions(String version1, String version2) + { + // Treat null as MAX VERSION + if (version1 == null && version2 == null) { + return 0; + } + + if (version1 != null && version2 == null) { + return -1; + } + + if (version1 == null && version2 != null) { + return 1; + } + + String[] string1Vals = version1.split("\\."); + String[] string2Vals = version2.split("\\."); + + int length = Math.max(string1Vals.length, string2Vals.length); + + for (int i = 0; i < length; i++) + { + Integer v1 = (i < string1Vals.length)?Integer.parseInt(string1Vals[i]):0; + Integer v2 = (i < string2Vals.length)?Integer.parseInt(string2Vals[i]):0; + + //Making sure Version1 bigger than version2 + if (v1 > v2) + { + return 1; + } + //Making sure Version1 smaller than version2 + else if(v1 < v2) + { + return -1; + } + } + + //Both are equal + return 0; + } +} \ No newline at end of file From 8304c1cc62644bde7294dfdfea1f495d80f5be34 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Mon, 24 Aug 2020 21:30:16 -0600 Subject: [PATCH 107/198] Add getting the library from the bundle --- .../r4/providers/R4BundleLibrarySourceProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java index 39f6b2cb6..4786e9efb 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java @@ -3,9 +3,14 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Objects; +import java.util.Optional; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.r4.model.*; +import org.opencds.cqf.cql.engine.fhir.model.FhirModelResolver; +import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; + +import ca.uhn.fhir.util.BundleUtil; public class R4BundleLibrarySourceProvider extends VersionComparingLibrarySourceProvider { @@ -27,9 +32,13 @@ public InputStream getLibrarySource(VersionedIdentifier versionedIdentifier) { } public Library getLibrary(String name, String version) { - // TODO: Check the bundle + FhirModelResolver resolver = new R4FhirModelResolver(); + Optional result = BundleUtil.toListOfResourcesOfType(resolver.getFhirContext(), this.bundle, Library.class) + .stream() + .filter(library -> library.getName().equals(name) && library.getVersion().equals(version)) + .findFirst(); - return null; + return result.isPresent() ? result.get() : null; } private InputStream getCqlStream(Library library) { From 6f8278db19b11f21a3842d8d55afafce36605a67 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Mon, 24 Aug 2020 21:37:59 -0600 Subject: [PATCH 108/198] Probably not worth those heavy resources. --- .../R4BundleLibrarySourceProvider.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java index 4786e9efb..233780b4b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/R4BundleLibrarySourceProvider.java @@ -3,14 +3,10 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Objects; -import java.util.Optional; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.r4.model.*; -import org.opencds.cqf.cql.engine.fhir.model.FhirModelResolver; -import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; - -import ca.uhn.fhir.util.BundleUtil; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; public class R4BundleLibrarySourceProvider extends VersionComparingLibrarySourceProvider { @@ -32,13 +28,18 @@ public InputStream getLibrarySource(VersionedIdentifier versionedIdentifier) { } public Library getLibrary(String name, String version) { - FhirModelResolver resolver = new R4FhirModelResolver(); - Optional result = BundleUtil.toListOfResourcesOfType(resolver.getFhirContext(), this.bundle, Library.class) - .stream() - .filter(library -> library.getName().equals(name) && library.getVersion().equals(version)) - .findFirst(); - - return result.isPresent() ? result.get() : null; + if (bundle != null) { + Library theResource = null; + for (BundleEntryComponent entry : bundle.getEntry()) { + if (entry.getResource().fhirType().equals("Library")) { + theResource = (Library)entry.getResource(); + if (theResource.getName().equals(name) && theResource.getVersion().equals(version)) { + return theResource; + } + } + } + } + return null; } private InputStream getCqlStream(Library library) { From 56a27403c63377cb8f7f945ca41641a17bd09049 Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 25 Aug 2020 11:06:18 -0600 Subject: [PATCH 109/198] added system so cql can pull observations --- .../java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 6a7bf5491..29fb9ba86 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -54,6 +54,7 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna qrCategoryCoding.setSystem("http://hl7.org/fhir/observation-category"); obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); Coding qrCoding = new Coding(); + qrCoding.setSystem("http://loinc.org"); qrCoding.setCode("74465-6"); qrCoding.setDisplay("Questionnaire response Document"); obs.setCode(new CodeableConcept().addCoding(qrCoding)); From 7fcf97ffd53632c5d03577a994eb8a77774872eb Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Aug 2020 08:41:03 -0600 Subject: [PATCH 110/198] added an additional extension to the observation to match the US core patient example http://hl7.org/fhir/us/core/Patient-example.json.html --- .../cqf/r4/providers/QuestionnaireProvider.java | 12 ++++-------- r4/src/main/resources/hapi.properties | 4 ++-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 29fb9ba86..97fee2f48 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -75,14 +75,10 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna obs.setDerivedFrom(Collections.singletonList(questionnaireResponseReference)); Extension linkIdExtension = new Extension(); linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); - linkIdExtension.setValue(new StringType(item.getLinkId())); -// Extension refExtension = new Extension(); -// refExtension.setUrl(ExtensionEnum.GRCONTAINED.getUrl()); -// if(!mReport.hasId()){ -// mReport.setId(UUID.randomUUID().toString()); -// } -// refExtension.setValue(new Reference("#" + mReport.getId())); -// newGuidanceResponse.addExtension(refExtension); obs. + Extension innerLinkIdExtension = new Extension(); + innerLinkIdExtension.setUrl("text"); + innerLinkIdExtension.setValue(new StringType(item.getLinkId())); + linkIdExtension.setExtension(Collections.singletonList(innerLinkIdExtension)); obs.addExtension(linkIdExtension); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index ad7d4373b..ed284e449 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -165,8 +165,8 @@ oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) -questionnaireResponseExtract.enabled=false -questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +questionnaireResponseExtract.enabled=true +questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-r4/fhir questionnaireResponseExtract.username= questionnaireResponseExtract.password= From 1bfedf7056736c325cc0cb64d8cdcc35c9b8d807 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Aug 2020 08:53:32 -0600 Subject: [PATCH 111/198] added dstu3 functionality to $extract operation --- .../providers/QuestionnaireProvider.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index e6bd00f2d..f3f5812d3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -7,6 +7,7 @@ import org.hl7.fhir.dstu3.model.*; import org.opencds.cqf.common.config.HapiProperties; +import java.util.Collections; import java.util.Date; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -21,7 +22,7 @@ public QuestionnaireProvider(FhirContext fhirContext){ @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { if(questionnaireResponse == null) { - throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); + throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); @@ -48,7 +49,12 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setSubject(questionnaireResponse.getSubject()); + Coding qrCategoryCoding = new Coding(); + qrCategoryCoding.setCode("survey"); + qrCategoryCoding.setSystem("http://hl7.org/fhir/observation-category"); + obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); Coding qrCoding = new Coding(); + qrCoding.setSystem("http://loinc.org"); qrCoding.setCode("74465-6"); qrCoding.setDisplay("Questionnaire response Document"); obs.setCode(new CodeableConcept().addCoding(qrCoding)); @@ -60,7 +66,23 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna case "Coding": obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); break; + case "boolean": + obs.setValue(new BooleanType(item.getAnswer().get(0).getValueBooleanType().booleanValue())); + break; } + Reference questionnaireResponseReference = new Reference(); + questionnaireResponseReference.setReference("QuestionnaireResponse" + "/" + questionnaireResponse.getIdElement().getIdPart()); + Observation.ObservationRelatedComponent related = new Observation.ObservationRelatedComponent() + .setType(Observation.ObservationRelationshipType.DERIVEDFROM) + .setTarget(questionnaireResponseReference); + obs.setRelated(Collections.singletonList(related)); + Extension linkIdExtension = new Extension(); + linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); + Extension innerLinkIdExtension = new Extension(); + innerLinkIdExtension.setUrl("text"); + innerLinkIdExtension.setValue(new StringType(item.getLinkId())); + linkIdExtension.setExtension(Collections.singletonList(innerLinkIdExtension)); + obs.addExtension(linkIdExtension); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); berc.setMethod(Bundle.HTTPVerb.PUT); berc.setUrl("Observation/" + obs.getId()); From afc31f06e0321a5e8f38f7e1058545cc690411fc Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Aug 2020 09:11:20 -0600 Subject: [PATCH 112/198] making the properties files between versions match --- dstu3/src/main/resources/hapi.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index e8fa3e5b5..c5b7d2b2c 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -165,8 +165,8 @@ oauth.serviceCode=SMART-on-FHIR oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) -questionnaireResponseExtract.enabled=false -questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +questionnaireResponseExtract.enabled=true +questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-r4/fhir questionnaireResponseExtract.username= questionnaireResponseExtract.password= From 0e119ea608dac62a335eb92a974d8d1f2ef3efea Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Aug 2020 09:13:48 -0600 Subject: [PATCH 113/198] fixing wrong server version --- dstu3/src/main/resources/hapi.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index c5b7d2b2c..517002b06 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -166,7 +166,7 @@ oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) questionnaireResponseExtract.enabled=true -questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-r4/fhir +questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-dstu3/fhir questionnaireResponseExtract.username= questionnaireResponseExtract.password= From a188f506c69e1dd7055df5386b4897647b1b3f38 Mon Sep 17 00:00:00 2001 From: bryant Date: Wed, 26 Aug 2020 11:36:19 -0600 Subject: [PATCH 114/198] changing questionnaireresponse/$extract endpoint to work for sites --- r4/src/main/resources/hapi.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 0b55b0443..efe6eb813 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -166,7 +166,7 @@ oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) questionnaireResponseExtract.enabled=true -questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-r4/fhir +questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= questionnaireResponseExtract.password= From 1f0afc5f4c8259c13cb9784149521e233b4e7eac Mon Sep 17 00:00:00 2001 From: bryant Date: Thu, 27 Aug 2020 14:19:01 -0600 Subject: [PATCH 115/198] accumulator working; Report does NOT have contained object --- .../cqf/r4/evaluation/MeasureEvaluation.java | 88 ++++++++++++++++--- 1 file changed, 77 insertions(+), 11 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3325bfd74..6ab2cb257 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -8,20 +8,15 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.ListResource; -import org.hl7.fhir.r4.model.Measure; -import org.hl7.fhir.r4.model.MeasureReport; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Quantity; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.r4.builders.MeasureReportBuilder; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; @@ -218,6 +213,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p throw new RuntimeException("Measure scoring is required in order to calculate."); } + List sde = new ArrayList<>(); + HashMap> sdeAccumulators = null; for (Measure.MeasureGroupComponent group : measure.getGroup()) { MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent(); reportGroup.setId(group.getId()); @@ -256,6 +253,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap measurePopulationPatients = null; HashMap measurePopulationExclusionPatients = null; + sdeAccumulators = new HashMap<>(); + sde = measure.getSupplementalData(); for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) { MeasurePopulationType populationType = MeasurePopulationType .fromCode(pop.getCode().getCodingFirstRep().getCode()); @@ -330,7 +329,6 @@ private MeasureReport evaluate(Measure measure, Context context, List p // For each patient in the initial population for (Patient patient : patients) { - // Are they in the initial population? boolean inInitialPopulation = evaluatePopulationCriteria(context, patient, initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null, @@ -377,6 +375,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } } } + populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde); } // Calculate actual measure score, Count(numerator) / Count(denominator) @@ -413,6 +412,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } } } + populateSDEAccumulators(measure, context, patient, sdeAccumulators,sde); } break; @@ -426,6 +426,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p null); populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde); } break; @@ -479,13 +480,78 @@ private MeasureReport evaluate(Measure measure, Context context, List p FhirMeasureBundler bundler = new FhirMeasureBundler(); org.hl7.fhir.r4.model.Bundle evaluatedResources = bundler.bundle(resources.values()); evaluatedResources.setId(UUID.randomUUID().toString()); - report.setEvaluatedResource(Collections.singletonList(new Reference('#' + evaluatedResources.getId()))); - report.addContained(evaluatedResources); + report.setEvaluatedResource(Collections.singletonList(new Reference(evaluatedResources.getId()))); +// report.addContained(evaluatedResources); + } + if (sdeAccumulators.size() > 0) { + report = processAccumulators(report, sdeAccumulators, sde); } return report; } + private void populateSDEAccumulators(Measure measure, Context context, Patient patient,HashMap> sdeAccumulators, List sde){ + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + List sdeList = sde.stream().map(sdeItem -> context.resolveExpressionRef(sdeItem.getCriteria().getExpression()).evaluate(context)).collect(Collectors.toList()); + if(!sdeList.isEmpty()) { + for (int i = 0; i < sdeList.size(); i++) { + Object sdeListItem = sdeList.get(i); + String sdeAccumulatorKey = sde.get(i).getCode().getText(); + switch (sdeListItem.getClass().getSimpleName()) { + case "Code": + HashMap sdeCodeItem = sdeAccumulators.get(sdeAccumulatorKey); + String code = ((Code)sdeListItem).getCode(); + if (null != sdeCodeItem) { + Integer sdeItemValue = sdeCodeItem.get(sdeListItem); + sdeItemValue++; + sdeCodeItem.put(code, sdeItemValue); + sdeAccumulators.put(sdeAccumulatorKey, sdeCodeItem); + } else { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(code, 1); + sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + } + break; + case "ArrayList": + if(((ArrayList)sdeListItem).size() > 0){ + Coding sdeItemCoding = (Coding) ((ArrayList)sdeListItem).get(0); + String sdeItemCode = sdeItemCoding.getCode(); + HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); + if (null != sdeItem) { + Integer sdeItemValue = sdeItem.get(sdeListItem); + sdeItemValue++; + sdeItem.put(sdeItemCode, sdeItemValue); + sdeAccumulators.put(sdeAccumulatorKey, sdeItem); + } else { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(sdeItemCode, 1); + sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + } + } + break; + } + } + } + } + + private MeasureReport processAccumulators(MeasureReport report, HashMap> sdeAccumulators, + List sde){ + sdeAccumulators.forEach((sdeKey, sdeAcumulator) -> { + sdeAcumulator.forEach((sdeAcumulatorKey, sdeAcumulatorValue)->{ + System.out.println(sdeAcumulatorKey + "/" + sdeAcumulatorValue); + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(UUID.randomUUID().toString()); + obs.setCode(new CodeableConcept().setText(sdeAcumulatorKey.toString())); + obs.setValue(new StringType(sdeAcumulatorValue.toString())); + report.addContained(obs); + }); + }); + //entry.getKey() + "/" + entry.getValue()).forEach(System.out::println); + + return report; + } + private void populateResourceMap(Context context, MeasurePopulationType type, HashMap resources, HashMap> codeToResourceMap) { if (context.getEvaluatedResources().isEmpty()) { From ff72bb4c1c3636c97fbb7980b466dccfa51e22cb Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 09:03:02 -0600 Subject: [PATCH 116/198] report with supplemental data being generated for individual patient --- .../cqf/r4/evaluation/MeasureEvaluation.java | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 6ab2cb257..b49cf38a6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -477,11 +477,18 @@ private MeasureReport evaluate(Measure measure, Context context, List p } if (!resources.isEmpty()) { + List evaluatedResourceIds = new ArrayList<>(); + resources.forEach((key, resource) -> { + evaluatedResourceIds.add(new Reference("#" + resource.getId())); + }); + report.setEvaluatedResource(evaluatedResourceIds); + /* FhirMeasureBundler bundler = new FhirMeasureBundler(); org.hl7.fhir.r4.model.Bundle evaluatedResources = bundler.bundle(resources.values()); evaluatedResources.setId(UUID.randomUUID().toString()); report.setEvaluatedResource(Collections.singletonList(new Reference(evaluatedResources.getId()))); -// report.addContained(evaluatedResources); + report.addContained(evaluatedResources); + */ } if (sdeAccumulators.size() > 0) { report = processAccumulators(report, sdeAccumulators, sde); @@ -496,39 +503,41 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p if(!sdeList.isEmpty()) { for (int i = 0; i < sdeList.size(); i++) { Object sdeListItem = sdeList.get(i); - String sdeAccumulatorKey = sde.get(i).getCode().getText(); - switch (sdeListItem.getClass().getSimpleName()) { - case "Code": - HashMap sdeCodeItem = sdeAccumulators.get(sdeAccumulatorKey); - String code = ((Code)sdeListItem).getCode(); - if (null != sdeCodeItem) { - Integer sdeItemValue = sdeCodeItem.get(sdeListItem); - sdeItemValue++; - sdeCodeItem.put(code, sdeItemValue); - sdeAccumulators.put(sdeAccumulatorKey, sdeCodeItem); - } else { - HashMap newSDEItem = new HashMap<>(); - newSDEItem.put(code, 1); - sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); - } - break; - case "ArrayList": - if(((ArrayList)sdeListItem).size() > 0){ - Coding sdeItemCoding = (Coding) ((ArrayList)sdeListItem).get(0); - String sdeItemCode = sdeItemCoding.getCode(); - HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); - if (null != sdeItem) { - Integer sdeItemValue = sdeItem.get(sdeListItem); + if(null != sdeListItem) { + String sdeAccumulatorKey = sde.get(i).getCode().getText(); + switch (sdeListItem.getClass().getSimpleName()) { + case "Code": + HashMap sdeCodeItem = sdeAccumulators.get(sdeAccumulatorKey); + String code = ((Code) sdeListItem).getCode(); + if (null != sdeCodeItem) { + Integer sdeItemValue = sdeCodeItem.get(sdeListItem); sdeItemValue++; - sdeItem.put(sdeItemCode, sdeItemValue); - sdeAccumulators.put(sdeAccumulatorKey, sdeItem); + sdeCodeItem.put(code, sdeItemValue); + sdeAccumulators.put(sdeAccumulatorKey, sdeCodeItem); } else { HashMap newSDEItem = new HashMap<>(); - newSDEItem.put(sdeItemCode, 1); + newSDEItem.put(code, 1); sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); } - } - break; + break; + case "ArrayList": + if (((ArrayList) sdeListItem).size() > 0) { + Coding sdeItemCoding = (Coding) ((ArrayList) sdeListItem).get(0); + String sdeItemCode = sdeItemCoding.getCode(); + HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); + if (null != sdeItem) { + Integer sdeItemValue = sdeItem.get(sdeListItem); + sdeItemValue++; + sdeItem.put(sdeItemCode, sdeItemValue); + sdeAccumulators.put(sdeAccumulatorKey, sdeItem); + } else { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(sdeItemCode, 1); + sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + } + } + break; + } } } } @@ -536,19 +545,21 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p private MeasureReport processAccumulators(MeasureReport report, HashMap> sdeAccumulators, List sde){ - sdeAccumulators.forEach((sdeKey, sdeAcumulator) -> { - sdeAcumulator.forEach((sdeAcumulatorKey, sdeAcumulatorValue)->{ - System.out.println(sdeAcumulatorKey + "/" + sdeAcumulatorValue); + List newRefList = new ArrayList<>(); + sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { + sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ + System.out.println(sdeAccumulatorKey + "/" + sdeAcumulatorValue); Observation obs = new Observation(); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setId(UUID.randomUUID().toString()); - obs.setCode(new CodeableConcept().setText(sdeAcumulatorKey.toString())); + obs.setCode(new CodeableConcept().setText(sdeAccumulatorKey.toString())); obs.setValue(new StringType(sdeAcumulatorValue.toString())); + newRefList.add(new Reference("#" + obs.getId())); report.addContained(obs); }); }); - //entry.getKey() + "/" + entry.getValue()).forEach(System.out::println); - + newRefList.addAll(report.getEvaluatedResource()); + report.setEvaluatedResource(newRefList); return report; } From 33f5de39f3e3c61dd8edf962827c63c2e9ba811e Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 10:49:15 -0600 Subject: [PATCH 117/198] supplemental data for a group is functional --- .../cqf/r4/evaluation/MeasureEvaluation.java | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index b49cf38a6..3953f3033 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -509,15 +509,19 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p case "Code": HashMap sdeCodeItem = sdeAccumulators.get(sdeAccumulatorKey); String code = ((Code) sdeListItem).getCode(); - if (null != sdeCodeItem) { - Integer sdeItemValue = sdeCodeItem.get(sdeListItem); + if (null != sdeCodeItem && null != sdeCodeItem.get(code)) { + Integer sdeItemValue = sdeCodeItem.get(code); sdeItemValue++; sdeCodeItem.put(code, sdeItemValue); - sdeAccumulators.put(sdeAccumulatorKey, sdeCodeItem); + sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue); } else { HashMap newSDEItem = new HashMap<>(); newSDEItem.put(code, 1); - sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + if(null == sdeAccumulators.get(sdeAccumulatorKey)){ + sdeAccumulators.put(sdeAccumulatorKey,newSDEItem); + }else{ + sdeAccumulators.get(sdeAccumulatorKey).put(code, 1); + } } break; case "ArrayList": @@ -525,15 +529,20 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p Coding sdeItemCoding = (Coding) ((ArrayList) sdeListItem).get(0); String sdeItemCode = sdeItemCoding.getCode(); HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); - if (null != sdeItem) { + if (null != sdeItem && null != sdeItem.get(sdeListItem)) { Integer sdeItemValue = sdeItem.get(sdeListItem); sdeItemValue++; sdeItem.put(sdeItemCode, sdeItemValue); - sdeAccumulators.put(sdeAccumulatorKey, sdeItem); + //sdeAccumulators.put(sdeAccumulatorKey, sdeItem); + sdeAccumulators.get(sdeAccumulatorKey).put(sdeItemCode, sdeItemValue); } else { HashMap newSDEItem = new HashMap<>(); newSDEItem.put(sdeItemCode, 1); - sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + if(null == sdeAccumulators.get(sdeAccumulatorKey)){ + sdeAccumulators.put(sdeAccumulatorKey,newSDEItem); + }else{ + sdeAccumulators.get(sdeAccumulatorKey).put(sdeItemCode, 1); + } } } break; @@ -548,7 +557,6 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap newRefList = new ArrayList<>(); sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ - System.out.println(sdeAccumulatorKey + "/" + sdeAcumulatorValue); Observation obs = new Observation(); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setId(UUID.randomUUID().toString()); From be173fd3b7f468dbf2993426239128c68ed58b39 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 10:57:35 -0600 Subject: [PATCH 118/198] clean up of a couple of variables --- .../java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3953f3033..3ae9c9f88 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -529,8 +529,8 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p Coding sdeItemCoding = (Coding) ((ArrayList) sdeListItem).get(0); String sdeItemCode = sdeItemCoding.getCode(); HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); - if (null != sdeItem && null != sdeItem.get(sdeListItem)) { - Integer sdeItemValue = sdeItem.get(sdeListItem); + if (null != sdeItem && null != sdeItem.get(sdeItemCode)) { + Integer sdeItemValue = sdeItem.get(sdeItemCode); sdeItemValue++; sdeItem.put(sdeItemCode, sdeItemValue); //sdeAccumulators.put(sdeAccumulatorKey, sdeItem); From 43f8e0f3dce3f77772023a9bdf7fa1dbe8742fee Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 11:11:30 -0600 Subject: [PATCH 119/198] refactored populateSDEAccumulators to remove duplicate code and fix --- .../cqf/r4/evaluation/MeasureEvaluation.java | 55 +++++++------------ 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3ae9c9f88..a7f4bafb3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -505,48 +505,35 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p Object sdeListItem = sdeList.get(i); if(null != sdeListItem) { String sdeAccumulatorKey = sde.get(i).getCode().getText(); + HashMap sdeItemMap = sdeAccumulators.get(sdeAccumulatorKey); + String code = ""; + switch (sdeListItem.getClass().getSimpleName()) { case "Code": - HashMap sdeCodeItem = sdeAccumulators.get(sdeAccumulatorKey); - String code = ((Code) sdeListItem).getCode(); - if (null != sdeCodeItem && null != sdeCodeItem.get(code)) { - Integer sdeItemValue = sdeCodeItem.get(code); - sdeItemValue++; - sdeCodeItem.put(code, sdeItemValue); - sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue); - } else { - HashMap newSDEItem = new HashMap<>(); - newSDEItem.put(code, 1); - if(null == sdeAccumulators.get(sdeAccumulatorKey)){ - sdeAccumulators.put(sdeAccumulatorKey,newSDEItem); - }else{ - sdeAccumulators.get(sdeAccumulatorKey).put(code, 1); - } - } + code = ((Code) sdeListItem).getCode(); break; case "ArrayList": if (((ArrayList) sdeListItem).size() > 0) { - Coding sdeItemCoding = (Coding) ((ArrayList) sdeListItem).get(0); - String sdeItemCode = sdeItemCoding.getCode(); - HashMap sdeItem = sdeAccumulators.get(sdeAccumulatorKey); - if (null != sdeItem && null != sdeItem.get(sdeItemCode)) { - Integer sdeItemValue = sdeItem.get(sdeItemCode); - sdeItemValue++; - sdeItem.put(sdeItemCode, sdeItemValue); - //sdeAccumulators.put(sdeAccumulatorKey, sdeItem); - sdeAccumulators.get(sdeAccumulatorKey).put(sdeItemCode, sdeItemValue); - } else { - HashMap newSDEItem = new HashMap<>(); - newSDEItem.put(sdeItemCode, 1); - if(null == sdeAccumulators.get(sdeAccumulatorKey)){ - sdeAccumulators.put(sdeAccumulatorKey,newSDEItem); - }else{ - sdeAccumulators.get(sdeAccumulatorKey).put(sdeItemCode, 1); - } - } + code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + }else{ + continue; } break; } + if (null != sdeItemMap && null != sdeItemMap.get(code)) { + Integer sdeItemValue = sdeItemMap.get(code); + sdeItemValue++; + sdeItemMap.put(code, sdeItemValue); + sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue); + } else { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(code, 1); + if (null == sdeAccumulators.get(sdeAccumulatorKey)) { + sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + } else { + sdeAccumulators.get(sdeAccumulatorKey).put(code, 1); + } + } } } } From 3a3f5917971756eb98f042f54f6ffccbdb4e2147 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 11:52:16 -0600 Subject: [PATCH 120/198] more refactor cleanup --- .../org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index a7f4bafb3..094176792 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -520,15 +520,18 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p } break; } + if(null == code){ + continue; + } if (null != sdeItemMap && null != sdeItemMap.get(code)) { Integer sdeItemValue = sdeItemMap.get(code); sdeItemValue++; sdeItemMap.put(code, sdeItemValue); sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue); } else { - HashMap newSDEItem = new HashMap<>(); - newSDEItem.put(code, 1); if (null == sdeAccumulators.get(sdeAccumulatorKey)) { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(code, 1); sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); } else { sdeAccumulators.get(sdeAccumulatorKey).put(code, 1); From 2bc8fd3fa0ebaa4e952a105bbd929d6f7b2ead8a Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 12:06:42 -0600 Subject: [PATCH 121/198] removed '#' from evaluatedResources references --- .../java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 094176792..5bdddef6e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -479,7 +479,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p if (!resources.isEmpty()) { List evaluatedResourceIds = new ArrayList<>(); resources.forEach((key, resource) -> { - evaluatedResourceIds.add(new Reference("#" + resource.getId())); + evaluatedResourceIds.add(new Reference(resource.getId())); }); report.setEvaluatedResource(evaluatedResourceIds); /* From 17763d29615590376450563b08534d8e5cd3874f Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 28 Aug 2020 14:57:11 -0600 Subject: [PATCH 122/198] added observationd data; Need to still loop through object to find the right one and not just index 0; --- .../cqf/r4/evaluation/MeasureEvaluation.java | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 5bdddef6e..6da317c96 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -56,16 +56,18 @@ public MeasureReport evaluatePatientMeasure(Measure measure, Context context, St patient = (Patient) patientRetrieve.iterator().next(); } + boolean isSingle = true; return evaluate(measure, context, patient == null ? Collections.emptyList() : Collections.singletonList(patient), - MeasureReport.MeasureReportType.INDIVIDUAL); + MeasureReport.MeasureReportType.INDIVIDUAL, isSingle); } public MeasureReport evaluateSubjectListMeasure(Measure measure, Context context, String practitionerRef) { logger.info("Generating patient-list report"); List patients = practitionerRef == null ? getAllPatients() : getPractitionerPatients(practitionerRef); - return evaluate(measure, context, patients, MeasureReport.MeasureReportType.SUBJECTLIST); + boolean isSingle = false; + return evaluate(measure, context, patients, MeasureReport.MeasureReportType.SUBJECTLIST, isSingle); } private List getPractitionerPatients(String practitionerRef) { @@ -91,7 +93,8 @@ private List getAllPatients() { public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) { logger.info("Generating summary report"); - return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY); + boolean isSingle = false; + return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } @SuppressWarnings("unchecked") @@ -191,7 +194,7 @@ private void addPopulationCriteriaReport(MeasureReport report, } private MeasureReport evaluate(Measure measure, Context context, List patients, - MeasureReport.MeasureReportType type) { + MeasureReport.MeasureReportType type, boolean isSingle) { MeasureReportBuilder reportBuilder = new MeasureReportBuilder(); reportBuilder.buildStatus("complete"); reportBuilder.buildType(type); @@ -491,13 +494,14 @@ private MeasureReport evaluate(Measure measure, Context context, List p */ } if (sdeAccumulators.size() > 0) { - report = processAccumulators(report, sdeAccumulators, sde); + report = processAccumulators(report, sdeAccumulators, sde, isSingle, patients); } return report; } - private void populateSDEAccumulators(Measure measure, Context context, Patient patient,HashMap> sdeAccumulators, List sde){ + private void populateSDEAccumulators(Measure measure, Context context, Patient patient,HashMap> sdeAccumulators, + List sde){ context.setContextValue("Patient", patient.getIdElement().getIdPart()); List sdeList = sde.stream().map(sdeItem -> context.resolveExpressionRef(sdeItem.getCriteria().getExpression()).evaluate(context)).collect(Collectors.toList()); if(!sdeList.isEmpty()) { @@ -505,6 +509,9 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p Object sdeListItem = sdeList.get(i); if(null != sdeListItem) { String sdeAccumulatorKey = sde.get(i).getCode().getText(); + if(null == sdeAccumulatorKey || sdeAccumulatorKey.length() < 1){ + sdeAccumulatorKey = sde.get(i).getCriteria().getExpression(); + } HashMap sdeItemMap = sdeAccumulators.get(sdeAccumulatorKey); String code = ""; @@ -543,7 +550,7 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p } private MeasureReport processAccumulators(MeasureReport report, HashMap> sdeAccumulators, - List sde){ + List sde, boolean isSingle, List patients){ List newRefList = new ArrayList<>(); sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ @@ -551,7 +558,33 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap Date: Mon, 31 Aug 2020 08:58:32 -0600 Subject: [PATCH 123/198] fixing of observation extensions with correct codes --- .../cqf/r4/evaluation/MeasureEvaluation.java | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 6da317c96..0fb1f33f7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -557,13 +557,29 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap { + pt.getExtension().forEach((ptExt) -> { + if (ptExt.getUrl().contains(coreCategory)) { + String code = ((Coding) ptExt.getExtension().get(0).getValue()).getCode(); + if(code.equalsIgnoreCase(sdeAccumulatorKey)) { + valueCoding.setSystem(((Coding) ptExt.getExtension().get(0).getValue()).getSystem()); + valueCoding.setCode(code); + valueCoding.setDisplay(((Coding) ptExt.getExtension().get(0).getValue()).getDisplay()); + } + } + }); + }); + } + CodeableConcept obsCodeableConcept = new CodeableConcept(); if(!isSingle) { - //TODO - how much variety is expected in this extension?? Extension groupExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo"); Extension extExtMeasure = new Extension() .setUrl("measure") - //TODO - needs to be canonical .setValue(new CanonicalType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure())); groupExtension.addExtension(extExtMeasure); Extension extExtPop = new Extension() @@ -572,18 +588,13 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap Date: Mon, 31 Aug 2020 09:33:31 -0600 Subject: [PATCH 124/198] changed valueString to valueInteger for counts in group report --- .../java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 0fb1f33f7..278548fe1 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -586,7 +586,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap Date: Mon, 31 Aug 2020 09:48:53 -0600 Subject: [PATCH 125/198] added extension to individual report --- .../cqf/r4/evaluation/MeasureEvaluation.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 278548fe1..5b09e06f8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -576,18 +576,18 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap Date: Thu, 3 Sep 2020 12:00:03 -0600 Subject: [PATCH 126/198] Recreate work in Dstu3 --- .../dstu3/evaluation/MeasureEvaluation.java | 153 ++++++++- .../cqf/dstu3/helpers/LibraryHelper.java | 1 + .../providers/MeasureOperationsProvider.java | 305 ++++++++++++++---- 3 files changed, 376 insertions(+), 83 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index 3f0939ee1..cb533d3cf 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -8,18 +8,14 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.ListResource; -import org.hl7.fhir.dstu3.model.Measure; -import org.hl7.fhir.dstu3.model.MeasureReport; -import org.hl7.fhir.dstu3.model.Patient; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.dstu3.builders.MeasureReportBuilder; import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; @@ -58,16 +54,20 @@ public MeasureReport evaluatePatientMeasure(Measure measure, Context context, St // patient = (Patient) patientRetrieve.iterator().next(); // } + + + boolean isSingle = true; return evaluate(measure, context, patient == null ? Collections.emptyList() : Collections.singletonList(patient), - MeasureReport.MeasureReportType.INDIVIDUAL); + MeasureReport.MeasureReportType.INDIVIDUAL, isSingle); } public MeasureReport evaluatePatientListMeasure(Measure measure, Context context, String practitionerRef) { logger.info("Generating patient-list report"); List patients = practitionerRef == null ? getAllPatients() : getPractitionerPatients(practitionerRef); - return evaluate(measure, context, patients, MeasureReport.MeasureReportType.PATIENTLIST); + boolean isSingle = false; + return evaluate(measure, context, patients, MeasureReport.MeasureReportType.PATIENTLIST, isSingle); } private List getPractitionerPatients(String practitionerRef) { @@ -93,7 +93,8 @@ private List getAllPatients() { public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) { logger.info("Generating summary report"); - return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY); + boolean isSingle = false; + return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } @SuppressWarnings("unchecked") @@ -194,7 +195,7 @@ private void addPopulationCriteriaReport(MeasureReport report, } private MeasureReport evaluate(Measure measure, Context context, List patients, - MeasureReport.MeasureReportType type) { + MeasureReport.MeasureReportType type, boolean isSingle) { MeasureReportBuilder reportBuilder = new MeasureReportBuilder(); reportBuilder.buildStatus("complete"); reportBuilder.buildType(type); @@ -214,6 +215,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p throw new RuntimeException("Measure scoring is required in order to calculate."); } + List sde = new ArrayList<>(); + HashMap> sdeAccumulators = null; for (Measure.MeasureGroupComponent group : measure.getGroup()) { MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent(); reportGroup.setIdentifier(group.getIdentifier()); @@ -252,6 +255,8 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap measurePopulationPatients = null; HashMap measurePopulationExclusionPatients = null; + sdeAccumulators = new HashMap<>(); + sde = measure.getSupplementalData(); for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) { MeasurePopulationType populationType = MeasurePopulationType .fromCode(pop.getCode().getCodingFirstRep().getCode()); @@ -373,6 +378,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } } } + populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde); } // Calculate actual measure score, Count(numerator) / Count(denominator) @@ -409,6 +415,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } } } + populateSDEAccumulators(measure, context, patient, sdeAccumulators,sde); } break; @@ -422,6 +429,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p null); populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources, codeToResourceMap); + populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde); } break; @@ -472,13 +480,134 @@ private MeasureReport evaluate(Measure measure, Context context, List p } if (!resources.isEmpty()) { + List evaluatedResourceIds = new ArrayList<>(); + resources.forEach((key, resource) -> { + evaluatedResourceIds.add(new Reference(resource.getId())); + }); + + // TODO: DSTU3 Doesn't support this.. + // report.setEvaluatedResources(evaluatedResourceIds); + + /* FhirMeasureBundler bundler = new FhirMeasureBundler(); org.hl7.fhir.dstu3.model.Bundle evaluatedResources = bundler.bundle(resources.values()); evaluatedResources.setId(UUID.randomUUID().toString()); - report.setEvaluatedResources(new Reference('#' + evaluatedResources.getId())); + report.setEvaluatedResources(new Reference(evaluatedResources.getId())); report.addContained(evaluatedResources); + */ + } + + if (sdeAccumulators.size() > 0) { + report = processAccumulators(report, sdeAccumulators, sde, isSingle, patients); + } + + return report; + } + + private void populateSDEAccumulators(Measure measure, Context context, Patient patient,HashMap> sdeAccumulators, + List sde){ + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + List sdeList = sde.stream().map(sdeItem -> context.resolveExpressionRef(sdeItem.getCriteria()).evaluate(context)).collect(Collectors.toList()); + if(!sdeList.isEmpty()) { + for (int i = 0; i < sdeList.size(); i++) { + Object sdeListItem = sdeList.get(i); + if(null != sdeListItem) { + String sdeAccumulatorKey = sde.get(i).getId(); + if(null == sdeAccumulatorKey || sdeAccumulatorKey.length() < 1){ + sdeAccumulatorKey = sde.get(i).getCriteria(); + } + HashMap sdeItemMap = sdeAccumulators.get(sdeAccumulatorKey); + String code = ""; + + switch (sdeListItem.getClass().getSimpleName()) { + case "Code": + code = ((Code) sdeListItem).getCode(); + break; + case "ArrayList": + if (((ArrayList) sdeListItem).size() > 0) { + code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + }else{ + continue; + } + break; + } + if(null == code){ + continue; + } + if (null != sdeItemMap && null != sdeItemMap.get(code)) { + Integer sdeItemValue = sdeItemMap.get(code); + sdeItemValue++; + sdeItemMap.put(code, sdeItemValue); + sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue); + } else { + if (null == sdeAccumulators.get(sdeAccumulatorKey)) { + HashMap newSDEItem = new HashMap<>(); + newSDEItem.put(code, 1); + sdeAccumulators.put(sdeAccumulatorKey, newSDEItem); + } else { + sdeAccumulators.get(sdeAccumulatorKey).put(code, 1); + } + } + } + } } + } + private MeasureReport processAccumulators(MeasureReport report, HashMap> sdeAccumulators, + List sde, boolean isSingle, List patients){ + List newRefList = new ArrayList<>(); + sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { + sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(UUID.randomUUID().toString()); + Coding valueCoding = new Coding(); + if(sdeKey.equalsIgnoreCase("sde-sex")){ + valueCoding.setCode(sdeAccumulatorKey); + }else { + String coreCategory = sdeKey.substring(sdeKey.lastIndexOf('-')); + patients.forEach((pt)-> { + pt.getExtension().forEach((ptExt) -> { + if (ptExt.getUrl().contains(coreCategory)) { + String code = ((Coding) ptExt.getExtension().get(0).getValue()).getCode(); + if(code.equalsIgnoreCase(sdeAccumulatorKey)) { + valueCoding.setSystem(((Coding) ptExt.getExtension().get(0).getValue()).getSystem()); + valueCoding.setCode(code); + valueCoding.setDisplay(((Coding) ptExt.getExtension().get(0).getValue()).getDisplay()); + } + } + }); + }); + } + CodeableConcept obsCodeableConcept = new CodeableConcept(); + Extension obsExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo"); + Extension extExtMeasure = new Extension() + .setUrl("measure") + .setValue(new StringType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure())); + obsExtension.addExtension(extExtMeasure); + Extension extExtPop = new Extension() + .setUrl("populationId") + .setValue(new StringType(sdeKey)); + obsExtension.addExtension(extExtPop); + obs.addExtension(obsExtension); + obs.setValue(new IntegerType(sdeAcumulatorValue)); + if(!isSingle) { + valueCoding.setCode(sdeAccumulatorKey); + obsCodeableConcept.setCoding(Collections.singletonList(valueCoding)); + obs.setCode(obsCodeableConcept); + }else{ + obs.setCode(new CodeableConcept().setText(sdeKey)); + obsCodeableConcept.setCoding(Collections.singletonList(valueCoding)); + obs.setValue(obsCodeableConcept); + } + newRefList.add(new Reference("#" + obs.getId())); + report.addContained(obs); + }); + }); + + // TODO: Evaluated resources + // newRefList.addAll(report.getEvaluatedResource()); + // report.setEvaluatedResource(newRefList); return report; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index f59466d00..48b9e81c2 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -37,6 +37,7 @@ public static List loadLibraries(Meas List libraries = new ArrayList(); // load libraries + //TODO: if there's a bad measure argument, this blows up for an obscure error for (Reference ref : measure.getLibrary()) { // if library is contained in measure, load it into server if (ref.getReferenceElement().getIdPart().startsWith("#")) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java index 0d690d25c..8bfedc54b 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java @@ -1,35 +1,21 @@ package org.opencds.cqf.dstu3.providers; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.CodeableConcept; -import org.hl7.fhir.dstu3.model.Coding; -import org.hl7.fhir.dstu3.model.Composition; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.IdType; -import org.hl7.fhir.dstu3.model.Library; -import org.hl7.fhir.dstu3.model.ListResource; -import org.hl7.fhir.dstu3.model.Measure; -import org.hl7.fhir.dstu3.model.MeasureReport; -import org.hl7.fhir.dstu3.model.Narrative; -import org.hl7.fhir.dstu3.model.Parameters; -import org.hl7.fhir.dstu3.model.Reference; -import org.hl7.fhir.dstu3.model.RelatedArtifact; -import org.hl7.fhir.dstu3.model.Resource; -import org.hl7.fhir.dstu3.model.StringType; +import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.rest.annotation.*; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.dstu3.model.*; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; +import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.dstu3.evaluation.MeasureEvaluation; import org.opencds.cqf.dstu3.evaluation.MeasureEvaluationSeed; @@ -236,52 +222,152 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, // } @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) - public Bundle careGapsReport(@OperationParam(name = "periodStart") String periodStart, - @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "topic") String topic, - @OperationParam(name = "patient") String patientRef) { - List measures = this.measureResourceProvider.getDao().search(new SearchParameterMap() - .add("topic", new TokenParam().setModifier(TokenParamModifier.TEXT).setValue(topic))) - .getResources(0, 1000); + public Parameters careGapsReport(@OperationParam(name = "periodStart") String periodStart, + @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, + @OperationParam(name = "topic") String topic,@OperationParam(name = "practitioner") String practitioner, + @OperationParam(name = "measure") String measure, @OperationParam(name="status")String status, + @OperationParam(name = "organization") String organization){ + //TODO: status - optional if null all gaps - if closed-gap code only those gaps that are closed if open-gap code only those that are open + //TODO: topic should allow many and be a union of them + //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." + Parameters returnParams = new Parameters(); + if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { + if(subject.startsWith("Patient/")){ + returnParams.addParameter(new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + subject) + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); + return returnParams; + }else if(subject.startsWith("Group/")) { + returnParams.setId((status==null?"all-gaps": status) + "-" + subject.replace("/","_") + "-report"); + (getPatientListFromGroup(subject)) + .forEach(groupSubject ->{ + Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); + if(null != patientGapBundle){ + returnParams.addParameter(new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + groupSubject) + .setResource(patientGapBundle)); + } + }); + } + return returnParams; + } + if (practitioner == null || practitioner.equals("")) { + return new Parameters().addParameter( + new Parameters.ParametersParameterComponent() + .setName("Gaps in Care Report - " + subject) + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure,status))); + } + return returnParams; + } + + private List getPatientListFromGroup(String subjectGroupRef){ + List patientList = new ArrayList<>(); + + DataProvider dataProvider = this.factory.createDataProvider("FHIR", "3"); + Iterable groupRetrieve = dataProvider.retrieve("Group", "id", subjectGroupRef, "Group", null, null, null, + null, null, null, null, null); + Group group; + if (groupRetrieve.iterator().hasNext()) { + group = (Group) groupRetrieve.iterator().next(); + group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); + } + return patientList; + } + + private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, + String practitioner, String measure, String status, String organization){ + if(periodStart == null || periodStart.equals("") || + periodEnd == null || periodEnd.equals("")){ + throw new IllegalArgumentException("periodStart and periodEnd are required."); + } + //TODO - remove this - covered in check of subject/practitioner/organization - left in for now 'cause we need a subject to develop + if (subject == null || subject.equals("")) { + throw new IllegalArgumentException("Subject is required."); + } + if(null != subject) { + if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { + throw new IllegalArgumentException("Subject must follow the format of either 'Patient/ID' OR 'Group/ID'."); + } + } + if(null != status && (!status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap"))){ + throw new IllegalArgumentException("If status is present, it must be either 'open-gap' or 'closed-gap'."); + } + if(null != practitioner && null == organization){ + throw new IllegalArgumentException("If a practitioner is specified then an organization must also be specified."); + } + if(null == subject && null == practitioner && null == organization){ + throw new IllegalArgumentException("periodStart AND periodEnd AND (subject OR organization OR (practitioner AND organization)) MUST be provided"); + } + return true; + } + + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure, String status) { + //TODO: this is an org hack. Need to figure out what the right thing is. + IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); + List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); + + SearchParameterMap theParams = new SearchParameterMap(); + + // if (theId != null) { + // var measureParam = new StringParam(theId.getIdPart()); + // theParams.add("_id", measureParam); + // } + + if (topic != null && !topic.equals("")) { + TokenParam topicParam = new TokenParam(topic); + theParams.add("topic", topicParam); + } + List measures = getMeasureList(theParams, measure); + Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); + // TODO: no timestamp on dstu3 care-gap report + //careGapReport.setTimestamp(new Date()); Composition composition = new Composition(); - // TODO - this is a placeholder code for now ... replace with preferred code - // once identified - CodeableConcept typeCode = new CodeableConcept() - .addCoding(new Coding().setSystem("http://loinc.org").setCode("57024-2")); - composition.setStatus(Composition.CompositionStatus.FINAL).setType(typeCode) - .setSubject(new Reference(patientRef.startsWith("Patient/") ? patientRef : "Patient/" + patientRef)) - .setTitle(topic + " Care Gap Report"); + composition.setStatus(Composition.CompositionStatus.FINAL) + .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) + .setTitle("Care Gap Report for " + subject) + .setDate(new Date()) + .setType(new CodeableConcept() + .addCoding(new Coding() + .setCode("gaps-doc") + .setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/gaps-doc-type") + .setDisplay("Gaps in Care Report"))); List reports = new ArrayList<>(); - MeasureReport report = new MeasureReport(); + List detectedIssues = new ArrayList(); + MeasureReport report = null; + boolean hasIssue = false; + for (IBaseResource resource : measures) { + Measure measureResource = (Measure) resource; + Composition.SectionComponent section = new Composition.SectionComponent(); - Measure measure = (Measure) resource; - section.addEntry( - new Reference(measure.getIdElement().getResourceType() + "/" + measure.getIdElement().getIdPart())); - if (measure.hasTitle()) { - section.setTitle(measure.getTitle()); - } - String improvementNotation = "increase"; // defaulting to "increase" - if (measure.hasImprovementNotation()) { - improvementNotation = measure.getImprovementNotation(); - section.setText(new Narrative().setStatus(Narrative.NarrativeStatus.GENERATED) - .setDiv(new XhtmlNode().setValue(improvementNotation))); + if (measureResource.hasTitle()) { + section.setTitle(measureResource.getTitle()); } - LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(this.libraryResolutionProvider); - MeasureEvaluationSeed seed = new MeasureEvaluationSeed(this.factory, libraryLoader, - this.libraryResolutionProvider); - seed.setup(measure, periodStart, periodEnd, null, null, null, null); - MeasureEvaluation evaluator = new MeasureEvaluation(this.registry, - seed.getMeasurementPeriod()); // TODO - this is configured for patient-level evaluation only - report = evaluator.evaluatePatientMeasure(seed.getMeasure(), seed.getContext(), patientRef); + report = evaluateMeasure(measureResource.getIdElement(), periodStart, periodEnd, null, "patient", subject, null, + null, null, null, null, null); + + report.setId(UUID.randomUUID().toString()); + report.setDate(new Date()); + // TODO: No improvement notation on dstu3 report + //report.setImprovementNotation(measureResource.getImprovementNotation()); + //TODO: this is an org hack && requires an Organization to be in the ruler + if (org != null && org.size() > 0) { + // TODO: No reporter on dstu3 report + //report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); + } + report.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/indv-measurereport-deqm")); + //section.setFocus(new Reference("MeasureReport/" + report.getId())); + //TODO: DetectedIssue + //section.addEntry(new Reference("MeasureReport/" + report.getId())); - if (report.hasGroup() && measure.hasScoring()) { + if (report.hasGroup() && measureResource.hasScoring()) { int numerator = 0; int denominator = 0; for (MeasureReport.MeasureReportGroupComponent group : report.getGroup()) { @@ -307,42 +393,119 @@ public Bundle careGapsReport(@OperationParam(name = "periodStart") String period } } + //TODO: implement this per the spec + //Holding off on implementiation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) double proportion = 0.0; - if (measure.getScoring().hasCoding() && denominator != 0) { - for (Coding coding : measure.getScoring().getCoding()) { + if (measureResource.getScoring().hasCoding() && denominator != 0) { + for (Coding coding : measureResource.getScoring().getCoding()) { if (coding.hasCode() && coding.getCode().equals("proportion")) { - proportion = numerator / denominator; + if (denominator != 0.0 ) { + proportion = numerator / denominator; + } } } } // TODO - this is super hacky ... change once improvementNotation is specified // as a code - if (improvementNotation.toLowerCase().contains("increase")) { - if (proportion < 1.0) { + String improvementNotation = measureResource.getImprovementNotation(); + if (((improvementNotation.equals("increase")) && (proportion < 1.0)) + || ((improvementNotation.equals("decrease")) && (proportion > 0.0)) + && (null == status || "".equalsIgnoreCase(status) || "open-gap".equalsIgnoreCase(status))) { + hasIssue = true; + DetectedIssue detectedIssue = new DetectedIssue(); + detectedIssue.setId(UUID.randomUUID().toString()); + detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); + detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); + // TODO: No evidence on DSTU3 detected issue + // detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); + CodeableConcept code = new CodeableConcept() + .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); + + // TODO: No code on DSTU3 detected issue + //detectedIssue.setCode(code); + + section.addEntry( + new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); composition.addSection(section); - reports.add(report); - } - } else if (improvementNotation.toLowerCase().contains("decrease")) { - if (proportion > 0.0) { - composition.addSection(section); - reports.add(report); - } + + detectedIssues.add(detectedIssue); } + reports.add(report); // TODO - add other types of improvement notation cases } } - - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); - - for (MeasureReport rep : reports) { - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + Parameters parameters = new Parameters(); + if((null == status || status == "") //everything + || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) //filter out closed-gap that has issues for OPEN-GAP + ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filet out open-gap without issues for CLOSE-GAP + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); + for (MeasureReport rep : reports) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + if (report.hasContained()) { + for (Resource contained : report.getContained()) { + if (contained instanceof Bundle) { + addEvaluatedResourcesToParameters((Bundle) contained, parameters); + if(null != parameters && !parameters.isEmpty()) { + List evaluatedResource = new ArrayList<>(); + parameters.getParameter().forEach(parameter -> { + Reference newEvaluatedResourceItem = new Reference(); + newEvaluatedResourceItem.setReference(parameter.getResource().getId()); + List evalResourceExt = new ArrayList<>(); + evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-ppopulationReference", + new CodeableConcept() + .addCoding(new Coding("http://teminology.hl7.org/CodeSystem/measure-population", "initial-population", "initial-population")))); + newEvaluatedResourceItem.setExtension(evalResourceExt); + evaluatedResource.add(newEvaluatedResourceItem); + }); + // TODO: Figure out for DSTU3 + //report.setEvaluatedResource(evaluatedResource); + } + } + } + } + } + for (DetectedIssue detectedIssue : detectedIssues) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); + } + } + if(careGapReport.getEntry().isEmpty()){ + return null; } - return careGapReport; } + private List getMeasureList(SearchParameterMap theParams, String measure){ + if(measure != null && measure.length() > 0){ + List finalMeasureList = new ArrayList<>(); + List allMeasures = this.measureResourceProvider + .getDao() + .search(theParams) + .getResources(0, 1000); + for(String singleName: measure.split(",")){ + if (singleName.equals("")) { + continue; + } + allMeasures.forEach(measureResource -> { + if(((Measure)measureResource).getName().equalsIgnoreCase(singleName.trim())) { + if (measureResource != null) { + finalMeasureList.add(measureResource); + } + } + }); + } + return finalMeasureList; + }else { + return + //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. + this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) + .stream() + .filter(resource -> ((Measure)resource).getUrl() != null && !((Measure)resource).getUrl().equals("")) + .collect(Collectors.toList()); + } + } + @Operation(name = "$collect-data", idempotent = true, type = Measure.class) public Parameters collectData(@IdParam IdType theId, @OperationParam(name = "periodStart") String periodStart, @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "patient") String patientRef, From 926dbd17f7c3d5670d5e6ed868b813a979cc47b5 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Thu, 3 Sep 2020 15:35:47 -0600 Subject: [PATCH 127/198] Fixed logic library returning false if no type is specified (now uses whether or not there is CQL or ELM content if no type is specified) Fixed an index out of range exception when no primary library could be identified --- .../cqf/dstu3/helpers/LibraryHelper.java | 20 ++++++++++++++----- .../opencds/cqf/r4/helpers/LibraryHelper.java | 20 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java index 48b9e81c2..be38ecc9d 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/helpers/LibraryHelper.java @@ -62,6 +62,11 @@ public static List loadLibraries(Meas } } + if (libraries.isEmpty()) { + throw new IllegalArgumentException(String + .format("Could not load library source for libraries referenced in Measure/%s.", measure.getId())); + } + VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { @@ -78,11 +83,6 @@ public static List loadLibraries(Meas } } - if (libraries.isEmpty()) { - throw new IllegalArgumentException(String - .format("Could not load library source for libraries referenced in Measure/%s.", measure.getId())); - } - return libraries; } @@ -92,6 +92,16 @@ private static boolean isLogicLibrary(org.hl7.fhir.dstu3.model.Library library) } if (!library.hasType()) { + // If no type is specified, assume it is a logic library based on whether there is a CQL content element. + if (library.hasContent()) { + for (Attachment a : library.getContent()) { + if (a.hasContentType() && (a.getContentType().equals("text/cql") + || a.getContentType().equals("application/elm+xml") + || a.getContentType().equals("application/elm+json"))) { + return true; + } + } + } return false; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 0e8e806e5..4d290c34e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -69,6 +69,11 @@ public static List loadLibraries(Meas } } + if (libraries.isEmpty()) { + throw new IllegalArgumentException(String + .format("Could not load library source for libraries referenced in Measure/%s.", measure.getId())); + } + VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { @@ -91,11 +96,6 @@ else if (artifact.getResource().contains(("/Library/"))) { } } - if (libraries.isEmpty()) { - throw new IllegalArgumentException(String - .format("Could not load library source for libraries referenced in Measure/%s.", measure.getId())); - } - return libraries; } @@ -105,6 +105,16 @@ private static boolean isLogicLibrary(org.hl7.fhir.r4.model.Library library) { } if (!library.hasType()) { + // If no type is specified, assume it is a logic library based on whether there is a CQL content element. + if (library.hasContent()) { + for (Attachment a : library.getContent()) { + if (a.hasContentType() && (a.getContentType().equals("text/cql") + || a.getContentType().equals("application/elm+xml") + || a.getContentType().equals("application/elm+json"))) { + return true; + } + } + } return false; } From 7bbffad70e10aab18c6bf88da49da0d9d14b8055 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sat, 12 Sep 2020 17:54:38 -0600 Subject: [PATCH 128/198] Add error room notifcation --- .travis.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9ba5459d2..e2b4b3237 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,5 +50,11 @@ before_cache: - rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-r4 notifications: slack: - rooms: - secure: UcwCk/9xDrkLXtaHPXJAsJuyZN7MIzRunndBpxMk/fwiAF56ZscPMu5IlPN72x2TqdkDPDAykGnczQ/w76txFcIzyH9/06fmXh//8XL/YWpERsfCW9q1XOn+xTTXwrxF5f6eaAMqYlNDe5tXLCOHCEnuHpdFNnFoNasZ2dTqaDNP+jjPLlSO0zhVv5zW4d75lpklg2+SQra5smgKW4eXJO6z4mXFJtkhN/lKwlgKCKbC+G8o3QO+zSJouyeCmr6xdPkTob1va8jNuR1SRIlk943btmZAUlfLh9pUMO2Hhay1BJhXpFrO5ZE3eCwU9jaoCyoTTPcVBytOKKNbGr1miRM/fDdxDYUMGQbqmkKDMqTZzkeLSbQPH92bs2lUILZf0xr2wbi8mZICdz0HZwRkaLzF6okIeQ69/b7/QNbDXAxKlT0DaShtidVbUfwNb/mUOXc3fkjn8vShPn7n9xtdLQvrGR9bJVHHBIDYS+dge2EtdPzsrmHsEhGS153uytPgF+zcYwRN83Xpyjk78+AMQFmzorErz6I+74ZQyXFzN4Z2Z1SfeGGpBp7uUK7dDD1WYVk370hf5oU19euWdSWmzBDajrLlalH25VKt140lQBMU6QyDmdraWM0f0iut034QbTUisOo+O2Km0AhZdTVTVmZSyLOJ6Ec+lusU2AHtayM= + - rooms: + - secure: UcwCk/9xDrkLXtaHPXJAsJuyZN7MIzRunndBpxMk/fwiAF56ZscPMu5IlPN72x2TqdkDPDAykGnczQ/w76txFcIzyH9/06fmXh//8XL/YWpERsfCW9q1XOn+xTTXwrxF5f6eaAMqYlNDe5tXLCOHCEnuHpdFNnFoNasZ2dTqaDNP+jjPLlSO0zhVv5zW4d75lpklg2+SQra5smgKW4eXJO6z4mXFJtkhN/lKwlgKCKbC+G8o3QO+zSJouyeCmr6xdPkTob1va8jNuR1SRIlk943btmZAUlfLh9pUMO2Hhay1BJhXpFrO5ZE3eCwU9jaoCyoTTPcVBytOKKNbGr1miRM/fDdxDYUMGQbqmkKDMqTZzkeLSbQPH92bs2lUILZf0xr2wbi8mZICdz0HZwRkaLzF6okIeQ69/b7/QNbDXAxKlT0DaShtidVbUfwNb/mUOXc3fkjn8vShPn7n9xtdLQvrGR9bJVHHBIDYS+dge2EtdPzsrmHsEhGS153uytPgF+zcYwRN83Xpyjk78+AMQFmzorErz6I+74ZQyXFzN4Z2Z1SfeGGpBp7uUK7dDD1WYVk370hf5oU19euWdSWmzBDajrLlalH25VKt140lQBMU6QyDmdraWM0f0iut034QbTUisOo+O2Km0AhZdTVTVmZSyLOJ6Ec+lusU2AHtayM= + on_success: always + on_failure: never + - rooms: + - secure: CNBq9ztma598N0CBeQNcvwvx6d+jHnI0B8iWAedqJ+oX+kYwCb4tBRTEwtflB0aQndAzn/5fba0S4tQ1gP9liRWb+RQIeBHVQgopEg7iNXYj6d4NIYBmVxpVq44szQI8T7mQJN/v1DFjXoCRbeMKiA5jN8OCrM4kowF4O15b+9RJ1bxbJULzop5K3WlZQQbsDSdxIuR3CGCmLakvf5G1cqHmHE1xWtXWFrNOSe32bp/Pdv7Vo5jaIeLG1b70G4SUUKk60+cwK9/NH4okewYgU5KlI+q51UW0Io8KEmsoFWV49dJ+1NfINM7uArVS3nNeJViMumR2ba1L1gUjGXj9FkuuIM8OS9FaxSss0IaQ8z+gNekuQLyvrE4020UNGY4/b1B8IueQcs+sucs4juCnvyTGdAEGiT23mVWdurqjT6qFTzYHSbXXd23KlmFoeLs46Iwo4mXwx6pxoUmBbNKpSXs7/irCz0GJHXjJAOb3JXxyBohyimKlfBZu1ZJhLytcsQD9SIVpX5B97SD1xD99BAN3sFgDUtZ91usLc3eSg1jaHmaSimw9PVGcoXHEkxiJYzQVPuZNQtwmRSu0zezAv9DhbkNzZAEBkC+vt1dBWzj9T9An3AwIlyANSb0P2qtC7wreHzvLq5duHA0zKGaMjtAZecnEXpIeuhuJhfximP4= + on_success: never + on_failure: always From 46b5de85aec648317f49b9c1e953cf74c8f9c486 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 4 Sep 2020 14:32:02 -0600 Subject: [PATCH 129/198] Updates to support HAPI 5.0.2 --- .../cqf/common/config/FhirServerConfig.java | 8 +++++- .../retrieve/JpaFhirRetrieveProvider.java | 4 +-- .../dstu3/evaluation/MeasureEvaluation.java | 2 +- .../cqf/dstu3/evaluation/ProviderFactory.java | 2 +- .../ActivityDefinitionApplyProvider.java | 2 +- .../providers/ApplyCqlOperationProvider.java | 2 +- .../providers/CacheValueSetsProvider.java | 4 +-- .../providers/CodeSystemUpdateProvider.java | 2 +- .../providers/DataRequirementsProvider.java | 10 +++---- .../cqf/dstu3/providers/HQMFProvider.java | 10 +++---- .../providers/JpaTerminologyProvider.java | 17 ++++++------ .../providers/LibraryOperationsProvider.java | 2 +- .../providers/MeasureOperationsProvider.java | 10 +++---- .../cqf/dstu3/providers/OAuthProvider.java | 9 ++++--- .../PlanDefinitionApplyProvider.java | 2 +- .../cqf/dstu3/servlet/BaseServlet.java | 27 ++++++++++--------- pom.xml | 6 ++--- .../cqf/r4/evaluation/MeasureEvaluation.java | 2 +- .../cqf/r4/evaluation/ProviderFactory.java | 2 +- .../ActivityDefinitionApplyProvider.java | 2 +- .../providers/ApplyCqlOperationProvider.java | 2 +- .../r4/providers/CacheValueSetsProvider.java | 4 +-- .../providers/CodeSystemUpdateProvider.java | 2 +- .../providers/DataRequirementsProvider.java | 10 +++---- .../cqf/r4/providers/HQMFProvider.java | 10 +++---- .../r4/providers/JpaTerminologyProvider.java | 17 +++++++----- .../providers/LibraryOperationsProvider.java | 4 +-- .../providers/MeasureOperationsProvider.java | 10 +++---- .../cqf/r4/providers/OAuthProvider.java | 9 ++++--- .../PlanDefinitionApplyProvider.java | 2 +- .../opencds/cqf/r4/servlet/BaseServlet.java | 25 ++++++++--------- 31 files changed, 117 insertions(+), 103 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index 1a861e418..a6038e644 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -9,7 +9,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; -import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; @@ -118,4 +119,9 @@ public LoggingInterceptor loggingInterceptor() { public ResponseHighlighterInterceptor responseHighlighterInterceptor() { return new ResponseHighlighterInterceptor(); } + + @Bean + public PartitionSettings partitionSettings() { + return new PartitionSettings(); + } } diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index 0bcefe741..2719bf68f 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -16,8 +16,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.IBundleProvider; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index cb533d3cf..657a3db33 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -22,7 +22,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index fe5cbe955..67afaa133 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -12,7 +12,7 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.client.api.IGenericClient; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java index 31930aeb4..a015d2e8c 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java @@ -26,7 +26,7 @@ import org.opencds.cqf.dstu3.helpers.Helper; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java index a86928ab4..d82bba842 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java @@ -28,7 +28,7 @@ import org.opencds.cqf.cql.engine.runtime.DateTime; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java index 825848f0f..563e327dc 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java @@ -12,8 +12,8 @@ import org.hl7.fhir.dstu3.model.ValueSet; import org.opencds.cqf.dstu3.helpers.Helper; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index 1e6c2af75..e67461477 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -17,7 +17,7 @@ import org.opencds.cqf.dstu3.builders.OperationOutcomeBuilder; import org.opencds.cqf.dstu3.builders.RandomIdBuilder; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java index def152b0f..017050d28 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java @@ -63,11 +63,11 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.dstu3.helpers.LibraryHelper; -import org.opencds.cqf.measure.stu3.CodeTerminologyRef; -import org.opencds.cqf.measure.stu3.CqfMeasure; -import org.opencds.cqf.measure.stu3.TerminologyRef; -import org.opencds.cqf.measure.stu3.TerminologyRef.TerminologyRefType; -import org.opencds.cqf.measure.stu3.VersionedTerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; +import org.opencds.cqf.tooling.measure.stu3.TerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.TerminologyRef.TerminologyRefType; +import org.opencds.cqf.tooling.measure.stu3.VersionedTerminologyRef; public class DataRequirementsProvider { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java index 66714f1e2..56b4eb8e4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java @@ -42,11 +42,11 @@ import org.jsoup.Jsoup; import org.opencds.cqf.common.providers.InMemoryLibraryResourceProvider; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.library.stu3.NarrativeProvider; -import org.opencds.cqf.measure.stu3.CodeTerminologyRef; -import org.opencds.cqf.measure.stu3.CqfMeasure; -import org.opencds.cqf.measure.stu3.TerminologyRef; -import org.opencds.cqf.measure.stu3.TerminologyRef.TerminologyRefType; +import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; +import org.opencds.cqf.tooling.measure.stu3.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; +import org.opencds.cqf.tooling.measure.stu3.TerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.TerminologyRef.TerminologyRefType; import org.w3c.dom.Document; import ca.uhn.fhir.context.FhirContext; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java index 7e390d73c..9eaba6eab 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; -import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; @@ -14,9 +13,11 @@ import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.jpa.rp.dstu3.ValueSetResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; @@ -106,7 +107,7 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource } } - List expansion = terminologySvcDstu3.expandValueSet(valueSet.getId()); + List expansion = terminologySvcDstu3.expandValueSet(new ValueSetExpansionOptions().setCount(Integer.MAX_VALUE), valueSet.getId()); for (VersionIndependentConcept concept : expansion) { codes.add(new Code().withCode(concept.getCode()).withSystem(concept.getSystem())); } @@ -116,11 +117,11 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource @Override public synchronized Code lookup(Code code, CodeSystemInfo codeSystem) throws ResourceNotFoundException { - CodeSystem cs = terminologySvcDstu3.fetchCodeSystem(context, codeSystem.getId()); - for (CodeSystem.ConceptDefinitionComponent concept : cs.getConcept()) { - if (concept.getCode().equals(code.getCode())) - return code.withSystem(codeSystem.getId()).withDisplay(concept.getDisplay()); - } + LookupCodeResult cs = terminologySvcDstu3.lookupCode(terminologySvcDstu3, codeSystem.getId(), code.getCode()); + + code.setDisplay(cs.getCodeDisplay()); + code.setSystem(codeSystem.getId()); + return code; } } \ No newline at end of file diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index 342c5717b..41ed8dcb2 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -18,7 +18,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.providers.LibrarySourceProvider; -import org.opencds.cqf.library.stu3.NarrativeProvider; +import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; import ca.uhn.fhir.jpa.rp.dstu3.LibraryResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java index 8bfedc54b..e97cf29b8 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java @@ -5,7 +5,7 @@ import javax.servlet.http.HttpServletRequest; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.*; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; @@ -20,14 +20,14 @@ import org.opencds.cqf.dstu3.evaluation.MeasureEvaluation; import org.opencds.cqf.dstu3.evaluation.MeasureEvaluationSeed; import org.opencds.cqf.dstu3.helpers.LibraryHelper; -import org.opencds.cqf.library.stu3.NarrativeProvider; -import org.opencds.cqf.measure.stu3.CqfMeasure; +import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; +import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java index 02229014f..6ed12c9b6 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java @@ -1,8 +1,9 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; @@ -12,8 +13,8 @@ import javax.servlet.http.HttpServletRequest; public class OAuthProvider extends JpaConformanceProviderDstu3 { - public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { - super(theRestfulServer, theSystemDao, theDaoConfig); + public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { + super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); } @Metadata diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java index 6ec03ca29..6712aeced 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java @@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index b994abc55..d982479fc 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -20,19 +20,19 @@ import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.dstu3.evaluation.ProviderFactory; import org.opencds.cqf.dstu3.providers.*; -import org.opencds.cqf.library.stu3.NarrativeProvider; -import org.opencds.cqf.measure.stu3.CodeTerminologyRef; -import org.opencds.cqf.measure.stu3.CqfMeasure; -import org.opencds.cqf.measure.stu3.PopulationCriteriaMap; -import org.opencds.cqf.measure.stu3.VersionedTerminologyRef; +import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; +import org.opencds.cqf.tooling.measure.stu3.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; +import org.opencds.cqf.tooling.measure.stu3.PopulationCriteriaMap; +import org.opencds.cqf.tooling.measure.stu3.VersionedTerminologyRef; import org.springframework.context.ApplicationContext; import org.springframework.web.cors.CorsConfiguration; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; @@ -41,8 +41,9 @@ import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider; import ca.uhn.fhir.jpa.rp.dstu3.ValueSetResourceProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; @@ -83,18 +84,18 @@ protected void initialize() throws ServletException { registerProvider(systemProvider); ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersDstu3", - ResourceProviderFactory.class); + ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); if(HapiProperties.getOAuthEnabled()) { OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, - appCtx.getBean(DaoConfig.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); this.registerProvider(oauthProvider); this.setServerConformanceProvider(oauthProvider); }else { JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, - appCtx.getBean(DaoConfig.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); setServerConformanceProvider(confProvider); } diff --git a/pom.xml b/pom.xml index e07cbb09b..42d42c030 100644 --- a/pom.xml +++ b/pom.xml @@ -54,11 +54,11 @@ 9.4.28.v20200408 2.10.1 - 4.2.0 - 1.1.1 + 5.0.2 + 1.3.0-SNAPSHOT 1.5.0-SNAPSHOT 1.0.0-SNAPSHOT - 1.4.9 + 1.5.0-SNAPSHOT 1.3.0-SNAPSHOT 1.7.30 diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 5b09e06f8..6cefc9a58 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -23,7 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index 885fe3071..b60a6c509 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -12,7 +12,7 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.rest.client.api.IGenericClient; // This class is a relatively dumb factory for data providers. It supports only diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java index 18325637e..672b65de1 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java @@ -26,7 +26,7 @@ import org.opencds.cqf.r4.helpers.Helper; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java index 8aa6cfd3a..ec0215e11 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java @@ -28,7 +28,7 @@ import org.opencds.cqf.cql.engine.runtime.DateTime; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java index bbb9f49b9..addf35302 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java @@ -12,8 +12,8 @@ import org.hl7.fhir.r4.model.ValueSet; import org.opencds.cqf.r4.helpers.Helper; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index cb60b446a..c0ddbf56d 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -17,7 +17,7 @@ import org.opencds.cqf.r4.builders.OperationOutcomeBuilder; import org.opencds.cqf.r4.builders.RandomIdBuilder; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java index 971ba12f4..03557ad5f 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java @@ -57,11 +57,11 @@ import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; -import org.opencds.cqf.measure.r4.CodeTerminologyRef; -import org.opencds.cqf.measure.r4.CqfMeasure; -import org.opencds.cqf.measure.r4.TerminologyRef; -import org.opencds.cqf.measure.r4.TerminologyRef.TerminologyRefType; -import org.opencds.cqf.measure.r4.VersionedTerminologyRef; +import org.opencds.cqf.tooling.measure.r4.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.r4.CqfMeasure; +import org.opencds.cqf.tooling.measure.r4.TerminologyRef; +import org.opencds.cqf.tooling.measure.r4.TerminologyRef.TerminologyRefType; +import org.opencds.cqf.tooling.measure.r4.VersionedTerminologyRef; import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.opencds.cqf.r4.helpers.LibraryHelper; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java index f137da2aa..4672a1544 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java @@ -41,11 +41,11 @@ import org.jsoup.Jsoup; import org.opencds.cqf.common.providers.InMemoryLibraryResourceProvider; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.library.r4.NarrativeProvider; -import org.opencds.cqf.measure.r4.CodeTerminologyRef; -import org.opencds.cqf.measure.r4.CqfMeasure; -import org.opencds.cqf.measure.r4.TerminologyRef; -import org.opencds.cqf.measure.r4.TerminologyRef.TerminologyRefType; +import org.opencds.cqf.tooling.library.r4.NarrativeProvider; +import org.opencds.cqf.tooling.measure.r4.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.r4.CqfMeasure; +import org.opencds.cqf.tooling.measure.r4.TerminologyRef; +import org.opencds.cqf.tooling.measure.r4.TerminologyRef.TerminologyRefType; import org.w3c.dom.Document; import ca.uhn.fhir.context.FhirContext; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java index 4715c185b..9a3e92139 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java @@ -14,9 +14,11 @@ import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.ValueSetExpansionOptions; +import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; import ca.uhn.fhir.jpa.rp.r4.ValueSetResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.term.VersionIndependentConcept; +import ca.uhn.fhir.util.VersionIndependentConcept; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; @@ -104,7 +106,8 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource } - List expansion = terminologySvcR4.expandValueSet(valueSet.getId()); + List expansion = terminologySvcR4 + .expandValueSet(new ValueSetExpansionOptions().setCount(Integer.MAX_VALUE), valueSet.getId()); for (VersionIndependentConcept concept : expansion) { codes.add(new Code().withCode(concept.getCode()).withSystem(concept.getSystem())); } @@ -114,11 +117,11 @@ public synchronized Iterable expand(ValueSetInfo valueSet) throws Resource @Override public synchronized Code lookup(Code code, CodeSystemInfo codeSystem) throws ResourceNotFoundException { - CodeSystem cs = terminologySvcR4.fetchCodeSystem(context, codeSystem.getId()); - for (CodeSystem.ConceptDefinitionComponent concept : cs.getConcept()) { - if (concept.getCode().equals(code.getCode())) - return code.withSystem(codeSystem.getId()).withDisplay(concept.getDisplay()); - } + LookupCodeResult cs = terminologySvcR4.lookupCode(terminologySvcR4, codeSystem.getId(), code.getCode()); + + code.setDisplay(cs.getCodeDisplay()); + code.setSystem(codeSystem.getId()); + return code; } } \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 3d15dcb09..22bcead2e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -54,11 +54,11 @@ import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.evaluator.execution.provider.BundleRetrieveProvider; -import org.opencds.cqf.library.r4.NarrativeProvider; +import org.opencds.cqf.tooling.library.r4.NarrativeProvider; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; -import ca.uhn.fhir.jpa.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.annotation.IdParam; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 05993372d..ed71d37d8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -5,7 +5,7 @@ import javax.servlet.http.HttpServletRequest; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.*; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; @@ -16,8 +16,8 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; -import org.opencds.cqf.library.r4.NarrativeProvider; -import org.opencds.cqf.measure.r4.CqfMeasure; +import org.opencds.cqf.tooling.library.r4.NarrativeProvider; +import org.opencds.cqf.tooling.measure.r4.CqfMeasure; import org.opencds.cqf.r4.evaluation.MeasureEvaluation; import org.opencds.cqf.r4.evaluation.MeasureEvaluationSeed; import org.opencds.cqf.r4.helpers.LibraryHelper; @@ -25,8 +25,8 @@ import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.MethodOutcome; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index e114560c0..3abfc24b0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -11,9 +11,10 @@ import org.hl7.fhir.r4.model.UriType; import org.opencds.cqf.common.config.HapiProperties; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; @@ -25,8 +26,8 @@ public class OAuthProvider extends JpaConformanceProviderR4 { * It should only get instantiated if hapi.properties has oauth.enabled set to true. */ - public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig) { - super(theRestfulServer, theSystemDao, theDaoConfig); + public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { + super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); } @Metadata diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index ead5599c9..6513c67fa 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -41,7 +41,7 @@ import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Operation; diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index b09b91d6a..2803b9ae3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -18,21 +18,21 @@ import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; -import org.opencds.cqf.library.r4.NarrativeProvider; -import org.opencds.cqf.measure.r4.CodeTerminologyRef; -import org.opencds.cqf.measure.r4.CqfMeasure; -import org.opencds.cqf.measure.r4.PopulationCriteriaMap; -import org.opencds.cqf.measure.r4.VersionedTerminologyRef; +import org.opencds.cqf.tooling.library.r4.NarrativeProvider; +import org.opencds.cqf.tooling.measure.r4.CodeTerminologyRef; +import org.opencds.cqf.tooling.measure.r4.CqfMeasure; +import org.opencds.cqf.tooling.measure.r4.PopulationCriteriaMap; +import org.opencds.cqf.tooling.measure.r4.VersionedTerminologyRef; import org.opencds.cqf.r4.evaluation.ProviderFactory; import org.opencds.cqf.r4.providers.*; import org.springframework.context.ApplicationContext; import org.springframework.web.cors.CorsConfiguration; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.dao.DaoRegistry; -import ca.uhn.fhir.jpa.dao.IFhirResourceDao; -import ca.uhn.fhir.jpa.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; @@ -41,8 +41,9 @@ import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.rp.r4.ValueSetResourceProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; -import ca.uhn.fhir.jpa.util.ResourceProviderFactory; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; @@ -89,12 +90,12 @@ protected void initialize() throws ServletException { if(HapiProperties.getOAuthEnabled()) { OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, - appCtx.getBean(DaoConfig.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); this.registerProvider(oauthProvider); this.setServerConformanceProvider(oauthProvider); }else { JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, - appCtx.getBean(DaoConfig.class)); + appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); setServerConformanceProvider(confProvider); } From 67c0955e4a41d11cfba2d89b859d18d14e125519 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sat, 5 Sep 2020 00:09:36 -0600 Subject: [PATCH 130/198] force versions for translator dependency versions --- pom.xml | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 42d42c030..e0a2cc0cd 100644 --- a/pom.xml +++ b/pom.xml @@ -92,8 +92,9 @@ - - + + + org.opencds.cqf.cql evaluator.execution ${cql-evaluator.version} @@ -151,6 +152,63 @@ ${cds-hooks.version} + + + + + + + + org.opencds.cqf.cql + evaluator.execution + + + org.opencds.cqf + tooling + + + org.slf4j + slf4j-log4j12 + + + + + + org.opencds.cqf.cql + engine + + + org.slf4j + slf4j-log4j12 + + + + + + org.opencds.cqf.cql + engine.fhir + + + org.slf4j + slf4j-log4j12 + + + + + + info.cqframework + cql-to-elm + + + + info.cqframework + cql-formatter + + + org.opencds.cqf + cds + + com.fasterxml.jackson.core jackson-databind From 4b68090d9c3d04a7e4cff1ccccae642c71536c90 Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Thu, 10 Sep 2020 11:37:07 -0600 Subject: [PATCH 131/198] Adding continuous measure evaluation capability. --- .../dstu3/evaluation/MeasureEvaluation.java | 83 +++++++++++++++--- .../cqf/r4/evaluation/MeasureEvaluation.java | 84 ++++++++++++++++--- .../r4/evaluation/MeasureEvaluationSeed.java | 5 ++ 3 files changed, 147 insertions(+), 25 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index 657a3db33..f4e5272bd 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -10,11 +10,14 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.cqframework.cql.elm.execution.ExpressionDef; +import org.cqframework.cql.elm.execution.FunctionDef; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.Variable; import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.dstu3.builders.MeasureReportBuilder; @@ -97,14 +100,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } - @SuppressWarnings("unchecked") - private Iterable evaluateCriteria(Context context, Patient patient, - Measure.MeasureGroupPopulationComponent pop) { - if (!pop.hasCriteria()) { - return Collections.emptyList(); - } - - context.setContextValue("Patient", patient.getIdElement().getIdPart()); + private void clearExpressionCache(Context context) { // Hack to clear expression cache // See cqf-ruler github issue #153 try { @@ -116,6 +112,66 @@ private Iterable evaluateCriteria(Context context, Patient patient, } catch (Exception e) { logger.warn("Error resetting expression cache", e); } + } + + private Resource evaluateObservationCriteria(Context context, Patient patient, Resource resource, Measure.MeasureGroupPopulationComponent pop, MeasureReport report) { + if (pop == null || !pop.hasCriteria()) { + return null; + } + + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + + clearExpressionCache(context); + + String observationName = pop.getCriteria(); + ExpressionDef ed = context.resolveExpressionRef(observationName); + if (!(ed instanceof FunctionDef)) { + throw new IllegalArgumentException(String.format("Measure observation %s does not reference a function definition", observationName)); + } + + Object result = null; + context.pushWindow(); + try { + context.push(new Variable().withName(((FunctionDef)ed).getOperand().get(0).getName()).withValue(resource)); + result = ed.getExpression().evaluate(context); + } + finally { + context.popWindow(); + } + + if (result instanceof Resource) { + return (Resource)result; + } + + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(UUID.randomUUID().toString()); + CodeableConcept cc = new CodeableConcept(); + cc.setText(observationName); + obs.setCode(cc); + Extension obsExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo"); + Extension extExtMeasure = new Extension() + .setUrl("measure") + .setValue(new UriType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure())); + obsExtension.addExtension(extExtMeasure); + Extension extExtPop = new Extension() + .setUrl("populationId") + .setValue(new StringType(observationName)); + obsExtension.addExtension(extExtPop); + obs.addExtension(obsExtension); + return obs; + } + + @SuppressWarnings("unchecked") + private Iterable evaluateCriteria(Context context, Patient patient, + Measure.MeasureGroupPopulationComponent pop) { + if (pop == null || !pop.hasCriteria()) { + return Collections.emptyList(); + } + + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + + clearExpressionCache(context); Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context); if (result == null) { @@ -319,6 +375,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } break; case MEASUREOBSERVATION: + measureObservationCriteria = pop; measureObservation = new HashMap<>(); break; } @@ -408,10 +465,12 @@ private MeasureReport evaluate(Measure measure, Context context, List p measurePopulationExclusionPatients); if (inMeasurePopulation) { - // TODO: Evaluate measure observations - for (Resource resource : evaluateCriteria(context, patient, - measureObservationCriteria)) { - measureObservation.put(resource.getIdElement().getIdPart(), resource); + for (Resource resource : measurePopulation.values()) { + Resource observation = evaluateObservationCriteria(context, patient, resource, measureObservationCriteria, report); + measureObservation.put(resource.getIdElement().getIdPart(), observation); + report.addContained(observation); + // TODO: Add to the evaluatedResources bundle + //report.getEvaluatedResources().add(new Reference("#" + observation.getId())); } } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 6cefc9a58..f5a385ced 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -10,12 +10,15 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.cqframework.cql.elm.execution.ExpressionDef; +import org.cqframework.cql.elm.execution.FunctionDef; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.Context; +import org.opencds.cqf.cql.engine.execution.Variable; import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.r4.builders.MeasureReportBuilder; @@ -97,15 +100,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } - @SuppressWarnings("unchecked") - private Iterable evaluateCriteria(Context context, Patient patient, - Measure.MeasureGroupPopulationComponent pop) { - if (!pop.hasCriteria()) { - return Collections.emptyList(); - } - - context.setContextValue("Patient", patient.getIdElement().getIdPart()); - + private void clearExpressionCache(Context context) { // Hack to clear expression cache // See cqf-ruler github issue #153 try { @@ -117,6 +112,67 @@ private Iterable evaluateCriteria(Context context, Patient patient, } catch (Exception e) { logger.warn("Error resetting expression cache", e); } + } + + private Resource evaluateObservationCriteria(Context context, Patient patient, Resource resource, Measure.MeasureGroupPopulationComponent pop, MeasureReport report) { + if (pop == null || !pop.hasCriteria()) { + return null; + } + + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + + clearExpressionCache(context); + + String observationName = pop.getCriteria().getExpression(); + ExpressionDef ed = context.resolveExpressionRef(observationName); + if (!(ed instanceof FunctionDef)) { + throw new IllegalArgumentException(String.format("Measure observation %s does not reference a function definition", observationName)); + } + + Object result = null; + context.pushWindow(); + try { + context.push(new Variable().withName(((FunctionDef)ed).getOperand().get(0).getName()).withValue(resource)); + result = ed.getExpression().evaluate(context); + } + finally { + context.popWindow(); + } + + if (result instanceof Resource) { + return (Resource)result; + } + + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + obs.setId(UUID.randomUUID().toString()); + CodeableConcept cc = new CodeableConcept(); + cc.setText(observationName); + obs.setCode(cc); + Extension obsExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo"); + Extension extExtMeasure = new Extension() + .setUrl("measure") + .setValue(new CanonicalType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure())); + obsExtension.addExtension(extExtMeasure); + Extension extExtPop = new Extension() + .setUrl("populationId") + .setValue(new StringType(observationName)); + obsExtension.addExtension(extExtPop); + obs.addExtension(obsExtension); + return obs; + } + + @SuppressWarnings("unchecked") + private Iterable evaluateCriteria(Context context, Patient patient, + Measure.MeasureGroupPopulationComponent pop) { + if (pop == null || !pop.hasCriteria()) { + return Collections.emptyList(); + } + + context.setContextValue("Patient", patient.getIdElement().getIdPart()); + + clearExpressionCache(context); + Object result = context.resolveExpressionRef(pop.getCriteria().getExpression()).evaluate(context); if (result == null) { return Collections.emptyList(); @@ -320,6 +376,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p } break; case MEASUREOBSERVATION: + measureObservationCriteria = pop; measureObservation = new HashMap<>(); break; } @@ -408,10 +465,11 @@ private MeasureReport evaluate(Measure measure, Context context, List p measurePopulationExclusionPatients); if (inMeasurePopulation) { - // TODO: Evaluate measure observations - for (Resource resource : evaluateCriteria(context, patient, - measureObservationCriteria)) { - measureObservation.put(resource.getIdElement().getIdPart(), resource); + for (Resource resource : measurePopulation.values()) { + Resource observation = evaluateObservationCriteria(context, patient, resource, measureObservationCriteria, report); + measureObservation.put(resource.getIdElement().getIdPart(), observation); + report.addContained(observation); + report.getEvaluatedResource().add(new Reference("#" + observation.getId())); } } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java index d79fe3ae0..a389c30f5 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluationSeed.java @@ -11,6 +11,7 @@ import org.opencds.cqf.common.helpers.UsingHelper; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.cql.engine.data.DataProvider; +import org.opencds.cqf.cql.engine.debug.DebugMap; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.runtime.DateTime; @@ -102,5 +103,9 @@ public void setup(Measure measure, String periodStart, String periodEnd, String } context.setExpressionCaching(true); + + DebugMap debugMap = new DebugMap(); + debugMap.setIsLoggingEnabled(true); + context.setDebugMap(debugMap); } } From 71d71589bcb4de43abb75032cdae64105cd7c879 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 10 Sep 2020 10:18:27 -0600 Subject: [PATCH 132/198] fix detecting server address --- .../java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 4 ++-- .../main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 07bf77fd6..53544d07d 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -135,8 +135,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) throw new ServletException(String.format("Invalid content type %s. Please use application/json.", request.getContentType())); } - String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") - .replace(request.getServletPath(), "") + "/fhir"; + + String baseUrl = HapiProperties.getServerAddress(); String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 27cece5a4..b7bab7d64 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -137,8 +137,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) request.getContentType())); } - String baseUrl = request.getRequestURL().toString().replace(request.getPathInfo(), "") - .replace(request.getServletPath(), "") + "/fhir"; + + String baseUrl = HapiProperties.getServerAddress(); String service = request.getPathInfo().replace("/", ""); JsonParser parser = new JsonParser(); From ce14a96625ccf847a29c0b1a64d85e0e10965b09 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 11 Sep 2020 14:27:06 -0600 Subject: [PATCH 133/198] cds hooks logging and smarter config handling --- Dockerfile | 4 +-- .../cqf/dstu3/servlet/CdsHooksServlet.java | 8 ++++++ .../cqf/r4/servlet/CdsHooksServlet.java | 8 ++++++ scripts/docker-entrypoint-override.sh | 28 ++++++++++++------- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4e3df05d8..75e4112e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM jetty:9-jre11 USER jetty:jetty -RUN mkdir -p /var/lib/jetty/target +RUN mkdir -p /var/lib/jetty/webapps/config COPY ./cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war COPY ./cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war EXPOSE 8080 @@ -14,7 +14,7 @@ ENV SERVER_ADDRESS_R4="http://localhost:8080/cqf-ruler-r4/fhir" # ENV DATASOURCE_DRIVER="org.apache.derby.jdbc.EmbeddedDriver" # ENV DATASOURCE_URL="jdbc:derby:directory:target/jpaserver_derby_files;create=true" # ENV DATASOURCE_URL= DATASOURCE_USERNAME= -ENV JAVA_OPTIONS="-Dhapi.properties.DSTU3=/var/lib/jetty/webapps/dstu3.properties -Dhapi.properties.R4=/var/lib/jetty/webapps/r4.properties" +ENV JAVA_OPTIONS="-Dhapi.properties.DSTU3=/var/lib/jetty/webapps/config/dstu3.properties -Dhapi.properties.R4=/var/lib/jetty/webapps/config/r4.properties" COPY --chown=jetty:jetty ./scripts/docker-entrypoint-override.sh /docker-entrypoint-override.sh ENTRYPOINT [ "/docker-entrypoint-override.sh" ] diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 53544d07d..36735f275 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -147,6 +147,14 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Hook hook = HookFactory.createHook(cdsHooksRequest); + logger.info("cds-hooks hook: " + hook.getRequest().getHook()); + logger.info("cds-hooks hook instance: " + hook.getRequest().getHookInstance()); + logger.info("cds-hooks maxCodesPerQuery: " + this.getProviderConfiguration().getMaxCodesPerQuery()); + logger.info("cds-hooks expandValueSets: " + this.getProviderConfiguration().getExpandValueSets()); + logger.info("cds-hooks searchStyle: " + this.getProviderConfiguration().getSearchStyle()); + logger.info("cds-hooks local server address: " + baseUrl); + logger.info("cds-hooks fhir server address: " + hook.getRequest().getFhirServerUrl()); + PlanDefinition planDefinition = planDefinitionProvider.getDao().read(new IdType(hook.getRequest().getServiceName())); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResolutionProvider); Library library = LibraryHelper.resolvePrimaryLibrary(planDefinition, libraryLoader, libraryResolutionProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index b7bab7d64..6d822b62b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -149,6 +149,14 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) Hook hook = HookFactory.createHook(cdsHooksRequest); + logger.info("cds-hooks hook: " + hook.getRequest().getHook()); + logger.info("cds-hooks hook instance: " + hook.getRequest().getHookInstance()); + logger.info("cds-hooks maxCodesPerQuery: " + this.getProviderConfiguration().getMaxCodesPerQuery()); + logger.info("cds-hooks expandValueSets: " + this.getProviderConfiguration().getExpandValueSets()); + logger.info("cds-hooks searchStyle: " + this.getProviderConfiguration().getSearchStyle()); + logger.info("cds-hooks local server address: " + baseUrl); + logger.info("cds-hooks fhir server address: " + hook.getRequest().getFhirServerUrl()); + PlanDefinition planDefinition = planDefinitionProvider.getDao() .read(new IdType(hook.getRequest().getServiceName())); LibraryLoader libraryLoader = LibraryHelper.createLibraryLoader(libraryResolutionProvider); diff --git a/scripts/docker-entrypoint-override.sh b/scripts/docker-entrypoint-override.sh index f7cbb7489..aeaae9e22 100755 --- a/scripts/docker-entrypoint-override.sh +++ b/scripts/docker-entrypoint-override.sh @@ -2,16 +2,24 @@ # TODO: we probably want to test the existence of the various variables before writing them to a file # datasource.url= $DATASOURCE_URL -( -cat<> /var/lib/jetty/webapps/dstu3.properties +# ensure the config directory / file is created, strip out any old server address, pre-prend new server address +echo "Configuring Docker container" +echo "DSTU3 address: " $SERVER_ADDRESS_DSTU3 -( -cat<> /var/lib/jetty/webapps/r4.properties +echo "R4 address: " $SERVER_ADDRESS_R4 +cd /var/lib/jetty/webapps/config +echo server_address=$SERVER_ADDRESS_DSTU3 > temp-dstu3.properties +touch dstu3.properties +grep -v "server_address" dstu3.properties >> temp-dstu3.properties +cp temp-dstu3.properties dstu3.properties +rm temp-dstu3.properties + +echo server_address=$SERVER_ADDRESS_R4 > temp-r4.properties +touch r4.properties +grep -v "server_address" r4.properties >> temp-r4.properties +cp temp-r4.properties r4.properties +rm temp-r4.properties + +cd /var/lib/jetty exec /docker-entrypoint.sh "$@" \ No newline at end of file From 192964d5e686409bc2d21f4bdbf930967b46eca1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sat, 12 Sep 2020 11:57:15 -0600 Subject: [PATCH 134/198] Add config for prefetch template max uri length --- .../org/opencds/cqf/common/config/HapiProperties.java | 2 ++ .../org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 9 ++++++--- dstu3/src/main/resources/hapi.properties | 3 ++- .../org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 10 +++++++--- r4/src/main/resources/hapi.properties | 1 + 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 01b91bcd4..ffbb606bf 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -73,6 +73,7 @@ public class HapiProperties { static final String CDSHOOKS_FHIRSERVER_MAXCODESPERQUERY = "cds_hooks.fhirServer.maxCodesPerQuery"; static final String CDSHOOKS_FHIRSERVER_EXPANDVALUESETS = "cds_hooks.fhirServer.expandValueSets"; static final String CDSHOOKS_FHIRSERVER_SEARCHSTYLE= "cds_hooks.fhirServer.searchStyle"; + static final String CDSHOOKS_PREFETCH_MAXURILENGTH= "cds_hooks.prefetch.maxUriLength"; private static Properties properties; @@ -406,4 +407,5 @@ public static SearchStyleEnum getCdsHooksFhirServerSearchStyleEnum() { return SearchStyleEnum.GET; } + public static Integer getCdsHooksPreFetchMaxUriLength() { return HapiProperties.getIntegerProperty(CDSHOOKS_PREFETCH_MAXURILENGTH, 8000);} } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 36735f275..ea6bb4da4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -76,7 +76,8 @@ public ProviderConfiguration getProviderConfiguration() { providerConfiguration = new ProviderConfiguration( HapiProperties.getCdsHooksFhirServerExpandValueSets(), HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), - HapiProperties.getCdsHooksFhirServerSearchStyleEnum()); + HapiProperties.getCdsHooksFhirServerSearchStyleEnum(), + HapiProperties.getCdsHooksPreFetchMaxUriLength()); } return providerConfiguration; @@ -279,8 +280,10 @@ private JsonObject getService(String service) { } private JsonObject getServices() { - return new DiscoveryResolutionStu3( - FhirContext.forDstu3().newRestfulGenericClient(HapiProperties.getServerAddress())).resolve() + DiscoveryResolutionStu3 discoveryResolutionStu3 = new DiscoveryResolutionStu3( + FhirContext.forDstu3().newRestfulGenericClient(HapiProperties.getServerAddress())); + discoveryResolutionStu3.setMaxUriLength(this.getProviderConfiguration().getMaxUriLength()); + return discoveryResolutionStu3.resolve() .getAsJson(); } diff --git a/dstu3/src/main/resources/hapi.properties b/dstu3/src/main/resources/hapi.properties index 517002b06..c6fd324b3 100644 --- a/dstu3/src/main/resources/hapi.properties +++ b/dstu3/src/main/resources/hapi.properties @@ -180,4 +180,5 @@ observationTransform.replaceCode=false ################################################## cds_hooks.fhirServer.maxCodesPerQuery= cds_hooks.fhirServer.expandValueSets= -cds_hooks.fhirServer.searchStyle= \ No newline at end of file +cds_hooks.fhirServer.searchStyle= +cds_hooks.prefetch.maxUriLength= \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 6d822b62b..e79f17441 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -76,7 +76,8 @@ public ProviderConfiguration getProviderConfiguration() { providerConfiguration = new ProviderConfiguration( HapiProperties.getCdsHooksFhirServerExpandValueSets() , HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), - HapiProperties.getCdsHooksFhirServerSearchStyleEnum()); + HapiProperties.getCdsHooksFhirServerSearchStyleEnum(), + HapiProperties.getCdsHooksPreFetchMaxUriLength()); } return providerConfiguration; @@ -285,8 +286,11 @@ private JsonObject getService(String service) { } private JsonObject getServices() { - return new DiscoveryResolutionR4(FhirContext.forR4().newRestfulGenericClient(HapiProperties.getServerAddress())) - .resolve().getAsJson(); + DiscoveryResolutionR4 discoveryResolutionR4 = new DiscoveryResolutionR4( + FhirContext.forR4().newRestfulGenericClient(HapiProperties.getServerAddress())); + discoveryResolutionR4.setMaxUriLength(this.getProviderConfiguration().getMaxUriLength()); + return discoveryResolutionR4.resolve() + .getAsJson(); } private String toJsonResponse(List cards) { diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index efe6eb813..1ceecd3a3 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -181,3 +181,4 @@ observationTransform.replaceCode=false cds_hooks.fhirServer.maxCodesPerQuery= cds_hooks.fhirServer.expandValueSets= cds_hooks.fhirServer.searchStyle= +cds_hooks.prefetch.maxUriLength= From edf4f9bbe52dc21334b8873776adea1a81d5caf3 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Sat, 12 Sep 2020 17:36:42 -0600 Subject: [PATCH 135/198] Add logging for max uri length --- .../main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java | 1 + r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java | 1 + 2 files changed, 2 insertions(+) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index ea6bb4da4..8b21c4476 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -153,6 +153,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) logger.info("cds-hooks maxCodesPerQuery: " + this.getProviderConfiguration().getMaxCodesPerQuery()); logger.info("cds-hooks expandValueSets: " + this.getProviderConfiguration().getExpandValueSets()); logger.info("cds-hooks searchStyle: " + this.getProviderConfiguration().getSearchStyle()); + logger.info("cds-hooks prefetch maxUriLength: " + this.getProviderConfiguration().getMaxUriLength()); logger.info("cds-hooks local server address: " + baseUrl); logger.info("cds-hooks fhir server address: " + hook.getRequest().getFhirServerUrl()); diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index e79f17441..37489cac2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -155,6 +155,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) logger.info("cds-hooks maxCodesPerQuery: " + this.getProviderConfiguration().getMaxCodesPerQuery()); logger.info("cds-hooks expandValueSets: " + this.getProviderConfiguration().getExpandValueSets()); logger.info("cds-hooks searchStyle: " + this.getProviderConfiguration().getSearchStyle()); + logger.info("cds-hooks prefetch maxUriLength: " + this.getProviderConfiguration().getMaxUriLength()); logger.info("cds-hooks local server address: " + baseUrl); logger.info("cds-hooks fhir server address: " + hook.getRequest().getFhirServerUrl()); From 72c9b7ff28bfc7457eb0ad0c3b1b5bc20b173e2c Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 9 Sep 2020 17:22:28 -0600 Subject: [PATCH 136/198] Fix various build warnings --- .../cqf/common/config/FhirServerConfig.java | 9 ---- .../dstu3/evaluation/MeasureEvaluation.java | 25 ++++++++--- .../providers/JpaTerminologyProvider.java | 2 - .../providers/MeasureOperationsProvider.java | 43 ++++++++++++++----- .../cqf/r4/evaluation/MeasureEvaluation.java | 25 ++++++++--- .../opencds/cqf/r4/helpers/LibraryHelper.java | 1 - .../r4/providers/JpaTerminologyProvider.java | 3 -- .../providers/LibraryOperationsProvider.java | 21 ++++----- .../providers/MeasureOperationsProvider.java | 3 +- 9 files changed, 81 insertions(+), 51 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index a6038e644..68421952e 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -76,15 +76,6 @@ public ModelConfig modelConfig() { return modelConfig; } - /** - * The following bean configures the database connection. The 'url' property - * value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates - * that the server should save resources in a directory called - * "jpaserver_derby_files". - * - * A URL to a remote database could also be placed here, along with login - * credentials and other properties supported by BasicDataSource. - */ @Bean(destroyMethod = "close") public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index f4e5272bd..8ea319151 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -12,7 +12,20 @@ import org.cqframework.cql.elm.execution.ExpressionDef; import org.cqframework.cql.elm.execution.FunctionDef; -import org.hl7.fhir.dstu3.model.*; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.IdType; +import org.hl7.fhir.dstu3.model.IntegerType; +import org.hl7.fhir.dstu3.model.ListResource; +import org.hl7.fhir.dstu3.model.Measure; +import org.hl7.fhir.dstu3.model.MeasureReport; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.Reference; +import org.hl7.fhir.dstu3.model.Resource; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.dstu3.model.UriType; import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; @@ -21,7 +34,6 @@ import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.dstu3.builders.MeasureReportBuilder; -import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -100,6 +112,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } + @SuppressWarnings("unchecked") private void clearExpressionCache(Context context) { // Hack to clear expression cache // See cqf-ruler github issue #153 @@ -583,8 +596,8 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p code = ((Code) sdeListItem).getCode(); break; case "ArrayList": - if (((ArrayList) sdeListItem).size() > 0) { - code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + if (((ArrayList) sdeListItem).size() > 0) { + code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); }else{ continue; } @@ -616,7 +629,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap sde, boolean isSingle, List patients){ List newRefList = new ArrayList<>(); sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { - sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ + sdeAccumulator.forEach((sdeAccumulatorKey, sdeAccumulatorValue)->{ Observation obs = new Observation(); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setId(UUID.randomUUID().toString()); @@ -649,7 +662,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap getPatientListFromGroup(String subjectGroupRef){ return patientList; } + @SuppressWarnings("unused") private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, String practitioner, String measure, String status, String organization){ if(periodStart == null || periodStart.equals("") || @@ -394,7 +417,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } //TODO: implement this per the spec - //Holding off on implementiation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) + //Holding off on implementation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) double proportion = 0.0; if (measureResource.getScoring().hasCoding() && denominator != 0) { for (Coding coding : measureResource.getScoring().getCoding()) { @@ -419,8 +442,8 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); // TODO: No evidence on DSTU3 detected issue // detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); - CodeableConcept code = new CodeableConcept() - .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); + // CodeableConcept code = new CodeableConcept() + // .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); // TODO: No code on DSTU3 detected issue //detectedIssue.setCode(code); diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index f5a385ced..3e1a2dcd6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -13,7 +13,21 @@ import org.cqframework.cql.elm.execution.ExpressionDef; import org.cqframework.cql.elm.execution.FunctionDef; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.MeasureReport; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.evaluation.MeasurePopulationType; import org.opencds.cqf.common.evaluation.MeasureScoring; import org.opencds.cqf.cql.engine.data.DataProvider; @@ -22,7 +36,6 @@ import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.cql.engine.runtime.Interval; import org.opencds.cqf.r4.builders.MeasureReportBuilder; -import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -578,8 +591,8 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p code = ((Code) sdeListItem).getCode(); break; case "ArrayList": - if (((ArrayList) sdeListItem).size() > 0) { - code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + if (((ArrayList) sdeListItem).size() > 0) { + code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); }else{ continue; } @@ -611,7 +624,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap sde, boolean isSingle, List patients){ List newRefList = new ArrayList<>(); sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> { - sdeAccumulator.forEach((sdeAccumulatorKey, sdeAcumulatorValue)->{ + sdeAccumulator.forEach((sdeAccumulatorKey, sdeAccumulatorValue)->{ Observation obs = new Observation(); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setId(UUID.randomUUID().toString()); @@ -644,7 +657,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap { @@ -180,6 +172,7 @@ public Parameters getNarrative(@IdParam IdType theId) { // NOTICE: This is trash code that needs to be removed. Don't fix this. It's for // a one-off + // @SuppressWarnings({"unchecked", "rawtypes" }) @Operation(name = "$evaluate", idempotent = true, type = Library.class) public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId") String patientId, @OperationParam(name = "periodStart") String periodStart, @@ -335,13 +328,13 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" .getResourceType() + "/" + ((Resource) ((List) res).get(0)).getIdElement().getIdPart())); } else { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } } else if (res instanceof Iterable) { - result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); + result.addParameter().setName("value").setResource(bundler.bundle((Iterable) res)); } else if (res instanceof Resource) { if (executionResults != null && executionResults.equals("Summary")) { result.addParameter().setName("value") @@ -444,6 +437,8 @@ private Iterable resolveLibraries(List> getLocations(org.hl7.elm.r1.Library library) { Map> locations = new HashMap<>(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index ed71d37d8..24c010af2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -268,6 +268,7 @@ private List getPatientListFromGroup(String subjectGroupRef){ return patientList; } + @SuppressWarnings("unused") private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, String practitioner, String measure, String status, String organization){ if(periodStart == null || periodStart.equals("") || @@ -385,7 +386,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } //TODO: implement this per the spec - //Holding off on implementiation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) + //Holding off on implementation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) double proportion = 0.0; if (measureResource.getScoring().hasCoding() && denominator != 0) { for (Coding coding : measureResource.getScoring().getCoding()) { From 5b68c2c694eceaf4686fee3927ad0b1ef5b07191 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 10 Sep 2020 13:32:49 -0600 Subject: [PATCH 137/198] Add reference checks to HAPI properties --- .../cqf/common/config/FhirServerConfig.java | 9 ++ .../cqf/common/config/HapiProperties.java | 127 ++++++++++++++---- 2 files changed, 108 insertions(+), 28 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index 68421952e..74c4c4a1d 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -29,6 +29,9 @@ public class FhirServerConfig { private Boolean allowOverrideDefaultSearchParams = HapiProperties.getAllowOverrideDefaultSearchParams(); private String emailFrom = HapiProperties.getEmailFrom(); + private Boolean enforceReferentialIntegrityOnWrite = HapiProperties.getEnforceReferentialIntegrityOnWrite(); + private Boolean enforceReferentialIntegrityOnDelete = HapiProperties.getEnforceReferentialIntegrityOnDelete(); + public FhirServerConfig() { ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); @@ -37,6 +40,10 @@ public FhirServerConfig() { ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); + ourLog.info("Server configured to " + (this.enforceReferentialIntegrityOnDelete ? "enforce" : "ignore") + + " referential integrity on delete"); + ourLog.info("Server configured to " + (this.enforceReferentialIntegrityOnDelete ? "enforce" : "ignore") + + " referential integrity on write"); } /** @@ -52,6 +59,8 @@ public DaoConfig daoConfig() { retVal.setExpungeEnabled(this.expungeEnabled); retVal.setAutoCreatePlaceholderReferenceTargets(this.allowPlaceholderReferences); retVal.setEmailFromAddress(this.emailFrom); + retVal.setEnforceReferentialIntegrityOnDelete(this.enforceReferentialIntegrityOnDelete); + retVal.setEnforceReferentialIntegrityOnWrite(this.enforceReferentialIntegrityOnWrite); Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); retVal.setFetchSizeDefaultMaximum(maxFetchSize); diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index ffbb606bf..34506e9d9 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -16,6 +16,8 @@ public class HapiProperties { static final String ALLOW_EXTERNAL_REFERENCES = "allow_external_references"; static final String ALLOW_MULTIPLE_DELETE = "allow_multiple_delete"; static final String ALLOW_PLACEHOLDER_REFERENCES = "allow_placeholder_references"; + static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE = "enforce_referential_integrity_on_write"; + static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE = "enforce_referential_integrity_on_delete"; static final String REUSE_CACHED_SEARCH_RESULTS_MILLIS = "reuse_cached_search_results_millis"; static final String DATASOURCE_DRIVER = "datasource.driver"; static final String DATASOURCE_MAX_POOL_SIZE = "datasource.max_pool_size"; @@ -64,7 +66,7 @@ public class HapiProperties { static final String QUESTIONNAIRE_RESPONSE_ENDPOINT = "questionnaireResponseExtract.endpoint"; static final String QUESTIONNAIRE_RESPONSE_USERNAME = "questionnaireResponseExtract.username"; static final String QUESTIONNAIRE_RESPONSE_PASSWORD = "questionnaireResponseExtract.password"; - + static final String OBSERVATION_TRANSFORM_ENABLED = "observationTransform.enabled"; static final String OBSERVATION_TRANSFORM_USERNAME = "observationTransform.username"; static final String OBSERVATION_TRANSFORM_PASSWORD = "observationTransform.password"; @@ -259,7 +261,7 @@ public static String getDataSourceDriver() { } // public static Object getDriver() { - // return new org.apache.derby.jdbc.EmbeddedDriver(); + // return new org.apache.derby.jdbc.EmbeddedDriver(); // } public static Integer getDataSourceMaxPoolSize() { @@ -372,32 +374,101 @@ public static Long getReuseCachedSearchResultsMillis() { return Long.valueOf(value); } - //************************* OAuth ******************************************************* - public static Boolean getOAuthEnabled(){return HapiProperties.getBooleanProperty(OAUTH_ENABLED, false);} - public static Boolean getOauthSecurityCors(){return HapiProperties.getBooleanProperty(OAUTH_SECURITY_CORS, true);} - public static String getOauthSecurityUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_URL, "");} - public static String getOauthSecurityExtAuthUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_URL, "");} - public static String getOauthSecurityExtAuthValueUri(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_VALUE_URI, "");} - public static String getOauthSecurityExtTokenUrl(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_URL, "");} - public static String getOauthSecurityExtTokenValueUri(){return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_VALUE_URI, "");} - public static String getOauthServiceSystem(){return HapiProperties.getProperty(OAUTH_SERVICE_SYSTEM, "");} - public static String getOauthServiceCode(){return HapiProperties.getProperty(OAUTH_SERVICE_CODE, "");} - public static String getOauthServiceDisplay(){return HapiProperties.getProperty(OAUTH_SERVICE_DISPLAY, "");} - public static String getOauthServiceText(){return HapiProperties.getProperty(OAUTH_SERVICE_TEXT, "");} - - public static Boolean getQuestionnaireResponseExtractEnabled(){return HapiProperties.getBooleanProperty(QUESTIONNAIRE_RESPONSE_ENABLED, false);} - public static String getQuestionnaireResponseExtractEndpoint() {return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_ENDPOINT);} - public static String getQuestionnaireResponseExtractUserName(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_USERNAME);}; - public static String getQuestionnaireResponseExtractPassword(){return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD);}; - - public static Boolean getObservationTransformEnabled(){return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_ENABLED, false);} - public static String getObservationTransformUsername(){return HapiProperties.getProperty(OBSERVATION_TRANSFORM_USERNAME);} - public static String getObservationTransformPassword(){return HapiProperties.getProperty(OBSERVATION_TRANSFORM_PASSWORD);} - public static Boolean getObservationTransformReplaceCode(){return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_REPLACE_CODE, false);} - - //************************* CDS_HOOKS **************** - public static Integer getCdsHooksFhirServerMaxCodesPerQuery() { return HapiProperties.getIntegerProperty(CDSHOOKS_FHIRSERVER_MAXCODESPERQUERY, 64);} - public static Boolean getCdsHooksFhirServerExpandValueSets() { return HapiProperties.getBooleanProperty(CDSHOOKS_FHIRSERVER_EXPANDVALUESETS, true);} + public static boolean getEnforceReferentialIntegrityOnDelete() { + return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE, true); + } + + public static boolean getEnforceReferentialIntegrityOnWrite() { + return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE, true); + } + + // ************************* OAuth + // ******************************************************* + public static Boolean getOAuthEnabled() { + return HapiProperties.getBooleanProperty(OAUTH_ENABLED, false); + } + + public static Boolean getOauthSecurityCors() { + return HapiProperties.getBooleanProperty(OAUTH_SECURITY_CORS, true); + } + + public static String getOauthSecurityUrl() { + return HapiProperties.getProperty(OAUTH_SECURITY_URL, ""); + } + + public static String getOauthSecurityExtAuthUrl() { + return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_URL, ""); + } + + public static String getOauthSecurityExtAuthValueUri() { + return HapiProperties.getProperty(OAUTH_SECURITY_EXT_AUTH_VALUE_URI, ""); + } + + public static String getOauthSecurityExtTokenUrl() { + return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_URL, ""); + } + + public static String getOauthSecurityExtTokenValueUri() { + return HapiProperties.getProperty(OAUTH_SECURITY_EXT_TOKEN_VALUE_URI, ""); + } + + public static String getOauthServiceSystem() { + return HapiProperties.getProperty(OAUTH_SERVICE_SYSTEM, ""); + } + + public static String getOauthServiceCode() { + return HapiProperties.getProperty(OAUTH_SERVICE_CODE, ""); + } + + public static String getOauthServiceDisplay() { + return HapiProperties.getProperty(OAUTH_SERVICE_DISPLAY, ""); + } + + public static String getOauthServiceText() { + return HapiProperties.getProperty(OAUTH_SERVICE_TEXT, ""); + } + + public static Boolean getQuestionnaireResponseExtractEnabled() { + return HapiProperties.getBooleanProperty(QUESTIONNAIRE_RESPONSE_ENABLED, false); + } + + public static String getQuestionnaireResponseExtractEndpoint() { + return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_ENDPOINT); + } + + public static String getQuestionnaireResponseExtractUserName() { + return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_USERNAME); + }; + + public static String getQuestionnaireResponseExtractPassword() { + return HapiProperties.getProperty(QUESTIONNAIRE_RESPONSE_PASSWORD); + }; + + public static Boolean getObservationTransformEnabled() { + return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_ENABLED, false); + } + + public static String getObservationTransformUsername() { + return HapiProperties.getProperty(OBSERVATION_TRANSFORM_USERNAME); + } + + public static String getObservationTransformPassword() { + return HapiProperties.getProperty(OBSERVATION_TRANSFORM_PASSWORD); + } + + public static Boolean getObservationTransformReplaceCode() { + return HapiProperties.getBooleanProperty(OBSERVATION_TRANSFORM_REPLACE_CODE, false); + } + + // ************************* CDS_HOOKS **************** + public static Integer getCdsHooksFhirServerMaxCodesPerQuery() { + return HapiProperties.getIntegerProperty(CDSHOOKS_FHIRSERVER_MAXCODESPERQUERY, 64); + } + + public static Boolean getCdsHooksFhirServerExpandValueSets() { + return HapiProperties.getBooleanProperty(CDSHOOKS_FHIRSERVER_EXPANDVALUESETS, true); + } + public static SearchStyleEnum getCdsHooksFhirServerSearchStyleEnum() { String searchStyleEnumString = HapiProperties.getProperty(CDSHOOKS_FHIRSERVER_SEARCHSTYLE); From 8a5581d256ffdc831bbe08433d8c27c94552e682 Mon Sep 17 00:00:00 2001 From: mdube Date: Wed, 9 Sep 2020 09:40:17 -0400 Subject: [PATCH 138/198] Initial commit --- example_nested_plandef.json | 252 ++++++++++++++++++ example_plandef_bundle.json | 222 +++++++++++++++ .../PlanDefinitionApplyProvider.java | 42 ++- setup_example2.sh | 5 + 4 files changed, 516 insertions(+), 5 deletions(-) create mode 100644 example_nested_plandef.json create mode 100644 example_plandef_bundle.json create mode 100644 setup_example2.sh diff --git a/example_nested_plandef.json b/example_nested_plandef.json new file mode 100644 index 000000000..47b68e0d3 --- /dev/null +++ b/example_nested_plandef.json @@ -0,0 +1,252 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "PlanDefinition", + "id": "pd-example1", + "meta": { + "versionId": "1", + "lastUpdated": "2020-07-31T20:16:38.519+00:00" + }, + "action": [ + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "sub-plan-1", + "title": "Sub Plan Definition", + "definitionCanonical": "PlanDefinition/pd-example2" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/pd-example1" + } + }, + { + "resource": { + "resourceType": "PlanDefinition", + "id": "pd-example2", + "meta": { + "versionId": "1", + "lastUpdated": "2020-07-31T20:16:38.519+00:00" + }, + "action": [ + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-1", + "title": "Administer Medication 1", + "definitionCanonical": "ActivityDefinition/ad-example" + }, + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-2", + "title": "Administer Medication 2", + "definitionCanonical": "ActivityDefinition/ad-example" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/pd-example2" + } + }, + { + "resource": { + "resourceType": "ActivityDefinition", + "id": "ad-example", + "kind": "MedicationRequest", + "productCodeableConcept": { + "text": "Medication 1" + } + }, + "request": { + "method": "PUT", + "url": "ActivityDefinition/ad-example" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "example", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" + ] + }, + "text": { + "status": "generated", + "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2106-3", + "display": "White" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1002-5", + "display": "American Indian or Alaska Native" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2028-9", + "display": "Asian" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1586-7", + "display": "Shoshone" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2036-2", + "display": "Filipino" + } + }, + { + "url": "text", + "valueString": "Mixed" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2135-2", + "display": "Hispanic or Latino" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2184-0", + "display": "Dominican" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2148-5", + "display": "Mexican" + } + }, + { + "url": "text", + "valueString": "Hispanic or Latino" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", + "valueCode": "F" + } + ], + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "1032702" + } + ], + "active": true, + "name": [ + { + "family": "Shaw", + "given": [ + "Amy", + "V." + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-555-5555", + "use": "home" + }, + { + "system": "email", + "value": "amy.shaw@example.com" + } + ], + "gender": "female", + "birthDate": "2007-02-20", + "address": [ + { + "line": [ + "49 Meadow St" + ], + "city": "Mounds", + "state": "OK", + "postalCode": "74047", + "country": "US" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/example" + } + } + ] +} \ No newline at end of file diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json new file mode 100644 index 000000000..8566106d7 --- /dev/null +++ b/example_plandef_bundle.json @@ -0,0 +1,222 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "PlanDefinition", + "id": "pd-example", + "meta": { + "versionId": "1", + "lastUpdated": "2020-07-31T20:16:38.519+00:00" + }, + "action": [ + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-1", + "title": "Administer Medication 1", + "definitionCanonical": "ActivityDefinition/ad-example" + }, + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-2", + "title": "Administer Medication 2", + "definitionCanonical": "ActivityDefinition/ad-example" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/pd-example" + } + }, + { + "resource": { + "resourceType": "ActivityDefinition", + "id": "ad-example", + "kind": "MedicationRequest", + "productCodeableConcept": { + "text": "Medication 1" + } + }, + "request": { + "method": "PUT", + "url": "ActivityDefinition/ad-example" + } + }, + { + "resource": { + "resourceType": "Patient", + "id": "example", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" + ] + }, + "text": { + "status": "generated", + "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2106-3", + "display": "White" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1002-5", + "display": "American Indian or Alaska Native" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2028-9", + "display": "Asian" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1586-7", + "display": "Shoshone" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2036-2", + "display": "Filipino" + } + }, + { + "url": "text", + "valueString": "Mixed" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2135-2", + "display": "Hispanic or Latino" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2184-0", + "display": "Dominican" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2148-5", + "display": "Mexican" + } + }, + { + "url": "text", + "valueString": "Hispanic or Latino" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", + "valueCode": "F" + } + ], + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "1032702" + } + ], + "active": true, + "name": [ + { + "family": "Shaw", + "given": [ + "Amy", + "V." + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-555-5555", + "use": "home" + }, + { + "system": "email", + "value": "amy.shaw@example.com" + } + ], + "gender": "female", + "birthDate": "2007-02-20", + "address": [ + { + "line": [ + "49 Meadow St" + ], + "city": "Mounds", + "state": "OK", + "postalCode": "74047", + "country": "US" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/example" + } + } + ] +} \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 6513c67fa..b563547c3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -112,29 +112,57 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, userType, userLanguage, userTaskContext, setting, settingContext); + logger.info("call resolveActions"); return resolveActions(session); } private CarePlan resolveActions(Session session) { + logger.info("Resolving the Actions"); for (PlanDefinition.PlanDefinitionActionComponent action : session.getPlanDefinition().getAction()) { + logger.info("Action is " + action); // TODO - Apply input/output dataRequirements? if (meetsConditions(session, action)) { + logger.info("conditions are met"); resolveDefinition(session, action); resolveDynamicActions(session, action); } } - + logger.info("Returning careplan"); return session.getCarePlan(); } private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { + logger.info("Resolving the definitions"); if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); String definition = action.getDefinitionCanonicalType().getValue(); if (definition.startsWith(session.getPlanDefinition().fhirType())) { - logger.error("Currently cannot resolve nested PlanDefinitions"); - throw new NotImplementedException( - "Plan Definition refers to sub Plan Definition, this is not yet supported"); + IdType id = new IdType(definition); + CarePlan plan = null; + try { + plan = applyPlanDefinition(id, + session.getPatientId(), + session.getEncounterId(), + session.getPractitionerId(), + session.getOrganizationId(), + session.getUserType(), + session.getUserLanguage(), + session.getUserTaskContext(), + session.getSetting(), + session.getSettingContext()); + } catch (IOException e) { + e.printStackTrace(); + } catch (JAXBException e) { + e.printStackTrace(); + } + if (plan != null) { + session.getCarePlanBuilder().buildContained(plan).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + plan.getId())).build()); + } + else + { + logger.error("nested plan failed"); + } } else { @@ -197,14 +225,18 @@ else if (result instanceof String) { } private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionActionComponent action) { + logger.info("in meets"); if (action.hasAction()) { + logger.info("action has action"); for (PlanDefinition.PlanDefinitionActionComponent containedAction : action.getAction()) { + logger.info("meets action is "+containedAction); meetsConditions(session, containedAction); } } for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { // TODO start // TODO stop + logger.info("meetsConditions for Loop"); if (condition.hasExpression() && condition.getExpression().hasDescription()) { logger.info("Resolving condition with description: " + condition.getExpression().getDescription()); } @@ -247,7 +279,7 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc } } } - + logger.info("in meets return true"); return true; } diff --git a/setup_example2.sh b/setup_example2.sh new file mode 100644 index 000000000..b8a3b66fa --- /dev/null +++ b/setup_example2.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +# Post Bundle +curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_plandef_bundle.json http://localhost:8080/cqf-ruler-r4/fhir +curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_nested_plandef.json http://localhost:8080/cqf-ruler-r4/fhir \ No newline at end of file From 4c7680a053052d6746c182c219e034e7656f6d29 Mon Sep 17 00:00:00 2001 From: mdube Date: Wed, 9 Sep 2020 11:17:49 -0400 Subject: [PATCH 139/198] Fix issue with nested activities Get contained activities Remove debug statements --- example_plandef_bundle.json | 22 +--------- .../PlanDefinitionApplyProvider.java | 40 +++++++------------ 2 files changed, 17 insertions(+), 45 deletions(-) diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json index 8566106d7..ddb8b2663 100644 --- a/example_plandef_bundle.json +++ b/example_plandef_bundle.json @@ -11,30 +11,12 @@ "lastUpdated": "2020-07-31T20:16:38.519+00:00" }, "action": [ - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], + { "id": "medication-action-1", "title": "Administer Medication 1", "definitionCanonical": "ActivityDefinition/ad-example" }, - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], + { "id": "medication-action-2", "title": "Administer Medication 2", "definitionCanonical": "ActivityDefinition/ad-example" diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index b563547c3..45b319b08 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -8,19 +8,9 @@ import javax.xml.bind.JAXBException; +import org.fhir.ucum.Canonical; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.ActivityDefinition; -import org.hl7.fhir.r4.model.CarePlan; -import org.hl7.fhir.r4.model.DomainResource; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.PlanDefinition; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.RelatedArtifact; -import org.hl7.fhir.r4.model.RequestGroup; -import org.hl7.fhir.r4.model.Resource; -import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.exceptions.NotImplementedException; import org.opencds.cqf.cql.engine.execution.Context; @@ -112,27 +102,22 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, userType, userLanguage, userTaskContext, setting, settingContext); - logger.info("call resolveActions"); return resolveActions(session); } private CarePlan resolveActions(Session session) { - logger.info("Resolving the Actions"); for (PlanDefinition.PlanDefinitionActionComponent action : session.getPlanDefinition().getAction()) { - logger.info("Action is " + action); // TODO - Apply input/output dataRequirements? if (meetsConditions(session, action)) { - logger.info("conditions are met"); resolveDefinition(session, action); resolveDynamicActions(session, action); } } - logger.info("Returning careplan"); + return session.getCarePlan(); } private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { - logger.info("Resolving the definitions"); if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); String definition = action.getDefinitionCanonicalType().getValue(); @@ -156,8 +141,18 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct e.printStackTrace(); } if (plan != null) { - session.getCarePlanBuilder().buildContained(plan).buildActivity( - new CarePlanActivityBuilder().buildReference(new Reference("#" + plan.getId())).build()); + // iterate over nested plans and get contained activities + List containedList = plan.getContained(); + for(Resource r: containedList) + { + session.getCarePlanBuilder().buildContained(r).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + r.getId())).build()); + } + List canonicalList = plan.getInstantiatesCanonical(); + for(CanonicalType c: canonicalList) + { + session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); + } } else { @@ -225,18 +220,14 @@ else if (result instanceof String) { } private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionActionComponent action) { - logger.info("in meets"); if (action.hasAction()) { - logger.info("action has action"); for (PlanDefinition.PlanDefinitionActionComponent containedAction : action.getAction()) { - logger.info("meets action is "+containedAction); meetsConditions(session, containedAction); } } for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) { // TODO start // TODO stop - logger.info("meetsConditions for Loop"); if (condition.hasExpression() && condition.getExpression().hasDescription()) { logger.info("Resolving condition with description: " + condition.getExpression().getDescription()); } @@ -279,7 +270,6 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc } } } - logger.info("in meets return true"); return true; } From 889640e35362f8c4b20fc41f1c8e70b3acaba56f Mon Sep 17 00:00:00 2001 From: mdube Date: Wed, 9 Sep 2020 12:41:11 -0400 Subject: [PATCH 140/198] Remove unnecessary lists --- .../cqf/r4/providers/PlanDefinitionApplyProvider.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 45b319b08..12cddd346 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -142,14 +142,12 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct } if (plan != null) { // iterate over nested plans and get contained activities - List containedList = plan.getContained(); - for(Resource r: containedList) + for(Resource r: plan.getContained()) { session.getCarePlanBuilder().buildContained(r).buildActivity( new CarePlanActivityBuilder().buildReference(new Reference("#" + r.getId())).build()); } - List canonicalList = plan.getInstantiatesCanonical(); - for(CanonicalType c: canonicalList) + for(CanonicalType c: plan.getInstantiatesCanonical()) { session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); } From ef14fa1123c3d5fa891455b656626c679a3abe4f Mon Sep 17 00:00:00 2001 From: mdube Date: Wed, 9 Sep 2020 15:23:27 -0400 Subject: [PATCH 141/198] Remove extra example --- example_plandef_bundle.json | 204 ------------------------------------ setup_example2.sh | 1 - 2 files changed, 205 deletions(-) delete mode 100644 example_plandef_bundle.json diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json deleted file mode 100644 index ddb8b2663..000000000 --- a/example_plandef_bundle.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "transaction", - "entry": [ - { - "resource": { - "resourceType": "PlanDefinition", - "id": "pd-example", - "meta": { - "versionId": "1", - "lastUpdated": "2020-07-31T20:16:38.519+00:00" - }, - "action": [ - { - "id": "medication-action-1", - "title": "Administer Medication 1", - "definitionCanonical": "ActivityDefinition/ad-example" - }, - { - "id": "medication-action-2", - "title": "Administer Medication 2", - "definitionCanonical": "ActivityDefinition/ad-example" - } - ] - }, - "request": { - "method": "PUT", - "url": "PlanDefinition/pd-example" - } - }, - { - "resource": { - "resourceType": "ActivityDefinition", - "id": "ad-example", - "kind": "MedicationRequest", - "productCodeableConcept": { - "text": "Medication 1" - } - }, - "request": { - "method": "PUT", - "url": "ActivityDefinition/ad-example" - } - }, - { - "resource": { - "resourceType": "Patient", - "id": "example", - "meta": { - "profile": [ - "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" - ] - }, - "text": { - "status": "generated", - "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" - }, - "extension": [ - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2106-3", - "display": "White" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1002-5", - "display": "American Indian or Alaska Native" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2028-9", - "display": "Asian" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1586-7", - "display": "Shoshone" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2036-2", - "display": "Filipino" - } - }, - { - "url": "text", - "valueString": "Mixed" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2135-2", - "display": "Hispanic or Latino" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2184-0", - "display": "Dominican" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2148-5", - "display": "Mexican" - } - }, - { - "url": "text", - "valueString": "Hispanic or Latino" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", - "valueCode": "F" - } - ], - "identifier": [ - { - "use": "usual", - "type": { - "coding": [ - { - "system": "http://terminology.hl7.org/CodeSystem/v2-0203", - "code": "MR", - "display": "Medical Record Number" - } - ], - "text": "Medical Record Number" - }, - "system": "http://hospital.smarthealthit.org", - "value": "1032702" - } - ], - "active": true, - "name": [ - { - "family": "Shaw", - "given": [ - "Amy", - "V." - ] - } - ], - "telecom": [ - { - "system": "phone", - "value": "555-555-5555", - "use": "home" - }, - { - "system": "email", - "value": "amy.shaw@example.com" - } - ], - "gender": "female", - "birthDate": "2007-02-20", - "address": [ - { - "line": [ - "49 Meadow St" - ], - "city": "Mounds", - "state": "OK", - "postalCode": "74047", - "country": "US" - } - ] - }, - "request": { - "method": "PUT", - "url": "Patient/example" - } - } - ] -} \ No newline at end of file diff --git a/setup_example2.sh b/setup_example2.sh index b8a3b66fa..61c232174 100644 --- a/setup_example2.sh +++ b/setup_example2.sh @@ -1,5 +1,4 @@ #!/bin/sh # Post Bundle -curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_plandef_bundle.json http://localhost:8080/cqf-ruler-r4/fhir curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_nested_plandef.json http://localhost:8080/cqf-ruler-r4/fhir \ No newline at end of file From d2a85bd93ff690bef897bc8c412d6ad89acf0d0f Mon Sep 17 00:00:00 2001 From: mdube Date: Tue, 15 Sep 2020 10:08:28 -0400 Subject: [PATCH 142/198] Address review comments --- .../PlanDefinitionApplyProvider.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 12cddd346..30a541fdf 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -8,9 +8,20 @@ import javax.xml.bind.JAXBException; -import org.fhir.ucum.Canonical; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.ActivityDefinition; +import org.hl7.fhir.r4.model.CanonicalType; +import org.hl7.fhir.r4.model.CarePlan; +import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.PlanDefinition; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.RelatedArtifact; +import org.hl7.fhir.r4.model.RequestGroup; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.config.HapiProperties; import org.opencds.cqf.common.exceptions.NotImplementedException; import org.opencds.cqf.cql.engine.execution.Context; @@ -135,12 +146,7 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); - } catch (IOException e) { - e.printStackTrace(); - } catch (JAXBException e) { - e.printStackTrace(); - } - if (plan != null) { + // iterate over nested plans and get contained activities for(Resource r: plan.getContained()) { @@ -151,9 +157,9 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct { session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); } - } - else - { + + } catch (IOException | JAXBException e) { + e.printStackTrace(); logger.error("nested plan failed"); } } From f962a00022183a7b56384d828e831dfb1be3fae4 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Tue, 15 Sep 2020 16:06:15 -0600 Subject: [PATCH 143/198] Update eval for Quantity in library eval --- .../opencds/cqf/r4/providers/LibraryOperationsProvider.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index bd37485f8..8fe00e16c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -18,6 +18,7 @@ import org.cqframework.cql.elm.execution.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; @@ -45,6 +46,7 @@ import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.cql.engine.runtime.Quantity; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.evaluator.execution.provider.BundleRetrieveProvider; import org.opencds.cqf.tooling.library.r4.NarrativeProvider; @@ -343,6 +345,8 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { result.addParameter().setName("value").setResource((Resource) res); } + } else if (res instanceof Quantity) { + result.addParameter().setName("value").setValue(new DecimalType(((Quantity) res).getValue())); } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } From 5e9377dca78d7b1ee65691c8583b51c17e8ddcba Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 16 Sep 2020 03:02:05 -0600 Subject: [PATCH 144/198] Fix the FHIR Quantity, not CQL Quantity --- .../cqf/r4/providers/LibraryOperationsProvider.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 8fe00e16c..9fa57e6d2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -18,7 +18,7 @@ import org.cqframework.cql.elm.execution.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; @@ -46,7 +46,6 @@ import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; -import org.opencds.cqf.cql.engine.runtime.Quantity; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.evaluator.execution.provider.BundleRetrieveProvider; import org.opencds.cqf.tooling.library.r4.NarrativeProvider; @@ -346,7 +345,13 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" result.addParameter().setName("value").setResource((Resource) res); } } else if (res instanceof Quantity) { - result.addParameter().setName("value").setValue(new DecimalType(((Quantity) res).getValue())); + Quantity quantity = (Quantity) res; + org.opencds.cqf.cql.engine.runtime.Quantity cqlQuantity = new org.opencds.cqf.cql.engine.runtime.Quantity(); + cqlQuantity.setValue(quantity.getValue()); + if (quantity.hasUnit()) { + cqlQuantity.setUnit(quantity.getUnit()); + } + result.addParameter().setName("value").setValue(new StringType(cqlQuantity.toString())); } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } From 751eba7ce26c1f2d01304ac28e443d9011410287 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Tue, 15 Sep 2020 16:06:15 -0600 Subject: [PATCH 145/198] Update eval for Quantity in library $evaluate --- .../cqf/r4/providers/LibraryOperationsProvider.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index bd37485f8..9fa57e6d2 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -18,6 +18,7 @@ import org.cqframework.cql.elm.execution.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; @@ -343,6 +344,14 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { result.addParameter().setName("value").setResource((Resource) res); } + } else if (res instanceof Quantity) { + Quantity quantity = (Quantity) res; + org.opencds.cqf.cql.engine.runtime.Quantity cqlQuantity = new org.opencds.cqf.cql.engine.runtime.Quantity(); + cqlQuantity.setValue(quantity.getValue()); + if (quantity.hasUnit()) { + cqlQuantity.setUnit(quantity.getUnit()); + } + result.addParameter().setName("value").setValue(new StringType(cqlQuantity.toString())); } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } From dd3c377dd9605877e2ced14f14d92c04b3cdc9d8 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 16 Sep 2020 03:48:52 -0600 Subject: [PATCH 146/198] Make FHIR type valueQuantity --- .../cqf/r4/providers/LibraryOperationsProvider.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 9fa57e6d2..547bffefc 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -18,7 +18,6 @@ import org.cqframework.cql.elm.execution.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; @@ -27,6 +26,7 @@ import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Type; import org.opencds.cqf.cds.providers.PriorityRetrieveProvider; import org.opencds.cqf.common.evaluation.LibraryLoader; import org.opencds.cqf.common.helpers.ClientHelperDos; @@ -344,14 +344,8 @@ public Bundle evaluate(@IdParam IdType theId, @OperationParam(name = "patientId" } else { result.addParameter().setName("value").setResource((Resource) res); } - } else if (res instanceof Quantity) { - Quantity quantity = (Quantity) res; - org.opencds.cqf.cql.engine.runtime.Quantity cqlQuantity = new org.opencds.cqf.cql.engine.runtime.Quantity(); - cqlQuantity.setValue(quantity.getValue()); - if (quantity.hasUnit()) { - cqlQuantity.setUnit(quantity.getUnit()); - } - result.addParameter().setName("value").setValue(new StringType(cqlQuantity.toString())); + } else if (res instanceof Type) { + result.addParameter().setName("value").setValue((Type) res); } else { result.addParameter().setName("value").setValue(new StringType(res.toString())); } From 9678ed83d38dbfd8a235d621ee99c2a1f6ef7776 Mon Sep 17 00:00:00 2001 From: mdube Date: Thu, 17 Sep 2020 09:21:51 -0400 Subject: [PATCH 147/198] Remove Test Files --- example_nested_plandef.json | 252 ------------------------------------ setup_example2.sh | 4 - 2 files changed, 256 deletions(-) delete mode 100644 example_nested_plandef.json delete mode 100644 setup_example2.sh diff --git a/example_nested_plandef.json b/example_nested_plandef.json deleted file mode 100644 index 47b68e0d3..000000000 --- a/example_nested_plandef.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "transaction", - "entry": [ - { - "resource": { - "resourceType": "PlanDefinition", - "id": "pd-example1", - "meta": { - "versionId": "1", - "lastUpdated": "2020-07-31T20:16:38.519+00:00" - }, - "action": [ - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], - "id": "sub-plan-1", - "title": "Sub Plan Definition", - "definitionCanonical": "PlanDefinition/pd-example2" - } - ] - }, - "request": { - "method": "PUT", - "url": "PlanDefinition/pd-example1" - } - }, - { - "resource": { - "resourceType": "PlanDefinition", - "id": "pd-example2", - "meta": { - "versionId": "1", - "lastUpdated": "2020-07-31T20:16:38.519+00:00" - }, - "action": [ - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], - "id": "medication-action-1", - "title": "Administer Medication 1", - "definitionCanonical": "ActivityDefinition/ad-example" - }, - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], - "id": "medication-action-2", - "title": "Administer Medication 2", - "definitionCanonical": "ActivityDefinition/ad-example" - } - ] - }, - "request": { - "method": "PUT", - "url": "PlanDefinition/pd-example2" - } - }, - { - "resource": { - "resourceType": "ActivityDefinition", - "id": "ad-example", - "kind": "MedicationRequest", - "productCodeableConcept": { - "text": "Medication 1" - } - }, - "request": { - "method": "PUT", - "url": "ActivityDefinition/ad-example" - } - }, - { - "resource": { - "resourceType": "Patient", - "id": "example", - "meta": { - "profile": [ - "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" - ] - }, - "text": { - "status": "generated", - "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" - }, - "extension": [ - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2106-3", - "display": "White" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1002-5", - "display": "American Indian or Alaska Native" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2028-9", - "display": "Asian" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1586-7", - "display": "Shoshone" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2036-2", - "display": "Filipino" - } - }, - { - "url": "text", - "valueString": "Mixed" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2135-2", - "display": "Hispanic or Latino" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2184-0", - "display": "Dominican" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2148-5", - "display": "Mexican" - } - }, - { - "url": "text", - "valueString": "Hispanic or Latino" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", - "valueCode": "F" - } - ], - "identifier": [ - { - "use": "usual", - "type": { - "coding": [ - { - "system": "http://terminology.hl7.org/CodeSystem/v2-0203", - "code": "MR", - "display": "Medical Record Number" - } - ], - "text": "Medical Record Number" - }, - "system": "http://hospital.smarthealthit.org", - "value": "1032702" - } - ], - "active": true, - "name": [ - { - "family": "Shaw", - "given": [ - "Amy", - "V." - ] - } - ], - "telecom": [ - { - "system": "phone", - "value": "555-555-5555", - "use": "home" - }, - { - "system": "email", - "value": "amy.shaw@example.com" - } - ], - "gender": "female", - "birthDate": "2007-02-20", - "address": [ - { - "line": [ - "49 Meadow St" - ], - "city": "Mounds", - "state": "OK", - "postalCode": "74047", - "country": "US" - } - ] - }, - "request": { - "method": "PUT", - "url": "Patient/example" - } - } - ] -} \ No newline at end of file diff --git a/setup_example2.sh b/setup_example2.sh deleted file mode 100644 index 61c232174..000000000 --- a/setup_example2.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# Post Bundle -curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_nested_plandef.json http://localhost:8080/cqf-ruler-r4/fhir \ No newline at end of file From 238306c496b43cb7ba8462414d48a20bf8485d3c Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 26 Aug 2020 10:38:23 -0400 Subject: [PATCH 148/198] bundle and script --- example_plandef_bundle.json | 64 +++++++++++++++++++++++++++++++++++++ setup_example.sh | 7 ++++ 2 files changed, 71 insertions(+) create mode 100644 example_plandef_bundle.json create mode 100755 setup_example.sh diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json new file mode 100644 index 000000000..a13e91013 --- /dev/null +++ b/example_plandef_bundle.json @@ -0,0 +1,64 @@ +{ + "resourceType": "Bundle", + "type": "transaction", + "entry": [ + { + "resource": { + "resourceType": "PlanDefinition", + "id": "pd-example", + "meta": { + "versionId": "1", + "lastUpdated": "2020-07-31T20:16:38.519+00:00" + }, + "action": [ + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-1", + "title": "Administer Medication 1", + "definitionCanonical": "ActivityDefinition/ad-example" + }, + { + "condition": [ + { + "kind": "applicability", + "expression": { + "language": "text/cql", + "expression": "true" + } + } + ], + "id": "medication-action-2", + "title": "Administer Medication 2", + "definitionCanonical": "ActivityDefinition/ad-example" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/pd-example" + } + }, + { + "resource": { + "resourceType": "ActivityDefinition", + "id": "ad-example", + "kind": "MedicationRequest", + "productCodeableConcept": { + "text": "Medication 1" + } + }, + "request": { + "method": "PUT", + "url": "ActivityDefinition/ad-example" + } + } + ] +} \ No newline at end of file diff --git a/setup_example.sh b/setup_example.sh new file mode 100755 index 000000000..b8260d3fe --- /dev/null +++ b/setup_example.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Post Patient resource +curl -s 'https://www.hl7.org/fhir/us/core/Patient-example.json' | curl -v -s -X PUT -H 'Content-Type: application/fhir+json' 'http://localhost:8080/cqf-ruler-r4/fhir/Patient/example' -d@- + +# Post PlanDefinition Bundle +curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_plandef_bundle.json http://localhost:8080/cqf-ruler-r4/fhir From 2f20a6c6a909bdffc72e348853c465d86a737618 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 26 Aug 2020 11:06:34 -0400 Subject: [PATCH 149/198] add patient to bundle --- example_plandef_bundle.json | 158 ++++++++++++++++++++++++++++++++++++ setup_example.sh | 5 +- 2 files changed, 159 insertions(+), 4 deletions(-) diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json index a13e91013..8566106d7 100644 --- a/example_plandef_bundle.json +++ b/example_plandef_bundle.json @@ -59,6 +59,164 @@ "method": "PUT", "url": "ActivityDefinition/ad-example" } + }, + { + "resource": { + "resourceType": "Patient", + "id": "example", + "meta": { + "profile": [ + "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" + ] + }, + "text": { + "status": "generated", + "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2106-3", + "display": "White" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1002-5", + "display": "American Indian or Alaska Native" + } + }, + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2028-9", + "display": "Asian" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "1586-7", + "display": "Shoshone" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2036-2", + "display": "Filipino" + } + }, + { + "url": "text", + "valueString": "Mixed" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", + "extension": [ + { + "url": "ombCategory", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2135-2", + "display": "Hispanic or Latino" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2184-0", + "display": "Dominican" + } + }, + { + "url": "detailed", + "valueCoding": { + "system": "urn:oid:2.16.840.1.113883.6.238", + "code": "2148-5", + "display": "Mexican" + } + }, + { + "url": "text", + "valueString": "Hispanic or Latino" + } + ] + }, + { + "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", + "valueCode": "F" + } + ], + "identifier": [ + { + "use": "usual", + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "MR", + "display": "Medical Record Number" + } + ], + "text": "Medical Record Number" + }, + "system": "http://hospital.smarthealthit.org", + "value": "1032702" + } + ], + "active": true, + "name": [ + { + "family": "Shaw", + "given": [ + "Amy", + "V." + ] + } + ], + "telecom": [ + { + "system": "phone", + "value": "555-555-5555", + "use": "home" + }, + { + "system": "email", + "value": "amy.shaw@example.com" + } + ], + "gender": "female", + "birthDate": "2007-02-20", + "address": [ + { + "line": [ + "49 Meadow St" + ], + "city": "Mounds", + "state": "OK", + "postalCode": "74047", + "country": "US" + } + ] + }, + "request": { + "method": "PUT", + "url": "Patient/example" + } } ] } \ No newline at end of file diff --git a/setup_example.sh b/setup_example.sh index b8260d3fe..a93f9a5e6 100755 --- a/setup_example.sh +++ b/setup_example.sh @@ -1,7 +1,4 @@ #!/bin/sh -# Post Patient resource -curl -s 'https://www.hl7.org/fhir/us/core/Patient-example.json' | curl -v -s -X PUT -H 'Content-Type: application/fhir+json' 'http://localhost:8080/cqf-ruler-r4/fhir/Patient/example' -d@- - -# Post PlanDefinition Bundle +# Post Bundle curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_plandef_bundle.json http://localhost:8080/cqf-ruler-r4/fhir From 12117e96e4355ae120e280b79be04118b96ec5c7 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 06:25:47 -0400 Subject: [PATCH 150/198] use request groups --- .../cqf/r4/builders/RequestGroupBuilder.java | 11 +++++ .../PlanDefinitionApplyProvider.java | 42 +++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java index 517034dd7..7e2403a11 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java +++ b/r4/src/main/java/org/opencds/cqf/r4/builders/RequestGroupBuilder.java @@ -5,6 +5,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.RequestGroup; +import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.common.builders.BaseBuilder; public class RequestGroupBuilder extends BaseBuilder { @@ -50,8 +51,18 @@ public RequestGroupBuilder buildAction(List extensions) { complexProperty.setExtension(extensions); return this; } + + public RequestGroupBuilder buildContained(Resource result) { + complexProperty.addContained(result); + return this; + } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 30a541fdf..b7e1315d0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -110,8 +110,11 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name if (userLanguage != null) builder.buildLanguage(userLanguage); + // Each Group of actions shares a RequestGroup + RequestGroupBuilder requestGroupBuilder = new RequestGroupBuilder().buildStatus().buildIntent(); + Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, - userType, userLanguage, userTaskContext, setting, settingContext); + userType, userLanguage, userTaskContext, setting, settingContext, requestGroupBuilder); return resolveActions(session); } @@ -150,8 +153,29 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct // iterate over nested plans and get contained activities for(Resource r: plan.getContained()) { - session.getCarePlanBuilder().buildContained(r).buildActivity( - new CarePlanActivityBuilder().buildReference(new Reference("#" + r.getId())).build()); + // Build the RequestGroup + RequestGroup requestGroup = session + .getRequestGroupBuilder() + .buildContained(r) + .addAction(new RequestGroupActionBuilder() + .buildResource(new Reference("#" + r.getId())).build()) + .build(); + + if (requestGroup.getId() == null) { + requestGroup.setId(UUID.randomUUID().toString()); + } + + // Add it to the CarePlan + session + .getCarePlanBuilder() + .buildContained(requestGroup) + .buildActivity( + new CarePlanActivityBuilder() + .buildReference( + new Reference("#" + requestGroup.getId())) + .build() + ); + } for(CanonicalType c: plan.getInstantiatesCanonical()) { @@ -180,11 +204,15 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } + result = session.getRequestGroupBuilder().buildContained(result).build(); + + if (result.getId() == null) { logger.warn("ActivityDefinition %s returned resource with no id, setting one", action.getDefinitionCanonicalType().getId()); result.setId(UUID.randomUUID().toString()); } + session.getCarePlanBuilder().buildContained(result).buildActivity( new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); } catch (Exception e) { @@ -474,10 +502,11 @@ class Session { private final String settingContext; private CarePlanBuilder carePlanBuilder; private String encounterId; + private final RequestGroupBuilder requestGroupBuilder; public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String patientId, String encounterId, String practitionerId, String organizationId, String userType, String userLanguage, String userTaskContext, - String setting, String settingContext) { + String setting, String settingContext, RequestGroupBuilder requestGroupBuilder) { this.patientId = patientId; this.planDefinition = planDefinition; this.carePlanBuilder = builder; @@ -489,6 +518,7 @@ public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String pa this.userTaskContext = userTaskContext; this.setting = setting; this.settingContext = settingContext; + this.requestGroupBuilder = requestGroupBuilder; } public PlanDefinition getPlanDefinition() { @@ -542,4 +572,8 @@ public String getSettingContext() { public CarePlanBuilder getCarePlanBuilder() { return carePlanBuilder; } + + public RequestGroupBuilder getRequestGroupBuilder() { + return requestGroupBuilder; + } } From 8a5a37a52687aada6b7ecd839ad4af7cc4402f06 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 07:30:42 -0400 Subject: [PATCH 151/198] build requestgroup at the endg --- .../providers/PlanDefinitionApplyProvider.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index b7e1315d0..10ba9fe18 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -126,6 +126,15 @@ private CarePlan resolveActions(Session session) { resolveDefinition(session, action); resolveDynamicActions(session, action); } + + RequestGroup result = session.getRequestGroupBuilder().build(); + + if (result.getId() == null) { + result.setId(UUID.randomUUID().toString()); + } + + session.getCarePlanBuilder().buildContained(result).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); } return session.getCarePlan(); @@ -204,17 +213,14 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getUserTaskContext(), session.getSetting(), session.getSettingContext()); } - result = session.getRequestGroupBuilder().buildContained(result).build(); - - if (result.getId() == null) { logger.warn("ActivityDefinition %s returned resource with no id, setting one", action.getDefinitionCanonicalType().getId()); result.setId(UUID.randomUUID().toString()); } - session.getCarePlanBuilder().buildContained(result).buildActivity( - new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); + session.getRequestGroupBuilder().buildContained(result); + } catch (Exception e) { logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", action.getDefinition(), e.toString()); From cdc1978b4dbb83df9a2dc6b1c1fc940adacc169f Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 08:44:11 -0400 Subject: [PATCH 152/198] closer... --- .../PlanDefinitionApplyProvider.java | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 10ba9fe18..2619e973c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -5,6 +5,8 @@ import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.xml.bind.JAXBException; @@ -126,20 +128,31 @@ private CarePlan resolveActions(Session session) { resolveDefinition(session, action); resolveDynamicActions(session, action); } + } - RequestGroup result = session.getRequestGroupBuilder().build(); - - if (result.getId() == null) { - result.setId(UUID.randomUUID().toString()); - } + RequestGroup result = session.getRequestGroupBuilder().build(); - session.getCarePlanBuilder().buildContained(result).buildActivity( - new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); + if (result.getId() == null) { + result.setId(UUID.randomUUID().toString()); } + session.getCarePlanBuilder().buildContained(result).buildActivity( + new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); + return session.getCarePlan(); } + public List getAllContainedResources(Resource resource) { + if (!(resource instanceof DomainResource)) { + return new ArrayList<>(); + } + List contained = ((DomainResource) resource).getContained(); + + return Stream + .concat(contained.stream(), contained.stream().flatMap(r -> getAllContainedResources(r).stream())) + .collect(Collectors.toList()); + } + private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); @@ -159,33 +172,15 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getSetting(), session.getSettingContext()); - // iterate over nested plans and get contained activities - for(Resource r: plan.getContained()) - { - // Build the RequestGroup - RequestGroup requestGroup = session - .getRequestGroupBuilder() - .buildContained(r) - .addAction(new RequestGroupActionBuilder() - .buildResource(new Reference("#" + r.getId())).build()) - .build(); - - if (requestGroup.getId() == null) { - requestGroup.setId(UUID.randomUUID().toString()); - } - - // Add it to the CarePlan + // Pull contained resources up to the CarePlan + // > Contained resources SHALL NOT contain additional contained resources. + // https://www.hl7.org/fhir/references.html#contained + getAllContainedResources(plan).stream().forEach(r -> session .getCarePlanBuilder() - .buildContained(requestGroup) - .buildActivity( - new CarePlanActivityBuilder() - .buildReference( - new Reference("#" + requestGroup.getId())) - .build() - ); + .buildContained(r) + ); - } for(CanonicalType c: plan.getInstantiatesCanonical()) { session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); @@ -219,7 +214,13 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct result.setId(UUID.randomUUID().toString()); } - session.getRequestGroupBuilder().buildContained(result); + session + .getRequestGroupBuilder() + .buildContained(result) + .addAction(new RequestGroupActionBuilder() + .buildResource(new Reference("#" + result.getId())) + .build() + ); } catch (Exception e) { logger.error("ERROR: ActivityDefinition %s could not be applied and threw exception %s", From 067977233546def7ab18df0648aa49f3efa6752a Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 09:53:52 -0400 Subject: [PATCH 153/198] all references are now contained --- .../PlanDefinitionApplyProvider.java | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 2619e973c..4443989bb 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -139,7 +139,9 @@ private CarePlan resolveActions(Session session) { session.getCarePlanBuilder().buildContained(result).buildActivity( new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); - return session.getCarePlan(); + CarePlan cp = session.getCarePlan(); + + return cp; } public List getAllContainedResources(Resource resource) { @@ -153,6 +155,13 @@ public List getAllContainedResources(Resource resource) { .collect(Collectors.toList()); } + public void removeAllContainedResources(List resources) { + resources + .stream() + .filter(r -> !(r instanceof DomainResource)) + .forEach(r -> ((DomainResource)r).setContained(null)); + } + private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); @@ -175,12 +184,27 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct // Pull contained resources up to the CarePlan // > Contained resources SHALL NOT contain additional contained resources. // https://www.hl7.org/fhir/references.html#contained - getAllContainedResources(plan).stream().forEach(r -> + getAllContainedResources(plan).stream() + .forEach(r -> session .getCarePlanBuilder() .buildContained(r) ); + if (plan.getId() == null) { + plan.setId(UUID.randomUUID().toString()); + } + + // Add the overall carePlan to the contained as well + session.getCarePlanBuilder().buildContained(plan); + + // Add an action to the request group which points to this CarePlan + session.getRequestGroupBuilder() + .addAction(new RequestGroupActionBuilder() + .buildResource(new Reference("#" + plan.getId())) + .build() + ); + for(CanonicalType c: plan.getInstantiatesCanonical()) { session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); From 9d560861a3ec87a7f114f319d0bce9a1ee9375db Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 09:54:25 -0400 Subject: [PATCH 154/198] cleanup --- .../cqf/r4/providers/PlanDefinitionApplyProvider.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 4443989bb..b37741338 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -139,9 +139,7 @@ private CarePlan resolveActions(Session session) { session.getCarePlanBuilder().buildContained(result).buildActivity( new CarePlanActivityBuilder().buildReference(new Reference("#" + result.getId())).build()); - CarePlan cp = session.getCarePlan(); - - return cp; + return session.getCarePlan(); } public List getAllContainedResources(Resource resource) { @@ -155,13 +153,6 @@ public List getAllContainedResources(Resource resource) { .collect(Collectors.toList()); } - public void removeAllContainedResources(List resources) { - resources - .stream() - .filter(r -> !(r instanceof DomainResource)) - .forEach(r -> ((DomainResource)r).setContained(null)); - } - private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); From 531bf310e8708a89a4c1f1fce285fe1efe1f4a26 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Wed, 16 Sep 2020 10:26:18 -0400 Subject: [PATCH 155/198] remove extra plandefs to use the existing ones --- example_plandef_bundle.json | 222 ------------------------------------ setup_example.sh | 4 - 2 files changed, 226 deletions(-) delete mode 100644 example_plandef_bundle.json delete mode 100755 setup_example.sh diff --git a/example_plandef_bundle.json b/example_plandef_bundle.json deleted file mode 100644 index 8566106d7..000000000 --- a/example_plandef_bundle.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "resourceType": "Bundle", - "type": "transaction", - "entry": [ - { - "resource": { - "resourceType": "PlanDefinition", - "id": "pd-example", - "meta": { - "versionId": "1", - "lastUpdated": "2020-07-31T20:16:38.519+00:00" - }, - "action": [ - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], - "id": "medication-action-1", - "title": "Administer Medication 1", - "definitionCanonical": "ActivityDefinition/ad-example" - }, - { - "condition": [ - { - "kind": "applicability", - "expression": { - "language": "text/cql", - "expression": "true" - } - } - ], - "id": "medication-action-2", - "title": "Administer Medication 2", - "definitionCanonical": "ActivityDefinition/ad-example" - } - ] - }, - "request": { - "method": "PUT", - "url": "PlanDefinition/pd-example" - } - }, - { - "resource": { - "resourceType": "ActivityDefinition", - "id": "ad-example", - "kind": "MedicationRequest", - "productCodeableConcept": { - "text": "Medication 1" - } - }, - "request": { - "method": "PUT", - "url": "ActivityDefinition/ad-example" - } - }, - { - "resource": { - "resourceType": "Patient", - "id": "example", - "meta": { - "profile": [ - "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" - ] - }, - "text": { - "status": "generated", - "div": "
\n\t\t\t

\n\t\t\t\tGenerated Narrative with Details\n\t\t\t

\n\t\t\t

\n\t\t\t\tid: example

\n\t\t\t

\n\t\t\t\tidentifier: Medical Record Number = 1032702 (USUAL)

\n\t\t\t

\n\t\t\t\tactive: true

\n\t\t\t

\n\t\t\t\tname: Amy V. Shaw

\n\t\t\t

\n\t\t\t\ttelecom: ph: 555-555-5555(HOME), amy.shaw@example.com

\n\t\t\t

\n\t\t\t\tgender:

\n\t\t\t

\n\t\t\t\tbirthsex: Female

\n\t\t\t

\n\t\t\t\tbirthDate: Feb 20, 2007

\n\t\t\t

\n\t\t\t\taddress: 49 Meadow St Mounds OK 74047 US

\n\t\t\t

\n\t\t\t\trace: White, American Indian or Alaska Native, Asian, Shoshone, Filipino

\n\t\t\t

\n\t\t\t\tethnicity: Hispanic or Latino, Dominican, Mexican

\n\t\t
" - }, - "extension": [ - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2106-3", - "display": "White" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1002-5", - "display": "American Indian or Alaska Native" - } - }, - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2028-9", - "display": "Asian" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "1586-7", - "display": "Shoshone" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2036-2", - "display": "Filipino" - } - }, - { - "url": "text", - "valueString": "Mixed" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", - "extension": [ - { - "url": "ombCategory", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2135-2", - "display": "Hispanic or Latino" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2184-0", - "display": "Dominican" - } - }, - { - "url": "detailed", - "valueCoding": { - "system": "urn:oid:2.16.840.1.113883.6.238", - "code": "2148-5", - "display": "Mexican" - } - }, - { - "url": "text", - "valueString": "Hispanic or Latino" - } - ] - }, - { - "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex", - "valueCode": "F" - } - ], - "identifier": [ - { - "use": "usual", - "type": { - "coding": [ - { - "system": "http://terminology.hl7.org/CodeSystem/v2-0203", - "code": "MR", - "display": "Medical Record Number" - } - ], - "text": "Medical Record Number" - }, - "system": "http://hospital.smarthealthit.org", - "value": "1032702" - } - ], - "active": true, - "name": [ - { - "family": "Shaw", - "given": [ - "Amy", - "V." - ] - } - ], - "telecom": [ - { - "system": "phone", - "value": "555-555-5555", - "use": "home" - }, - { - "system": "email", - "value": "amy.shaw@example.com" - } - ], - "gender": "female", - "birthDate": "2007-02-20", - "address": [ - { - "line": [ - "49 Meadow St" - ], - "city": "Mounds", - "state": "OK", - "postalCode": "74047", - "country": "US" - } - ] - }, - "request": { - "method": "PUT", - "url": "Patient/example" - } - } - ] -} \ No newline at end of file diff --git a/setup_example.sh b/setup_example.sh deleted file mode 100755 index a93f9a5e6..000000000 --- a/setup_example.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -# Post Bundle -curl -s -X POST -H 'Content-Type: application/fhir+json' -d @example_plandef_bundle.json http://localhost:8080/cqf-ruler-r4/fhir From 9f36170d8634439ba0efa83a8137eca1a79b6d8b Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Thu, 17 Sep 2020 14:43:04 -0400 Subject: [PATCH 156/198] allow absolute canonical urls for PlanDefinition References --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index b37741338..f0270ded6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -157,7 +157,7 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); String definition = action.getDefinitionCanonicalType().getValue(); - if (definition.startsWith(session.getPlanDefinition().fhirType())) { + if (definition.contains(session.getPlanDefinition().fhirType())) { IdType id = new IdType(definition); CarePlan plan = null; try { From 18e147d83277a473c0ded7637ac311ee81dc91cc Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:15:57 -0400 Subject: [PATCH 157/198] remove unused import --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index f0270ded6..f5c138437 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -25,7 +25,6 @@ import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.common.exceptions.NotImplementedException; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.cql.engine.model.ModelResolver; From 66ef53c21f776c7b7acd63da8def867ddf5859d1 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:17:47 -0400 Subject: [PATCH 158/198] remove uncessary stream() --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index f5c138437..66c522289 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -174,7 +174,7 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct // Pull contained resources up to the CarePlan // > Contained resources SHALL NOT contain additional contained resources. // https://www.hl7.org/fhir/references.html#contained - getAllContainedResources(plan).stream() + getAllContainedResources(plan) .forEach(r -> session .getCarePlanBuilder() From 0da45a3379cf3aa88159fc0ce844465001b3c912 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:22:12 -0400 Subject: [PATCH 159/198] remove unnecessary JAXBException --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 66c522289..6dbf903ac 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -88,7 +88,7 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name @OperationParam(name = "userTaskContext") String userTaskContext, @OperationParam(name = "setting") String setting, @OperationParam(name = "settingContext") String settingContext) - throws IOException, JAXBException, FHIRException { + throws IOException, FHIRException { PlanDefinition planDefinition = this.planDefinitionDao.read(theId); if (planDefinition == null) { @@ -200,7 +200,7 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getCarePlanBuilder().buildInstantiatesCanonical(c.getValueAsString()); } - } catch (IOException | JAXBException e) { + } catch (IOException e) { e.printStackTrace(); logger.error("nested plan failed"); } From 8982e6c6422110470f022bb0c8d16e7e0b77da2c Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:22:55 -0400 Subject: [PATCH 160/198] remove redundant initializer --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 6dbf903ac..78e7dfc2c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -158,7 +158,7 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct String definition = action.getDefinitionCanonicalType().getValue(); if (definition.contains(session.getPlanDefinition().fhirType())) { IdType id = new IdType(definition); - CarePlan plan = null; + CarePlan plan; try { plan = applyPlanDefinition(id, session.getPatientId(), From 0e5b75c7f97cf04ab5c023cdaaec85df0660d5e0 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:24:57 -0400 Subject: [PATCH 161/198] remove redundant initializer --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 78e7dfc2c..c8feb4143 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -301,7 +301,7 @@ private Boolean meetsConditions(Session session, PlanDefinition.PlanDefinitionAc logger.info("Evaluating action condition expression " + condition.getExpression()); String cql = condition.getExpression().getExpression(); String language = condition.getExpression().getLanguage(); - Object result = null; + Object result; result = (language.equals("text/cql.name")) ? executionProvider.evaluateInContext(session.getPlanDefinition(), cql, session.getPatientId(), true) From 5f5f89dd043784a08f235e611c0cdf48694204ce Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:26:41 -0400 Subject: [PATCH 162/198] mark fields as final --- .../r4/providers/PlanDefinitionApplyProvider.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index c8feb4143..34ca41361 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -51,14 +51,14 @@ public class PlanDefinitionApplyProvider { - private CqlExecutionProvider executionProvider; - private ModelResolver modelResolver; - private ActivityDefinitionApplyProvider activityDefinitionApplyProvider; + private final CqlExecutionProvider executionProvider; + private final ModelResolver modelResolver; + private final ActivityDefinitionApplyProvider activityDefinitionApplyProvider; - private IFhirResourceDao planDefinitionDao; - private IFhirResourceDao activityDefinitionDao; + private final IFhirResourceDao planDefinitionDao; + private final IFhirResourceDao activityDefinitionDao; - private FhirContext fhirContext; + private final FhirContext fhirContext; private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); @@ -522,7 +522,7 @@ class Session { private final String setting; private final String settingContext; private CarePlanBuilder carePlanBuilder; - private String encounterId; + private final String encounterId; private final RequestGroupBuilder requestGroupBuilder; public Session(PlanDefinition planDefinition, CarePlanBuilder builder, String patientId, String encounterId, From 57e1c580d1fced4c2722ee05258cfb22824fcff6 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:27:12 -0400 Subject: [PATCH 163/198] remove unused JAXBException import --- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 34ca41361..3006e80c7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -8,8 +8,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.xml.bind.JAXBException; - import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.ActivityDefinition; import org.hl7.fhir.r4.model.CanonicalType; From 6d026cc53cff8dc0211f605b71b0f2852ae066a6 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 09:28:56 -0400 Subject: [PATCH 164/198] remove redundant variable assignment --- .../cqf/r4/providers/PlanDefinitionApplyProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 3006e80c7..a0ab09e05 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -430,9 +430,8 @@ private void resolveActions(List a if (activityDefinition.hasDescription()) { actionBuilder.buildDescripition(activityDefinition.getDescription()); } - Resource resource; try { - resource = this.activityDefinitionApplyProvider + this.activityDefinitionApplyProvider .apply(new IdType(action.getDefinitionCanonicalType().getId()), patientId, null, null, null, null, null, null, null, null) .setId(UUID.randomUUID().toString()); @@ -449,7 +448,7 @@ private void resolveActions(List a .named("$apply").withParameters(inParams).useHttpGet().execute(); List response = outParams.getParameter(); - resource = response.get(0).getResource().setId(UUID.randomUUID().toString()); + Resource resource = response.get(0).getResource().setId(UUID.randomUUID().toString()); actionBuilder.buildResourceTarget(resource); actionBuilder .buildResource(new ReferenceBuilder().buildReference(resource.getId()).build()); From 90fb296017516eb32ebed19988a6dfe8824bfcd0 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 12:26:07 -0400 Subject: [PATCH 165/198] move contained helpers to their own class --- .../cqf/r4/helpers/ContainedHelper.java | 58 +++++++++++++++++++ .../PlanDefinitionApplyProvider.java | 28 +-------- 2 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java new file mode 100644 index 000000000..690d1f6db --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java @@ -0,0 +1,58 @@ +package org.opencds.cqf.r4.helpers; + +import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ContainedHelper { + + // > Contained resources SHALL NOT contain additional contained resources. + // https://www.hl7.org/fhir/references.html#contained + public static DomainResource liftContainedResourcesToParent(DomainResource resource) { + getContainedResourcesInContainedResources(resource) + .forEach(resource::addContained); // add them to the parent + + return resource; // Return the resource to allow for method chaining + } + + private static List getContainedResourcesInContainedResources(Resource resource) { + if (!(resource instanceof DomainResource)) { + return new ArrayList<>(); + } + return streamContainedResourcesInContainedResources(resource).collect(Collectors.toList()); + } + + public static List getAllContainedResources(Resource resource) { + if (!(resource instanceof DomainResource)) { + return new ArrayList<>(); + } + return streamAllContainedResources(resource).collect(Collectors.toList()); + } + + private static Stream streamContainedResourcesInContainedResources(Resource resource) { + if (!(resource instanceof DomainResource)) { + return Stream.empty(); + } + return ((DomainResource) resource) + .getContained() // We don't need to re-add any resources that are already on the parent. + .stream() + .flatMap(ContainedHelper::streamAllContainedResources); // Get the resources contained + } + + private static Stream streamAllContainedResources(Resource resource) { + if (!(resource instanceof DomainResource)) { + return Stream.empty(); + } + List contained = ((DomainResource) resource).getContained(); + + return Stream + .concat(contained.stream(), + contained + .stream() + .flatMap(ContainedHelper::streamAllContainedResources)); + } +} diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index a0ab09e05..924c87a84 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -37,6 +37,7 @@ import org.opencds.cqf.r4.builders.RequestGroupActionBuilder; import org.opencds.cqf.r4.builders.RequestGroupBuilder; import org.opencds.cqf.r4.helpers.CanonicalHelper; +import org.opencds.cqf.r4.helpers.ContainedHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -115,7 +116,7 @@ public CarePlan applyPlanDefinition(@IdParam IdType theId, @OperationParam(name Session session = new Session(planDefinition, builder, patientId, encounterId, practitionerId, organizationId, userType, userLanguage, userTaskContext, setting, settingContext, requestGroupBuilder); - return resolveActions(session); + return (CarePlan) ContainedHelper.liftContainedResourcesToParent(resolveActions(session)); } private CarePlan resolveActions(Session session) { @@ -139,17 +140,6 @@ private CarePlan resolveActions(Session session) { return session.getCarePlan(); } - public List getAllContainedResources(Resource resource) { - if (!(resource instanceof DomainResource)) { - return new ArrayList<>(); - } - List contained = ((DomainResource) resource).getContained(); - - return Stream - .concat(contained.stream(), contained.stream().flatMap(r -> getAllContainedResources(r).stream())) - .collect(Collectors.toList()); - } - private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionActionComponent action) { if (action.hasDefinition()) { logger.debug("Resolving definition " + action.getDefinitionCanonicalType().getValue()); @@ -169,25 +159,13 @@ private void resolveDefinition(Session session, PlanDefinition.PlanDefinitionAct session.getSetting(), session.getSettingContext()); - // Pull contained resources up to the CarePlan - // > Contained resources SHALL NOT contain additional contained resources. - // https://www.hl7.org/fhir/references.html#contained - getAllContainedResources(plan) - .forEach(r -> - session - .getCarePlanBuilder() - .buildContained(r) - ); - if (plan.getId() == null) { plan.setId(UUID.randomUUID().toString()); } - // Add the overall carePlan to the contained as well - session.getCarePlanBuilder().buildContained(plan); - // Add an action to the request group which points to this CarePlan session.getRequestGroupBuilder() + .buildContained(plan) .addAction(new RequestGroupActionBuilder() .buildResource(new Reference("#" + plan.getId())) .build() From 1733e437bd6982e8b931c1b8b92df70a1940304d Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 12:35:21 -0400 Subject: [PATCH 166/198] javadoc --- .../cqf/r4/helpers/ContainedHelper.java | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java index 690d1f6db..ed524a961 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java @@ -8,10 +8,27 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +/** + * This class consists exclusively of static methods that assist with managing contained FHIR + * Resources. + * + * The FHIR specification does not allow contained resources to contain additional resources: + * > Contained resources SHALL NOT contain additional contained resources. + * https://www.hl7.org/fhir/references.html#contained + * + * When returning a resource from an annotated method that responds to an incoming request any + * resources contained in another resource will be removed. `liftContainedResourcesToParent` in + * this class will move all contained resources to the parent so they are present in responses + * to clients. + */ public class ContainedHelper { - // > Contained resources SHALL NOT contain additional contained resources. - // https://www.hl7.org/fhir/references.html#contained + /** + * Adds all contained resources in resources contained on the parent to the parent. + * + * @param resource the parent resource where contained resources should be + * @return the modified parent resource + */ public static DomainResource liftContainedResourcesToParent(DomainResource resource) { getContainedResourcesInContainedResources(resource) .forEach(resource::addContained); // add them to the parent @@ -19,6 +36,12 @@ public static DomainResource liftContainedResourcesToParent(DomainResource resou return resource; // Return the resource to allow for method chaining } + /** + * Returns all contained resources that are not already directly present on the parent resource. + * + * @param resource the parent resource + * @return list of the contained resources + */ private static List getContainedResourcesInContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { return new ArrayList<>(); @@ -26,6 +49,12 @@ private static List getContainedResourcesInContainedResources(Resource return streamContainedResourcesInContainedResources(resource).collect(Collectors.toList()); } + /** + * Returns all contained resources, including resources directly contained on the parent and + * resources contained on any other contained resource + * @param resource the parent resource + * @return list of all contained resources + */ public static List getAllContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { return new ArrayList<>(); @@ -33,6 +62,9 @@ public static List getAllContainedResources(Resource resource) { return streamAllContainedResources(resource).collect(Collectors.toList()); } + /** + * @see ContainedHelper#streamContainedResourcesInContainedResources(Resource) + */ private static Stream streamContainedResourcesInContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { return Stream.empty(); @@ -43,6 +75,9 @@ private static Stream streamContainedResourcesInContainedResources(Res .flatMap(ContainedHelper::streamAllContainedResources); // Get the resources contained } + /** + * @see ContainedHelper#streamAllContainedResources(Resource) + */ private static Stream streamAllContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { return Stream.empty(); From 498eb7c5bb5fff1f51f9c0a2fec778748a72cc17 Mon Sep 17 00:00:00 2001 From: Reece Adamson Date: Fri, 18 Sep 2020 15:03:04 -0400 Subject: [PATCH 167/198] address PR comments --- .../main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java | 4 ++-- .../opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java index ed524a961..5c19824c1 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java @@ -63,7 +63,7 @@ public static List getAllContainedResources(Resource resource) { } /** - * @see ContainedHelper#streamContainedResourcesInContainedResources(Resource) + * @see ContainedHelper#getContainedResourcesInContainedResources(Resource) */ private static Stream streamContainedResourcesInContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { @@ -76,7 +76,7 @@ private static Stream streamContainedResourcesInContainedResources(Res } /** - * @see ContainedHelper#streamAllContainedResources(Resource) + * @see ContainedHelper#getAllContainedResources(Resource) */ private static Stream streamAllContainedResources(Resource resource) { if (!(resource instanceof DomainResource)) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 924c87a84..e5b5a6b44 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -5,8 +5,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.ActivityDefinition; From e2271ae7e7390d6de4585eb2c5773dc89946cde5 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 18 Sep 2020 13:25:47 -0600 Subject: [PATCH 168/198] Fixe to dockerfile --- Dockerfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 75e4112e8..f0a8df057 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,20 @@ FROM jetty:9-jre11 USER jetty:jetty + +# Default database directory +RUN mkdir -p /var/lib/jetty/target + +#Default config directory RUN mkdir -p /var/lib/jetty/webapps/config -COPY ./cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war -COPY ./cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war + +COPY --chown=jetty:jetty ./cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war +COPY --chown=jetty:jetty ./cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war EXPOSE 8080 ENV SERVER_ADDRESS_DSTU3="http://localhost:8080/cqf-ruler-dstu3/fhir" ENV SERVER_ADDRESS_R4="http://localhost:8080/cqf-ruler-r4/fhir" -# TODO: Handle these. We probably want some convention to map between the ENV variables and the hapi.properties -# ENV SERVER_BASE="/cqf-ruler/baseDstu3" -# ENV HIBERNATE_DIALECT="hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect" -# ENV DATASOURCE_DRIVER="org.apache.derby.jdbc.EmbeddedDriver" -# ENV DATASOURCE_URL="jdbc:derby:directory:target/jpaserver_derby_files;create=true" -# ENV DATASOURCE_URL= DATASOURCE_USERNAME= + ENV JAVA_OPTIONS="-Dhapi.properties.DSTU3=/var/lib/jetty/webapps/config/dstu3.properties -Dhapi.properties.R4=/var/lib/jetty/webapps/config/r4.properties" COPY --chown=jetty:jetty ./scripts/docker-entrypoint-override.sh /docker-entrypoint-override.sh From 8d1fdfffb098da495c1bf968e93a8d4a2b49a5d7 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 21 Sep 2020 12:45:56 -0600 Subject: [PATCH 169/198] More tweaks to docker config management --- Dockerfile | 3 +- scripts/docker-entrypoint-override.sh | 40 +++++++++++++++------------ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index f0a8df057..b6a2e2a90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,7 @@ EXPOSE 8080 ENV SERVER_ADDRESS_DSTU3="http://localhost:8080/cqf-ruler-dstu3/fhir" ENV SERVER_ADDRESS_R4="http://localhost:8080/cqf-ruler-r4/fhir" - -ENV JAVA_OPTIONS="-Dhapi.properties.DSTU3=/var/lib/jetty/webapps/config/dstu3.properties -Dhapi.properties.R4=/var/lib/jetty/webapps/config/r4.properties" +ENV JAVA_OPTIONS="" COPY --chown=jetty:jetty ./scripts/docker-entrypoint-override.sh /docker-entrypoint-override.sh ENTRYPOINT [ "/docker-entrypoint-override.sh" ] diff --git a/scripts/docker-entrypoint-override.sh b/scripts/docker-entrypoint-override.sh index aeaae9e22..b2f82818e 100755 --- a/scripts/docker-entrypoint-override.sh +++ b/scripts/docker-entrypoint-override.sh @@ -1,25 +1,29 @@ #!/bin/sh -# TODO: we probably want to test the existence of the various variables before writing them to a file -# datasource.url= $DATASOURCE_URL -# ensure the config directory / file is created, strip out any old server address, pre-prend new server address -echo "Configuring Docker container" -echo "DSTU3 address: " $SERVER_ADDRESS_DSTU3 +# ensure the config directory / file is created, strip out any old server address append new server address +echo "configuring docker container" +echo "dstu3 address: " $SERVER_ADDRESS_DSTU3 +echo "r4 address: " $SERVER_ADDRESS_R4 -echo "R4 address: " $SERVER_ADDRESS_R4 -cd /var/lib/jetty/webapps/config +echo server_address=$SERVER_ADDRESS_DSTU3 >> /var/lib/jetty/webapps/dstu3.properties +if [ -e /var/lib/jetty/webapps/config/dstu3.properties ]; then + echo "importing dstu3 properties" + sed '/server_address/d' /var/lib/jetty/webapps/config/dstu3.properties >> /var/lib/jetty/webapps/dstu3.properties +fi -echo server_address=$SERVER_ADDRESS_DSTU3 > temp-dstu3.properties -touch dstu3.properties -grep -v "server_address" dstu3.properties >> temp-dstu3.properties -cp temp-dstu3.properties dstu3.properties -rm temp-dstu3.properties +echo server_address=$SERVER_ADDRESS_R4 >> /var/lib/jetty/webapps/r4.properties +if [ -e /var/lib/jetty/webapps/config/r4.properties ]; then + echo "importing r4 properties" + sed '/server_address/d' /var/lib/jetty/webapps/config/r4.properties >> /var/lib/jetty/webapps/r4.properties +fi -echo server_address=$SERVER_ADDRESS_R4 > temp-r4.properties -touch r4.properties -grep -v "server_address" r4.properties >> temp-r4.properties -cp temp-r4.properties r4.properties -rm temp-r4.properties +# set Java options +DEFAULT_OPTIONS=" -XX:+UseContainerSupport -XX:MaxRAMPercentage=80.0 -Dhapi.properties.DSTU3=/var/lib/jetty/webapps/dstu3.properties -Dhapi.properties.R4=/var/lib/jetty/webapps/r4.properties" +if [ -z "$JAVA_OPTIONS" ] +then + export JAVA_OPTIONS="$DEFAULT_OPTIONS" +else + export JAVA_OPTIONS="$JAVA_OPTIONS:$DEFAULT_OPTIONS" +fi -cd /var/lib/jetty exec /docker-entrypoint.sh "$@" \ No newline at end of file From 06a12bcb029c5668e24d17d484b1b69e92196ad6 Mon Sep 17 00:00:00 2001 From: mdube Date: Tue, 22 Sep 2020 12:56:33 -0400 Subject: [PATCH 170/198] Change comment The > in the comment was causing a MavenReportException while generating javadoc: error: bad use of '>' --- .../main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java index 5c19824c1..5fd6134bb 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/ContainedHelper.java @@ -13,7 +13,7 @@ * Resources. * * The FHIR specification does not allow contained resources to contain additional resources: - * > Contained resources SHALL NOT contain additional contained resources. + * > Contained resources SHALL NOT contain additional contained resources. * https://www.hl7.org/fhir/references.html#contained * * When returning a resource from an annotated method that responds to an incoming request any From 04e1a890d5abf7a696114f8faf4e5a4262832dc5 Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 22 Sep 2020 13:41:14 -0600 Subject: [PATCH 171/198] loop for items and answers in QuestionnaireResponse extract operation --- .../providers/QuestionnaireProvider.java | 39 +++++++++++++++---- .../r4/providers/QuestionnaireProvider.java | 39 +++++++++++++++---- r4/src/main/resources/hapi.properties | 3 +- 3 files changed, 64 insertions(+), 17 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index f3f5812d3..a135d4b77 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -39,12 +39,35 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon newBundle.setIdentifier(bundleId); questionnaireResponse.getItem().stream().forEach(item ->{ - newBundle.addEntry(extractItem(item, authored, questionnaireResponse)); + processItems(item, authored, questionnaireResponse, newBundle); }); return newBundle; } - private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, QuestionnaireResponse questionnaireResponse){ + private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, + QuestionnaireResponse questionnaireResponse, Bundle newBundle){ + if(item.hasAnswer()){ + item.getAnswer().forEach(answer ->{ + Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse); + if(null != newBundleEntryComponent){ + newBundle.addEntry(newBundleEntryComponent); + } + if(answer.hasItem()){ + answer.getItem().forEach(answerItem->{ + processItems(answerItem, authored, questionnaireResponse, newBundle); + }); + } + }); + } + if(item.hasItem()){ + item.getItem().forEach(itemItem ->{ + processItems(itemItem, authored, questionnaireResponse, newBundle); + }); + } + } + + private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, + String linkId, Date authored, QuestionnaireResponse questionnaireResponse){ Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -58,16 +81,16 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna qrCoding.setCode("74465-6"); qrCoding.setDisplay("Questionnaire response Document"); obs.setCode(new CodeableConcept().addCoding(qrCoding)); - obs.setId(questionnaireResponse.getIdElement().getIdPart() + "." + item.getLinkId()); - switch(item.getAnswer().get(0).getValue().fhirType()){ + obs.setId("qr" + questionnaireResponse.getIdElement().getIdPart() + "." + linkId); + switch(answer.getValue().fhirType()){ case "string": - obs.setValue(new StringType(item.getAnswer().get(0).getValueStringType().getValue())); + obs.setValue(new StringType(answer.getValueStringType().getValue())); break; case "Coding": - obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); + obs.setValue(new CodeableConcept().addCoding(answer.getValueCoding())); break; case "boolean": - obs.setValue(new BooleanType(item.getAnswer().get(0).getValueBooleanType().booleanValue())); + obs.setValue(new BooleanType(answer.getValueBooleanType().booleanValue())); break; } Reference questionnaireResponseReference = new Reference(); @@ -80,7 +103,7 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); Extension innerLinkIdExtension = new Extension(); innerLinkIdExtension.setUrl("text"); - innerLinkIdExtension.setValue(new StringType(item.getLinkId())); + innerLinkIdExtension.setValue(new StringType(linkId)); linkIdExtension.setExtension(Collections.singletonList(innerLinkIdExtension)); obs.addExtension(linkIdExtension); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 97fee2f48..e12a5c651 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -39,12 +39,35 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon newBundle.setIdentifier(bundleId); questionnaireResponse.getItem().stream().forEach(item ->{ - newBundle.addEntry(extractItem(item, authored, questionnaireResponse)); + processItems(item, authored, questionnaireResponse, newBundle); }); return newBundle; } - private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, QuestionnaireResponse questionnaireResponse){ + private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, + QuestionnaireResponse questionnaireResponse, Bundle newBundle){ + if(item.hasAnswer()){ + item.getAnswer().forEach(answer ->{ + Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse); + if(null != newBundleEntryComponent){ + newBundle.addEntry(newBundleEntryComponent); + } + if(answer.hasItem()){ + answer.getItem().forEach(answerItem->{ + processItems(answerItem, authored, questionnaireResponse, newBundle); + }); + } + }); + } + if(item.hasItem()){ + item.getItem().forEach(itemItem ->{ + processItems(itemItem, authored, questionnaireResponse, newBundle); + }); + } + } + + private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, + String linkId, Date authored, QuestionnaireResponse questionnaireResponse){ Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -58,16 +81,16 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna qrCoding.setCode("74465-6"); qrCoding.setDisplay("Questionnaire response Document"); obs.setCode(new CodeableConcept().addCoding(qrCoding)); - obs.setId(questionnaireResponse.getIdElement().getIdPart() + "." + item.getLinkId()); - switch(item.getAnswer().get(0).getValue().fhirType()){ + obs.setId("qr" + questionnaireResponse.getIdElement().getIdPart() + "." + linkId); + switch(answer.getValue().fhirType()){ case "string": - obs.setValue(new StringType(item.getAnswer().get(0).getValueStringType().getValue())); + obs.setValue(new StringType(answer.getValueStringType().getValue())); break; case "Coding": - obs.setValue(new CodeableConcept().addCoding(item.getAnswer().get(0).getValueCoding())); + obs.setValue(new CodeableConcept().addCoding(answer.getValueCoding())); break; case "boolean": - obs.setValue(new BooleanType(item.getAnswer().get(0).getValueBooleanType().booleanValue())); + obs.setValue(new BooleanType(answer.getValueBooleanType().booleanValue())); break; } Reference questionnaireResponseReference = new Reference(); @@ -77,7 +100,7 @@ private Bundle.BundleEntryComponent extractItem(QuestionnaireResponse.Questionna linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); Extension innerLinkIdExtension = new Extension(); innerLinkIdExtension.setUrl("text"); - innerLinkIdExtension.setValue(new StringType(item.getLinkId())); + innerLinkIdExtension.setValue(new StringType(linkId)); linkIdExtension.setExtension(Collections.singletonList(innerLinkIdExtension)); obs.addExtension(linkIdExtension); Bundle.BundleEntryRequestComponent berc = new Bundle.BundleEntryRequestComponent(); diff --git a/r4/src/main/resources/hapi.properties b/r4/src/main/resources/hapi.properties index 1ceecd3a3..24c7c818c 100644 --- a/r4/src/main/resources/hapi.properties +++ b/r4/src/main/resources/hapi.properties @@ -166,7 +166,8 @@ oauth.serviceDisplay=SMART-on-FHIR oauth.serviceText=OAuth2 using SMART-on-FHIR profile (see http://docs.smarthealthit.org) questionnaireResponseExtract.enabled=true -questionnaireResponseExtract.endpoint=https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir +questionnaireResponseExtract.endpoint=http://localhost:8080/cqf-ruler-r4/fhir +# https://cds4cpm-develop.sandbox.alphora.com/cqf-ruler-r4/fhir questionnaireResponseExtract.username= questionnaireResponseExtract.password= From 6c47b10f01851ea376c91a69aa80eecd26b44daa Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Tue, 29 Sep 2020 16:07:49 -0600 Subject: [PATCH 172/198] #251: Fixed primary library resolution incorrectly using id in some cases. --- .../opencds/cqf/r4/helpers/LibraryHelper.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java index 21d58e7fc..8ef378574 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java +++ b/r4/src/main/java/org/opencds/cqf/r4/helpers/LibraryHelper.java @@ -39,6 +39,19 @@ public static LibraryLoader createLibraryLoader(org.cqframework.cql.cql2elm.Libr return new LibraryLoader(libraryManager, modelManager); } + public static org.hl7.fhir.r4.model.Library resolveLibraryReference(LibraryResolutionProvider libraryResourceProvider, String reference) { + // Raw references to Library/libraryId or libraryId + if (reference.startsWith("Library/") || !reference.contains("/")) { + return libraryResourceProvider.resolveLibraryById(reference.replace("Library/", "")); + } + // Full url (e.g. http://hl7.org/fhir/us/Library/FHIRHelpers) + else if (reference.contains(("/Library/"))) { + return libraryResourceProvider.resolveLibraryByCanonicalUrl(reference); + } + + return null; + } + public static List loadLibraries(Measure measure, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider libraryResourceProvider) { @@ -46,9 +59,10 @@ public static List loadLibraries(Meas // load libraries //TODO: if there's a bad measure argument, this blows up for an obscure error + org.hl7.fhir.r4.model.Library primaryLibrary = null; for (CanonicalType ref : measure.getLibrary()) { // if library is contained in measure, load it into server - String id = CanonicalHelper.getId(ref); + String id = ref.getValue(); //CanonicalHelper.getId(ref); if (id.startsWith("#")) { id = id.substring(1); for (Resource resource : measure.getContained()) { @@ -60,7 +74,11 @@ public static List loadLibraries(Meas } // We just loaded it into the server so we can access it by Id - org.hl7.fhir.r4.model.Library library = libraryResourceProvider.resolveLibraryById(id); + org.hl7.fhir.r4.model.Library library = resolveLibraryReference(libraryResourceProvider, id); + if (primaryLibrary == null) { + primaryLibrary = library; + } + if (library != null && isLogicLibrary(library)) { libraries.add( libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) @@ -73,19 +91,12 @@ public static List loadLibraries(Meas .format("Could not load library source for libraries referenced in Measure/%s.", measure.getId())); } - VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); - org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); + //VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier(); + //org.hl7.fhir.r4.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion()); for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource()) { org.hl7.fhir.r4.model.Library library = null; - // Raw references to Library/libraryId or libraryId - if (artifact.getResource().startsWith("Library/") || ! artifact.getResource().contains("/")) { - library = libraryResourceProvider.resolveLibraryById(artifact.getResource().replace("Library/", "")); - } - // Full url (e.g. http://hl7.org/fhir/us/Library/FHIRHelpers) - else if (artifact.getResource().contains(("/Library/"))) { - library = libraryResourceProvider.resolveLibraryByCanonicalUrl(artifact.getResource()); - } + library = resolveLibraryReference(libraryResourceProvider, artifact.getResource()); if (library != null && isLogicLibrary(library)) { libraries.add( From a6a2a920b9624c5794d2e1037f8907f01b0e893e Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 30 Sep 2020 16:36:20 -0600 Subject: [PATCH 173/198] Prep for 0.4.0 Release --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index e0a2cc0cd..6ef626535 100644 --- a/pom.xml +++ b/pom.xml @@ -55,11 +55,11 @@ 9.4.28.v20200408 2.10.1 5.0.2 - 1.3.0-SNAPSHOT - 1.5.0-SNAPSHOT - 1.0.0-SNAPSHOT - 1.5.0-SNAPSHOT - 1.3.0-SNAPSHOT + 1.3.0 + 1.5.0 + 1.0.0 + 1.5.0 + 1.3.0 1.7.30 From 93b6972ea57a7dac90cc62b26d2b5be82cb0935a Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 30 Sep 2020 23:23:20 -0600 Subject: [PATCH 174/198] Add names to poms --- common/pom.xml | 1 + cqf-ruler-dstu3/pom.xml | 1 + cqf-ruler-r4/pom.xml | 1 + dstu3/pom.xml | 1 + r4/pom.xml | 1 + 5 files changed, 5 insertions(+) diff --git a/common/pom.xml b/common/pom.xml index 8ceb6b9c5..e6420a1b8 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -9,4 +9,5 @@ org.opencds.cqf common + cqf-ruler-common diff --git a/cqf-ruler-dstu3/pom.xml b/cqf-ruler-dstu3/pom.xml index 507a0b53b..539a245d3 100644 --- a/cqf-ruler-dstu3/pom.xml +++ b/cqf-ruler-dstu3/pom.xml @@ -10,6 +10,7 @@ org.opencds.cqf cqf-ruler-dstu3 + cqf-ruler-dstu3 war diff --git a/cqf-ruler-r4/pom.xml b/cqf-ruler-r4/pom.xml index 8678c84c7..4b31f873d 100644 --- a/cqf-ruler-r4/pom.xml +++ b/cqf-ruler-r4/pom.xml @@ -10,6 +10,7 @@ org.opencds.cqf cqf-ruler-r4 + cqf-ruler-r4 war diff --git a/dstu3/pom.xml b/dstu3/pom.xml index 26e1f9038..88aaed16c 100644 --- a/dstu3/pom.xml +++ b/dstu3/pom.xml @@ -10,6 +10,7 @@ org.opencds.cqf dstu3 + dstu3 diff --git a/r4/pom.xml b/r4/pom.xml index 586736961..e008ffe1c 100644 --- a/r4/pom.xml +++ b/r4/pom.xml @@ -9,6 +9,7 @@ org.opencds.cqf r4 + r4 From 38092ebbdba6519671c7f1f05cca8c949fdf794a Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 30 Sep 2020 23:40:25 -0600 Subject: [PATCH 175/198] Rev to 0.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ef626535..101ab0e47 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ 0 4 - 0 + 1 ${version.major}.${version.minor}.${version.patch}-SNAPSHOT From a72f949e648de6685628e8a46d64a452e9841652 Mon Sep 17 00:00:00 2001 From: ZackAustin Date: Mon, 5 Oct 2020 15:20:59 -0600 Subject: [PATCH 176/198] Fix Measure Report Evaluated Resources. --- .../dstu3/evaluation/MeasureEvaluation.java | 16 +++++------- .../cqf/r4/evaluation/MeasureEvaluation.java | 26 ++++++------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java index 8ea319151..fa802024e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/MeasureEvaluation.java @@ -1,13 +1,7 @@ package org.opencds.cqf.dstu3.evaluation; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import org.cqframework.cql.elm.execution.ExpressionDef; @@ -278,6 +272,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap resources = new HashMap<>(); HashMap> codeToResourceMap = new HashMap<>(); + Set evaluatedResourcesList = new HashSet<>(); MeasureScoring measureScoring = MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode()); if (measureScoring == null) { @@ -545,16 +540,17 @@ private MeasureReport evaluate(Measure measure, Context context, List p } if (!list.isEmpty()) { - list.setId(UUID.randomUUID().toString()); + list.setId("List/" + UUID.randomUUID().toString()); list.setTitle(key); resources.put(list.getId(), list); + list.getEntry().forEach(listResource -> evaluatedResourcesList.add(listResource.getItem().getReference())); } } if (!resources.isEmpty()) { List evaluatedResourceIds = new ArrayList<>(); - resources.forEach((key, resource) -> { - evaluatedResourceIds.add(new Reference(resource.getId())); + evaluatedResourcesList.forEach((resource) -> { + evaluatedResourceIds.add(new Reference(resource)); }); // TODO: DSTU3 Doesn't support this.. diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3e1a2dcd6..93bd942ad 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -1,13 +1,7 @@ package org.opencds.cqf.r4.evaluation; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.stream.Collectors; import org.cqframework.cql.elm.execution.ExpressionDef; @@ -279,6 +273,7 @@ private MeasureReport evaluate(Measure measure, Context context, List p HashMap resources = new HashMap<>(); HashMap> codeToResourceMap = new HashMap<>(); + Set evaluatedResourcesList = new HashSet<>(); MeasureScoring measureScoring = MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode()); if (measureScoring == null) { @@ -544,26 +539,21 @@ private MeasureReport evaluate(Measure measure, Context context, List p } if (!list.isEmpty()) { - list.setId(UUID.randomUUID().toString()); + list.setId("List/" + UUID.randomUUID().toString()); list.setTitle(key); resources.put(list.getId(), list); + list.getEntry().forEach(listResource -> evaluatedResourcesList.add(listResource.getItem().getReference())); } } - if (!resources.isEmpty()) { + if (!evaluatedResourcesList.isEmpty()) { List evaluatedResourceIds = new ArrayList<>(); - resources.forEach((key, resource) -> { - evaluatedResourceIds.add(new Reference(resource.getId())); + evaluatedResourcesList.forEach((resource) -> { + evaluatedResourceIds.add(new Reference(resource)); }); report.setEvaluatedResource(evaluatedResourceIds); - /* - FhirMeasureBundler bundler = new FhirMeasureBundler(); - org.hl7.fhir.r4.model.Bundle evaluatedResources = bundler.bundle(resources.values()); - evaluatedResources.setId(UUID.randomUUID().toString()); - report.setEvaluatedResource(Collections.singletonList(new Reference(evaluatedResources.getId()))); - report.addContained(evaluatedResources); - */ } + if (sdeAccumulators.size() > 0) { report = processAccumulators(report, sdeAccumulators, sde, isSingle, patients); } From e83d4f96b55e16e174f2ca55bc36db0a4062c3ec Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Mon, 5 Oct 2020 21:45:39 -0600 Subject: [PATCH 177/198] Issue #200 - Expose ruler version via CapabilityStatement --- .../CqfRulerJpaConformanceProviderDstu3.java | 36 ++++++++++++++++ .../cqf/dstu3/providers/OAuthProvider.java | 3 +- .../cqf/dstu3/servlet/BaseServlet.java | 2 +- .../CqfRulerJpaConformanceProviderR4.java | 41 +++++++++++++++++++ .../cqf/r4/providers/OAuthProvider.java | 3 +- .../opencds/cqf/r4/servlet/BaseServlet.java | 2 +- 6 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java create mode 100644 r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java new file mode 100644 index 000000000..f1bc80505 --- /dev/null +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java @@ -0,0 +1,36 @@ +package org.opencds.cqf.dstu3.providers; + +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.dstu3.model.*; +import org.opencds.cqf.dstu3.servlet.BaseServlet; + +import javax.servlet.http.HttpServletRequest; + +public class CqfRulerJpaConformanceProviderDstu3 extends JpaConformanceProviderDstu3 { + + public CqfRulerJpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { + super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); + } + + @Metadata + @Override + public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { + CapabilityStatement retVal; + retVal = super.getServerConformance(theRequest, theRequestDetails); + + Extension softwareModuleExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/capabilitystatement-softwareModule"); + Extension softwareModuleNameExtension = new Extension().setUrl("name").setValue(new StringType("CQF Ruler FHIR DSTU3 Server")); + Extension softwareModuleVersionExtension = new Extension().setUrl("version").setValue(new StringType(BaseServlet.class.getPackage().getImplementationVersion())); + softwareModuleExtension.addExtension(softwareModuleNameExtension); + softwareModuleExtension.addExtension(softwareModuleVersionExtension); + retVal.getSoftware().addExtension(softwareModuleExtension); + + return retVal; + } +} \ No newline at end of file diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java index 6ed12c9b6..d2c1438cc 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java @@ -9,10 +9,11 @@ import ca.uhn.fhir.rest.server.RestfulServer; import org.hl7.fhir.dstu3.model.*; import org.opencds.cqf.common.config.HapiProperties; +import org.opencds.cqf.dstu3.servlet.BaseServlet; import javax.servlet.http.HttpServletRequest; -public class OAuthProvider extends JpaConformanceProviderDstu3 { +public class OAuthProvider extends CqfRulerJpaConformanceProviderDstu3 { public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index d982479fc..d71173c17 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -94,7 +94,7 @@ protected void initialize() throws ServletException { this.setServerConformanceProvider(oauthProvider); }else { - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, + JpaConformanceProviderDstu3 confProvider = new CqfRulerJpaConformanceProviderDstu3(this, systemDao, appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); setServerConformanceProvider(confProvider); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java new file mode 100644 index 000000000..4c679520d --- /dev/null +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java @@ -0,0 +1,41 @@ +package org.opencds.cqf.r4.providers; + +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServer; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.Meta; +import org.opencds.cqf.r4.servlet.BaseServlet; + + +import javax.servlet.http.HttpServletRequest; + +public class CqfRulerJpaConformanceProviderR4 extends JpaConformanceProviderR4 { + + public CqfRulerJpaConformanceProviderR4(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { + super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); + } + + @Metadata + @Override + public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { + CapabilityStatement retVal; + retVal = super.getServerConformance(theRequest, theRequestDetails); + + Extension softwareModuleExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/capabilitystatement-softwareModule"); + Extension softwareModuleNameExtension = new Extension().setUrl("name").setValue(new StringType("CQF Ruler FHIR R4 Server")); + Extension softwareModuleVersionExtension = new Extension().setUrl("version").setValue(new StringType(BaseServlet.class.getPackage().getImplementationVersion())); + softwareModuleExtension.addExtension(softwareModuleNameExtension); + softwareModuleExtension.addExtension(softwareModuleVersionExtension); + retVal.getSoftware().addExtension(softwareModuleExtension); + + return retVal; + } +} \ No newline at end of file diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index 3abfc24b0..2a124c70f 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -18,8 +18,9 @@ import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.RestfulServer; +import org.opencds.cqf.r4.servlet.BaseServlet; -public class OAuthProvider extends JpaConformanceProviderR4 { +public class OAuthProvider extends CqfRulerJpaConformanceProviderR4 { /** * This class is NOT designed to be a real OAuth provider. * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 2803b9ae3..8a3ba1ca0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -94,7 +94,7 @@ protected void initialize() throws ServletException { this.registerProvider(oauthProvider); this.setServerConformanceProvider(oauthProvider); }else { - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, + JpaConformanceProviderR4 confProvider = new CqfRulerJpaConformanceProviderR4(this, systemDao, appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); setServerConformanceProvider(confProvider); From db1e3d41271680996fd14fdac40a3ec7e7c60f27 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Fri, 2 Oct 2020 21:32:56 -0600 Subject: [PATCH 178/198] Springify all the providers and cds-hooks servlet --- .../cqf/common/config/FhirServerConfig.java | 21 ++- .../retrieve/JpaFhirRetrieveProvider.java | 5 + dstu3/pom.xml | 6 + .../dstu3/config/FhirServerConfigDstu3.java | 49 ++++++ .../cqf/dstu3/evaluation/ProviderFactory.java | 5 + .../ActivityDefinitionApplyProvider.java | 5 + .../providers/ApplyCqlOperationProvider.java | 5 + .../providers/CacheValueSetsProvider.java | 5 + .../providers/CodeSystemUpdateProvider.java | 5 + .../CqfRulerJpaConformanceProviderDstu3.java | 19 +-- .../dstu3/providers/CqlExecutionProvider.java | 5 + .../providers/DataRequirementsProvider.java | 2 + .../cqf/dstu3/providers/HQMFProvider.java | 2 + .../providers/JpaTerminologyProvider.java | 5 + .../providers/LibraryOperationsProvider.java | 4 + .../providers/MeasureOperationsProvider.java | 4 + .../cqf/dstu3/providers/OAuthProvider.java | 30 ++-- .../dstu3/providers/ObservationProvider.java | 5 + .../PlanDefinitionApplyProvider.java | 4 + .../providers/QuestionnaireProvider.java | 6 + .../cqf/dstu3/servlet/BaseServlet.java | 147 +++--------------- .../cqf/dstu3/servlet/CdsHooksServlet.java | 49 +++--- pom.xml | 130 ++++++++-------- .../cqf/r4/config/FhirServerConfigR4.java | 49 ++++++ .../cqf/r4/evaluation/MeasureEvaluation.java | 1 + .../cqf/r4/evaluation/ProviderFactory.java | 5 + .../ActivityDefinitionApplyProvider.java | 5 + .../providers/ApplyCqlOperationProvider.java | 5 + .../r4/providers/CacheValueSetsProvider.java | 5 + .../providers/CodeSystemUpdateProvider.java | 5 + .../CqfRulerJpaConformanceProviderR4.java | 23 +-- .../r4/providers/CqlExecutionProvider.java | 5 + .../providers/DataRequirementsProvider.java | 2 + .../cqf/r4/providers/HQMFProvider.java | 2 + .../r4/providers/JpaTerminologyProvider.java | 5 + .../providers/LibraryOperationsProvider.java | 6 +- .../providers/MeasureOperationsProvider.java | 4 + .../cqf/r4/providers/OAuthProvider.java | 25 +-- .../cqf/r4/providers/ObservationProvider.java | 5 + .../PlanDefinitionApplyProvider.java | 4 + .../r4/providers/QuestionnaireProvider.java | 6 + .../opencds/cqf/r4/servlet/BaseServlet.java | 147 +++--------------- .../cqf/r4/servlet/CdsHooksServlet.java | 49 +++--- 43 files changed, 448 insertions(+), 428 deletions(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java index 74c4c4a1d..6d03856a5 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java +++ b/common/src/main/java/org/opencds/cqf/common/config/FhirServerConfig.java @@ -4,11 +4,15 @@ import java.sql.Driver; import org.apache.commons.dbcp2.BasicDataSource; +import org.opencds.cqf.cds.providers.ProviderConfiguration; +import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -17,6 +21,7 @@ @Configuration @EnableTransactionManagement +@ComponentScan(basePackages = "org.opencds.cqf.common") public class FhirServerConfig { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfig.class); @@ -123,5 +128,19 @@ public ResponseHighlighterInterceptor responseHighlighterInterceptor() { @Bean public PartitionSettings partitionSettings() { return new PartitionSettings(); - } + } + + @Bean() + public ProviderConfiguration providerConfiguration() { + return new ProviderConfiguration( + HapiProperties.getCdsHooksFhirServerExpandValueSets(), + HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), + HapiProperties.getCdsHooksFhirServerSearchStyleEnum(), + HapiProperties.getCdsHooksPreFetchMaxUriLength()); + } + + @Bean() + public SearchParameterResolver searchParameterResolver(FhirContext fhirContext) { + return new SearchParameterResolver(fhirContext); + } } diff --git a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java index 2719bf68f..4c082ecb1 100644 --- a/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java +++ b/common/src/main/java/org/opencds/cqf/common/retrieve/JpaFhirRetrieveProvider.java @@ -9,24 +9,29 @@ import java.util.Map; import java.util.stream.Collectors; +import javax.inject.Inject; + import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.cql.engine.fhir.retrieve.SearchParamFhirRetrieveProvider; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterMap; import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.IBundleProvider; +@Component public class JpaFhirRetrieveProvider extends SearchParamFhirRetrieveProvider { private static final Logger logger = LoggerFactory.getLogger(JpaFhirRetrieveProvider.class); DaoRegistry registry; + @Inject public JpaFhirRetrieveProvider(DaoRegistry registry, SearchParameterResolver searchParameterResolver) { super(searchParameterResolver); this.registry = registry; diff --git a/dstu3/pom.xml b/dstu3/pom.xml index 88aaed16c..c489dab2f 100644 --- a/dstu3/pom.xml +++ b/dstu3/pom.xml @@ -18,5 +18,11 @@ common ${project.version} + + + javax + javaee-api + 8.0.1 + diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java index a19151e6f..8ac0b35d4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/config/FhirServerConfigDstu3.java @@ -1,11 +1,26 @@ package org.opencds.cqf.dstu3.config; +import java.util.ArrayList; +import java.util.List; + import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.opencds.cqf.common.config.HapiProperties; +import org.opencds.cqf.dstu3.providers.ActivityDefinitionApplyProvider; +import org.opencds.cqf.dstu3.providers.ApplyCqlOperationProvider; +import org.opencds.cqf.dstu3.providers.CacheValueSetsProvider; +import org.opencds.cqf.dstu3.providers.CodeSystemUpdateProvider; +import org.opencds.cqf.dstu3.providers.CqlExecutionProvider; +import org.opencds.cqf.dstu3.providers.LibraryOperationsProvider; +import org.opencds.cqf.dstu3.providers.MeasureOperationsProvider; +import org.opencds.cqf.dstu3.providers.ObservationProvider; +import org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider; +import org.opencds.cqf.dstu3.providers.QuestionnaireProvider; +import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -17,6 +32,7 @@ import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; @Configuration +@ComponentScan(basePackages = "org.opencds.cqf.dstu3") public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 { protected final DataSource myDataSource; @@ -71,4 +87,37 @@ public JpaTransactionManager transactionManager(EntityManagerFactory entityManag retVal.setEntityManagerFactory(entityManagerFactory); return retVal; } + + @Bean(name= "myOperationProvidersDstu3") + public List> operationProviders() { + // TODO: Make this registry dynamic + // Scan an interface, create a plugin-api, etc. + // Basically, anything that's not included in base HAPI and implements an operation. + List> classes = new ArrayList<>(); + classes.add(ActivityDefinitionApplyProvider.class); + classes.add(ApplyCqlOperationProvider.class); + classes.add(CacheValueSetsProvider.class); + classes.add(CodeSystemUpdateProvider.class); + classes.add(CqlExecutionProvider.class); + classes.add(LibraryOperationsProvider.class); + classes.add(MeasureOperationsProvider.class); + classes.add(PlanDefinitionApplyProvider.class); + + // The plugin API will need to a way to determine whether a particular + // service should be registered + if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { + classes.add(QuestionnaireProvider.class); + }; + + if (HapiProperties.getObservationTransformEnabled()) { + classes.add(ObservationProvider.class); + } + + return classes; + } + + @Bean() + public NarrativeProvider narrativeProvider() { + return new NarrativeProvider(); + } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java index 67afaa133..5d5387b32 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/evaluation/ProviderFactory.java @@ -1,5 +1,7 @@ package org.opencds.cqf.dstu3.evaluation; +import javax.inject.Inject; + import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.ClientHelper; import org.opencds.cqf.common.providers.Dstu3ApelonFhirTerminologyProvider; @@ -10,6 +12,7 @@ import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.cql.engine.fhir.terminology.Dstu3FhirTerminologyProvider; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; @@ -18,6 +21,7 @@ // This class is a relatively dumb factory for data providers. It supports only // creating JPA providers for FHIR and only basic auth for terminology +@Component public class ProviderFactory implements EvaluationProviderFactory { DaoRegistry registry; @@ -25,6 +29,7 @@ public class ProviderFactory implements EvaluationProviderFactory { FhirContext fhirContext; ISearchParamRegistry searchParamRegistry; + @Inject public ProviderFactory(FhirContext fhirContext, DaoRegistry registry, TerminologyProvider defaultTerminologyProvider) { this.defaultTerminologyProvider = defaultTerminologyProvider; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java index a015d2e8c..2aab800e4 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ActivityDefinitionApplyProvider.java @@ -4,6 +4,8 @@ import java.util.Collections; import java.util.List; +import javax.inject.Inject; + import org.hl7.fhir.dstu3.model.ActivityDefinition; import org.hl7.fhir.dstu3.model.Attachment; import org.hl7.fhir.dstu3.model.BooleanType; @@ -24,6 +26,7 @@ import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.dstu3.helpers.Helper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -35,12 +38,14 @@ /** * Created by Bryn on 1/16/2017. */ +@Component public class ActivityDefinitionApplyProvider { private CqlExecutionProvider executionProvider; private ModelResolver modelResolver; private IFhirResourceDao activityDefinitionDao; + @Inject public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProvider executionProvider, IFhirResourceDao activityDefinitionDao) { this.modelResolver = new Dstu3FhirModelResolver(); diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java index d82bba842..e0520cd6e 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ApplyCqlOperationProvider.java @@ -5,6 +5,8 @@ import java.util.Date; import java.util.List; +import javax.inject.Inject; + import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; @@ -26,6 +28,7 @@ import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -33,12 +36,14 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +@Component public class ApplyCqlOperationProvider { private EvaluationProviderFactory providerFactory; private IFhirResourceDao bundleDao; FhirContext context; + @Inject public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao, FhirContext context) { this.providerFactory = providerFactory; this.bundleDao = bundleDao; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java index 563e327dc..e9d822cf5 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CacheValueSetsProvider.java @@ -4,6 +4,8 @@ import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; + import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Endpoint; import org.hl7.fhir.dstu3.model.IdType; @@ -11,6 +13,7 @@ import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.ValueSet; import org.opencds.cqf.dstu3.helpers.Helper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; @@ -24,11 +27,13 @@ import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; +@Component public class CacheValueSetsProvider { private IFhirSystemDao systemDao; private IFhirResourceDao endpointDao; + @Inject public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { this.systemDao = systemDao; this.endpointDao = endpointDao; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java index e67461477..299fa1eaa 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CodeSystemUpdateProvider.java @@ -9,6 +9,8 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.inject.Inject; + import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.Enumerations; import org.hl7.fhir.dstu3.model.IdType; @@ -16,6 +18,7 @@ import org.hl7.fhir.dstu3.model.ValueSet; import org.opencds.cqf.dstu3.builders.OperationOutcomeBuilder; import org.opencds.cqf.dstu3.builders.RandomIdBuilder; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -24,10 +27,12 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; +@Component public class CodeSystemUpdateProvider { private IFhirResourceDao valueSetDao; private IFhirResourceDao codeSystemDao; + @Inject public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, IFhirResourceDao codeSystemDao) { this.valueSetDao = valueSetDao; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java index f1bc80505..0a9d9e22b 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqfRulerJpaConformanceProviderDstu3.java @@ -1,23 +1,18 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; +import javax.servlet.http.HttpServletRequest; + +import org.hl7.fhir.dstu3.model.CapabilityStatement; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.StringType; +import org.opencds.cqf.dstu3.servlet.BaseServlet; + import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServer; -import org.hl7.fhir.dstu3.model.*; -import org.opencds.cqf.dstu3.servlet.BaseServlet; - -import javax.servlet.http.HttpServletRequest; public class CqfRulerJpaConformanceProviderDstu3 extends JpaConformanceProviderDstu3 { - public CqfRulerJpaConformanceProviderDstu3(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { - super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); - } - @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java index c5a1f1c70..d73e18415 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/CqlExecutionProvider.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Map; +import javax.inject.Inject; + import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.CqlTranslatorException; @@ -37,6 +39,7 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.dstu3.helpers.FhirMeasureBundler; import org.opencds.cqf.dstu3.helpers.LibraryHelper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; @@ -44,10 +47,12 @@ /** * Created by Bryn on 1/16/2017. */ +@Component public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResolutionProvider; + @Inject public CqlExecutionProvider(LibraryResolutionProvider libraryResolutionProvider, EvaluationProviderFactory providerFactory) { this.providerFactory = providerFactory; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java index 017050d28..4853a5176 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/DataRequirementsProvider.java @@ -67,8 +67,10 @@ import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; import org.opencds.cqf.tooling.measure.stu3.TerminologyRef; import org.opencds.cqf.tooling.measure.stu3.TerminologyRef.TerminologyRefType; +import org.springframework.stereotype.Component; import org.opencds.cqf.tooling.measure.stu3.VersionedTerminologyRef; +@Component public class DataRequirementsProvider { // For creating the CQF measure we need to: diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java index 56b4eb8e4..948955d04 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/HQMFProvider.java @@ -47,11 +47,13 @@ import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; import org.opencds.cqf.tooling.measure.stu3.TerminologyRef; import org.opencds.cqf.tooling.measure.stu3.TerminologyRef.TerminologyRefType; +import org.springframework.stereotype.Component; import org.w3c.dom.Document; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; +@Component public class HQMFProvider { private static Map measureTypeValueSetMap = new HashMap() { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java index 237c07c07..40f833043 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/JpaTerminologyProvider.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent; @@ -11,6 +13,7 @@ import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -26,11 +29,13 @@ /** * Created by Christopher Schuler on 7/17/2017. */ +@Component public class JpaTerminologyProvider implements TerminologyProvider { private ITermReadSvcDstu3 terminologySvcDstu3; private ValueSetResourceProvider valueSetResourceProvider; + @Inject public JpaTerminologyProvider(ITermReadSvcDstu3 terminologySvcDstu3, FhirContext context, ValueSetResourceProvider valueSetResourceProvider) { this.terminologySvcDstu3 = terminologySvcDstu3; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java index 41ed8dcb2..ca303c83f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/LibraryOperationsProvider.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Objects; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import org.cqframework.cql.cql2elm.CqlTranslator; @@ -19,6 +20,7 @@ import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.common.providers.LibrarySourceProvider; import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.rp.dstu3.LibraryResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -32,12 +34,14 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; +@Component public class LibraryOperationsProvider implements org.opencds.cqf.common.providers.LibraryResolutionProvider { private NarrativeProvider narrativeProvider; private DataRequirementsProvider dataRequirementsProvider; private LibraryResourceProvider libraryResourceProvider; + @Inject public LibraryOperationsProvider(LibraryResourceProvider libraryResourceProvider, NarrativeProvider narrativeProvider) { this.narrativeProvider = narrativeProvider; diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java index 33f1cf6d4..e25a54f93 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/MeasureOperationsProvider.java @@ -8,6 +8,7 @@ import java.util.UUID; import java.util.stream.Collectors; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; @@ -48,6 +49,7 @@ import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; @@ -62,6 +64,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +@Component public class MeasureOperationsProvider { private NarrativeProvider narrativeProvider; @@ -75,6 +78,7 @@ public class MeasureOperationsProvider { private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class); + @Inject public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider, LibraryResolutionProvider libraryResolutionProvider, diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java index d2c1438cc..2bb5073db 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/OAuthProvider.java @@ -1,23 +1,25 @@ package org.opencds.cqf.dstu3.providers; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.rest.annotation.Metadata; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServer; -import org.hl7.fhir.dstu3.model.*; +import javax.servlet.http.HttpServletRequest; + +import org.hl7.fhir.dstu3.model.CapabilityStatement; +import org.hl7.fhir.dstu3.model.CodeableConcept; +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.UriType; import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.dstu3.servlet.BaseServlet; +import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.api.server.RequestDetails; +/** + * This class is NOT designed to be a real OAuth provider. + * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. + * It should only get instantiated if hapi.properties has oauth.enabled set to true. + */ +@Component public class OAuthProvider extends CqfRulerJpaConformanceProviderDstu3 { - public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { - super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); - } - @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java index 395ef38b0..6b2794717 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/ObservationProvider.java @@ -10,17 +10,22 @@ import org.hl7.fhir.dstu3.model.ConceptMap; import org.hl7.fhir.dstu3.model.Observation; import org.opencds.cqf.common.config.HapiProperties; +import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; +import javax.inject.Inject; + import static org.opencds.cqf.common.helpers.ClientHelper.getClient; +@Component public class ObservationProvider { private FhirContext fhirContext; + @Inject public ObservationProvider(FhirContext fhirContext){ this.fhirContext = fhirContext; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java index 6712aeced..9a659ae31 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/PlanDefinitionApplyProvider.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.UUID; +import javax.inject.Inject; import javax.xml.bind.JAXBException; import org.hl7.fhir.dstu3.model.ActivityDefinition; @@ -38,6 +39,7 @@ import org.opencds.cqf.dstu3.builders.RequestGroupBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -46,6 +48,7 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +@Component public class PlanDefinitionApplyProvider { private CqlExecutionProvider executionProvider; @@ -59,6 +62,7 @@ public class PlanDefinitionApplyProvider { private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); + @Inject public PlanDefinitionApplyProvider(FhirContext fhirContext, ActivityDefinitionApplyProvider activityDefinitionApplyProvider, IFhirResourceDao planDefinitionDao, diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index a135d4b77..41e64db2a 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -6,15 +6,21 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.dstu3.model.*; import org.opencds.cqf.common.config.HapiProperties; +import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Date; +import javax.inject.Inject; + import static org.opencds.cqf.common.helpers.ClientHelper.getClient; +@Component public class QuestionnaireProvider { private FhirContext fhirContext; + + @Inject public QuestionnaireProvider(FhirContext fhirContext){ this.fhirContext = fhirContext; } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index d71173c17..0545e5b01 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -1,26 +1,15 @@ package org.opencds.cqf.dstu3.servlet; import java.util.Arrays; +import java.util.List; import javax.servlet.ServletException; -import org.hl7.fhir.dstu3.model.ActivityDefinition; import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.CodeSystem; -import org.hl7.fhir.dstu3.model.Endpoint; -import org.hl7.fhir.dstu3.model.Library; -import org.hl7.fhir.dstu3.model.Measure; import org.hl7.fhir.dstu3.model.Meta; -import org.hl7.fhir.dstu3.model.PlanDefinition; -import org.hl7.fhir.dstu3.model.ValueSet; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; -import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; -import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; -import org.opencds.cqf.dstu3.evaluation.ProviderFactory; -import org.opencds.cqf.dstu3.providers.*; -import org.opencds.cqf.tooling.library.stu3.NarrativeProvider; +import org.opencds.cqf.dstu3.providers.CqfRulerJpaConformanceProviderDstu3; +import org.opencds.cqf.dstu3.providers.OAuthProvider; import org.opencds.cqf.tooling.measure.stu3.CodeTerminologyRef; import org.opencds.cqf.tooling.measure.stu3.CqfMeasure; import org.opencds.cqf.tooling.measure.stu3.PopulationCriteriaMap; @@ -31,19 +20,13 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.rp.dstu3.LibraryResourceProvider; -import ca.uhn.fhir.jpa.rp.dstu3.MeasureResourceProvider; -import ca.uhn.fhir.jpa.rp.dstu3.ValueSetResourceProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; @@ -79,6 +62,10 @@ protected void initialize() throws ServletException { IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); this.registry = appCtx.getBean(DaoRegistry.class); + DaoConfig daoConfig = appCtx.getBean(DaoConfig.class); + + ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class); + // System and Resource Providers Object systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); registerProvider(systemProvider); @@ -87,27 +74,26 @@ protected void initialize() throws ServletException { ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); - if(HapiProperties.getOAuthEnabled()) { - OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); - this.registerProvider(oauthProvider); - this.setServerConformanceProvider(oauthProvider); - }else { + List> operationsProviders = appCtx.getBean("myOperationProvidersDstu3", List.class); + operationsProviders.forEach(x -> registerProvider(appCtx.getBean(x))); - JpaConformanceProviderDstu3 confProvider = new CqfRulerJpaConformanceProviderDstu3(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); - confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); - setServerConformanceProvider(confProvider); + if(HapiProperties.getOAuthEnabled()) { + OAuthProvider oauthProvider = new OAuthProvider(); + oauthProvider.setDaoConfig(daoConfig); + oauthProvider.setSystemDao(systemDao); + oauthProvider.setSearchParamRegistry(searchParamRegistry); + oauthProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); + this.setServerConformanceProvider(oauthProvider); + }else { + + JpaConformanceProviderDstu3 confProvider = new CqfRulerJpaConformanceProviderDstu3(); + confProvider.setDaoConfig(daoConfig); + confProvider.setSystemDao(systemDao); + confProvider.setSearchParamRegistry(searchParamRegistry); + confProvider.setImplementationDescription("CQF Ruler FHIR DSTU3 Server"); + this.setServerConformanceProvider(confProvider); } - JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( - appCtx.getBean("terminologyService", ITermReadSvcDstu3.class), getFhirContext(), - (ValueSetResourceProvider) this.getResourceProvider(ValueSet.class)); - EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, - localSystemTerminologyProvider); - - resolveProviders(providerFactory, localSystemTerminologyProvider, this.registry); - /* * ETag Support */ @@ -180,87 +166,4 @@ protected void initialize() throws ServletException { registerInterceptor(interceptor); } } - - protected NarrativeProvider getNarrativeProvider() { - return new NarrativeProvider(); - } - - // Since resource provider resolution not lazy, the providers here must be - // resolved in the correct - // order of dependencies. - @SuppressWarnings("unchecked") - private void resolveProviders(EvaluationProviderFactory providerFactory, - JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { - NarrativeProvider narrativeProvider = this.getNarrativeProvider(); - HQMFProvider hqmfProvider = new HQMFProvider(); - - // Code System Update - CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), - this.getDao(CodeSystem.class)); - this.registerProvider(csUpdate); - - // Cache Value Sets - CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), - this.getDao(Endpoint.class)); - this.registerProvider(cvs); - - // Library processing - LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider( - (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider); - this.registerProvider(libraryProvider); - - // CQL Execution - CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory); - this.registerProvider(cql); - - // Bundle processing - ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, - this.getDao(Bundle.class), this.fhirContext); - this.registerProvider(bundleProvider); - - // Measure processing - MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, - narrativeProvider, hqmfProvider, libraryProvider, - (MeasureResourceProvider) this.getResourceProvider(Measure.class)); - this.registerProvider(measureProvider); - - // // ActivityDefinition processing - ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, - this.getDao(ActivityDefinition.class)); - this.registerProvider(actDefProvider); - - JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, - new SearchParameterResolver(this.fhirContext)); - - // PlanDefinition processing - PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, - this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); - this.registerProvider(planDefProvider); - - CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); - CdsHooksServlet.setLibraryResolutionProvider(libraryProvider); - CdsHooksServlet.setSystemTerminologyProvider(localSystemTerminologyProvider); - CdsHooksServlet.setSystemRetrieveProvider(localSystemRetrieveProvider); - - // QuestionnaireResponse processing - if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { - QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); - this.registerProvider(questionnaireProvider); - } - // Observation processing - if(HapiProperties.getObservationTransformEnabled()) { - ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); - this.registerProvider(observationProvider); - } - } - - protected IFhirResourceDao getDao(Class clazz) { - return this.registry.getResourceDao(clazz); - } - - @SuppressWarnings("unchecked") - protected BaseJpaResourceProvider getResourceProvider(Class clazz) { - return (BaseJpaResourceProvider) this.getResourceProviders().stream() - .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); - } } diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java index 8b21c4476..3b93d6dd3 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/CdsHooksServlet.java @@ -50,6 +50,7 @@ import org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -61,44 +62,32 @@ public class CdsHooksServlet extends HttpServlet { private FhirVersionEnum version = FhirVersionEnum.DSTU3; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); - private static PlanDefinitionApplyProvider planDefinitionProvider; + private org.opencds.cqf.dstu3.providers.PlanDefinitionApplyProvider planDefinitionProvider; - private static LibraryResolutionProvider libraryResolutionProvider; + private LibraryResolutionProvider libraryResolutionProvider; - private static JpaFhirRetrieveProvider fhirRetrieveProvider; + private JpaFhirRetrieveProvider fhirRetrieveProvider; - private static JpaTerminologyProvider jpaTerminologyProvider; + private org.opencds.cqf.dstu3.providers.JpaTerminologyProvider jpaTerminologyProvider; private ProviderConfiguration providerConfiguration; - public ProviderConfiguration getProviderConfiguration() { - if (providerConfiguration == null) { - providerConfiguration = new ProviderConfiguration( - HapiProperties.getCdsHooksFhirServerExpandValueSets(), - HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), - HapiProperties.getCdsHooksFhirServerSearchStyleEnum(), - HapiProperties.getCdsHooksPreFetchMaxUriLength()); - } - - return providerConfiguration; - } - - // TODO: There's probably a way to wire this all up using Spring - public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { - CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; - } - - public static void setLibraryResolutionProvider( - LibraryResolutionProvider libraryResolutionProvider) { - CdsHooksServlet.libraryResolutionProvider = libraryResolutionProvider; - } - - public static void setSystemRetrieveProvider(JpaFhirRetrieveProvider fhirRetrieveProvider) { - CdsHooksServlet.fhirRetrieveProvider = fhirRetrieveProvider; + @SuppressWarnings("unchecked") + @Override + public void init() { + // System level providers + ApplicationContext appCtx = (ApplicationContext) getServletContext() + .getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); + + this.providerConfiguration = appCtx.getBean(ProviderConfiguration.class); + this.planDefinitionProvider = appCtx.getBean(PlanDefinitionApplyProvider.class); + this.libraryResolutionProvider = (LibraryResolutionProvider)appCtx.getBean(LibraryResolutionProvider.class); + this.fhirRetrieveProvider = appCtx.getBean(JpaFhirRetrieveProvider.class); + this.jpaTerminologyProvider = appCtx.getBean(JpaTerminologyProvider.class); } - public static void setSystemTerminologyProvider(JpaTerminologyProvider jpaTerminologyProvider) { - CdsHooksServlet.jpaTerminologyProvider = jpaTerminologyProvider; + protected ProviderConfiguration getProviderConfiguration() { + return this.providerConfiguration; } // CORS Pre-flight diff --git a/pom.xml b/pom.xml index 101ab0e47..6f23db478 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - 4.0.0 @@ -94,63 +94,63 @@ - - org.opencds.cqf.cql - evaluator.execution - ${cql-evaluator.version} - - - org.opencds.cqf - tooling - ${cqf-tooling.version} - - - org.slf4j - slf4j-log4j12 - - - - - - org.opencds.cqf.cql - engine - ${cql-engine.version} - - - org.slf4j - slf4j-log4j12 - - - - - - org.opencds.cqf.cql - engine.fhir - ${cql-engine.version} - - - org.slf4j - slf4j-log4j12 - - - - - - info.cqframework - cql-to-elm - ${cqframework.version} - - - - info.cqframework - cql-formatter - ${cqframework.version} - - - org.opencds.cqf - cds - ${cds-hooks.version} - + + org.opencds.cqf.cql + evaluator.execution + ${cql-evaluator.version} + + + org.opencds.cqf + tooling + ${cqf-tooling.version} + + + org.slf4j + slf4j-log4j12 + + + + + + org.opencds.cqf.cql + engine + ${cql-engine.version} + + + org.slf4j + slf4j-log4j12 + + + + + + org.opencds.cqf.cql + engine.fhir + ${cql-engine.version} + + + org.slf4j + slf4j-log4j12 + + + + + + info.cqframework + cql-to-elm + ${cqframework.version} + + + + info.cqframework + cql-formatter + ${cqframework.version} + + + org.opencds.cqf + cds + ${cds-hooks.version} + @@ -162,6 +162,13 @@ org.opencds.cqf.cql evaluator.execution + + + javax + javaee-api + 8.0.1 + + org.opencds.cqf tooling @@ -278,9 +285,9 @@ - org.slf4j - jcl-over-slf4j - ${slf4j.version} + org.slf4j + jcl-over-slf4j + ${slf4j.version} @@ -409,6 +416,7 @@ 1.8 1.8 + true diff --git a/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java b/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java index 7e8b3e8ac..bb79e4ded 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java +++ b/r4/src/main/java/org/opencds/cqf/r4/config/FhirServerConfigR4.java @@ -1,11 +1,26 @@ package org.opencds.cqf.r4.config; +import java.util.ArrayList; +import java.util.List; + import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import org.opencds.cqf.common.config.HapiProperties; +import org.opencds.cqf.r4.providers.ActivityDefinitionApplyProvider; +import org.opencds.cqf.r4.providers.ApplyCqlOperationProvider; +import org.opencds.cqf.r4.providers.CacheValueSetsProvider; +import org.opencds.cqf.r4.providers.CodeSystemUpdateProvider; +import org.opencds.cqf.r4.providers.CqlExecutionProvider; +import org.opencds.cqf.r4.providers.LibraryOperationsProvider; +import org.opencds.cqf.r4.providers.MeasureOperationsProvider; +import org.opencds.cqf.r4.providers.ObservationProvider; +import org.opencds.cqf.r4.providers.PlanDefinitionApplyProvider; +import org.opencds.cqf.r4.providers.QuestionnaireProvider; +import org.opencds.cqf.tooling.library.r4.NarrativeProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -17,6 +32,7 @@ import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; @Configuration +@ComponentScan(basePackages = "org.opencds.cqf.r4") public class FhirServerConfigR4 extends BaseJavaConfigR4 { protected final DataSource myDataSource; @@ -71,4 +87,37 @@ public JpaTransactionManager transactionManager(EntityManagerFactory entityManag retVal.setEntityManagerFactory(entityManagerFactory); return retVal; } + + @Bean(name= "myOperationProvidersR4") + public List> operationProviders() { + // TODO: Make this registry dynamic + // Scan an interface, create a plugin-api, etc. + // Basically, anything that's not included in base HAPI and implements an operation. + List> classes = new ArrayList<>(); + classes.add(ActivityDefinitionApplyProvider.class); + classes.add(ApplyCqlOperationProvider.class); + classes.add(CacheValueSetsProvider.class); + classes.add(CodeSystemUpdateProvider.class); + classes.add(CqlExecutionProvider.class); + classes.add(LibraryOperationsProvider.class); + classes.add(MeasureOperationsProvider.class); + classes.add(PlanDefinitionApplyProvider.class); + + // The plugin API will need to a way to determine whether a particular + // service should be registered + if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { + classes.add(QuestionnaireProvider.class); + }; + + if (HapiProperties.getObservationTransformEnabled()) { + classes.add(ObservationProvider.class); + } + + return classes; + } + + @Bean() + public NarrativeProvider narrativeProvider() { + return new NarrativeProvider(); + } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3e1a2dcd6..d795b626f 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -113,6 +113,7 @@ public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) return evaluate(measure, context, getAllPatients(), MeasureReport.MeasureReportType.SUMMARY, isSingle); } + @SuppressWarnings("unchecked") private void clearExpressionCache(Context context) { // Hack to clear expression cache // See cqf-ruler github issue #153 diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java index b60a6c509..ed0631c0a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/ProviderFactory.java @@ -1,5 +1,7 @@ package org.opencds.cqf.r4.evaluation; +import javax.inject.Inject; + import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.helpers.ClientHelper; import org.opencds.cqf.common.providers.R4ApelonFhirTerminologyProvider; @@ -10,6 +12,7 @@ import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; import org.opencds.cqf.cql.engine.fhir.terminology.R4FhirTerminologyProvider; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; @@ -17,12 +20,14 @@ // This class is a relatively dumb factory for data providers. It supports only // creating JPA providers for FHIR, and only basic auth for terminology +@Component public class ProviderFactory implements EvaluationProviderFactory { DaoRegistry registry; TerminologyProvider defaultTerminologyProvider; FhirContext fhirContext; + @Inject public ProviderFactory(FhirContext fhirContext, DaoRegistry registry, TerminologyProvider defaultTerminologyProvider) { this.defaultTerminologyProvider = defaultTerminologyProvider; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java index 672b65de1..41a7fca12 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ActivityDefinitionApplyProvider.java @@ -4,6 +4,8 @@ import java.util.Collections; import java.util.List; +import javax.inject.Inject; + import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.ActivityDefinition; import org.hl7.fhir.r4.model.Attachment; @@ -24,6 +26,7 @@ import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.r4.helpers.Helper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -35,12 +38,14 @@ /** * Created by Bryn on 1/16/2017. */ +@Component public class ActivityDefinitionApplyProvider { private CqlExecutionProvider executionProvider; private ModelResolver modelResolver; private IFhirResourceDao activityDefinitionDao; + @Inject public ActivityDefinitionApplyProvider(FhirContext fhirContext, CqlExecutionProvider executionProvider, IFhirResourceDao activityDefinitionDao) { this.modelResolver = new R4FhirModelResolver(); diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java index ec0215e11..3dc33f364 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ApplyCqlOperationProvider.java @@ -4,6 +4,8 @@ import java.util.AbstractMap; import java.util.Date; +import javax.inject.Inject; + import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.elm.execution.Library; @@ -26,6 +28,7 @@ import org.opencds.cqf.common.helpers.TranslatorHelper; import org.opencds.cqf.cql.engine.execution.Context; import org.opencds.cqf.cql.engine.runtime.DateTime; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -33,12 +36,14 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +@Component public class ApplyCqlOperationProvider { private EvaluationProviderFactory providerFactory; private IFhirResourceDao bundleDao; private FhirContext context; + @Inject public ApplyCqlOperationProvider(EvaluationProviderFactory providerFactory, IFhirResourceDao bundleDao, FhirContext context) { this.providerFactory = providerFactory; this.bundleDao = bundleDao; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java index addf35302..a39c7ea19 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CacheValueSetsProvider.java @@ -4,6 +4,8 @@ import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; + import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; @@ -11,6 +13,7 @@ import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.ValueSet; import org.opencds.cqf.r4.helpers.Helper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; @@ -24,11 +27,13 @@ import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; +@Component public class CacheValueSetsProvider { private IFhirSystemDao systemDao; private IFhirResourceDao endpointDao; + @Inject public CacheValueSetsProvider(IFhirSystemDao systemDao, IFhirResourceDao endpointDao) { this.systemDao = systemDao; this.endpointDao = endpointDao; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java index c0ddbf56d..9451ae9c6 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CodeSystemUpdateProvider.java @@ -9,6 +9,8 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.inject.Inject; + import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; @@ -16,6 +18,7 @@ import org.hl7.fhir.r4.model.ValueSet; import org.opencds.cqf.r4.builders.OperationOutcomeBuilder; import org.opencds.cqf.r4.builders.RandomIdBuilder; +import org.springframework.stereotype.Component; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -24,10 +27,12 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.UriParam; +@Component public class CodeSystemUpdateProvider { private IFhirResourceDao valueSetDao; private IFhirResourceDao codeSystemDao; + @Inject public CodeSystemUpdateProvider(IFhirResourceDao valueSetDao, IFhirResourceDao codeSystemDao) { this.valueSetDao = valueSetDao; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java index 4c679520d..80859885c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqfRulerJpaConformanceProviderR4.java @@ -1,28 +1,17 @@ package org.opencds.cqf.r4.providers; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.rest.annotation.Metadata; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServer; +import javax.servlet.http.HttpServletRequest; + +import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CapabilityStatement; -import org.hl7.fhir.r4.model.Meta; import org.opencds.cqf.r4.servlet.BaseServlet; - -import javax.servlet.http.HttpServletRequest; +import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; +import ca.uhn.fhir.rest.annotation.Metadata; +import ca.uhn.fhir.rest.api.server.RequestDetails; public class CqfRulerJpaConformanceProviderR4 extends JpaConformanceProviderR4 { - - public CqfRulerJpaConformanceProviderR4(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { - super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); - } - @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java index ac855087d..31ef27cb0 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/CqlExecutionProvider.java @@ -7,6 +7,8 @@ import java.util.List; import java.util.Map; +import javax.inject.Inject; + import org.apache.commons.lang3.tuple.Triple; import org.cqframework.cql.cql2elm.CqlTranslator; import org.cqframework.cql.cql2elm.CqlTranslatorException; @@ -37,6 +39,7 @@ import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.annotation.Operation; @@ -45,11 +48,13 @@ /** * Created by Bryn on 1/16/2017. */ +@Component public class CqlExecutionProvider { private EvaluationProviderFactory providerFactory; private LibraryResolutionProvider libraryResourceProvider; private FhirContext context; + @Inject public CqlExecutionProvider(LibraryResolutionProvider libraryResourceProvider, EvaluationProviderFactory providerFactory, FhirContext context) { this.providerFactory = providerFactory; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java index 03557ad5f..8605d7292 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/DataRequirementsProvider.java @@ -61,10 +61,12 @@ import org.opencds.cqf.tooling.measure.r4.CqfMeasure; import org.opencds.cqf.tooling.measure.r4.TerminologyRef; import org.opencds.cqf.tooling.measure.r4.TerminologyRef.TerminologyRefType; +import org.springframework.stereotype.Component; import org.opencds.cqf.tooling.measure.r4.VersionedTerminologyRef; import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.opencds.cqf.r4.helpers.LibraryHelper; +@Component public class DataRequirementsProvider { // For creating the CQF measure we need to: diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java index 4672a1544..f5fe84798 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/HQMFProvider.java @@ -46,11 +46,13 @@ import org.opencds.cqf.tooling.measure.r4.CqfMeasure; import org.opencds.cqf.tooling.measure.r4.TerminologyRef; import org.opencds.cqf.tooling.measure.r4.TerminologyRef.TerminologyRefType; +import org.springframework.stereotype.Component; import org.w3c.dom.Document; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; +@Component public class HQMFProvider { private static Map measureTypeValueSetMap = new HashMap() { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java index bcfd7b0a2..8b83fbd48 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/JpaTerminologyProvider.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.ValueSet; @@ -11,6 +13,7 @@ import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; @@ -23,11 +26,13 @@ import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +@Component public class JpaTerminologyProvider implements TerminologyProvider { private ITermReadSvcR4 terminologySvcR4; private ValueSetResourceProvider valueSetResourceProvider; + @Inject public JpaTerminologyProvider(ITermReadSvcR4 terminologySvcR4, FhirContext context, ValueSetResourceProvider valueSetResourceProvider) { this.terminologySvcR4 = terminologySvcR4; diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java index 547bffefc..ed26d31bf 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/LibraryOperationsProvider.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Objects; +import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.tuple.Pair; @@ -49,6 +50,7 @@ import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; import org.opencds.cqf.cql.evaluator.execution.provider.BundleRetrieveProvider; import org.opencds.cqf.tooling.library.r4.NarrativeProvider; +import org.springframework.stereotype.Component; import org.opencds.cqf.r4.helpers.FhirMeasureBundler; import org.opencds.cqf.r4.helpers.LibraryHelper; @@ -66,6 +68,7 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; +@Component public class LibraryOperationsProvider implements LibraryResolutionProvider { private NarrativeProvider narrativeProvider; @@ -74,6 +77,7 @@ public class LibraryOperationsProvider implements LibraryResolutionProvider libraryResolutionProvider, diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java index 2a124c70f..baf5d9d8a 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/OAuthProvider.java @@ -2,35 +2,24 @@ import javax.servlet.http.HttpServletRequest; -import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.UriType; import org.opencds.cqf.common.config.HapiProperties; +import org.springframework.stereotype.Component; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServer; -import org.opencds.cqf.r4.servlet.BaseServlet; +/** + * This class is NOT designed to be a real OAuth provider. + * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. + * It should only get instantiated if hapi.properties has oauth.enabled set to true. + */ +@Component public class OAuthProvider extends CqfRulerJpaConformanceProviderR4 { - /** - * This class is NOT designed to be a real OAuth provider. - * It is designed to provide a capability statement and to pass thru the path to the real oauth verification server. - * It should only get instantiated if hapi.properties has oauth.enabled set to true. - */ - - public OAuthProvider(RestfulServer theRestfulServer, IFhirSystemDao theSystemDao, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry) { - super(theRestfulServer, theSystemDao, theDaoConfig, theSearchParamRegistry); - } - @Metadata @Override public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) { diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java index c636cec94..83a2d6ba5 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/ObservationProvider.java @@ -10,17 +10,22 @@ import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.Observation; import org.opencds.cqf.common.config.HapiProperties; +import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; +import javax.inject.Inject; + import static org.opencds.cqf.common.helpers.ClientHelper.getClient; +@Component public class ObservationProvider { private FhirContext fhirContext; + @Inject public ObservationProvider(FhirContext fhirContext){ this.fhirContext = fhirContext; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java index 6513c67fa..3c5197217 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/PlanDefinitionApplyProvider.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.UUID; +import javax.inject.Inject; import javax.xml.bind.JAXBException; import org.hl7.fhir.exceptions.FHIRException; @@ -39,6 +40,7 @@ import org.opencds.cqf.r4.helpers.CanonicalHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; @@ -47,6 +49,7 @@ import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.OperationParam; +@Component public class PlanDefinitionApplyProvider { private CqlExecutionProvider executionProvider; @@ -60,6 +63,7 @@ public class PlanDefinitionApplyProvider { private static final Logger logger = LoggerFactory.getLogger(PlanDefinitionApplyProvider.class); + @Inject public PlanDefinitionApplyProvider(FhirContext fhirContext, ActivityDefinitionApplyProvider activityDefinitionApplyProvider, IFhirResourceDao planDefinitionDao, diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index e12a5c651..0bdbcc2b3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -6,15 +6,21 @@ import ca.uhn.fhir.rest.client.api.IGenericClient; import org.hl7.fhir.r4.model.*; import org.opencds.cqf.common.config.HapiProperties; +import org.springframework.stereotype.Component; import java.util.Collections; import java.util.Date; +import javax.inject.Inject; + import static org.opencds.cqf.common.helpers.ClientHelper.getClient; +@Component public class QuestionnaireProvider { private FhirContext fhirContext; + + @Inject public QuestionnaireProvider(FhirContext fhirContext){ this.fhirContext = fhirContext; } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index 8a3ba1ca0..d1e7e8024 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -1,55 +1,37 @@ package org.opencds.cqf.r4.servlet; import java.util.Arrays; +import java.util.List; import javax.servlet.ServletException; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.ActivityDefinition; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeSystem; -import org.hl7.fhir.r4.model.Endpoint; -import org.hl7.fhir.r4.model.Library; -import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.PlanDefinition; -import org.hl7.fhir.r4.model.ValueSet; import org.opencds.cqf.common.config.HapiProperties; -import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; -import org.opencds.cqf.common.retrieve.JpaFhirRetrieveProvider; -import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver; -import org.opencds.cqf.tooling.library.r4.NarrativeProvider; +import org.opencds.cqf.r4.providers.CqfRulerJpaConformanceProviderR4; +import org.opencds.cqf.r4.providers.OAuthProvider; import org.opencds.cqf.tooling.measure.r4.CodeTerminologyRef; import org.opencds.cqf.tooling.measure.r4.CqfMeasure; import org.opencds.cqf.tooling.measure.r4.PopulationCriteriaMap; import org.opencds.cqf.tooling.measure.r4.VersionedTerminologyRef; -import org.opencds.cqf.r4.evaluation.ProviderFactory; -import org.opencds.cqf.r4.providers.*; import org.springframework.context.ApplicationContext; import org.springframework.web.cors.CorsConfiguration; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider; +import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; -import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; -import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider; -import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; -import ca.uhn.fhir.jpa.rp.r4.ValueSetResourceProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; -import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; -import ca.uhn.fhir.jpa.api.rp.ResourceProviderFactory; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; public class BaseServlet extends RestfulServer { @@ -80,6 +62,9 @@ protected void initialize() throws ServletException { IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoR4", IFhirSystemDao.class); this.registry = appCtx.getBean(DaoRegistry.class); + DaoConfig daoConfig = appCtx.getBean(DaoConfig.class); + ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class); + // System and Resource Providers Object systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); registerProvider(systemProvider); @@ -88,27 +73,26 @@ protected void initialize() throws ServletException { ResourceProviderFactory resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); registerProviders(resourceProviders.createProviders()); + List> operationsProviders = appCtx.getBean("myOperationProvidersR4", List.class); + operationsProviders.forEach(x -> registerProvider(appCtx.getBean(x))); + if(HapiProperties.getOAuthEnabled()) { - OAuthProvider oauthProvider = new OAuthProvider(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); - this.registerProvider(oauthProvider); - this.setServerConformanceProvider(oauthProvider); - }else { - JpaConformanceProviderR4 confProvider = new CqfRulerJpaConformanceProviderR4(this, systemDao, - appCtx.getBean(DaoConfig.class), appCtx.getBean(ISearchParamRegistry.class)); - confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); - setServerConformanceProvider(confProvider); + OAuthProvider oauthProvider = new OAuthProvider(); + oauthProvider.setDaoConfig(daoConfig); + oauthProvider.setSystemDao(systemDao); + oauthProvider.setSearchParamRegistry(searchParamRegistry); + oauthProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); + this.setServerConformanceProvider(oauthProvider); + }else { + + CqfRulerJpaConformanceProviderR4 confProvider = new CqfRulerJpaConformanceProviderR4(); + confProvider.setDaoConfig(daoConfig); + confProvider.setSystemDao(systemDao); + confProvider.setSearchParamRegistry(searchParamRegistry); + confProvider.setImplementationDescription("CQF Ruler FHIR R4 Server"); + this.setServerConformanceProvider(confProvider); } - JpaTerminologyProvider localSystemTerminologyProvider = new JpaTerminologyProvider( - appCtx.getBean("terminologyService", ITermReadSvcR4.class), getFhirContext(), - (ValueSetResourceProvider) this.getResourceProvider(ValueSet.class)); - EvaluationProviderFactory providerFactory = new ProviderFactory(this.fhirContext, this.registry, localSystemTerminologyProvider); - - resolveProviders(providerFactory, localSystemTerminologyProvider, this.registry); - - // CdsHooksServlet.provider = provider; - /* * ETag Support */ @@ -184,85 +168,4 @@ protected void initialize() throws ServletException { registerInterceptor(interceptor); } } - - protected NarrativeProvider getNarrativeProvider() { - return new NarrativeProvider(); - } - - // Since resource provider resolution not lazy, the providers here must be - // resolved in the correct - // order of dependencies. - @SuppressWarnings("unchecked") - private void resolveProviders(EvaluationProviderFactory providerFactory, - JpaTerminologyProvider localSystemTerminologyProvider, DaoRegistry registry) throws ServletException { - NarrativeProvider narrativeProvider = this.getNarrativeProvider(); - HQMFProvider hqmfProvider = new HQMFProvider(); - - // Code System Update - CodeSystemUpdateProvider csUpdate = new CodeSystemUpdateProvider(this.getDao(ValueSet.class), this.getDao(CodeSystem.class)); - this.registerProvider(csUpdate); - - // Cache Value Sets - CacheValueSetsProvider cvs = new CacheValueSetsProvider(this.registry.getSystemDao(), this.getDao(Endpoint.class)); - this.registerProvider(cvs); - - // Library processing - LibraryOperationsProvider libraryProvider = new LibraryOperationsProvider( - (LibraryResourceProvider) this.getResourceProvider(Library.class), narrativeProvider, registry, localSystemTerminologyProvider); - this.registerProvider(libraryProvider); - - // CQL Execution - CqlExecutionProvider cql = new CqlExecutionProvider(libraryProvider, providerFactory, this.fhirContext); - this.registerProvider(cql); - - // Bundle processing - ApplyCqlOperationProvider bundleProvider = new ApplyCqlOperationProvider(providerFactory, - this.getDao(Bundle.class), this.fhirContext); - this.registerProvider(bundleProvider); - - // Measure processing - MeasureOperationsProvider measureProvider = new MeasureOperationsProvider(this.registry, providerFactory, - narrativeProvider, hqmfProvider, libraryProvider, - (MeasureResourceProvider) this.getResourceProvider(Measure.class)); - this.registerProvider(measureProvider); - - // // ActivityDefinition processing - ActivityDefinitionApplyProvider actDefProvider = new ActivityDefinitionApplyProvider(this.fhirContext, cql, - this.getDao(ActivityDefinition.class)); - this.registerProvider(actDefProvider); - - JpaFhirRetrieveProvider localSystemRetrieveProvider = new JpaFhirRetrieveProvider(registry, - new SearchParameterResolver(this.fhirContext)); - - // PlanDefinition processing - PlanDefinitionApplyProvider planDefProvider = new PlanDefinitionApplyProvider(this.fhirContext, actDefProvider, - this.getDao(PlanDefinition.class), this.getDao(ActivityDefinition.class), cql); - this.registerProvider(planDefProvider); - - CdsHooksServlet.setPlanDefinitionProvider(planDefProvider); - CdsHooksServlet.setLibraryResolutionProvider(libraryProvider); - CdsHooksServlet.setSystemTerminologyProvider(localSystemTerminologyProvider); - CdsHooksServlet.setSystemRetrieveProvider(localSystemRetrieveProvider); - - // QuestionnaireResponse processing - if(HapiProperties.getQuestionnaireResponseExtractEnabled()) { - QuestionnaireProvider questionnaireProvider = new QuestionnaireProvider(this.fhirContext); - this.registerProvider(questionnaireProvider); - } - // Observation processing - if(HapiProperties.getObservationTransformEnabled()) { - ObservationProvider observationProvider = new ObservationProvider(this.fhirContext); - this.registerProvider(observationProvider); - } - } - - protected IFhirResourceDao getDao(Class clazz) { - return this.registry.getResourceDao(clazz); - } - - @SuppressWarnings("unchecked") - protected BaseJpaResourceProvider getResourceProvider(Class clazz) { - return (BaseJpaResourceProvider) this.getResourceProviders().stream() - .filter(x -> x.getResourceType().getSimpleName().equals(clazz.getSimpleName())).findFirst().get(); - } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java index 37489cac2..2658ef37c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/CdsHooksServlet.java @@ -50,6 +50,7 @@ import org.opencds.cqf.r4.providers.PlanDefinitionApplyProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -61,44 +62,32 @@ public class CdsHooksServlet extends HttpServlet { private FhirVersionEnum version = FhirVersionEnum.R4; private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); - private static PlanDefinitionApplyProvider planDefinitionProvider; + private org.opencds.cqf.r4.providers.PlanDefinitionApplyProvider planDefinitionProvider; - private static LibraryResolutionProvider libraryResolutionProvider; + private LibraryResolutionProvider libraryResolutionProvider; - private static JpaFhirRetrieveProvider fhirRetrieveProvider; + private JpaFhirRetrieveProvider fhirRetrieveProvider; - private static JpaTerminologyProvider jpaTerminologyProvider; + private org.opencds.cqf.r4.providers.JpaTerminologyProvider jpaTerminologyProvider; private ProviderConfiguration providerConfiguration; - public ProviderConfiguration getProviderConfiguration() { - if (providerConfiguration == null) { - providerConfiguration = new ProviderConfiguration( - HapiProperties.getCdsHooksFhirServerExpandValueSets() , - HapiProperties.getCdsHooksFhirServerMaxCodesPerQuery(), - HapiProperties.getCdsHooksFhirServerSearchStyleEnum(), - HapiProperties.getCdsHooksPreFetchMaxUriLength()); - } - - return providerConfiguration; - } - - // TODO: There's probably a way to wire this all up using Spring - public static void setPlanDefinitionProvider(PlanDefinitionApplyProvider planDefinitionProvider) { - CdsHooksServlet.planDefinitionProvider = planDefinitionProvider; - } - - public static void setLibraryResolutionProvider( - LibraryResolutionProvider libraryResolutionProvider) { - CdsHooksServlet.libraryResolutionProvider = libraryResolutionProvider; - } - - public static void setSystemRetrieveProvider(JpaFhirRetrieveProvider fhirRetrieveProvider) { - CdsHooksServlet.fhirRetrieveProvider = fhirRetrieveProvider; + @SuppressWarnings("unchecked") + @Override + public void init() { + // System level providers + ApplicationContext appCtx = (ApplicationContext) getServletContext() + .getAttribute("org.springframework.web.context.WebApplicationContext.ROOT"); + + this.providerConfiguration = appCtx.getBean(ProviderConfiguration.class); + this.planDefinitionProvider = appCtx.getBean(PlanDefinitionApplyProvider.class); + this.libraryResolutionProvider = (LibraryResolutionProvider)appCtx.getBean(LibraryResolutionProvider.class); + this.fhirRetrieveProvider = appCtx.getBean(JpaFhirRetrieveProvider.class); + this.jpaTerminologyProvider = appCtx.getBean(JpaTerminologyProvider.class); } - public static void setSystemTerminologyProvider(JpaTerminologyProvider jpaTerminologyProvider) { - CdsHooksServlet.jpaTerminologyProvider = jpaTerminologyProvider; + protected ProviderConfiguration getProviderConfiguration() { + return this.providerConfiguration; } // CORS Pre-flight From 7ece7e45061bf12406e7b95ad77c259c02b05029 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 6 Oct 2020 11:35:12 -0600 Subject: [PATCH 179/198] Add version information to jar and war --- pom.xml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pom.xml b/pom.xml index 6f23db478..5ab9b4b4e 100644 --- a/pom.xml +++ b/pom.xml @@ -419,10 +419,31 @@ true + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + true + + + + org.apache.maven.plugins maven-war-plugin 3.2.3 + + + + true + true + + + org.apache.maven.plugins From 4e0aa7c8a32c1d76078c9c7b14cb815a5d73243e Mon Sep 17 00:00:00 2001 From: Bryn Rhodes Date: Mon, 12 Oct 2020 13:09:09 -0600 Subject: [PATCH 180/198] Bump CDS Hooks component to develop snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5ab9b4b4e..ce51d9995 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 1.5.0 1.0.0 1.5.0 - 1.3.0 + 1.3.1-SNAPSHOT 1.7.30 From 4ab9f1d6b540a83216f73797ced220fe1034274e Mon Sep 17 00:00:00 2001 From: bryant Date: Mon, 19 Oct 2020 13:39:12 -0600 Subject: [PATCH 181/198] change so that the Observation code is the code for the Questionnare question --- .../providers/QuestionnaireProvider.java | 62 +++++++++++++++---- .../r4/providers/QuestionnaireProvider.java | 61 ++++++++++++++---- 2 files changed, 98 insertions(+), 25 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index a135d4b77..f9006b4ab 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -9,6 +9,8 @@ import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -26,7 +28,7 @@ public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; + return observationsFromQuestionnaireResponse; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ @@ -37,37 +39,44 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); + //dstu3 requires the questionnaire to be a reference, like below, instead of just an id like r4 + // "questionnaire": { + // "reference":"http://localhost:8080/cqf-ruler-dstu3/fhir/Questionnaire/mypain-questionnaire" + // }, + Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire().getReference()); questionnaireResponse.getItem().stream().forEach(item ->{ - processItems(item, authored, questionnaireResponse, newBundle); + processItems(item, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); return newBundle; } private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, - QuestionnaireResponse questionnaireResponse, Bundle newBundle){ + QuestionnaireResponse questionnaireResponse, Bundle newBundle, + Map questionnaireCodeMap){ if(item.hasAnswer()){ item.getAnswer().forEach(answer ->{ - Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse); + Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse, questionnaireCodeMap); if(null != newBundleEntryComponent){ newBundle.addEntry(newBundleEntryComponent); } if(answer.hasItem()){ answer.getItem().forEach(answerItem->{ - processItems(answerItem, authored, questionnaireResponse, newBundle); + processItems(answerItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } }); } if(item.hasItem()){ item.getItem().forEach(itemItem ->{ - processItems(itemItem, authored, questionnaireResponse, newBundle); + processItems(itemItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } } private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, - String linkId, Date authored, QuestionnaireResponse questionnaireResponse){ + String linkId, Date authored, QuestionnaireResponse questionnaireResponse, + Map questionnaireCodeMap){ Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -76,11 +85,7 @@ private Bundle.BundleEntryComponent createObservationFromItemAnswer(Questionnair qrCategoryCoding.setCode("survey"); qrCategoryCoding.setSystem("http://hl7.org/fhir/observation-category"); obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); - Coding qrCoding = new Coding(); - qrCoding.setSystem("http://loinc.org"); - qrCoding.setCode("74465-6"); - qrCoding.setDisplay("Questionnaire response Document"); - obs.setCode(new CodeableConcept().addCoding(qrCoding)); + obs.setCode(new CodeableConcept().addCoding((Coding) questionnaireCodeMap.get(linkId))); obs.setId("qr" + questionnaireResponse.getIdElement().getIdPart() + "." + linkId); switch(answer.getValue().fhirType()){ case "string": @@ -130,4 +135,37 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr .execute(); return outcomeBundle; } + + private Map getQuestionnaireCodeMap(String questionnaireId){ + String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); + if (null == url || url.length() < 1) { + throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); + } + String user = HapiProperties.getQuestionnaireResponseExtractUserName(); + String password = HapiProperties.getQuestionnaireResponseExtractPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireId).execute(); + + return createCodeMap(questionnaire); + } + + // this is based on "if a questionnaire.item has items then this item is a header and will not have a specific code to be used with an answer" + private Map createCodeMap(Questionnaire questionnaire){ + Map questionnaireCodeMap = new HashMap(); + questionnaire.getItem().forEach((item) ->{ + processQuestionnaireItems(item, questionnaireCodeMap); + }); + return questionnaireCodeMap; + } + + private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, Map questionnaireCodeMap){ + if(item.hasItem()){ + item.getItem().forEach(qItem -> { + processQuestionnaireItems(qItem, questionnaireCodeMap); + }); + }else{ + questionnaireCodeMap.put(item.getLinkId(), item.getCodeFirstRep()); + } + } } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index e12a5c651..7914765f9 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -9,6 +9,8 @@ import java.util.Collections; import java.util.Date; +import java.util.HashMap; +import java.util.Map; import static org.opencds.cqf.common.helpers.ClientHelper.getClient; @@ -25,8 +27,9 @@ public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return returnBundle; +// Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return observationsFromQuestionnaireResponse; +// return returnBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ @@ -37,37 +40,40 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); + Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire()); questionnaireResponse.getItem().stream().forEach(item ->{ - processItems(item, authored, questionnaireResponse, newBundle); + processItems(item, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); return newBundle; } private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, - QuestionnaireResponse questionnaireResponse, Bundle newBundle){ + QuestionnaireResponse questionnaireResponse, Bundle newBundle, + Map questionnaireCodeMap){ if(item.hasAnswer()){ item.getAnswer().forEach(answer ->{ - Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse); + Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse, questionnaireCodeMap); if(null != newBundleEntryComponent){ newBundle.addEntry(newBundleEntryComponent); } if(answer.hasItem()){ answer.getItem().forEach(answerItem->{ - processItems(answerItem, authored, questionnaireResponse, newBundle); + processItems(answerItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } }); } if(item.hasItem()){ item.getItem().forEach(itemItem ->{ - processItems(itemItem, authored, questionnaireResponse, newBundle); + processItems(itemItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } } private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, - String linkId, Date authored, QuestionnaireResponse questionnaireResponse){ + String linkId, Date authored, QuestionnaireResponse questionnaireResponse, + Map questionnaireCodeMap){ Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -76,11 +82,7 @@ private Bundle.BundleEntryComponent createObservationFromItemAnswer(Questionnair qrCategoryCoding.setCode("survey"); qrCategoryCoding.setSystem("http://hl7.org/fhir/observation-category"); obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); - Coding qrCoding = new Coding(); - qrCoding.setSystem("http://loinc.org"); - qrCoding.setCode("74465-6"); - qrCoding.setDisplay("Questionnaire response Document"); - obs.setCode(new CodeableConcept().addCoding(qrCoding)); + obs.setCode(new CodeableConcept().addCoding((Coding) questionnaireCodeMap.get(linkId))); obs.setId("qr" + questionnaireResponse.getIdElement().getIdPart() + "." + linkId); switch(answer.getValue().fhirType()){ case "string": @@ -127,4 +129,37 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr .execute(); return outcomeBundle; } + + private Map getQuestionnaireCodeMap(String questionnaireId){ + String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); + if (null == url || url.length() < 1) { + throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); + } + String user = HapiProperties.getQuestionnaireResponseExtractUserName(); + String password = HapiProperties.getQuestionnaireResponseExtractPassword(); + + IGenericClient client = getClient(fhirContext, url, user, password); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (url + "/Questionnaire/" + questionnaireId).execute(); + + return createCodeMap(questionnaire); + } + + // this is based on "if a questionnaire.item has items then this item is a header and will not have a specific code to be used with an answer" + private Map createCodeMap(Questionnaire questionnaire){ + Map questionnaireCodeMap = new HashMap(); + questionnaire.getItem().forEach((item) ->{ + processQuestionnaireItems(item, questionnaireCodeMap); + }); + return questionnaireCodeMap; + } + + private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, Map questionnaireCodeMap){ + if(item.hasItem()){ + item.getItem().forEach(qItem -> { + processQuestionnaireItems(qItem, questionnaireCodeMap); + }); + }else{ + questionnaireCodeMap.put(item.getLinkId(), item.getCodeFirstRep()); + } + } } From 3d304afaf1eabf9d0e9a739f6d8a408ee830f961 Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 20 Oct 2020 11:46:45 -0600 Subject: [PATCH 182/198] adjusted to use the full url in QR.questionnaire --- .../opencds/cqf/dstu3/providers/QuestionnaireProvider.java | 4 ---- .../org/opencds/cqf/r4/providers/QuestionnaireProvider.java | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index f9006b4ab..dab7a0143 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -39,10 +39,6 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); - //dstu3 requires the questionnaire to be a reference, like below, instead of just an id like r4 - // "questionnaire": { - // "reference":"http://localhost:8080/cqf-ruler-dstu3/fhir/Questionnaire/mypain-questionnaire" - // }, Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire().getReference()); questionnaireResponse.getItem().stream().forEach(item ->{ diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 7914765f9..0cab092d8 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -133,13 +133,13 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr private Map getQuestionnaireCodeMap(String questionnaireId){ String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { - throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); + throw new IllegalArgumentException("Unable to GET Questionnaire. No observation.endpoint defined in hapi.properties."); } String user = HapiProperties.getQuestionnaireResponseExtractUserName(); String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); - Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (url + "/Questionnaire/" + questionnaireId).execute(); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireId).execute(); return createCodeMap(questionnaire); } From ddcbb091b149b9a5c00febb15eadab920c3f3c0d Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 20 Oct 2020 11:50:28 -0600 Subject: [PATCH 183/198] renamed variable to be more accurate --- .../opencds/cqf/dstu3/providers/QuestionnaireProvider.java | 4 ++-- .../org/opencds/cqf/r4/providers/QuestionnaireProvider.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index dab7a0143..6fd9107f9 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -132,7 +132,7 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr return outcomeBundle; } - private Map getQuestionnaireCodeMap(String questionnaireId){ + private Map getQuestionnaireCodeMap(String questionnaireUrl){ String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); @@ -141,7 +141,7 @@ private Map getQuestionnaireCodeMap(String questionnaireId){ String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); - Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireId).execute(); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireUrl).execute(); return createCodeMap(questionnaire); } diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 0cab092d8..4e95cf09c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -130,7 +130,7 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr return outcomeBundle; } - private Map getQuestionnaireCodeMap(String questionnaireId){ + private Map getQuestionnaireCodeMap(String questionnaireUrl){ String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { throw new IllegalArgumentException("Unable to GET Questionnaire. No observation.endpoint defined in hapi.properties."); @@ -139,7 +139,7 @@ private Map getQuestionnaireCodeMap(String questionnaireId){ String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); - Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireId).execute(); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireUrl).execute(); return createCodeMap(questionnaire); } From cd8b76cb9cb71b37e5983470be11531ef2fdf46e Mon Sep 17 00:00:00 2001 From: bryant Date: Tue, 20 Oct 2020 11:56:18 -0600 Subject: [PATCH 184/198] removal of debug code --- .../org/opencds/cqf/r4/providers/QuestionnaireProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 4e95cf09c..f2cc5ece1 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -27,9 +27,8 @@ public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); -// Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); - return observationsFromQuestionnaireResponse; -// return returnBundle; + Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + return returnBundle; } private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ From c02f9c8e123c7c4d7d2912c9c78990879fc002e0 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 12 Nov 2020 15:14:13 -0700 Subject: [PATCH 185/198] Fix server base default address --- .../main/java/org/opencds/cqf/common/config/HapiProperties.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java index 34506e9d9..7e9740201 100644 --- a/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java +++ b/common/src/main/java/org/opencds/cqf/common/config/HapiProperties.java @@ -310,7 +310,7 @@ public static String getCorsAllowedOrigin() { } public static String getServerBase() { - return HapiProperties.getProperty(SERVER_BASE, "/cqf-ruler/baseR4"); + return HapiProperties.getProperty(SERVER_BASE, "/cqf-ruler-" + HapiProperties.getFhirVersion().toString().toLowerCase() + "/fhir"); } public static String getServerName() { From 56c35af8c6c504fc6028822b54a5bb566a2ece38 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 17 Nov 2020 11:34:20 -0700 Subject: [PATCH 186/198] Update build script --- .travis.yml | 11 ++--------- scripts/build.sh | 37 +++++++++++++++++++++++++++++++++++++ scripts/install.sh | 3 +++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100755 scripts/build.sh create mode 100755 scripts/install.sh diff --git a/.travis.yml b/.travis.yml index e2b4b3237..4e3a94ce9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,16 +9,9 @@ cache: directories: - "$HOME/.m2/repository" install: -- mvn install -U -DskipTests=true -Dmaven.javadoc.skip=true -B -V +- ./scripts/install.sh script: -- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B -P release; fi -- if ! [[ "$TRAVIS_BRANCH" =~ master* ]]; then mvn test -B; fi -before_deploy: -- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_SECRET_KEYS | base64 --decode - | $GPG_EXECUTABLE --import; fi -- if [[ "$TRAVIS_BRANCH" =~ master* ]]; then echo $GPG_OWNERTRUST | base64 --decode - | $GPG_EXECUTABLE --import-ownertrust; fi -- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin +- ./scripts/build.sh deploy: - provider: script script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 000000000..6a5a92630 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Fail if any command fails or if there are unbound variables and check syntax +set -euxo pipefail +bash -n "$0" + +# Run tests +CMD="mvn test -T 4 -B" +if [[ "$TRAVIS_BRANCH" =~ master* ]]; then CMD="$CMD -P release"; fi +eval $CMD + + +# Run Deploy +CMD="mvn deploy -DskipTests=true -T 4 -B" + +# Import maven settings +cp .travis.settings.xml $HOME/.m2/settings.xml + +# Import signing key +if [[ "$TRAVIS_BRANCH" =~ master* ]]; then + echo $GPG_SECRET_KEYS | base64 --decode| $GPG_EXECUTABLE --import; + echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust; + CMD="$CMD -P release" +fi + +eval $CMD + +# Set up for docker publish +DOCKER_IMAGE="contentgroup/cqf-ruler" +DOCKER_TAG=$TRAVIS_BRANCH +if [[ "$TRAVIS_BRANCH" =~ master* ]]; then + DOCKER_TAG="latest" +fi + +# Push image to registry +echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin +docker build . -t "$DOCKER_IMAGE:$DOCKER_TAG" && docker push "$DOCKER_IMAGE:$DOCKER_TAG" \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 000000000..16869b776 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +mvn install -U -DskipTests=true -Dmaven.javadoc.skip=true -T 4 -B -V \ No newline at end of file From 0fb1feeef30f6dcb77bc8fed289d0bc5de3e4ac1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 17 Nov 2020 11:44:20 -0700 Subject: [PATCH 187/198] Fix duplicate deploy --- .travis.yml | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e3a94ce9..9627a7d95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,28 +12,6 @@ install: - ./scripts/install.sh script: - ./scripts/build.sh -deploy: -- provider: script - script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true - && docker build . -t contentgroup/cqf-ruler:gic && docker push contentgroup/cqf-ruler:gic - cleanup: false - skip_cleanup: true - on: - branch: davinci_gic -- provider: script - script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -DskipTests=true - && docker build . -t contentgroup/cqf-ruler:develop && docker push contentgroup/cqf-ruler:develop - cleanup: false - skip_cleanup: true - on: - branch: develop -- provider: script - script: cp .travis.settings.xml $HOME/.m2/settings.xml && mvn deploy -P release - -DskipTests=true && docker build . -t contentgroup/cqf-ruler && docker push contentgroup/cqf-ruler - cleanup: false - skip_cleanup: true - on: - branch: master before_cache: - rm -rf $HOME/.m2/repository/org/opencds/cqf/parent - rm -rf $HOME/.m2/repository/org/opencds/cqf/common From 71785d4698aab3cebcc68522f739490d05cb1962 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Thu, 10 Dec 2020 08:45:22 -0800 Subject: [PATCH 188/198] Fixing issue where sdeListItem.get(0) is returning an ArrayList instead of the expected Coding. --- .../org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 3e1a2dcd6..116b90398 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -592,7 +592,11 @@ private void populateSDEAccumulators(Measure measure, Context context, Patient p break; case "ArrayList": if (((ArrayList) sdeListItem).size() > 0) { - code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + if (((ArrayList) sdeListItem).get(0).getClass().getSimpleName().equals("Coding")) { + code = ((Coding) ((ArrayList) sdeListItem).get(0)).getCode(); + } else { + continue; + } }else{ continue; } From 3cfd5854f99d9d2f09dce13fcedbe405a32891eb Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Thu, 10 Dec 2020 08:45:42 -0800 Subject: [PATCH 189/198] Not all sdeKey values have a - in it --- .../java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java index 116b90398..7134bde2e 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java +++ b/r4/src/main/java/org/opencds/cqf/r4/evaluation/MeasureEvaluation.java @@ -636,7 +636,7 @@ private MeasureReport processAccumulators(MeasureReport report, HashMap= 0 ? sdeKey.lastIndexOf('-') : 0); patients.forEach((pt)-> { pt.getExtension().forEach((ptExt) -> { if (ptExt.getUrl().contains(coreCategory)) { From 900b1238ef271534d29dc19b40d88fdc940333c3 Mon Sep 17 00:00:00 2001 From: Michael Misiaszek Date: Thu, 10 Dec 2020 10:25:27 -0800 Subject: [PATCH 190/198] Update Dockerfile Need to build the WAR's --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b6a2e2a90..336396632 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,9 @@ FROM jetty:9-jre11 USER jetty:jetty +# Build WAR +RUN mvn package + # Default database directory RUN mkdir -p /var/lib/jetty/target @@ -21,4 +24,4 @@ ENTRYPOINT [ "/docker-entrypoint-override.sh" ] # FROM runner as test-data -# COPY --chown=jetty:jetty ./target/jpaserver_derby_files /var/lib/jetty/target/jpaserver_derby_files \ No newline at end of file +# COPY --chown=jetty:jetty ./target/jpaserver_derby_files /var/lib/jetty/target/jpaserver_derby_files From a09ab3e84d2ad645af91d7dbf4b03d9b92b41ed6 Mon Sep 17 00:00:00 2001 From: Michael Misiaszek Date: Thu, 10 Dec 2020 11:42:10 -0800 Subject: [PATCH 191/198] Update Dockerfile adding build to get war files into image --- Dockerfile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 336396632..321d48f72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,24 @@ +# Build the WAR +FROM maven as Build-WAR + +RUN mkdir /build +WORKDIR /build +COPY . . +RUN mvn package -DskipTests + +# Build the image FROM jetty:9-jre11 USER jetty:jetty -# Build WAR -RUN mvn package - # Default database directory RUN mkdir -p /var/lib/jetty/target -#Default config directory +# Default config directory RUN mkdir -p /var/lib/jetty/webapps/config -COPY --chown=jetty:jetty ./cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war -COPY --chown=jetty:jetty ./cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war +COPY --chown=jetty:jetty --from=Build-WAR /build/cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war +COPY --chown=jetty:jetty --from=Build-WAR /build/cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war EXPOSE 8080 ENV SERVER_ADDRESS_DSTU3="http://localhost:8080/cqf-ruler-dstu3/fhir" From 38961e742497145f5a89055146541581c80b92f6 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Mon, 4 Jan 2021 11:39:16 -0800 Subject: [PATCH 192/198] Revert "Update Dockerfile" This reverts commit a09ab3e84d2ad645af91d7dbf4b03d9b92b41ed6. --- Dockerfile | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 321d48f72..336396632 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,18 @@ -# Build the WAR -FROM maven as Build-WAR - -RUN mkdir /build -WORKDIR /build -COPY . . -RUN mvn package -DskipTests - -# Build the image FROM jetty:9-jre11 USER jetty:jetty +# Build WAR +RUN mvn package + # Default database directory RUN mkdir -p /var/lib/jetty/target -# Default config directory +#Default config directory RUN mkdir -p /var/lib/jetty/webapps/config -COPY --chown=jetty:jetty --from=Build-WAR /build/cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war -COPY --chown=jetty:jetty --from=Build-WAR /build/cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war +COPY --chown=jetty:jetty ./cqf-ruler-dstu3/target/cqf-ruler-dstu3.war /var/lib/jetty/webapps/cqf-ruler-dstu3.war +COPY --chown=jetty:jetty ./cqf-ruler-r4/target/cqf-ruler-r4.war /var/lib/jetty/webapps/cqf-ruler-r4.war EXPOSE 8080 ENV SERVER_ADDRESS_DSTU3="http://localhost:8080/cqf-ruler-dstu3/fhir" From 4587090499edb75b5ed914fdeaf9e686af06c38f Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Mon, 4 Jan 2021 11:39:27 -0800 Subject: [PATCH 193/198] Revert "Update Dockerfile" This reverts commit 900b1238ef271534d29dc19b40d88fdc940333c3. --- Dockerfile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 336396632..b6a2e2a90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,9 +2,6 @@ FROM jetty:9-jre11 USER jetty:jetty -# Build WAR -RUN mvn package - # Default database directory RUN mkdir -p /var/lib/jetty/target @@ -24,4 +21,4 @@ ENTRYPOINT [ "/docker-entrypoint-override.sh" ] # FROM runner as test-data -# COPY --chown=jetty:jetty ./target/jpaserver_derby_files /var/lib/jetty/target/jpaserver_derby_files +# COPY --chown=jetty:jetty ./target/jpaserver_derby_files /var/lib/jetty/target/jpaserver_derby_files \ No newline at end of file From 15e5d0415b7a084b45d533196589f85981565c49 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 13 Jan 2021 22:03:52 -0700 Subject: [PATCH 194/198] Updated to 1.5.1-SNAPSHOT engine --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ce51d9995..292e54b81 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 1.3.0 1.5.0 1.0.0 - 1.5.0 + 1.5.1-SNAPSHOT 1.3.1-SNAPSHOT 1.7.30 From 89a94bc9c199c95d5bdfb213fbf96698dc5d1060 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Wed, 13 Jan 2021 22:05:51 -0700 Subject: [PATCH 195/198] update to 1.5.1 release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 292e54b81..a12970419 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 1.3.0 1.5.0 1.0.0 - 1.5.1-SNAPSHOT + 1.5.1 1.3.1-SNAPSHOT 1.7.30 From 89f23726c435f74eb10399c63e3ac6a150863dac Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Thu, 14 Jan 2021 15:56:39 -0800 Subject: [PATCH 196/198] Allowing numeric-only resource IDs --- r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java | 1 + 1 file changed, 1 insertion(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java index d1e7e8024..b4f2a9fa3 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java +++ b/r4/src/main/java/org/opencds/cqf/r4/servlet/BaseServlet.java @@ -63,6 +63,7 @@ protected void initialize() throws ServletException { this.registry = appCtx.getBean(DaoRegistry.class); DaoConfig daoConfig = appCtx.getBean(DaoConfig.class); + daoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY); ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class); // System and Resource Providers From 38d2ad6a149f609f6ba52aac1abaac35346793e7 Mon Sep 17 00:00:00 2001 From: Adam Stevenson Date: Thu, 14 Jan 2021 22:46:54 -0700 Subject: [PATCH 197/198] DSTU3 ID strategy changed to ANY --- .../main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java index 0545e5b01..44a35fc70 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/servlet/BaseServlet.java @@ -63,7 +63,7 @@ protected void initialize() throws ServletException { this.registry = appCtx.getBean(DaoRegistry.class); DaoConfig daoConfig = appCtx.getBean(DaoConfig.class); - + daoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY); ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class); // System and Resource Providers From 6ebcda9f74c64ebc98123c6e8f52dbc4b1907372 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Thu, 18 Feb 2021 17:08:26 -0700 Subject: [PATCH 198/198] Simplify branching strategy, automate releases, make build warnings errors --- .travis.yml | 16 ++- README.md | 103 +++++++++++++++--- .../providers/QuestionnaireProvider.java | 16 +-- pom.xml | 60 +++++----- .../r4/providers/QuestionnaireProvider.java | 79 +++++++------- scripts/build.sh | 13 +-- scripts/deploy.sh | 55 ++++++++++ scripts/install.sh | 6 +- scripts/maintenance.sh | 7 ++ scripts/setversion.sh | 4 + scripts/test.sh | 14 +++ 11 files changed, 272 insertions(+), 101 deletions(-) create mode 100755 scripts/deploy.sh create mode 100755 scripts/maintenance.sh create mode 100755 scripts/setversion.sh create mode 100755 scripts/test.sh diff --git a/.travis.yml b/.travis.yml index 9627a7d95..d89c31865 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ cache: install: - ./scripts/install.sh script: -- ./scripts/build.sh +- ./scripts/test.sh before_cache: - rm -rf $HOME/.m2/repository/org/opencds/cqf/parent - rm -rf $HOME/.m2/repository/org/opencds/cqf/common @@ -19,6 +19,20 @@ before_cache: - rm -rf $HOME/.m2/repository/org/opencds/cqf/r4 - rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-dstu3 - rm -rf $HOME/.m2/repository/org/opencds/cqf/cqf-ruler-r4 +after_success: +- ./scripts/deploy.sh +deploy: + provider: releases + api_key: + secure: M4rVJAVsMEnKPKas8jt+Ih/VVw1kpeyYyDP7wQxklW4qNiCRPBCx+UBSEx3J4JF4BWSNUCwER/6XGo530bDwA2IFzlEzfuAqB2FJLCT2Po9KvaWChEGs9GSRxDybkT2mqOIriEm1ePO+1eNs2yjY/qpmEGgYik8cs9TMnt1io0s6IGVHS1bl0bw0urwVti7TjgMNmHrjtVoO9pwj7REs5htXNhl7QuNOfhCawMkkLvi7Q8vUQnYBffT6llDZVWkAKfHfvHGA2HDBTB48sa9EciVgy7ORKEmhba0vinn3Vbfm4RaOoMTSjJjlBC+ojeHyYgBxoQFnMrXFO21x7vlPAbbSKviOuQUxxFr8OJ9Ijb4JXWTWNBYyTA9a2E8BQw48zbqw7vZRkw+x2x1JUZ3Vb+UW13iLASwQvGC+b/ivSZHxTZj8YNoHBnzU3fbJYZzpp28NKApVBsNI1+X07fZ0eq18bzIXy6XU5hb7aVhn4XqmUEAE1+kanxSFwq7DCC4OJpdKSaV9Dq9+9ZpJvJcsOZ9uP10E6dMWKoDPF1vME/FK43riRyZ2Nax3pqq3q21TEthOWg3LfETvk4hGYfAkzsN/+nKlnwHHlxKCJW3p7KKjN6FE9IZZ3xhL7CdK+dKA9jwfgo2Cq9QlnmpJDM9vfZhawzna9JDh0wInZh4H+oc= + file_glob: true + file: "target/**/*.jar" + skip_cleanup: true + name: CQF Ruler $TRAVIS_TAG + overwrite: true + target_commitish: $TRAVIS_COMMIT + on: + tags: true notifications: slack: - rooms: diff --git a/README.md b/README.md index 11ce9e01e..4bb721482 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,107 @@ # cqf-ruler -The CQF Ruler is an implementation of FHIR's [Clinical Reasoning Module]( +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.opencds.cqf/cqf-ruler-r4/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.opencds.cqf/cqf-ruler-r4) [![Build Status](https://www.travis-ci.com/DBCG/cqf-ruler.svg?branch=master)](https://www.travis-ci.com/DBCG/cqf-ruler) [![docker image](https://img.shields.io/docker/v/contentgroup/cqf-ruler/latest?style=flat&color=brightgreen&label=docker%20image)](https://hub.docker.com/r/contentgroup/cqf-ruler/tags) [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.fhir.org/#narrow/stream/179220-cql) + +The CQF Ruler is a set of plugins for the [HAPI FHIR Server](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) that provides an implementation of FHIR's [Clinical Reasoning Module]( http://hl7.org/fhir/clinicalreasoning-module.html) and serves as a -knowledge artifact repository and clinical decision support service. +knowledge artifact repository and a [cds-hooks](https://cds-hooks.org/) compatible clinical decision support service. It does this via integrating a number of other CQL-related projects, which are listed below. -## Usage +## Usage - - `mvn jetty:run -am --projects cqf-ruler-dstu3` - - Starts embedded Jetty server accessible at base URL `http://localhost:8080/cqf-ruler-dstu3/` +### Docker -For R4 use `mvn jetty:run -am --projects cqf-ruler-r4` +The easiest way to get started with the cqf-ruler is to pull and run the docker image - - `mvn package -DskipTests=false` - - Builds the project war file (cqf-ruler-dstu3.war and cqf-ruler-r4.war in the projects' target directory) - - Runs the test suite (tests are skipped by default) - -Visit the [wiki](https://github.com/DBCG/cqf-ruler/wiki) for more documentation. +```bash +docker pull contentgroup/cqf-ruler +docker run -p 8080:8080 contentgroup/cqf-ruler +``` + +This will make the cqf-ruler available on `http://localhost:8080/cqf-ruler-r4` -## Dependencies +Other options for deployment are listed on the [wiki](https://github.com/DBCG/cqf-ruler/wiki) for more documentation. -Before the instructions in the above "Usage" section will work, you need to -install several primary dependencies. +## Development -### Java +### Dependencies + +#### Java Go to [http://www.oracle.com/technetwork/java/javase/downloads/]( http://www.oracle.com/technetwork/java/javase/downloads/) and download the latest (version 11 or higher) JDK for your platform, and install it. -### Apache Maven 3.5.3 +#### Apache Maven 3.5.3 Go to [https://maven.apache.org](https://maven.apache.org), visit the main "Download" page, and under "Files" download the 3.5.3 binary. Then unpack that archive file and follow the installation instructions in its README.txt. The end result of this should be that the binary "mvn" is now in your path. + +### Build + +`mvn package` + +Builds the project war file (cqf-ruler-dstu3.war and cqf-ruler-r4.war in the projects' target directory) + +Visit the [wiki](https://github.com/DBCG/cqf-ruler/wiki) for more documentation. + +### Run + +To run the cqf-ruler directory from this project use: + +#### DSTU3 + +`mvn jetty:run -am --projects cqf-ruler-dstu3` + +The cqf-ruler will be available at `http://localhost:8080/cqf-ruler-dstu3/` + +#### R4 + +`mvn jetty:run -am --projects cqf-ruler-r4` + +The cqf-ruler will be available at `http://localhost:8080/cqf-ruler-r4/` + +## Commit Policy + +All new development takes place on `` branches off `master`. Once feature development on the branch is complete, the feature branch is submitted to `master` as a PR. The PR is reviewed by maintainers and regression testing by the CI build occurs. + +Changes to the `master` branch must be done through an approved PR. Delete branches after merging to keep the repository clean. + +Merges to `master` trigger a deployment to the Maven Snapshots repositories. Once ready for a release, the `master` branch is updated with the correct version number and is tagged. Tags trigger a full release to Maven Central and a corresponding release to Github. Releases SHALL NOT have a SNAPSHOT version, nor any SNAPSHOT dependencies. + +## Getting Help + +Additional documentation is on the [wiki](https://github.com/DBCG/cqf-ruler/wiki). + +Bugs and feature requests can be filed with [Github Issues](https://github.com/cqframework/cqf-ruler/issues). + +The implementers are active on the official FHIR [Zulip chat for CQL](https://chat.fhir.org/#narrow/stream/179220-cql). + +Inquires for commercial support can be directed to [info@alphora.com](info@alphora.com). + +## Related Projects + +[Clinical Quality Language](https://github.com/cqframework/clinical_quality_language) - Tooling in support of the CQL specification, including the CQL verifier/translator used in this project. + +[CQL Evaluator](https://github.com/DBCG/cql-evaluator) - Provides the CQL execution environment used by the cqf-ruler. + +[CQF Tooling](https://github.com/cqframework/cqf-tooling) - Provides several operations that the cqf-ruler exposes are services, such as $refresh-generated content. + +[CQL Support for Atom](https://atom.io/packages/language-cql) - Open source CQL IDE with syntax highlighting, linting, and local CQL evaluation. + +## License + +Copyright 2019+ Dynamic Content Group, LLC (dba Alphora) + +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](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. diff --git a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java index bd05c33c1..5f810d19f 100644 --- a/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java +++ b/dstu3/src/main/java/org/opencds/cqf/dstu3/providers/QuestionnaireProvider.java @@ -33,7 +33,7 @@ public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); - Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); + sendObservationBundle(observationsFromQuestionnaireResponse); return observationsFromQuestionnaireResponse; } @@ -45,7 +45,7 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); - Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire().getReference()); + Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire().getReference()); questionnaireResponse.getItem().stream().forEach(item ->{ processItems(item, authored, questionnaireResponse, newBundle, questionnaireCodeMap); @@ -55,7 +55,7 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, QuestionnaireResponse questionnaireResponse, Bundle newBundle, - Map questionnaireCodeMap){ + Map questionnaireCodeMap){ if(item.hasAnswer()){ item.getAnswer().forEach(answer ->{ Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse, questionnaireCodeMap); @@ -78,7 +78,7 @@ private void processItems(QuestionnaireResponse.QuestionnaireResponseItemCompone private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, String linkId, Date authored, QuestionnaireResponse questionnaireResponse, - Map questionnaireCodeMap){ + Map questionnaireCodeMap){ Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -138,7 +138,7 @@ private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalAr return outcomeBundle; } - private Map getQuestionnaireCodeMap(String questionnaireUrl){ + private Map getQuestionnaireCodeMap(String questionnaireUrl){ String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); @@ -153,15 +153,15 @@ private Map getQuestionnaireCodeMap(String questionnaireUrl){ } // this is based on "if a questionnaire.item has items then this item is a header and will not have a specific code to be used with an answer" - private Map createCodeMap(Questionnaire questionnaire){ - Map questionnaireCodeMap = new HashMap(); + private Map createCodeMap(Questionnaire questionnaire){ + Map questionnaireCodeMap = new HashMap<>(); questionnaire.getItem().forEach((item) ->{ processQuestionnaireItems(item, questionnaireCodeMap); }); return questionnaireCodeMap; } - private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, Map questionnaireCodeMap){ + private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, Map questionnaireCodeMap){ if(item.hasItem()){ item.getItem().forEach(qItem -> { processQuestionnaireItems(qItem, questionnaireCodeMap); diff --git a/pom.xml b/pom.xml index a12970419..e192f3c1a 100644 --- a/pom.xml +++ b/pom.xml @@ -38,16 +38,13 @@ scm:git:git://github.com/DBCG/cqf-ruler.git scm:git:ssh://github.com:DBCG/cqf-ruler.git - http://github.com/DBCG/cqf-ruler.git/tree/master + https://github.com/DBCG/cqf-ruler/tree/master - 0 - 4 - 1 - ${version.major}.${version.minor}.${version.patch}-SNAPSHOT + 0.4.1 UTF-8 UTF-8 @@ -416,7 +413,15 @@ 1.8 1.8 - true + 8 + true + true + + -Xlint:all + + -Xlint:-options + -Werror + @@ -544,11 +549,6 @@ maven-surefire-plugin 2.21.0 - - org.codehaus.mojo - build-helper-maven-plugin - 3.0.0 - org.codehaus.mojo flatten-maven-plugin @@ -577,18 +577,6 @@ - - org.apache.maven.plugins - maven-source-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - org.apache.maven.plugins - maven-failsafe-plugin - org.apache.maven.plugins maven-surefire-plugin @@ -600,11 +588,31 @@ + + ci + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + + + release - - ${version.major}.${version.minor}.${version.patch} - diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java index 1cd919b72..8ceb18c08 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/QuestionnaireProvider.java @@ -23,21 +23,23 @@ public class QuestionnaireProvider { private FhirContext fhirContext; @Inject - public QuestionnaireProvider(FhirContext fhirContext){ + public QuestionnaireProvider(FhirContext fhirContext) { this.fhirContext = fhirContext; } @Operation(name = "$extract", idempotent = false, type = QuestionnaireResponse.class) - public Bundle extractObservationFromQuestionnaireResponse(@OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { - if(questionnaireResponse == null) { - throw new IllegalArgumentException("Unable to perform operation $extract. The QuestionnaireResponse was null"); + public Bundle extractObservationFromQuestionnaireResponse( + @OperationParam(name = "questionnaireResponse") QuestionnaireResponse questionnaireResponse) { + if (questionnaireResponse == null) { + throw new IllegalArgumentException( + "Unable to perform operation $extract. The QuestionnaireResponse was null"); } Bundle observationsFromQuestionnaireResponse = createObservationBundle(questionnaireResponse); Bundle returnBundle = sendObservationBundle(observationsFromQuestionnaireResponse); return returnBundle; } - private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse){ + private Bundle createObservationBundle(QuestionnaireResponse questionnaireResponse) { Bundle newBundle = new Bundle(); Date authored = questionnaireResponse.getAuthored(); @@ -45,40 +47,40 @@ private Bundle createObservationBundle(QuestionnaireResponse questionnaireRespon bundleId.setValue("QuestionnaireResponse/" + questionnaireResponse.getIdElement().getIdPart()); newBundle.setType(Bundle.BundleType.TRANSACTION); newBundle.setIdentifier(bundleId); - Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire()); + Map questionnaireCodeMap = getQuestionnaireCodeMap(questionnaireResponse.getQuestionnaire()); - questionnaireResponse.getItem().stream().forEach(item ->{ + questionnaireResponse.getItem().stream().forEach(item -> { processItems(item, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); return newBundle; } private void processItems(QuestionnaireResponse.QuestionnaireResponseItemComponent item, Date authored, - QuestionnaireResponse questionnaireResponse, Bundle newBundle, - Map questionnaireCodeMap){ - if(item.hasAnswer()){ - item.getAnswer().forEach(answer ->{ - Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, item.getLinkId(), authored, questionnaireResponse, questionnaireCodeMap); - if(null != newBundleEntryComponent){ + QuestionnaireResponse questionnaireResponse, Bundle newBundle, Map questionnaireCodeMap) { + if (item.hasAnswer()) { + item.getAnswer().forEach(answer -> { + Bundle.BundleEntryComponent newBundleEntryComponent = createObservationFromItemAnswer(answer, + item.getLinkId(), authored, questionnaireResponse, questionnaireCodeMap); + if (null != newBundleEntryComponent) { newBundle.addEntry(newBundleEntryComponent); } - if(answer.hasItem()){ - answer.getItem().forEach(answerItem->{ + if (answer.hasItem()) { + answer.getItem().forEach(answerItem -> { processItems(answerItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } }); } - if(item.hasItem()){ - item.getItem().forEach(itemItem ->{ + if (item.hasItem()) { + item.getItem().forEach(itemItem -> { processItems(itemItem, authored, questionnaireResponse, newBundle, questionnaireCodeMap); }); } } - private Bundle.BundleEntryComponent createObservationFromItemAnswer(QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, - String linkId, Date authored, QuestionnaireResponse questionnaireResponse, - Map questionnaireCodeMap){ + private Bundle.BundleEntryComponent createObservationFromItemAnswer( + QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent answer, String linkId, Date authored, + QuestionnaireResponse questionnaireResponse, Map questionnaireCodeMap) { Observation obs = new Observation(); obs.setEffective(new DateTimeType(authored)); obs.setStatus(Observation.ObservationStatus.FINAL); @@ -89,7 +91,7 @@ private Bundle.BundleEntryComponent createObservationFromItemAnswer(Questionnair obs.setCategory(Collections.singletonList(new CodeableConcept().addCoding(qrCategoryCoding))); obs.setCode(new CodeableConcept().addCoding((Coding) questionnaireCodeMap.get(linkId))); obs.setId("qr" + questionnaireResponse.getIdElement().getIdPart() + "." + linkId); - switch(answer.getValue().fhirType()){ + switch (answer.getValue().fhirType()) { case "string": obs.setValue(new StringType(answer.getValueStringType().getValue())); break; @@ -101,7 +103,8 @@ private Bundle.BundleEntryComponent createObservationFromItemAnswer(Questionnair break; } Reference questionnaireResponseReference = new Reference(); - questionnaireResponseReference.setReference("QuestionnaireResponse" + "/" + questionnaireResponse.getIdElement().getIdPart()); + questionnaireResponseReference + .setReference("QuestionnaireResponse" + "/" + questionnaireResponse.getIdElement().getIdPart()); obs.setDerivedFrom(Collections.singletonList(questionnaireResponseReference)); Extension linkIdExtension = new Extension(); linkIdExtension.setUrl("http://hl7.org/fhir/uv/sdc/StructureDefinition/derivedFromLinkId"); @@ -120,50 +123,52 @@ private Bundle.BundleEntryComponent createObservationFromItemAnswer(Questionnair return bec; } - private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException{ + private Bundle sendObservationBundle(Bundle observationsBundle) throws IllegalArgumentException { String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { - throw new IllegalArgumentException("Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); + throw new IllegalArgumentException( + "Unable to transmit observation bundle. No observation.endpoint defined in hapi.properties."); } String user = HapiProperties.getQuestionnaireResponseExtractUserName(); String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); - Bundle outcomeBundle = client.transaction() - .withBundle(observationsBundle) - .execute(); + Bundle outcomeBundle = client.transaction().withBundle(observationsBundle).execute(); return outcomeBundle; } - private Map getQuestionnaireCodeMap(String questionnaireUrl){ + private Map getQuestionnaireCodeMap(String questionnaireUrl) { String url = HapiProperties.getQuestionnaireResponseExtractEndpoint(); if (null == url || url.length() < 1) { - throw new IllegalArgumentException("Unable to GET Questionnaire. No observation.endpoint defined in hapi.properties."); + throw new IllegalArgumentException( + "Unable to GET Questionnaire. No observation.endpoint defined in hapi.properties."); } String user = HapiProperties.getQuestionnaireResponseExtractUserName(); String password = HapiProperties.getQuestionnaireResponseExtractPassword(); IGenericClient client = getClient(fhirContext, url, user, password); - Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl (questionnaireUrl).execute(); + Questionnaire questionnaire = client.read().resource(Questionnaire.class).withUrl(questionnaireUrl).execute(); return createCodeMap(questionnaire); } - // this is based on "if a questionnaire.item has items then this item is a header and will not have a specific code to be used with an answer" - private Map createCodeMap(Questionnaire questionnaire){ - Map questionnaireCodeMap = new HashMap(); - questionnaire.getItem().forEach((item) ->{ + // this is based on "if a questionnaire.item has items then this item is a + // header and will not have a specific code to be used with an answer" + private Map createCodeMap(Questionnaire questionnaire) { + Map questionnaireCodeMap = new HashMap<>(); + questionnaire.getItem().forEach((item) -> { processQuestionnaireItems(item, questionnaireCodeMap); }); return questionnaireCodeMap; } - private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, Map questionnaireCodeMap){ - if(item.hasItem()){ + private void processQuestionnaireItems(Questionnaire.QuestionnaireItemComponent item, + Map questionnaireCodeMap) { + if (item.hasItem()) { item.getItem().forEach(qItem -> { processQuestionnaireItems(qItem, questionnaireCodeMap); }); - }else{ + } else { questionnaireCodeMap.put(item.getLinkId(), item.getCodeFirstRep()); } } diff --git a/scripts/build.sh b/scripts/build.sh index 6a5a92630..5ae417832 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -23,15 +23,4 @@ if [[ "$TRAVIS_BRANCH" =~ master* ]]; then CMD="$CMD -P release" fi -eval $CMD - -# Set up for docker publish -DOCKER_IMAGE="contentgroup/cqf-ruler" -DOCKER_TAG=$TRAVIS_BRANCH -if [[ "$TRAVIS_BRANCH" =~ master* ]]; then - DOCKER_TAG="latest" -fi - -# Push image to registry -echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin -docker build . -t "$DOCKER_IMAGE:$DOCKER_TAG" && docker push "$DOCKER_IMAGE:$DOCKER_TAG" \ No newline at end of file +eval $CMD \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 000000000..57042babc --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Fail if any command fails or if there are unbound variables and check syntax +set -euxo pipefail +bash -n "$0" + +if [[ "$TRAVIS_BRANCH" != "master" && -z "$TRAVIS_TAG" ]] +then + echo "Not on the master branch or a git tag. Skipping deploy." + exit 0 +fi + +if [[ ! -z "$TRAVIS_TAG" ]] +then + export MAVEN_PROJECT_VERSION="$(mvn org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate -Dexpression=project.version -q -DforceStdout)" + echo "Maven project version is: $MAVEN_PROJECT_VERSION" + if [[ "$TRAVIS_TAG" != "v$MAVEN_PROJECT_VERSION" ]] + then + echo "ERROR: Maven project version and tag do not match (expected tag v$MAVEN_PROJECT_VERSION)" + exit 1 + fi +fi + +# Import maven settings +cp .travis.settings.xml $HOME/.m2/settings.xml + +CMD="mvn deploy -DskipTests=true -Dmaven.test.skip=true -T 4 -B -P ci" + +# Import signing key and publish a release on a tag +if [[ ! -z "$TRAVIS_TAG" ]] +then + echo "Publishing Maven Central release for tag: $TRAVIS_TAG" + echo $GPG_SECRET_KEYS | base64 --decode| $GPG_EXECUTABLE --import; + echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust; + # Activate the release profile + CMD="$CMD,release" +else + echo "Publishing Maven Central snapshot / pre-release for branch: $TRAVIS_BRANCH" +fi + +eval $CMD + +echo "Building and publishing docker image" +# Set up for docker publish +DOCKER_IMAGE="contentgroup/cqf-ruler" +DOCKER_TAG=$TRAVIS_BRANCH +if [[ "$TRAVIS_BRANCH" == master ]]; then + DOCKER_TAG="latest" +elif [[ ! -z "$TRAVIS_TAG" ]]; then + DOCKER_TAG="$TRAVIS_TAG" +fi + +# Push image to registry +echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin +docker build . -t "$DOCKER_IMAGE:$DOCKER_TAG" && docker push "$DOCKER_IMAGE:$DOCKER_TAG" \ No newline at end of file diff --git a/scripts/install.sh b/scripts/install.sh index 16869b776..08cc0be35 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,3 +1,7 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash + +# Fail if any command fails or if there are unbound variables and check syntax +set -euxo pipefail +bash -n "$0" mvn install -U -DskipTests=true -Dmaven.javadoc.skip=true -T 4 -B -V \ No newline at end of file diff --git a/scripts/maintenance.sh b/scripts/maintenance.sh new file mode 100755 index 000000000..9f5feeac8 --- /dev/null +++ b/scripts/maintenance.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# Fail if any command fails or if there are unbound variables and check syntax +set -euxo pipefail +bash -n "$0" + +mvn versions:update-properties versions:use-releases versions:use-latest-releases \ No newline at end of file diff --git a/scripts/setversion.sh b/scripts/setversion.sh new file mode 100755 index 000000000..4c4e81298 --- /dev/null +++ b/scripts/setversion.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env sh + +# usage ./scripts/setversion.sh N.N.N (or N.N.N-SNAPSHOT) +mvn versions:set -DnewVersion=$1 \ No newline at end of file diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 000000000..d7dec20c2 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Fail if any command fails or if there are unbound variables and check syntax +set -euxo pipefail +bash -n "$0" + +CMD="mvn test -T 4 -B -V -P ci" + +if [[ ! -z "$TRAVIS_TAG" ]] +then + CMD="$CMD,release" +fi + +eval $CMD