001package ca.uhn.fhir.cql.dstu3.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.ITermReadSvcDstu3; 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.dstu3.model.IdType; 036import org.hl7.fhir.dstu3.model.ValueSet; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038import org.opencds.cqf.cql.engine.runtime.Code; 039import org.opencds.cqf.cql.engine.terminology.CodeSystemInfo; 040import org.opencds.cqf.cql.engine.terminology.TerminologyProvider; 041import org.opencds.cqf.cql.engine.terminology.ValueSetInfo; 042import org.springframework.beans.factory.annotation.Autowired; 043import org.springframework.stereotype.Component; 044 045import javax.annotation.PostConstruct; 046import java.util.ArrayList; 047import java.util.List; 048 049/** 050 * Created by Christopher Schuler on 7/17/2017. 051 */ 052@Component 053public class JpaTerminologyProvider implements TerminologyProvider { 054 055 private final ITermReadSvcDstu3 myTerminologySvc; 056 private final DaoRegistry myDaoRegistry; 057 private final IValidationSupport myValidationSupport; 058 059 private IFhirResourceDao<ValueSet> myValueSetDao; 060 061 @Autowired 062 public JpaTerminologyProvider(ITermReadSvcDstu3 theTerminologySvc, DaoRegistry theDaoRegistry, IValidationSupport theValidationSupport) { 063 myTerminologySvc = theTerminologySvc; 064 myDaoRegistry = theDaoRegistry; 065 myValidationSupport = theValidationSupport; 066 } 067 068 @PostConstruct 069 public void init() { 070 myValueSetDao = myDaoRegistry.getResourceDao(ValueSet.class); 071 } 072 073 @Override 074 public boolean in(Code code, ValueSetInfo valueSet) throws ResourceNotFoundException { 075 for (Code c : expand(valueSet)) { 076 if (c == null) 077 continue; 078 if (c.getCode().equals(code.getCode()) && c.getSystem().equals(code.getSystem())) { 079 return true; 080 } 081 } 082 return false; 083 } 084 085 @Override 086 public Iterable<Code> expand(ValueSetInfo valueSet) throws ResourceNotFoundException { 087 // This could possibly be refactored into a single call to the underlying HAPI Terminology service. Need to think through that.., 088 ValueSet vs; 089 if (valueSet.getId().startsWith("http://") || valueSet.getId().startsWith("https://")) { 090 if (valueSet.getVersion() != null 091 || (valueSet.getCodeSystems() != null && valueSet.getCodeSystems().size() > 0)) { 092 if (!(valueSet.getCodeSystems().size() == 1 && valueSet.getCodeSystems().get(0).getVersion() == null)) { 093 throw new UnsupportedOperationException(Msg.code(1643) + String.format( 094 "Could not expand value set %s; version and code system bindings are not supported at this time.", 095 valueSet.getId())); 096 } 097 } 098 099 IBundleProvider bundleProvider = myValueSetDao 100 .search(SearchParameterMap.newSynchronous().add(ValueSet.SP_URL, new UriParam(valueSet.getId()))); 101 List<IBaseResource> valueSets = bundleProvider.getAllResources(); 102 if (valueSets.isEmpty()) { 103 throw new IllegalArgumentException(Msg.code(1644) + String.format("Could not resolve value set %s.", valueSet.getId())); 104 } else if (valueSets.size() == 1) { 105 vs = (ValueSet) valueSets.get(0); 106 } else { 107 throw new IllegalArgumentException(Msg.code(1645) + "Found more than 1 ValueSet with url: " + valueSet.getId()); 108 } 109 } else { 110 vs = myValueSetDao.read(new IdType(valueSet.getId())); 111 if (vs == null) { 112 throw new IllegalArgumentException(Msg.code(1646) + String.format("Could not resolve value set %s.", valueSet.getId())); 113 } 114 } 115 116 // Attempt to expand the ValueSet if it's not already expanded. 117 if (!(vs.hasExpansion() && vs.getExpansion().hasContains())) { 118 vs = (ValueSet) myTerminologySvc.expandValueSet( 119 new ValueSetExpansionOptions().setCount(Integer.MAX_VALUE).setFailOnMissingCodeSystem(false), vs); 120 } 121 122 List<Code> codes = new ArrayList<>(); 123 124 // If expansion was successful, use the codes. 125 if (vs.hasExpansion() && vs.getExpansion().hasContains()) { 126 for (ValueSet.ValueSetExpansionContainsComponent vsecc : vs.getExpansion().getContains()) { 127 codes.add(new Code().withCode(vsecc.getCode()).withSystem(vsecc.getSystem())); 128 } 129 } 130 // If not, best-effort based on codes. Should probably make this configurable to match the behavior of the 131 // underlying terminology service implementation 132 else if (vs.hasCompose() && vs.getCompose().hasInclude()) { 133 for (ValueSet.ConceptSetComponent include : vs.getCompose().getInclude()) { 134 for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) { 135 if (concept.hasCode()) { 136 codes.add(new Code().withCode(concept.getCode()).withSystem(include.getSystem())); 137 } 138 } 139 } 140 } 141 142 return codes; 143 } 144 145 @Override 146 public Code lookup(Code code, CodeSystemInfo codeSystem) throws ResourceNotFoundException { 147 LookupCodeResult cs = myTerminologySvc.lookupCode(new ValidationSupportContext(myValidationSupport), codeSystem.getId(), code.getCode()); 148 149 code.setDisplay(cs.getCodeDisplay()); 150 code.setSystem(codeSystem.getId()); 151 152 return code; 153 } 154}