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