001/*- 002 * #%L 003 * HAPI FHIR - Converter 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.hapi.converters.server; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.FhirVersionEnum; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.interceptor.api.Interceptor; 026import ca.uhn.fhir.model.api.IResource; 027import ca.uhn.fhir.rest.api.Constants; 028import ca.uhn.fhir.rest.api.server.RequestDetails; 029import ca.uhn.fhir.rest.api.server.ResponseDetails; 030import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; 031import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 032import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; 033import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationConstants; 034import jakarta.servlet.http.HttpServletRequest; 035import jakarta.servlet.http.HttpServletResponse; 036import org.hl7.fhir.converter.NullVersionConverterAdvisor10_30; 037import org.hl7.fhir.converter.NullVersionConverterAdvisor10_40; 038import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_30; 039import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_40; 040import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40; 041import org.hl7.fhir.dstu3.model.Resource; 042import org.hl7.fhir.exceptions.FHIRException; 043import org.hl7.fhir.instance.model.api.IBaseResource; 044 045import java.util.StringTokenizer; 046 047import static org.apache.commons.lang3.StringUtils.defaultString; 048import static org.apache.commons.lang3.StringUtils.isBlank; 049import static org.apache.commons.lang3.StringUtils.isNotBlank; 050 051/** 052 * <b>This is an experimental interceptor! Use with caution as 053 * behaviour may change or be removed in a future version of 054 * FHIR.</b> 055 * <p> 056 * This interceptor partially implements the proposed 057 * Versioned API features. 058 * </p> 059 */ 060@Interceptor(order = AuthorizationConstants.ORDER_CONVERTER_INTERCEPTOR) 061public class VersionedApiConverterInterceptor extends InterceptorAdapter { 062 private final FhirContext myCtxDstu2; 063 private final FhirContext myCtxDstu2Hl7Org; 064 private final NullVersionConverterAdvisor10_40 advisor40; 065 private final NullVersionConverterAdvisor10_30 advisor30; 066 067 public VersionedApiConverterInterceptor() { 068 advisor40 = new NullVersionConverterAdvisor10_40(); 069 advisor30 = new NullVersionConverterAdvisor10_30(); 070 071 myCtxDstu2 = FhirContext.forDstu2(); 072 myCtxDstu2Hl7Org = FhirContext.forDstu2Hl7Org(); 073 } 074 075 @Override 076 public boolean outgoingResponse( 077 RequestDetails theRequestDetails, 078 ResponseDetails theResponseDetails, 079 HttpServletRequest theServletRequest, 080 HttpServletResponse theServletResponse) 081 throws AuthenticationException { 082 IBaseResource responseResource = theResponseDetails.getResponseResource(); 083 if (responseResource == null) { 084 return true; 085 } 086 087 String[] formatParams = theRequestDetails.getParameters().get(Constants.PARAM_FORMAT); 088 String accept = null; 089 if (formatParams != null && formatParams.length > 0) { 090 accept = formatParams[0]; 091 } 092 if (isBlank(accept)) { 093 accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT)); 094 } 095 StringTokenizer tok = new StringTokenizer(accept, ";"); 096 String wantVersionString = null; 097 while (tok.hasMoreTokens()) { 098 String next = tok.nextToken().trim(); 099 if (next.startsWith("fhirVersion=")) { 100 wantVersionString = next.substring("fhirVersion=".length()).trim(); 101 break; 102 } 103 } 104 105 FhirVersionEnum wantVersion = null; 106 if (isNotBlank(wantVersionString)) { 107 wantVersion = FhirVersionEnum.forVersionString(wantVersionString); 108 } 109 110 FhirVersionEnum haveVersion = responseResource.getStructureFhirVersionEnum(); 111 112 IBaseResource converted = null; 113 try { 114 if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU3) { 115 converted = VersionConvertorFactory_30_40.convertResource(toDstu3(responseResource)); 116 } else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) { 117 converted = VersionConvertorFactory_30_40.convertResource(toR4(responseResource)); 118 } else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.R4) { 119 converted = VersionConvertorFactory_10_40.convertResource(toR4(responseResource), advisor40); 120 } else if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU2) { 121 converted = VersionConvertorFactory_10_40.convertResource(toDstu2(responseResource), advisor40); 122 } else if (wantVersion == FhirVersionEnum.DSTU2 && haveVersion == FhirVersionEnum.DSTU3) { 123 converted = VersionConvertorFactory_10_30.convertResource(toDstu3(responseResource), advisor30); 124 } else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.DSTU2) { 125 converted = VersionConvertorFactory_10_30.convertResource(toDstu2(responseResource), advisor30); 126 } 127 } catch (FHIRException e) { 128 throw new InternalErrorException(Msg.code(73) + e); 129 } 130 131 if (converted != null) { 132 theResponseDetails.setResponseResource(converted); 133 } 134 135 return true; 136 } 137 138 private org.hl7.fhir.dstu2.model.Resource toDstu2(IBaseResource theResponseResource) { 139 if (theResponseResource instanceof IResource) { 140 return (org.hl7.fhir.dstu2.model.Resource) myCtxDstu2Hl7Org 141 .newJsonParser() 142 .parseResource(myCtxDstu2.newJsonParser().encodeResourceToString(theResponseResource)); 143 } 144 return (org.hl7.fhir.dstu2.model.Resource) theResponseResource; 145 } 146 147 private Resource toDstu3(IBaseResource theResponseResource) { 148 return (Resource) theResponseResource; 149 } 150 151 private org.hl7.fhir.r4.model.Resource toR4(IBaseResource theResponseResource) { 152 return (org.hl7.fhir.r4.model.Resource) theResponseResource; 153 } 154}