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