
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}