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}