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.ConceptValidationOptions;
023import ca.uhn.fhir.context.support.IValidationSupport;
024import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
025import ca.uhn.fhir.context.support.ValidationSupportContext;
026import ca.uhn.fhir.i18n.Msg;
027import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
028import ca.uhn.fhir.jpa.model.util.JpaConstants;
029import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
030import ca.uhn.fhir.rest.annotation.IdParam;
031import ca.uhn.fhir.rest.annotation.Operation;
032import ca.uhn.fhir.rest.annotation.OperationParam;
033import ca.uhn.fhir.rest.api.server.RequestDetails;
034import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
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.springframework.beans.factory.annotation.Autowired;
042
043import java.util.List;
044import java.util.Optional;
045import java.util.function.Supplier;
046import javax.servlet.http.HttpServletRequest;
047
048import static ca.uhn.fhir.jpa.provider.ValueSetOperationProvider.toValidateCodeResult;
049import static org.apache.commons.lang3.StringUtils.isNotBlank;
050
051public abstract class BaseJpaResourceProviderCodeSystem<T extends IBaseResource> extends BaseJpaResourceProvider<T> {
052
053        @Autowired
054        private JpaValidationSupportChain myValidationSupportChain;
055
056        /**
057         * $lookup operation
058         */
059        @SuppressWarnings("unchecked")
060        @Operation(
061                        name = JpaConstants.OPERATION_LOOKUP,
062                        idempotent = true,
063                        returnParameters = {
064                                @OperationParam(name = "name", typeName = "string", min = 1),
065                                @OperationParam(name = "version", typeName = "string", min = 0),
066                                @OperationParam(name = "display", typeName = "string", min = 1),
067                                @OperationParam(name = "abstract", typeName = "boolean", min = 1),
068                        })
069        public IBaseParameters lookup(
070                        HttpServletRequest theServletRequest,
071                        @OperationParam(name = "code", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theCode,
072                        @OperationParam(name = "system", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theSystem,
073                        @OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") IBaseCoding theCoding,
074                        @OperationParam(name = "version", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theVersion,
075                        @OperationParam(name = "displayLanguage", min = 0, max = 1, typeName = "code")
076                                        IPrimitiveType<String> theDisplayLanguage,
077                        @OperationParam(name = "property", min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "code")
078                                        List<IPrimitiveType<String>> theProperties,
079                        RequestDetails theRequestDetails) {
080
081                startRequest(theServletRequest);
082                try {
083                        IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao();
084                        IValidationSupport.LookupCodeResult result;
085                        applyVersionToSystem(theSystem, theVersion);
086                        result = dao.lookupCode(theCode, theSystem, theCoding, theDisplayLanguage, theRequestDetails);
087                        result.throwNotFoundIfAppropriate();
088                        return result.toParameters(theRequestDetails.getFhirContext(), theProperties);
089                } finally {
090                        endRequest(theServletRequest);
091                }
092        }
093
094        /**
095         * $subsumes operation
096         */
097        @Operation(
098                        name = JpaConstants.OPERATION_SUBSUMES,
099                        idempotent = true,
100                        returnParameters = {
101                                @OperationParam(name = "outcome", typeName = "code", min = 1),
102                        })
103        public IBaseParameters subsumes(
104                        HttpServletRequest theServletRequest,
105                        @OperationParam(name = "codeA", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theCodeA,
106                        @OperationParam(name = "codeB", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theCodeB,
107                        @OperationParam(name = "system", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theSystem,
108                        @OperationParam(name = "codingA", min = 0, max = 1, typeName = "Coding") IBaseCoding theCodingA,
109                        @OperationParam(name = "codingB", min = 0, max = 1, typeName = "Coding") IBaseCoding theCodingB,
110                        @OperationParam(name = "version", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theVersion,
111                        RequestDetails theRequestDetails) {
112
113                startRequest(theServletRequest);
114                try {
115                        IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao();
116                        IFhirResourceDaoCodeSystem.SubsumesResult result;
117                        applyVersionToSystem(theSystem, theVersion);
118                        result = dao.subsumes(theCodeA, theCodeB, theSystem, theCodingA, theCodingB, theRequestDetails);
119                        return result.toParameters(theRequestDetails.getFhirContext());
120                } finally {
121                        endRequest(theServletRequest);
122                }
123        }
124
125        static void applyVersionToSystem(IPrimitiveType<String> theSystem, IPrimitiveType<String> theVersion) {
126                if (theVersion != null && isNotBlank(theVersion.getValueAsString()) && theSystem != null) {
127                        theSystem.setValue(theSystem.getValueAsString() + "|" + theVersion.getValueAsString());
128                }
129        }
130
131        /**
132         * $validate-code operation
133         */
134        @SuppressWarnings("unchecked")
135        @Operation(
136                        name = JpaConstants.OPERATION_VALIDATE_CODE,
137                        idempotent = true,
138                        returnParameters = {
139                                @OperationParam(name = "result", typeName = "boolean", min = 1),
140                                @OperationParam(name = "message", typeName = "string"),
141                                @OperationParam(name = "display", typeName = "string")
142                        })
143        public IBaseParameters validateCode(
144                        HttpServletRequest theServletRequest,
145                        @IdParam(optional = true) IIdType theId,
146                        @OperationParam(name = "url", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theCodeSystemUrl,
147                        @OperationParam(name = "version", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theVersion,
148                        @OperationParam(name = "code", min = 0, max = 1, typeName = "code") IPrimitiveType<String> theCode,
149                        @OperationParam(name = "display", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theDisplay,
150                        @OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") IBaseCoding theCoding,
151                        @OperationParam(name = "codeableConcept", min = 0, max = 1, typeName = "CodeableConcept")
152                                        IBaseDatatype theCodeableConcept,
153                        RequestDetails theRequestDetails) {
154
155                CodeValidationResult result = null;
156                startRequest(theServletRequest);
157                try {
158                        // TODO: JA why not just always just the chain here? and we can then get rid of the corresponding DAO method
159                        // entirely
160                        // If a Remote Terminology Server has been configured, use it
161                        if (myValidationSupportChain.isRemoteTerminologyServiceConfigured()) {
162                                String codeSystemUrl = (theCodeSystemUrl != null && theCodeSystemUrl.hasValue())
163                                                ? theCodeSystemUrl.getValueAsString()
164                                                : null;
165
166                                if (theCoding != null) {
167                                        if (isNotBlank(theCoding.getSystem())) {
168                                                if (codeSystemUrl != null && !codeSystemUrl.equalsIgnoreCase(theCoding.getSystem())) {
169                                                        throw new InvalidRequestException(Msg.code(1160) + "Coding.system '" + theCoding.getSystem()
170                                                                        + "' does not equal param url '" + theCodeSystemUrl
171                                                                        + "'. Unable to validate-code.");
172                                                }
173                                                codeSystemUrl = theCoding.getSystem();
174                                                String code = theCoding.getCode();
175                                                String display = theCoding.getDisplay();
176
177                                                result = validateCodeWithTerminologyService(codeSystemUrl, code, display)
178                                                                .orElseGet(supplyUnableToValidateResult(codeSystemUrl, code));
179                                        }
180                                }
181                        } else {
182                                // Otherwise, use the local DAO layer to validate the code
183                                IFhirResourceDaoCodeSystem dao = (IFhirResourceDaoCodeSystem) getDao();
184                                result = dao.validateCode(
185                                                theId,
186                                                theCodeSystemUrl,
187                                                theVersion,
188                                                theCode,
189                                                theDisplay,
190                                                theCoding,
191                                                theCodeableConcept,
192                                                theRequestDetails);
193                        }
194                        return toValidateCodeResult(getContext(), result);
195                } finally {
196                        endRequest(theServletRequest);
197                }
198        }
199
200        private Optional<CodeValidationResult> validateCodeWithTerminologyService(
201                        String theCodeSystemUrl, String theCode, String theDisplay) {
202                return Optional.ofNullable(myValidationSupportChain.validateCode(
203                                new ValidationSupportContext(myValidationSupportChain),
204                                new ConceptValidationOptions(),
205                                theCodeSystemUrl,
206                                theCode,
207                                theDisplay,
208                                null));
209        }
210
211        private Supplier<CodeValidationResult> supplyUnableToValidateResult(String theCodeSystemUrl, String theCode) {
212                return () -> new CodeValidationResult()
213                                .setMessage(
214                                                "Terminology service was unable to provide validation for " + theCodeSystemUrl + "#" + theCode);
215        }
216}