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