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}