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