001/* 002 * #%L 003 * HAPI FHIR JPA Server 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.jpa.provider; 021 022import ca.uhn.fhir.context.support.TranslateConceptResults; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; 025import ca.uhn.fhir.jpa.api.model.TranslationRequest; 026import ca.uhn.fhir.jpa.model.util.JpaConstants; 027import ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl; 028import ca.uhn.fhir.rest.annotation.IdParam; 029import ca.uhn.fhir.rest.annotation.Operation; 030import ca.uhn.fhir.rest.annotation.OperationParam; 031import ca.uhn.fhir.rest.api.server.RequestDetails; 032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 033import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; 034import jakarta.servlet.http.HttpServletRequest; 035import org.hl7.fhir.instance.model.api.IBaseCoding; 036import org.hl7.fhir.instance.model.api.IBaseDatatype; 037import org.hl7.fhir.instance.model.api.IBaseParameters; 038import org.hl7.fhir.instance.model.api.IBaseResource; 039import org.hl7.fhir.instance.model.api.IIdType; 040import org.hl7.fhir.instance.model.api.IPrimitiveType; 041import org.hl7.fhir.r4.model.CodeableConcept; 042import org.hl7.fhir.r4.model.Coding; 043import org.hl7.fhir.r4.model.ConceptMap; 044import org.hl7.fhir.r4.model.Parameters; 045import org.springframework.beans.factory.annotation.Autowired; 046 047import static ca.uhn.fhir.util.DatatypeUtil.toBooleanValue; 048import static ca.uhn.fhir.util.DatatypeUtil.toStringValue; 049import static org.apache.commons.lang3.StringUtils.isNotBlank; 050 051public abstract class BaseJpaResourceProviderConceptMap<T extends IBaseResource> extends BaseJpaResourceProvider<T> { 052 053 @Autowired 054 private VersionCanonicalizer myVersionCanonicalizer; 055 056 @Operation( 057 name = JpaConstants.OPERATION_TRANSLATE, 058 idempotent = true, 059 returnParameters = { 060 @OperationParam(name = "result", typeName = "boolean", min = 1, max = 1), 061 @OperationParam(name = "message", typeName = "string", min = 0, max = 1), 062 }) 063 public IBaseParameters translate( 064 HttpServletRequest theServletRequest, 065 @IdParam(optional = true) IIdType theId, 066 @OperationParam(name = "url", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theUrl, 067 @OperationParam(name = "conceptMapVersion", min = 0, max = 1, typeName = "string") 068 IPrimitiveType<String> theConceptMapVersion, 069 @OperationParam(name = "code", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theSourceCode, 070 @OperationParam(name = "system", min = 0, max = 1, typeName = "uri") 071 IPrimitiveType<String> theSourceCodeSystem, 072 @OperationParam(name = "version", min = 0, max = 1, typeName = "string") 073 IPrimitiveType<String> theSourceCodeSystemVersion, 074 @OperationParam(name = "source", min = 0, max = 1, typeName = "uri") 075 IPrimitiveType<String> theSourceValueSet, 076 @OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") IBaseCoding theSourceCoding, 077 @OperationParam(name = "codeableConcept", min = 0, max = 1, typeName = "CodeableConcept") 078 IBaseDatatype theSourceCodeableConcept, 079 @OperationParam(name = "target", min = 0, max = 1, typeName = "uri") 080 IPrimitiveType<String> theTargetValueSet, 081 @OperationParam(name = "targetsystem", min = 0, max = 1, typeName = "uri") 082 IPrimitiveType<String> theTargetCodeSystem, 083 @OperationParam(name = "reverse", min = 0, max = 1, typeName = "boolean") 084 IPrimitiveType<Boolean> theReverse, 085 RequestDetails theRequestDetails) { 086 Coding sourceCoding = myVersionCanonicalizer.codingToCanonical(theSourceCoding); 087 CodeableConcept sourceCodeableConcept = 088 myVersionCanonicalizer.codeableConceptToCanonical(theSourceCodeableConcept); 089 090 boolean haveSourceCode = theSourceCode != null && isNotBlank(theSourceCode.getValue()); 091 boolean haveSourceCodeSystem = theSourceCodeSystem != null && theSourceCodeSystem.hasValue(); 092 boolean haveSourceCodeSystemVersion = 093 theSourceCodeSystemVersion != null && theSourceCodeSystemVersion.hasValue(); 094 boolean haveSourceCoding = sourceCoding != null && sourceCoding.hasCode(); 095 boolean haveSourceCodeableConcept = sourceCodeableConcept != null 096 && sourceCodeableConcept.hasCoding() 097 && sourceCodeableConcept.getCodingFirstRep().hasCode(); 098 boolean haveReverse = theReverse != null; 099 boolean haveId = theId != null && theId.hasIdPart(); 100 101 // <editor-fold desc="Filters"> 102 if ((!haveSourceCode && !haveSourceCoding && !haveSourceCodeableConcept) 103 || moreThanOneTrue(haveSourceCode, haveSourceCoding, haveSourceCodeableConcept)) { 104 throw new InvalidRequestException( 105 Msg.code(1154) 106 + "One (and only one) of the in parameters (code, coding, codeableConcept) must be provided, to identify the code that is to be translated."); 107 } 108 109 TranslationRequest translationRequest = new TranslationRequest(); 110 translationRequest.setUrl(toStringValue(theUrl)); 111 translationRequest.setConceptMapVersion(toStringValue(theConceptMapVersion)); 112 113 if (haveSourceCode) { 114 translationRequest.getCodeableConcept().addCoding().setCode(toStringValue(theSourceCode)); 115 116 if (haveSourceCodeSystem) { 117 translationRequest 118 .getCodeableConcept() 119 .getCodingFirstRep() 120 .setSystem(toStringValue(theSourceCodeSystem)); 121 } 122 123 if (haveSourceCodeSystemVersion) { 124 translationRequest 125 .getCodeableConcept() 126 .getCodingFirstRep() 127 .setVersion(toStringValue(theSourceCodeSystemVersion)); 128 } 129 } else if (haveSourceCoding) { 130 translationRequest.getCodeableConcept().addCoding(sourceCoding); 131 } else { 132 translationRequest.setCodeableConcept(sourceCodeableConcept); 133 } 134 135 translationRequest.setSource(toStringValue(theSourceValueSet)); 136 translationRequest.setTarget(toStringValue(theTargetValueSet)); 137 translationRequest.setTargetSystem(toStringValue(theTargetCodeSystem)); 138 139 if (haveReverse) { 140 translationRequest.setReverse(toBooleanValue(theReverse)); 141 } 142 143 if (haveId) { 144 translationRequest.setResourceId(theId); 145 } 146 147 startRequest(theServletRequest); 148 try { 149 IFhirResourceDaoConceptMap<ConceptMap> dao = (IFhirResourceDaoConceptMap<ConceptMap>) getDao(); 150 TranslateConceptResults result = dao.translate(translationRequest, theRequestDetails); 151 Parameters parameters = TermConceptMappingSvcImpl.toParameters(result); 152 return myVersionCanonicalizer.parametersFromCanonical(parameters); 153 } finally { 154 endRequest(theServletRequest); 155 } 156 } 157 158 private static boolean moreThanOneTrue(boolean... theBooleans) { 159 boolean haveOne = false; 160 for (boolean next : theBooleans) { 161 if (next) { 162 if (haveOne) { 163 return true; 164 } else { 165 haveOne = true; 166 } 167 } 168 } 169 return false; 170 } 171}