001/*-
002 * #%L
003 * HAPI FHIR - Core Library
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.interceptor.executor;
021
022import ca.uhn.fhir.i18n.Msg;
023import ca.uhn.fhir.interceptor.api.Hook;
024import ca.uhn.fhir.interceptor.api.HookParams;
025import ca.uhn.fhir.interceptor.api.IBaseInterceptorBroadcaster;
026import ca.uhn.fhir.interceptor.api.IBaseInterceptorService;
027import ca.uhn.fhir.interceptor.api.IPointcut;
028import ca.uhn.fhir.interceptor.api.Interceptor;
029import ca.uhn.fhir.interceptor.api.Pointcut;
030import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
031import ca.uhn.fhir.util.ReflectionUtil;
032import com.google.common.annotations.VisibleForTesting;
033import com.google.common.collect.ArrayListMultimap;
034import com.google.common.collect.ListMultimap;
035import io.opentelemetry.api.common.AttributeKey;
036import io.opentelemetry.api.trace.Span;
037import io.opentelemetry.instrumentation.annotations.WithSpan;
038import jakarta.annotation.Nonnull;
039import jakarta.annotation.Nullable;
040import org.apache.commons.lang3.Validate;
041import org.apache.commons.lang3.builder.ToStringBuilder;
042import org.apache.commons.lang3.builder.ToStringStyle;
043import org.apache.commons.lang3.reflect.MethodUtils;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import java.lang.annotation.Annotation;
048import java.lang.reflect.AnnotatedElement;
049import java.lang.reflect.InvocationTargetException;
050import java.lang.reflect.Method;
051import java.util.ArrayList;
052import java.util.Arrays;
053import java.util.Collection;
054import java.util.Collections;
055import java.util.Comparator;
056import java.util.EnumSet;
057import java.util.HashMap;
058import java.util.IdentityHashMap;
059import java.util.List;
060import java.util.Map;
061import java.util.Optional;
062import java.util.concurrent.atomic.AtomicInteger;
063import java.util.function.Predicate;
064import java.util.stream.Collectors;
065
066import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
067
068public abstract class BaseInterceptorService<POINTCUT extends Enum<POINTCUT> & IPointcut>
069                implements IBaseInterceptorService<POINTCUT>, IBaseInterceptorBroadcaster<POINTCUT> {
070        private static final Logger ourLog = LoggerFactory.getLogger(BaseInterceptorService.class);
071        private static final AttributeKey<String> OTEL_INTERCEPTOR_POINTCUT_NAME_ATT_KEY =
072                        AttributeKey.stringKey("hapifhir.interceptor.pointcut_name");
073        private static final AttributeKey<String> OTEL_INTERCEPTOR_CLASS_NAME_ATT_KEY =
074                        AttributeKey.stringKey("hapifhir.interceptor.class_name");
075        private static final AttributeKey<String> OTEL_INTERCEPTOR_METHOD_NAME_ATT_KEY =
076                        AttributeKey.stringKey("hapifhir.interceptor.method_name");
077
078        private final List<Object> myInterceptors = new ArrayList<>();
079        private final ListMultimap<POINTCUT, IInvoker> myGlobalInvokers = ArrayListMultimap.create();
080        private final ListMultimap<POINTCUT, IInvoker> myAnonymousInvokers = ArrayListMultimap.create();
081        private final Object myRegistryMutex = new Object();
082        private final Class<POINTCUT> myPointcutType;
083        private volatile EnumSet<POINTCUT> myRegisteredPointcuts;
084        private boolean myWarnOnInterceptorWithNoHooks = true;
085
086        /**
087         * Constructor which uses a default name of "default"
088         */
089        public BaseInterceptorService(Class<POINTCUT> thePointcutType) {
090                this(thePointcutType, "default");
091        }
092
093        /**
094         * Constructor
095         *
096         * @param theName The name for this registry (useful for troubleshooting)
097         * @deprecated The name parameter is not used for anything
098         */
099        @Deprecated(since = "8.0.0", forRemoval = true)
100        public BaseInterceptorService(Class<POINTCUT> thePointcutType, String theName) {
101                super();
102                myPointcutType = thePointcutType;
103                rebuildRegisteredPointcutSet();
104        }
105
106        /**
107         * Should a warning be issued if an interceptor is registered and it has no hooks
108         */
109        public void setWarnOnInterceptorWithNoHooks(boolean theWarnOnInterceptorWithNoHooks) {
110                myWarnOnInterceptorWithNoHooks = theWarnOnInterceptorWithNoHooks;
111        }
112
113        @VisibleForTesting
114        List<Object> getGlobalInterceptorsForUnitTest() {
115                return myInterceptors;
116        }
117
118        /**
119         * @deprecated This value is not used anywhere
120         */
121        @Deprecated(since = "8.0.0", forRemoval = true)
122        public void setName(@SuppressWarnings("unused") String theName) {
123                // nothing
124        }
125
126        protected void registerAnonymousInterceptor(POINTCUT thePointcut, Object theInterceptor, BaseInvoker theInvoker) {
127                Validate.notNull(thePointcut, "thePointcut must not be null");
128                Validate.notNull(theInterceptor, "theInterceptor must not be null");
129                synchronized (myRegistryMutex) {
130                        myAnonymousInvokers.put(thePointcut, theInvoker);
131                        if (!isInterceptorAlreadyRegistered(theInterceptor)) {
132                                myInterceptors.add(theInterceptor);
133                        }
134
135                        rebuildRegisteredPointcutSet();
136                }
137        }
138
139        @Override
140        public List<Object> getAllRegisteredInterceptors() {
141                synchronized (myRegistryMutex) {
142                        List<Object> retVal = new ArrayList<>(myInterceptors);
143                        return Collections.unmodifiableList(retVal);
144                }
145        }
146
147        @Override
148        @VisibleForTesting
149        public void unregisterAllInterceptors() {
150                synchronized (myRegistryMutex) {
151                        unregisterInterceptors(myAnonymousInvokers.values());
152                        unregisterInterceptors(myGlobalInvokers.values());
153                        unregisterInterceptors(myInterceptors);
154                }
155        }
156
157        @Override
158        public void unregisterInterceptors(@Nullable Collection<?> theInterceptors) {
159                if (theInterceptors != null) {
160                        // We construct a new list before iterating because the service's internal
161                        // interceptor lists get passed into this method, and we get concurrent
162                        // modification errors if we modify them at the same time as we iterate them
163                        new ArrayList<>(theInterceptors).forEach(this::unregisterInterceptor);
164                }
165        }
166
167        @Override
168        public void registerInterceptors(@Nullable Collection<?> theInterceptors) {
169                if (theInterceptors != null) {
170                        theInterceptors.forEach(this::registerInterceptor);
171                }
172        }
173
174        @Override
175        public void unregisterAllAnonymousInterceptors() {
176                synchronized (myRegistryMutex) {
177                        unregisterInterceptorsIf(t -> true, myAnonymousInvokers);
178                }
179        }
180
181        @Override
182        public void unregisterInterceptorsIf(Predicate<Object> theShouldUnregisterFunction) {
183                unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
184                unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
185        }
186
187        private void unregisterInterceptorsIf(
188                        Predicate<Object> theShouldUnregisterFunction, ListMultimap<POINTCUT, IInvoker> theGlobalInvokers) {
189                synchronized (myRegistryMutex) {
190                        for (Map.Entry<POINTCUT, IInvoker> nextInvoker : new ArrayList<>(theGlobalInvokers.entries())) {
191                                if (theShouldUnregisterFunction.test(nextInvoker.getValue().getInterceptor())) {
192                                        unregisterInterceptor(nextInvoker.getValue().getInterceptor());
193                                }
194                        }
195
196                        rebuildRegisteredPointcutSet();
197                }
198        }
199
200        @Override
201        public boolean registerInterceptor(Object theInterceptor) {
202                synchronized (myRegistryMutex) {
203                        if (isInterceptorAlreadyRegistered(theInterceptor)) {
204                                return false;
205                        }
206
207                        List<HookInvoker> addedInvokers = scanInterceptorAndAddToInvokerMultimap(theInterceptor, myGlobalInvokers);
208                        if (addedInvokers.isEmpty()) {
209                                if (myWarnOnInterceptorWithNoHooks) {
210                                        ourLog.warn(
211                                                        "Interceptor registered with no valid hooks - Type was: {}",
212                                                        theInterceptor.getClass().getName());
213                                }
214                                return false;
215                        }
216
217                        // Add to the global list
218                        myInterceptors.add(theInterceptor);
219                        sortByOrderAnnotation(myInterceptors);
220
221                        rebuildRegisteredPointcutSet();
222
223                        return true;
224                }
225        }
226
227        private void rebuildRegisteredPointcutSet() {
228                EnumSet<POINTCUT> registeredPointcuts = EnumSet.noneOf(myPointcutType);
229                registeredPointcuts.addAll(myAnonymousInvokers.keySet());
230                registeredPointcuts.addAll(myGlobalInvokers.keySet());
231                myRegisteredPointcuts = registeredPointcuts;
232        }
233
234        private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
235                for (Object next : myInterceptors) {
236                        if (next == theInterceptor) {
237                                return true;
238                        }
239                }
240                return false;
241        }
242
243        @Override
244        public boolean unregisterInterceptor(Object theInterceptor) {
245                synchronized (myRegistryMutex) {
246                        boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
247                        removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
248                        removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
249                        rebuildRegisteredPointcutSet();
250                        return removed;
251                }
252        }
253
254        private void sortByOrderAnnotation(List<Object> theObjects) {
255                IdentityHashMap<Object, Integer> interceptorToOrder = new IdentityHashMap<>();
256                for (Object next : theObjects) {
257                        Interceptor orderAnnotation = next.getClass().getAnnotation(Interceptor.class);
258                        int order = orderAnnotation != null ? orderAnnotation.order() : 0;
259                        interceptorToOrder.put(next, order);
260                }
261
262                theObjects.sort((a, b) -> {
263                        Integer orderA = interceptorToOrder.get(a);
264                        Integer orderB = interceptorToOrder.get(b);
265                        return orderA - orderB;
266                });
267        }
268
269        @Override
270        public Object callHooksAndReturnObject(POINTCUT thePointcut, HookParams theParams) {
271                assert haveAppropriateParams(thePointcut, theParams);
272                assert thePointcut.getReturnType() != void.class;
273
274                return doCallHooks(thePointcut, theParams);
275        }
276
277        @Override
278        public boolean hasHooks(POINTCUT thePointcut) {
279                return myRegisteredPointcuts.contains(thePointcut);
280        }
281
282        protected Class<?> getBooleanReturnType() {
283                return boolean.class;
284        }
285
286        @Override
287        public boolean callHooks(POINTCUT thePointcut, HookParams theParams) {
288                assert haveAppropriateParams(thePointcut, theParams);
289                assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == getBooleanReturnType();
290
291                Object retValObj = doCallHooks(thePointcut, theParams);
292                retValObj = defaultIfNull(retValObj, true);
293                return (Boolean) retValObj;
294        }
295
296        private Object doCallHooks(POINTCUT thePointcut, HookParams theParams) {
297                List<IInvoker> invokers = getInvokersForPointcut(thePointcut);
298                return callInvokers(thePointcut, theParams, invokers);
299        }
300
301        @VisibleForTesting
302        List<Object> getInterceptorsWithInvokersForPointcut(POINTCUT thePointcut) {
303                return getInvokersForPointcut(thePointcut).stream()
304                                .map(IInvoker::getInterceptor)
305                                .collect(Collectors.toList());
306        }
307
308        /**
309         * Returns a list of all invokers registered for the given pointcut. The list
310         * is ordered by the invoker order (specified on the {@link Interceptor#order()}
311         * and {@link Hook#order()} values.
312         *
313         * @return The list returned by this method will always be a newly created list, so it will be stable and can be modified.
314         */
315        @Override
316        public List<IInvoker> getInvokersForPointcut(POINTCUT thePointcut) {
317                List<IInvoker> invokers;
318
319                synchronized (myRegistryMutex) {
320                        List<IInvoker> globalInvokers = myGlobalInvokers.get(thePointcut);
321                        List<IInvoker> anonymousInvokers = myAnonymousInvokers.get(thePointcut);
322                        invokers = union(Arrays.asList(globalInvokers, anonymousInvokers));
323                }
324
325                return invokers;
326        }
327
328        /**
329         * Only call this when assertions are enabled, it's expensive
330         */
331        public static boolean haveAppropriateParams(IPointcut thePointcut, HookParams theParams) {
332                if (theParams.getParamsForType().values().size()
333                                != thePointcut.getParameterTypes().size()) {
334                        throw new IllegalArgumentException(Msg.code(1909)
335                                        + String.format(
336                                                        "Wrong number of params for pointcut %s - Wanted %s but found %s",
337                                                        thePointcut.name(),
338                                                        toErrorString(thePointcut.getParameterTypes()),
339                                                        theParams.getParamsForType().values().stream()
340                                                                        .map(t -> t != null ? t.getClass().getSimpleName() : "null")
341                                                                        .sorted()
342                                                                        .collect(Collectors.toList())));
343                }
344
345                List<String> wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
346
347                ListMultimap<Class<?>, Object> givenTypes = theParams.getParamsForType();
348                for (Class<?> nextTypeClass : givenTypes.keySet()) {
349                        String nextTypeName = nextTypeClass.getName();
350                        for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
351                                Validate.isTrue(
352                                                nextParamValue == null || nextTypeClass.isAssignableFrom(nextParamValue.getClass()),
353                                                "Invalid params for pointcut %s - %s is not of type %s",
354                                                thePointcut.name(),
355                                                nextParamValue != null ? nextParamValue.getClass() : "null",
356                                                nextTypeClass);
357                                Validate.isTrue(
358                                                wantedTypes.remove(nextTypeName),
359                                                "Invalid params for pointcut %s - Wanted %s but found %s",
360                                                thePointcut.name(),
361                                                toErrorString(thePointcut.getParameterTypes()),
362                                                nextTypeName);
363                        }
364                }
365
366                return true;
367        }
368
369        private List<HookInvoker> scanInterceptorAndAddToInvokerMultimap(
370                        Object theInterceptor, ListMultimap<POINTCUT, IInvoker> theInvokers) {
371                Class<?> interceptorClass = theInterceptor.getClass();
372                int typeOrder = determineOrder(interceptorClass);
373
374                List<HookInvoker> addedInvokers = scanInterceptorForHookMethods(theInterceptor, typeOrder);
375
376                // Invoke the REGISTERED pointcut for any added hooks
377                addedInvokers.stream()
378                                .filter(t -> Pointcut.INTERCEPTOR_REGISTERED.equals(t.getPointcut()))
379                                .forEach(t -> t.invoke(new HookParams()));
380
381                // Register the interceptor and its various hooks
382                for (HookInvoker nextAddedHook : addedInvokers) {
383                        POINTCUT nextPointcut = nextAddedHook.getPointcut();
384                        if (nextPointcut.equals(Pointcut.INTERCEPTOR_REGISTERED)) {
385                                continue;
386                        }
387                        theInvokers.put(nextPointcut, nextAddedHook);
388                }
389
390                // Make sure we're always sorted according to the order declared in @Order
391                for (POINTCUT nextPointcut : theInvokers.keys()) {
392                        List<IInvoker> nextInvokerList = theInvokers.get(nextPointcut);
393                        nextInvokerList.sort(Comparator.naturalOrder());
394                }
395
396                return addedInvokers;
397        }
398
399        /**
400         * @return Returns a list of any added invokers
401         */
402        private List<HookInvoker> scanInterceptorForHookMethods(Object theInterceptor, int theTypeOrder) {
403                ArrayList<HookInvoker> retVal = new ArrayList<>();
404                for (Method nextMethod : ReflectionUtil.getDeclaredMethods(theInterceptor.getClass(), true)) {
405                        Optional<HookDescriptor> hook = scanForHook(nextMethod);
406
407                        if (hook.isPresent()) {
408                                int methodOrder = theTypeOrder;
409                                int methodOrderAnnotation = hook.get().getOrder();
410                                if (methodOrderAnnotation != Interceptor.DEFAULT_ORDER) {
411                                        methodOrder = methodOrderAnnotation;
412                                }
413
414                                retVal.add(new HookInvoker(hook.get(), theInterceptor, nextMethod, methodOrder));
415                        }
416                }
417
418                return retVal;
419        }
420
421        protected abstract Optional<HookDescriptor> scanForHook(Method nextMethod);
422
423        public static Object callInvokers(IPointcut thePointcut, HookParams theParams, List<IInvoker> invokers) {
424
425                Object retVal = null;
426
427                /*
428                 * Call each hook in order
429                 */
430                for (IInvoker nextInvoker : invokers) {
431                        Object nextOutcome = nextInvoker.invoke(theParams);
432                        Class<?> pointcutReturnType = thePointcut.getReturnType();
433                        if (pointcutReturnType.equals(thePointcut.getBooleanReturnTypeForEnum())) {
434                                Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
435                                if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
436                                        ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
437                                        retVal = false;
438                                        break;
439                                } else {
440                                        retVal = true;
441                                }
442                        } else if (!pointcutReturnType.equals(void.class)) {
443                                if (nextOutcome != null) {
444                                        retVal = nextOutcome;
445                                        break;
446                                }
447                        }
448                }
449
450                return retVal;
451        }
452
453        /**
454         * First argument must be the global invoker list!!
455         */
456        public static List<IInvoker> union(List<List<IInvoker>> theInvokersLists) {
457                List<IInvoker> haveOne = null;
458                boolean haveMultiple = false;
459                for (List<IInvoker> nextInvokerList : theInvokersLists) {
460                        if (nextInvokerList == null || nextInvokerList.isEmpty()) {
461                                continue;
462                        }
463
464                        if (haveOne == null) {
465                                haveOne = nextInvokerList;
466                        } else {
467                                haveMultiple = true;
468                        }
469                }
470
471                if (haveOne == null) {
472                        return Collections.emptyList();
473                }
474
475                List<IInvoker> retVal;
476
477                if (!haveMultiple) {
478
479                        // The global list doesn't need to be sorted every time since it's sorted on
480                        // insertion each time. Doing so is a waste of cycles..
481                        if (haveOne == theInvokersLists.get(0)) {
482                                retVal = haveOne;
483                        } else {
484                                retVal = new ArrayList<>(haveOne);
485                                retVal.sort(Comparator.naturalOrder());
486                        }
487
488                } else {
489
490                        int totalSize = 0;
491                        for (List<IInvoker> list : theInvokersLists) {
492                                totalSize += list.size();
493                        }
494                        retVal = new ArrayList<>(totalSize);
495                        for (List<IInvoker> list : theInvokersLists) {
496                                retVal.addAll(list);
497                        }
498                        retVal.sort(Comparator.naturalOrder());
499                }
500
501                return retVal;
502        }
503
504        protected static <T extends Annotation> Optional<T> findAnnotation(
505                        AnnotatedElement theObject, Class<T> theHookClass) {
506                T annotation;
507                if (theObject instanceof Method) {
508                        annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
509                } else {
510                        annotation = theObject.getAnnotation(theHookClass);
511                }
512                return Optional.ofNullable(annotation);
513        }
514
515        private static int determineOrder(Class<?> theInterceptorClass) {
516                return findAnnotation(theInterceptorClass, Interceptor.class)
517                                .map(Interceptor::order)
518                                .orElse(Interceptor.DEFAULT_ORDER);
519        }
520
521        private static String toErrorString(List<String> theParameterTypes) {
522                return theParameterTypes.stream().sorted().collect(Collectors.joining(","));
523        }
524
525        private class HookInvoker extends BaseInvoker {
526
527                private final Method myMethod;
528                private final Class<?>[] myParameterTypes;
529                private final int[] myParameterIndexes;
530                private final POINTCUT myPointcut;
531
532                /**
533                 * Constructor
534                 */
535                private HookInvoker(
536                                HookDescriptor theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
537                        super(theInterceptor, theOrder);
538                        myPointcut = theHook.getPointcut();
539                        myParameterTypes = theHookMethod.getParameterTypes();
540                        myMethod = theHookMethod;
541
542                        Class<?> returnType = theHookMethod.getReturnType();
543                        if (myPointcut.getReturnType().equals(myPointcut.getBooleanReturnTypeForEnum())) {
544                                Validate.isTrue(
545                                                myPointcut.getBooleanReturnTypeForEnum().equals(returnType) || void.class.equals(returnType),
546                                                "Method does not return %s or void: %s",
547                                                myPointcut.getBooleanReturnTypeForEnum().getSimpleName(),
548                                                theHookMethod);
549                        } else if (myPointcut.getReturnType().equals(void.class)) {
550                                Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
551                        } else {
552                                Validate.isTrue(
553                                                myPointcut.getReturnType().isAssignableFrom(returnType) || void.class.equals(returnType),
554                                                "Method does not return %s or void: %s",
555                                                myPointcut.getReturnType(),
556                                                theHookMethod);
557                        }
558
559                        myParameterIndexes = new int[myParameterTypes.length];
560                        Map<Class<?>, AtomicInteger> typeToCount = new HashMap<>();
561                        for (int i = 0; i < myParameterTypes.length; i++) {
562                                AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
563                                myParameterIndexes[i] = counter.getAndIncrement();
564                        }
565
566                        myMethod.setAccessible(true);
567                }
568
569                @Override
570                public String toString() {
571                        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
572                                        .append("method", myMethod)
573                                        .toString();
574                }
575
576                public POINTCUT getPointcut() {
577                        return myPointcut;
578                }
579
580                /**
581                 * @return Returns true/false if the hook method returns a boolean, returns true otherwise
582                 */
583                @Override
584                public Object invoke(HookParams theParams) {
585
586                        Object[] args = new Object[myParameterTypes.length];
587                        for (int i = 0; i < myParameterTypes.length; i++) {
588                                Class<?> nextParamType = myParameterTypes[i];
589                                if (nextParamType.equals(Pointcut.class)) {
590                                        args[i] = myPointcut;
591                                } else {
592                                        int nextParamIndex = myParameterIndexes[i];
593                                        Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
594                                        args[i] = nextParamValue;
595                                }
596                        }
597
598                        // Invoke the method
599                        try {
600                                return invokeMethod(args);
601                        } catch (InvocationTargetException e) {
602                                Throwable targetException = e.getTargetException();
603                                if (myPointcut.isShouldLogAndSwallowException(targetException)) {
604                                        ourLog.error("Exception thrown by interceptor: " + targetException.toString(), targetException);
605                                        return null;
606                                }
607
608                                if (targetException instanceof RuntimeException) {
609                                        throw ((RuntimeException) targetException);
610                                } else {
611                                        throw new InternalErrorException(
612                                                        Msg.code(1910) + "Failure invoking interceptor for pointcut(s) " + getPointcut(),
613                                                        targetException);
614                                }
615                        } catch (Exception e) {
616                                throw new InternalErrorException(Msg.code(1911) + e);
617                        }
618                }
619
620                @WithSpan("hapifhir.interceptor")
621                private Object invokeMethod(Object[] args) throws InvocationTargetException, IllegalAccessException {
622                        // Add attributes to the opentelemetry span
623                        Span currentSpan = Span.current();
624                        currentSpan.setAttribute(OTEL_INTERCEPTOR_POINTCUT_NAME_ATT_KEY, myPointcut.name());
625                        currentSpan.setAttribute(
626                                        OTEL_INTERCEPTOR_CLASS_NAME_ATT_KEY,
627                                        myMethod.getDeclaringClass().getName());
628                        currentSpan.setAttribute(OTEL_INTERCEPTOR_METHOD_NAME_ATT_KEY, myMethod.getName());
629
630                        return myMethod.invoke(getInterceptor(), args);
631                }
632        }
633
634        protected class HookDescriptor {
635
636                private final POINTCUT myPointcut;
637                private final int myOrder;
638
639                public HookDescriptor(POINTCUT thePointcut, int theOrder) {
640                        myPointcut = thePointcut;
641                        myOrder = theOrder;
642                }
643
644                POINTCUT getPointcut() {
645                        return myPointcut;
646                }
647
648                int getOrder() {
649                        return myOrder;
650                }
651        }
652
653        public abstract static class BaseInvoker implements IInvoker {
654
655                private final int myOrder;
656                private final Object myInterceptor;
657
658                BaseInvoker(Object theInterceptor, int theOrder) {
659                        myInterceptor = theInterceptor;
660                        myOrder = theOrder;
661                }
662
663                @Override
664                public Object getInterceptor() {
665                        return myInterceptor;
666                }
667
668                @Override
669                public int getOrder() {
670                        return myOrder;
671                }
672
673                @Override
674                public int compareTo(IInvoker theInvoker) {
675                        return myOrder - theInvoker.getOrder();
676                }
677        }
678}