001package ca.uhn.fhir.cql.r4.provider; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server - Clinical Quality Language 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.context.support.IValidationSupport; 025import ca.uhn.fhir.context.support.IValidationSupport.LookupCodeResult; 026import ca.uhn.fhir.context.support.ValidationSupportContext; 027import ca.uhn.fhir.context.support.ValueSetExpansionOptions; 028import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 029import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 030import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 031import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; 032import ca.uhn.fhir.rest.api.server.IBundleProvider; 033import ca.uhn.fhir.rest.param.UriParam; 034import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 035import org.hl7.fhir.instance.model.api.IBaseResource; 036import org.hl7.fhir.r4.model.IdType; 037import org.hl7.fhir.r4.model.ValueSet; 038import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 039import org.opencds.cqf.cql.engine.runtime.Code; 040import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; 041import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; 042import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; 043import org.springframework.beans.factory.annotation.Autowired; 044import org.springframework.stereotype.Component; 045 046import javax.annotation.PostConstruct; 047import java.util.ArrayList; 048import java.util.List; 049 050@Component 051public class JpaTerminologyProvider implements TerminologyProvider { 052 053 private final ITermReadSvcR4 myTerminologySvc; 054 private final DaoRegistry myDaoRegistry; 055 private final IValidationSupport myValidationSupport; 056 057 private IFhirResourceDao<ValueSet> myValueSetDao; 058 059 @Autowired 060 public JpaTerminologyProvider(ITermReadSvcR4 theTerminologySvc, DaoRegistry theDaoRegistry, IValidationSupport theValidationSupport) { 061 myTerminologySvc = theTerminologySvc; 062 myDaoRegistry = theDaoRegistry; 063 myValidationSupport = theValidationSupport; 064 } 065 066 @PostConstruct 067 public void init() { 068 myValueSetDao = myDaoRegistry.getResourceDao(ValueSet.class); 069 } 070 071 @Override 072 public boolean in(Code code, ValueSetInfo valueSet) throws ResourceNotFoundException { 073 for (Code c : expand(valueSet)) { 074 if (c == null) 075 continue; 076 if (c.getCode().equals(code.getCode()) && c.getSystem().equals(code.getSystem())) { 077 return true; 078 } 079 } 080 return false; 081 } 082 083 @Override 084 public Iterable<Code> expand(ValueSetInfo valueSet) throws ResourceNotFoundException { 085 // This could possibly be refactored into a single call to the underlying HAPI Terminology service. Need to think through that.., 086 ValueSet vs; 087 if (valueSet.getId().startsWith("http://") || valueSet.getId().startsWith("https://")) { 088 if (valueSet.getVersion() != null 089 || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { 090 if (!(valueSet.getCodeSystems().size() == 1 && valueSet.getCodeSystems().get(0).getVersion() == null)) { 091 throw new UnsupportedOperationException(Msg.code(1667) + String.format( 092 "Could not expand value set %s; version and code system bindings are not supported at this time.", 093 valueSet.getId())); 094 } 095 } 096 097 IBundleProvider bundleProvider = myValueSetDao 098 .search(SearchParameterMap.newSynchronous().add(ValueSet.SP_URL, new UriParam(valueSet.getId()))); 099 List<IBaseResource> valueSets = bundleProvider.getAllResources(); 100 if (valueSets.isEmpty()) { 101 throw new IllegalArgumentException(Msg.code(1668) + String.format("Could not resolve value set %s.", valueSet.getId())); 102 } else if (valueSets.size() == 1) { 103 vs = (ValueSet) valueSets.get(0); 104 } else { 105 throw new IllegalArgumentException(Msg.code(1669) + "Found more than 1 ValueSet with url: " + valueSet.getId()); 106 } 107 } else { 108 vs = myValueSetDao.read(new IdType(valueSet.getId())); 109 if (vs == null) { 110 throw new IllegalArgumentException(Msg.code(1670) + String.format("Could not resolve value set %s.", valueSet.getId())); 111 } 112 } 113 114 // Attempt to expand the ValueSet if it's not already expanded. 115 if (!(vs.hasExpansion() && vs.getExpansion().hasContains())) { 116 vs = myTerminologySvc.expandValueSet( 117 new ValueSetExpansionOptions().setCount(Integer.MAX_VALUE).setFailOnMissingCodeSystem(false), vs); 118 } 119 120 List<Code> codes = new ArrayList<>(); 121 122 // If expansion was successful, use the codes. 123 if (vs.hasExpansion() && vs.getExpansion().hasContains()) { 124 for (ValueSetExpansionContainsComponent vsecc : vs.getExpansion().getContains()) { 125 codes.add(new Code().withCode(vsecc.getCode()).withSystem(vsecc.getSystem())); 126 } 127 } 128 // If not, best-effort based on codes. Should probably make this configurable to match the behavior of the 129 // underlying terminology service implementation 130 else if (vs.hasCompose() && vs.getCompose().hasInclude()) { 131 for (ValueSet.ConceptSetComponent include : vs.getCompose().getInclude()) { 132 for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) { 133 if (concept.hasCode()) { 134 codes.add(new Code().withCode(concept.getCode()).withSystem(include.getSystem())); 135 } 136 } 137 } 138 } 139 140 return codes; 141 } 142 143 @Override 144 public Code lookup(Code code, CodeSystemInfo codeSystem) throws ResourceNotFoundException { 145 LookupCodeResult cs = myTerminologySvc.lookupCode(new ValidationSupportContext(myValidationSupport), 146 codeSystem.getId(), code.getCode()); 147 148 code.setDisplay(cs.getCodeDisplay()); 149 code.setSystem(codeSystem.getId()); 150 151 return code; 152 } 153}