001/*-
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2025 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.util;
021
022import ca.uhn.fhir.interceptor.api.Hook;
023import ca.uhn.fhir.interceptor.api.HookParams;
024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
025import ca.uhn.fhir.interceptor.api.Pointcut;
026import ca.uhn.fhir.interceptor.executor.BaseInterceptorService;
027import ca.uhn.fhir.rest.api.server.RequestDetails;
028import jakarta.annotation.Nonnull;
029import jakarta.annotation.Nullable;
030
031import java.util.ArrayList;
032import java.util.Arrays;
033import java.util.Collection;
034import java.util.Comparator;
035import java.util.List;
036import java.util.Objects;
037import java.util.stream.Collectors;
038
039import static org.apache.commons.lang3.ObjectUtils.getIfNull;
040
041/**
042 * This is an {@link IInterceptorBroadcaster} which combines multiple interceptor
043 * broadcasters. Hook methods are called across all broadcasters, respecting
044 * the {@link Hook#order()} across all broadcasters.
045 */
046public class CompositeInterceptorBroadcaster implements IInterceptorBroadcaster {
047
048        private final List<IInterceptorBroadcaster> myServices;
049
050        /**
051         * Constructor
052         */
053        private CompositeInterceptorBroadcaster(Collection<IInterceptorBroadcaster> theServices) {
054                myServices = theServices.stream().filter(Objects::nonNull).collect(Collectors.toList());
055        }
056
057        @Override
058        public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
059                assert BaseInterceptorService.haveAppropriateParams(thePointcut, theParams);
060                assert thePointcut.getReturnType() == void.class
061                                || thePointcut.getReturnType() == thePointcut.getBooleanReturnTypeForEnum();
062
063                List<IInvoker> invokers = getInvokersForPointcut(thePointcut);
064                Object retVal = BaseInterceptorService.callInvokers(thePointcut, theParams, invokers);
065                retVal = getIfNull(retVal, true);
066                return (Boolean) retVal;
067        }
068
069        @Override
070        public Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams) {
071                assert BaseInterceptorService.haveAppropriateParams(thePointcut, theParams);
072                assert thePointcut.getReturnType() != void.class;
073
074                List<IInvoker> invokers = getInvokersForPointcut(thePointcut);
075                return BaseInterceptorService.callInvokers(thePointcut, theParams, invokers);
076        }
077
078        @Override
079        @Nonnull
080        public List<IInvoker> getInvokersForPointcut(Pointcut thePointcut) {
081                List<IInvoker> invokers = new ArrayList<>();
082                for (IInterceptorBroadcaster services : myServices) {
083                        if (services.hasHooks(thePointcut)) {
084                                List<IInvoker> serviceInvokers = services.getInvokersForPointcut(thePointcut);
085                                assert serviceInvokers != null;
086                                invokers.addAll(serviceInvokers);
087                        }
088                }
089                invokers.sort(Comparator.naturalOrder());
090                return invokers;
091        }
092
093        @Override
094        public boolean hasHooks(Pointcut thePointcut) {
095                for (IInterceptorBroadcaster service : myServices) {
096                        if (service.hasHooks(thePointcut)) {
097                                return true;
098                        }
099                }
100                return false;
101        }
102
103        /**
104         * @since 8.0.0
105         */
106        public static IInterceptorBroadcaster newCompositeBroadcaster(IInterceptorBroadcaster... theServices) {
107                return new CompositeInterceptorBroadcaster(Arrays.asList(theServices));
108        }
109
110        /**
111         * @since 5.5.0
112         */
113        public static IInterceptorBroadcaster newCompositeBroadcaster(
114                        @Nonnull IInterceptorBroadcaster theInterceptorBroadcaster, @Nullable RequestDetails theRequestDetails) {
115                assert theInterceptorBroadcaster != null;
116                if (theRequestDetails != null) {
117                        IInterceptorBroadcaster requestBroadcaster = theRequestDetails.getInterceptorBroadcaster();
118                        if (requestBroadcaster != null) {
119                                return newCompositeBroadcaster(theInterceptorBroadcaster, requestBroadcaster);
120                        }
121                }
122
123                return newCompositeBroadcaster(theInterceptorBroadcaster);
124        }
125}