001/*- 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.interceptor; 021 022import ca.uhn.fhir.context.BaseRuntimeChildDefinition; 023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; 024import ca.uhn.fhir.context.BaseRuntimeElementDefinition; 025import ca.uhn.fhir.context.FhirContext; 026import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition; 027import ca.uhn.fhir.context.support.IValidationSupport; 028import ca.uhn.fhir.context.support.TranslateConceptResult; 029import ca.uhn.fhir.context.support.TranslateConceptResults; 030import ca.uhn.fhir.util.FhirTerser; 031import ca.uhn.fhir.util.IModelVisitor; 032import com.google.common.collect.ArrayListMultimap; 033import com.google.common.collect.Multimap; 034import jakarta.annotation.Nonnull; 035import org.apache.commons.lang3.StringUtils; 036import org.apache.commons.lang3.Validate; 037import org.hl7.fhir.instance.model.api.IBase; 038import org.hl7.fhir.instance.model.api.IBaseCoding; 039import org.hl7.fhir.instance.model.api.IBaseResource; 040import org.hl7.fhir.instance.model.api.IPrimitiveType; 041 042import java.util.ArrayList; 043import java.util.HashMap; 044import java.util.List; 045import java.util.Map; 046import java.util.Objects; 047 048public class ResponseTerminologyTranslationSvc { 049 private BaseRuntimeChildDefinition myCodingSystemChild; 050 private BaseRuntimeChildDefinition myCodingCodeChild; 051 private BaseRuntimeElementDefinition<IPrimitiveType<?>> myUriDefinition; 052 private BaseRuntimeElementDefinition<IPrimitiveType<?>> myCodeDefinition; 053 private Class<? extends IBase> myCodeableConceptType; 054 private Class<? extends IBase> myCodingType; 055 private BaseRuntimeChildDefinition myCodeableConceptCodingChild; 056 private BaseRuntimeElementCompositeDefinition<?> myCodingDefinition; 057 private RuntimePrimitiveDatatypeDefinition myStringDefinition; 058 private BaseRuntimeChildDefinition myCodingDisplayChild; 059 private Map<String, String> myMappingSpec; 060 private final IValidationSupport myValidationSupport; 061 private final FhirContext myFhirContext; 062 063 public ResponseTerminologyTranslationSvc(@Nonnull IValidationSupport theValidationSupport) { 064 myValidationSupport = theValidationSupport; 065 Validate.notNull(theValidationSupport, "The validation support must not be null"); 066 067 myFhirContext = theValidationSupport.getFhirContext(); 068 Validate.notNull(myFhirContext, "The validation support must not return a null context"); 069 070 BaseRuntimeElementCompositeDefinition<?> codeableConceptDef = (BaseRuntimeElementCompositeDefinition<?>) 071 Objects.requireNonNull(myFhirContext.getElementDefinition("CodeableConcept")); 072 073 myCodeableConceptType = codeableConceptDef.getImplementingClass(); 074 myCodeableConceptCodingChild = codeableConceptDef.getChildByName("coding"); 075 076 myCodingDefinition = (BaseRuntimeElementCompositeDefinition<?>) 077 Objects.requireNonNull(myFhirContext.getElementDefinition("Coding")); 078 myCodingType = myCodingDefinition.getImplementingClass(); 079 myCodingSystemChild = myCodingDefinition.getChildByName("system"); 080 myCodingCodeChild = myCodingDefinition.getChildByName("code"); 081 myCodingDisplayChild = myCodingDefinition.getChildByName("display"); 082 083 myUriDefinition = (RuntimePrimitiveDatatypeDefinition) myFhirContext.getElementDefinition("uri"); 084 myCodeDefinition = (RuntimePrimitiveDatatypeDefinition) myFhirContext.getElementDefinition("code"); 085 myStringDefinition = (RuntimePrimitiveDatatypeDefinition) myFhirContext.getElementDefinition("string"); 086 } 087 088 public void processResourcesForTerminologyTranslation(List<IBaseResource> resources) { 089 FhirTerser terser = myFhirContext.newTerser(); 090 for (IBaseResource nextResource : resources) { 091 terser.visit(nextResource, new MappingVisitor()); 092 } 093 } 094 095 public void addMappingSpecification(String theSourceCodeSystemUrl, String theTargetCodeSystemUrl) { 096 Validate.notBlank(theSourceCodeSystemUrl, "theSourceCodeSystemUrl must not be null or blank"); 097 Validate.notBlank(theTargetCodeSystemUrl, "theTargetCodeSystemUrl must not be null or blank"); 098 099 getMappingSpecifications().put(theSourceCodeSystemUrl, theTargetCodeSystemUrl); 100 } 101 102 public void clearMappingSpecifications() { 103 myMappingSpec.clear(); 104 } 105 106 public Map<String, String> getMappingSpecifications() { 107 if (myMappingSpec == null) { 108 myMappingSpec = new HashMap<>(); 109 } 110 return myMappingSpec; 111 } 112 113 private class MappingVisitor implements IModelVisitor { 114 115 @Override 116 public void acceptElement( 117 IBaseResource theResource, 118 IBase theElement, 119 List<String> thePathToElement, 120 BaseRuntimeChildDefinition theChildDefinition, 121 BaseRuntimeElementDefinition<?> theDefinition) { 122 if (myCodeableConceptType.isAssignableFrom(theElement.getClass())) { 123 124 // Find all existing Codings 125 Multimap<String, String> foundSystemsToCodes = ArrayListMultimap.create(); 126 List<IBase> nextCodeableConceptCodings = 127 myCodeableConceptCodingChild.getAccessor().getValues(theElement); 128 for (IBase nextCodeableConceptCoding : nextCodeableConceptCodings) { 129 String system = myCodingSystemChild 130 .getAccessor() 131 .getFirstValueOrNull(nextCodeableConceptCoding) 132 .map(t -> (IPrimitiveType<?>) t) 133 .map(IPrimitiveType::getValueAsString) 134 .orElse(null); 135 String code = myCodingCodeChild 136 .getAccessor() 137 .getFirstValueOrNull(nextCodeableConceptCoding) 138 .map(t -> (IPrimitiveType<?>) t) 139 .map(IPrimitiveType::getValueAsString) 140 .orElse(null); 141 if (StringUtils.isNotBlank(system) 142 && StringUtils.isNotBlank(code) 143 && !foundSystemsToCodes.containsKey(system)) { 144 foundSystemsToCodes.put(system, code); 145 } 146 } 147 148 // Look for mappings 149 for (String nextSourceSystem : foundSystemsToCodes.keySet()) { 150 String wantTargetSystem = getMappingSpecifications().get(nextSourceSystem); 151 if (wantTargetSystem != null) { 152 if (!foundSystemsToCodes.containsKey(wantTargetSystem)) { 153 154 for (String code : foundSystemsToCodes.get(nextSourceSystem)) { 155 List<IBaseCoding> codings = new ArrayList<>(); 156 codings.add(createCodingFromPrimitives(nextSourceSystem, code, null)); 157 TranslateConceptResults translateConceptResults = myValidationSupport.translateConcept( 158 new IValidationSupport.TranslateCodeRequest(codings, wantTargetSystem)); 159 if (translateConceptResults != null) { 160 List<TranslateConceptResult> mappings = translateConceptResults.getResults(); 161 for (TranslateConceptResult nextMapping : mappings) { 162 163 IBase newCoding = createCodingFromPrimitives( 164 nextMapping.getSystem(), 165 nextMapping.getCode(), 166 nextMapping.getDisplay()); 167 168 // Add coding to existing CodeableConcept 169 myCodeableConceptCodingChild 170 .getMutator() 171 .addValue(theElement, newCoding); 172 } 173 } 174 } 175 } 176 } 177 } 178 } 179 } 180 181 private IBaseCoding createCodingFromPrimitives(String system, String code, String display) { 182 assert myUriDefinition != null; 183 assert myCodeDefinition != null; 184 IBaseCoding newCoding = (IBaseCoding) myCodingDefinition.newInstance(); 185 IPrimitiveType<?> newSystem = myUriDefinition.newInstance(system); 186 myCodingSystemChild.getMutator().addValue(newCoding, newSystem); 187 IPrimitiveType<?> newCode = myCodeDefinition.newInstance(code); 188 myCodingCodeChild.getMutator().addValue(newCoding, newCode); 189 if (StringUtils.isNotBlank(display)) { 190 assert myStringDefinition != null; 191 IPrimitiveType<?> newDisplay = myStringDefinition.newInstance(display); 192 myCodingDisplayChild.getMutator().addValue(newCoding, newDisplay); 193 } 194 return newCoding; 195 } 196 } 197}