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