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}