001/*-
002 * #%L
003 * HAPI FHIR - Server Framework
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.rest.server.interceptor;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
024import ca.uhn.fhir.fhirpath.IFhirPath;
025import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
026import ca.uhn.fhir.i18n.Msg;
027import ca.uhn.fhir.interceptor.api.Hook;
028import ca.uhn.fhir.interceptor.api.Pointcut;
029import ca.uhn.fhir.rest.api.Constants;
030import ca.uhn.fhir.rest.api.server.RequestDetails;
031import ca.uhn.fhir.rest.api.server.ResponseDetails;
032import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
033import ca.uhn.fhir.util.BundleUtil;
034import ca.uhn.fhir.util.ParametersUtil;
035import jakarta.annotation.Nonnull;
036import jakarta.annotation.Nullable;
037import org.hl7.fhir.instance.model.api.IBase;
038import org.hl7.fhir.instance.model.api.IBaseParameters;
039import org.hl7.fhir.instance.model.api.IBaseResource;
040import org.hl7.fhir.instance.model.api.IIdType;
041
042import java.util.List;
043
044import static org.apache.commons.lang3.StringUtils.isNotBlank;
045
046/**
047 * This interceptor looks for a URL parameter on requests called <code>_fhirpath</code> and
048 * replaces the resource being returned with a Parameters resource containing the results of
049 * the given FHIRPath expression evaluated against the resource that would otherwise
050 * have been returned.
051 *
052 * @see <a href="https://hapifhir.io/hapi-fhir/docs/interceptors/built_in_server_interceptors.html#response-customizing-evaluate-fhirpath">Interceptors - Response Customization: Evaluate FHIRPath</a>
053 * @since 5.0.0
054 */
055public class FhirPathFilterInterceptor {
056
057        @Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
058        public void preProcessOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails) {
059                IBaseResource responseResource = theResponseDetails.getResponseResource();
060                if (responseResource != null) {
061                        String[] fhirPathParams = theRequestDetails.getParameters().get(Constants.PARAM_FHIRPATH);
062                        if (fhirPathParams != null) {
063
064                                FhirContext ctx = theRequestDetails.getFhirContext();
065                                IBaseParameters responseParameters = ParametersUtil.newInstance(ctx);
066
067                                for (String expression : fhirPathParams) {
068                                        if (isNotBlank(expression)) {
069                                                IBase resultPart = ParametersUtil.addParameterToParameters(ctx, responseParameters, "result");
070                                                ParametersUtil.addPartString(ctx, resultPart, "expression", expression);
071
072                                                IFhirPath fhirPath = ctx.newFhirPath();
073                                                fhirPath.setEvaluationContext(new IFhirPathEvaluationContext() {
074                                                        @Override
075                                                        public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
076                                                                return BundleUtil.getReferenceInBundle(ctx, theReference.getValue(), responseResource);
077                                                        }
078                                                });
079                                                List<IBase> outputs;
080                                                try {
081                                                        outputs = fhirPath.evaluate(responseResource, expression, IBase.class);
082                                                } catch (FhirPathExecutionException e) {
083                                                        throw new InvalidRequestException(
084                                                                        Msg.code(327) + "Error parsing FHIRPath expression: " + e.getMessage());
085                                                }
086
087                                                for (IBase nextOutput : outputs) {
088                                                        if (nextOutput instanceof IBaseResource) {
089                                                                ParametersUtil.addPartResource(ctx, resultPart, "result", (IBaseResource) nextOutput);
090                                                        } else {
091                                                                ParametersUtil.addPart(ctx, resultPart, "result", nextOutput);
092                                                        }
093                                                }
094                                        }
095                                }
096
097                                theResponseDetails.setResponseResource(responseParameters);
098                        }
099                }
100        }
101}