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