
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}