From 522efc87d91001e7c03bcd6b66ce7996686f7a63 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Fri, 22 Jan 2021 19:17:08 -0500
Subject: [PATCH] Reusable interceptor service (#2318)
* Reusable interceptor service
* Azure fix
---
azure-pipelines.yml | 2 +-
.../api/IBaseInterceptorBroadcaster.java | 50 ++
.../api/IBaseInterceptorService.java | 94 +++
.../api/IInterceptorBroadcaster.java | 27 +-
.../interceptor/api/IInterceptorService.java | 72 +-
.../uhn/fhir/interceptor/api/IPointcut.java | 17 +
.../ca/uhn/fhir/interceptor/api/Pointcut.java | 5 +-
.../executor/BaseInterceptorService.java | 621 ++++++++++++++++++
.../executor/InterceptorService.java | 537 +--------------
9 files changed, 797 insertions(+), 628 deletions(-)
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorService.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IPointcut.java
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index b026fee6b54..04c4ea0b33d 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -52,7 +52,7 @@ jobs:
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/'
artifactName: 'full_logs.zip'
- - script: bash <(curl https://codecov.io/bash) -t $(CODECOV_TOKEN)
+ - script: bash <(curl https://codecov.io/bash) -C $(Build.SourceVersion)
displayName: 'codecov'
- task: PublishTestResults@2
inputs:
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
new file mode 100644
index 00000000000..404112fa2e1
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorBroadcaster.java
@@ -0,0 +1,50 @@
+package ca.uhn.fhir.interceptor.api;
+
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public interface IBaseInterceptorBroadcaster {
+
+ /**
+ * Invoke registered interceptor hook methods for the given Pointcut.
+ *
+ * @return Returns false if any of the invoked hook methods returned
+ * false, and returns true otherwise.
+ */
+ boolean callHooks(POINTCUT thePointcut, HookParams theParams);
+
+ /**
+ * Invoke registered interceptor hook methods for the given Pointcut. This method
+ * should only be called for pointcuts that return a type other than
+ * void or boolean
+ *
+ * @return Returns the object returned by the first hook method that did not return null
+ */
+ Object callHooksAndReturnObject(POINTCUT thePointcut, HookParams theParams);
+
+ /**
+ * Does this broadcaster have any hooks for the given pointcut?
+ *
+ * @param thePointcut The poointcut
+ * @return Does this broadcaster have any hooks for the given pointcut?
+ * @since 4.0.0
+ */
+ boolean hasHooks(POINTCUT thePointcut);
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorService.java
new file mode 100644
index 00000000000..b6efd6b7132
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/IBaseInterceptorService.java
@@ -0,0 +1,94 @@
+package ca.uhn.fhir.interceptor.api;
+
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import javax.annotation.Nullable;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+
+public interface IBaseInterceptorService extends IBaseInterceptorBroadcaster {
+
+ /**
+ * Register an interceptor that will be used in a {@link ThreadLocal} context.
+ * This means that events will only be broadcast to the given interceptor if
+ * they were fired from the current thread.
+ *
+ * Note that it is almost always desirable to call this method with a
+ * try-finally statement that removes the interceptor afterwards, since
+ * this can lead to memory leakage, poor performance due to ever-increasing
+ * numbers of interceptors, etc.
+ *
+ *
+ * Note that most methods such as {@link #getAllRegisteredInterceptors()} and
+ * {@link #unregisterAllInterceptors()} do not affect thread local interceptors
+ * as they are kept in a separate list.
+ *
+ *
+ * @param theInterceptor The interceptor
+ * @return Returns true if at least one valid hook method was found on this interceptor
+ */
+ boolean registerThreadLocalInterceptor(Object theInterceptor);
+
+ /**
+ * Unregisters a ThreadLocal interceptor
+ *
+ * @param theInterceptor The interceptor
+ * @see #registerThreadLocalInterceptor(Object)
+ */
+ void unregisterThreadLocalInterceptor(Object theInterceptor);
+
+ /**
+ * Register an interceptor. This method has no effect if the given interceptor is already registered.
+ *
+ * @param theInterceptor The interceptor to register
+ * @return Returns true if at least one valid hook method was found on this interceptor
+ */
+ boolean registerInterceptor(Object theInterceptor);
+
+ /**
+ * Unregister an interceptor. This method has no effect if the given interceptor is not already registered.
+ *
+ * @param theInterceptor The interceptor to unregister
+ * @return Returns true if the interceptor was found and removed
+ */
+ boolean unregisterInterceptor(Object theInterceptor);
+
+ /**
+ * Returns all currently registered interceptors (excluding any thread local interceptors).
+ */
+ List
*/
-public enum Pointcut {
+public enum Pointcut implements IPointcut {
/**
* Interceptor Framework Hook:
@@ -2178,6 +2178,7 @@ public enum Pointcut {
this(theReturnType, new ExceptionHandlingSpec(), theParameterTypes);
}
+ @Override
public boolean isShouldLogAndSwallowException(@Nonnull Throwable theException) {
for (Class extends Throwable> next : myExceptionHandlingSpec.myTypesToLogAndSwallow) {
if (next.isAssignableFrom(theException.getClass())) {
@@ -2187,11 +2188,13 @@ public enum Pointcut {
return false;
}
+ @Override
@Nonnull
public Class> getReturnType() {
return myReturnType;
}
+ @Override
@Nonnull
public List getParameterTypes() {
return myParameterTypes;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
new file mode 100644
index 00000000000..4458c7d6a91
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/BaseInterceptorService.java
@@ -0,0 +1,621 @@
+package ca.uhn.fhir.interceptor.executor;
+
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2021 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.HookParams;
+import ca.uhn.fhir.interceptor.api.IBaseInterceptorBroadcaster;
+import ca.uhn.fhir.interceptor.api.IBaseInterceptorService;
+import ca.uhn.fhir.interceptor.api.IPointcut;
+import ca.uhn.fhir.interceptor.api.Interceptor;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.util.ReflectionUtil;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public abstract class BaseInterceptorService implements IBaseInterceptorService, IBaseInterceptorBroadcaster {
+ private static final Logger ourLog = LoggerFactory.getLogger(BaseInterceptorService.class);
+ private final List myInterceptors = new ArrayList<>();
+ private final ListMultimap myGlobalInvokers = ArrayListMultimap.create();
+ private final ListMultimap myAnonymousInvokers = ArrayListMultimap.create();
+ private final Object myRegistryMutex = new Object();
+ private final ThreadLocal> myThreadlocalInvokers = new ThreadLocal<>();
+ private String myName;
+ private boolean myThreadlocalInvokersEnabled = true;
+
+ /**
+ * Constructor which uses a default name of "default"
+ */
+ public BaseInterceptorService() {
+ this("default");
+ }
+
+ /**
+ * Constructor
+ *
+ * @param theName The name for this registry (useful for troubleshooting)
+ */
+ public BaseInterceptorService(String theName) {
+ super();
+ myName = theName;
+ }
+
+ /**
+ * Are threadlocal interceptors enabled on this registry (defaults to true)
+ */
+ public boolean isThreadlocalInvokersEnabled() {
+ return myThreadlocalInvokersEnabled;
+ }
+
+ /**
+ * Are threadlocal interceptors enabled on this registry (defaults to true)
+ */
+ public void setThreadlocalInvokersEnabled(boolean theThreadlocalInvokersEnabled) {
+ myThreadlocalInvokersEnabled = theThreadlocalInvokersEnabled;
+ }
+
+ @VisibleForTesting
+ List getGlobalInterceptorsForUnitTest() {
+ return myInterceptors;
+ }
+
+ public void setName(String theName) {
+ myName = theName;
+ }
+
+ protected void registerAnonymousInterceptor(POINTCUT thePointcut, Object theInterceptor, BaseInvoker theInvoker) {
+ Validate.notNull(thePointcut);
+ Validate.notNull(theInterceptor);
+ synchronized (myRegistryMutex) {
+
+ myAnonymousInvokers.put(thePointcut, theInvoker);
+ if (!isInterceptorAlreadyRegistered(theInterceptor)) {
+ myInterceptors.add(theInterceptor);
+ }
+ }
+ }
+
+ @Override
+ public List getAllRegisteredInterceptors() {
+ synchronized (myRegistryMutex) {
+ List retVal = new ArrayList<>();
+ retVal.addAll(myInterceptors);
+ return Collections.unmodifiableList(retVal);
+ }
+ }
+
+ @Override
+ @VisibleForTesting
+ public void unregisterAllInterceptors() {
+ synchronized (myRegistryMutex) {
+ myAnonymousInvokers.clear();
+ myGlobalInvokers.clear();
+ myInterceptors.clear();
+ }
+ }
+
+ @Override
+ public void unregisterInterceptors(@Nullable Collection> theInterceptors) {
+ if (theInterceptors != null) {
+ theInterceptors.forEach(t -> unregisterInterceptor(t));
+ }
+ }
+
+ @Override
+ public void registerInterceptors(@Nullable Collection> theInterceptors) {
+ if (theInterceptors != null) {
+ theInterceptors.forEach(t -> registerInterceptor(t));
+ }
+ }
+
+ @Override
+ public void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction) {
+ unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
+ unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
+ }
+
+ private void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction, ListMultimap theGlobalInvokers) {
+ theGlobalInvokers.entries().removeIf(t -> theShouldUnregisterFunction.test(t.getValue().getInterceptor()));
+ }
+
+ @Override
+ public boolean registerThreadLocalInterceptor(Object theInterceptor) {
+ if (!myThreadlocalInvokersEnabled) {
+ return false;
+ }
+ ListMultimap invokers = getThreadLocalInvokerMultimap();
+ scanInterceptorAndAddToInvokerMultimap(theInterceptor, invokers);
+ return !invokers.isEmpty();
+
+ }
+
+ @Override
+ public void unregisterThreadLocalInterceptor(Object theInterceptor) {
+ if (myThreadlocalInvokersEnabled) {
+ ListMultimap invokers = getThreadLocalInvokerMultimap();
+ invokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
+ if (invokers.isEmpty()) {
+ myThreadlocalInvokers.remove();
+ }
+ }
+ }
+
+ private ListMultimap getThreadLocalInvokerMultimap() {
+ ListMultimap invokers = myThreadlocalInvokers.get();
+ if (invokers == null) {
+ invokers = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
+ myThreadlocalInvokers.set(invokers);
+ }
+ return invokers;
+ }
+
+ @Override
+ public boolean registerInterceptor(Object theInterceptor) {
+ synchronized (myRegistryMutex) {
+
+ if (isInterceptorAlreadyRegistered(theInterceptor)) {
+ return false;
+ }
+
+ List addedInvokers = scanInterceptorAndAddToInvokerMultimap(theInterceptor, myGlobalInvokers);
+ if (addedInvokers.isEmpty()) {
+ ourLog.warn("Interceptor registered with no valid hooks - Type was: {}", theInterceptor.getClass().getName());
+ return false;
+ }
+
+ // Add to the global list
+ myInterceptors.add(theInterceptor);
+ sortByOrderAnnotation(myInterceptors);
+
+ return true;
+ }
+ }
+
+ private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
+ for (Object next : myInterceptors) {
+ if (next == theInterceptor) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean unregisterInterceptor(Object theInterceptor) {
+ synchronized (myRegistryMutex) {
+ boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
+ removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
+ removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
+ return removed;
+ }
+ }
+
+ private void sortByOrderAnnotation(List theObjects) {
+ IdentityHashMap interceptorToOrder = new IdentityHashMap<>();
+ for (Object next : theObjects) {
+ Interceptor orderAnnotation = next.getClass().getAnnotation(Interceptor.class);
+ int order = orderAnnotation != null ? orderAnnotation.order() : 0;
+ interceptorToOrder.put(next, order);
+ }
+
+ theObjects.sort((a, b) -> {
+ Integer orderA = interceptorToOrder.get(a);
+ Integer orderB = interceptorToOrder.get(b);
+ return orderA - orderB;
+ });
+ }
+
+ @Override
+ public Object callHooksAndReturnObject(POINTCUT thePointcut, HookParams theParams) {
+ assert haveAppropriateParams(thePointcut, theParams);
+ assert thePointcut.getReturnType() != void.class;
+
+ return doCallHooks(thePointcut, theParams, null);
+ }
+
+ @Override
+ public boolean hasHooks(POINTCUT thePointcut) {
+ return myGlobalInvokers.containsKey(thePointcut)
+ || myAnonymousInvokers.containsKey(thePointcut)
+ || hasThreadLocalHooks(thePointcut);
+ }
+
+ private boolean hasThreadLocalHooks(POINTCUT thePointcut) {
+ ListMultimap hooks = myThreadlocalInvokersEnabled ? myThreadlocalInvokers.get() : null;
+ return hooks != null && hooks.containsKey(thePointcut);
+ }
+
+ @Override
+ public boolean callHooks(POINTCUT thePointcut, HookParams theParams) {
+ assert haveAppropriateParams(thePointcut, theParams);
+ assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == boolean.class;
+
+ Object retValObj = doCallHooks(thePointcut, theParams, true);
+ return (Boolean) retValObj;
+ }
+
+ private Object doCallHooks(POINTCUT thePointcut, HookParams theParams, Object theRetVal) {
+ List invokers = getInvokersForPointcut(thePointcut);
+
+ /*
+ * Call each hook in order
+ */
+ for (BaseInvoker nextInvoker : invokers) {
+ Object nextOutcome = nextInvoker.invoke(theParams);
+ Class> pointcutReturnType = thePointcut.getReturnType();
+ if (pointcutReturnType.equals(boolean.class)) {
+ Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
+ if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
+ ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
+ theRetVal = false;
+ break;
+ }
+ } else if (pointcutReturnType.equals(void.class) == false) {
+ if (nextOutcome != null) {
+ theRetVal = nextOutcome;
+ break;
+ }
+ }
+ }
+
+ return theRetVal;
+ }
+
+ @VisibleForTesting
+ List getInterceptorsWithInvokersForPointcut(POINTCUT thePointcut) {
+ return getInvokersForPointcut(thePointcut)
+ .stream()
+ .map(BaseInvoker::getInterceptor)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns an ordered list of invokers for the given pointcut. Note that
+ * a new and stable list is returned to.. do whatever you want with it.
+ */
+ private List getInvokersForPointcut(POINTCUT thePointcut) {
+ List invokers;
+
+ synchronized (myRegistryMutex) {
+ List globalInvokers = myGlobalInvokers.get(thePointcut);
+ List anonymousInvokers = myAnonymousInvokers.get(thePointcut);
+ List threadLocalInvokers = null;
+ if (myThreadlocalInvokersEnabled) {
+ ListMultimap pointcutToInvokers = myThreadlocalInvokers.get();
+ if (pointcutToInvokers != null) {
+ threadLocalInvokers = pointcutToInvokers.get(thePointcut);
+ }
+ }
+ invokers = union(globalInvokers, anonymousInvokers, threadLocalInvokers);
+ }
+
+ return invokers;
+ }
+
+ /**
+ * First argument must be the global invoker list!!
+ */
+ @SafeVarargs
+ private final List union(List... theInvokersLists) {
+ List haveOne = null;
+ boolean haveMultiple = false;
+ for (List nextInvokerList : theInvokersLists) {
+ if (nextInvokerList == null || nextInvokerList.isEmpty()) {
+ continue;
+ }
+
+ if (haveOne == null) {
+ haveOne = nextInvokerList;
+ } else {
+ haveMultiple = true;
+ }
+ }
+
+ if (haveOne == null) {
+ return Collections.emptyList();
+ }
+
+ List retVal;
+
+ if (haveMultiple == false) {
+
+ // The global list doesn't need to be sorted every time since it's sorted on
+ // insertion each time. Doing so is a waste of cycles..
+ if (haveOne == theInvokersLists[0]) {
+ retVal = haveOne;
+ } else {
+ retVal = new ArrayList<>(haveOne);
+ retVal.sort(Comparator.naturalOrder());
+ }
+
+ } else {
+
+ retVal = Arrays
+ .stream(theInvokersLists)
+ .filter(t -> t != null)
+ .flatMap(t -> t.stream())
+ .sorted()
+ .collect(Collectors.toList());
+
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Only call this when assertions are enabled, it's expensive
+ */
+ boolean haveAppropriateParams(POINTCUT thePointcut, HookParams theParams) {
+ Validate.isTrue(theParams.getParamsForType().values().size() == thePointcut.getParameterTypes().size(), "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()));
+
+ List wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
+
+ ListMultimap, Object> givenTypes = theParams.getParamsForType();
+ for (Class> nextTypeClass : givenTypes.keySet()) {
+ String nextTypeName = nextTypeClass.getName();
+ for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
+ 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);
+ Validate.isTrue(wantedTypes.remove(nextTypeName), "Invalid params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), nextTypeName);
+ }
+ }
+
+ return true;
+ }
+
+ private List scanInterceptorAndAddToInvokerMultimap(Object theInterceptor, ListMultimap theInvokers) {
+ Class> interceptorClass = theInterceptor.getClass();
+ int typeOrder = determineOrder(interceptorClass);
+
+ List addedInvokers = scanInterceptorForHookMethods(theInterceptor, typeOrder);
+
+ // Invoke the REGISTERED pointcut for any added hooks
+ addedInvokers.stream()
+ .filter(t -> Pointcut.INTERCEPTOR_REGISTERED.equals(t.getPointcut()))
+ .forEach(t -> t.invoke(new HookParams()));
+
+ // Register the interceptor and its various hooks
+ for (HookInvoker nextAddedHook : addedInvokers) {
+ IPointcut nextPointcut = nextAddedHook.getPointcut();
+ if (nextPointcut.equals(Pointcut.INTERCEPTOR_REGISTERED)) {
+ continue;
+ }
+ theInvokers.put((POINTCUT) nextPointcut, nextAddedHook);
+ }
+
+ // Make sure we're always sorted according to the order declared in
+ // @Order
+ for (IPointcut nextPointcut : theInvokers.keys()) {
+ List nextInvokerList = theInvokers.get((POINTCUT) nextPointcut);
+ nextInvokerList.sort(Comparator.naturalOrder());
+ }
+
+ return addedInvokers;
+ }
+
+ protected abstract static class BaseInvoker implements Comparable {
+
+ private final int myOrder;
+ private final Object myInterceptor;
+
+ BaseInvoker(Object theInterceptor, int theOrder) {
+ myInterceptor = theInterceptor;
+ myOrder = theOrder;
+ }
+
+ public Object getInterceptor() {
+ return myInterceptor;
+ }
+
+ abstract Object invoke(HookParams theParams);
+
+ @Override
+ public int compareTo(BaseInvoker theInvoker) {
+ return myOrder - theInvoker.myOrder;
+ }
+ }
+
+ private static class HookInvoker extends BaseInvoker {
+
+ private final Method myMethod;
+ private final Class>[] myParameterTypes;
+ private final int[] myParameterIndexes;
+ private final IPointcut myPointcut;
+
+ /**
+ * Constructor
+ */
+ private HookInvoker(HookDescriptor theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
+ super(theInterceptor, theOrder);
+ myPointcut = theHook.getPointcut();
+ myParameterTypes = theHookMethod.getParameterTypes();
+ myMethod = theHookMethod;
+
+ Class> returnType = theHookMethod.getReturnType();
+ if (myPointcut.getReturnType().equals(boolean.class)) {
+ Validate.isTrue(boolean.class.equals(returnType) || void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
+ } else if (myPointcut.getReturnType().equals(void.class)) {
+ Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
+ } else {
+ Validate.isTrue(myPointcut.getReturnType().isAssignableFrom(returnType) || void.class.equals(returnType), "Method does not return %s or void: %s", myPointcut.getReturnType(), theHookMethod);
+ }
+
+ myParameterIndexes = new int[myParameterTypes.length];
+ Map, AtomicInteger> typeToCount = new HashMap<>();
+ for (int i = 0; i < myParameterTypes.length; i++) {
+ AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
+ myParameterIndexes[i] = counter.getAndIncrement();
+ }
+
+ myMethod.setAccessible(true);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("method", myMethod)
+ .toString();
+ }
+
+ public IPointcut getPointcut() {
+ return myPointcut;
+ }
+
+ /**
+ * @return Returns true/false if the hook method returns a boolean, returns true otherwise
+ */
+ @Override
+ Object invoke(HookParams theParams) {
+
+ Object[] args = new Object[myParameterTypes.length];
+ for (int i = 0; i < myParameterTypes.length; i++) {
+ Class> nextParamType = myParameterTypes[i];
+ if (nextParamType.equals(Pointcut.class)) {
+ args[i] = myPointcut;
+ } else {
+ int nextParamIndex = myParameterIndexes[i];
+ Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
+ args[i] = nextParamValue;
+ }
+ }
+
+ // Invoke the method
+ try {
+ return myMethod.invoke(getInterceptor(), args);
+ } catch (InvocationTargetException e) {
+ Throwable targetException = e.getTargetException();
+ if (myPointcut.isShouldLogAndSwallowException(targetException)) {
+ ourLog.error("Exception thrown by interceptor: " + targetException.toString(), targetException);
+ return null;
+ }
+
+ if (targetException instanceof RuntimeException) {
+ throw ((RuntimeException) targetException);
+ } else {
+ throw new InternalErrorException("Failure invoking interceptor for pointcut(s) " + getPointcut(), targetException);
+ }
+ } catch (Exception e) {
+ throw new InternalErrorException(e);
+ }
+
+ }
+
+ }
+
+ /**
+ * @return Returns a list of any added invokers
+ */
+ private List scanInterceptorForHookMethods(Object theInterceptor, int theTypeOrder) {
+ ArrayList retVal = new ArrayList<>();
+ for (Method nextMethod : ReflectionUtil.getDeclaredMethods(theInterceptor.getClass(), true)) {
+ Optional hook = scanForHook(nextMethod);
+
+ if (hook.isPresent()) {
+ int methodOrder = theTypeOrder;
+ int methodOrderAnnotation = hook.get().getOrder();
+ if (methodOrderAnnotation != Interceptor.DEFAULT_ORDER) {
+ methodOrder = methodOrderAnnotation;
+ }
+
+ retVal.add(new HookInvoker(hook.get(), theInterceptor, nextMethod, methodOrder));
+ }
+ }
+
+ return retVal;
+ }
+
+ protected abstract Optional scanForHook(Method nextMethod);
+
+ protected static Optional findAnnotation(AnnotatedElement theObject, Class theHookClass) {
+ T annotation;
+ if (theObject instanceof Method) {
+ annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
+ } else {
+ annotation = theObject.getAnnotation(theHookClass);
+ }
+ return Optional.ofNullable(annotation);
+ }
+
+ private static int determineOrder(Class> theInterceptorClass) {
+ int typeOrder = Interceptor.DEFAULT_ORDER;
+ Optional typeOrderAnnotation = findAnnotation(theInterceptorClass, Interceptor.class);
+ if (typeOrderAnnotation.isPresent()) {
+ typeOrder = typeOrderAnnotation.get().order();
+ }
+ return typeOrder;
+ }
+
+ private static String toErrorString(List theParameterTypes) {
+ return theParameterTypes
+ .stream()
+ .sorted()
+ .collect(Collectors.joining(","));
+ }
+
+ protected static class HookDescriptor {
+
+ private final IPointcut myPointcut;
+ private final int myOrder;
+
+ HookDescriptor(IPointcut thePointcut, int theOrder) {
+ myPointcut = thePointcut;
+ myOrder = theOrder;
+ }
+
+ IPointcut getPointcut() {
+ return myPointcut;
+ }
+
+ int getOrder() {
+ return myOrder;
+ }
+
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
index 6b3b5e082fa..14a0d7fa459 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/executor/InterceptorService.java
@@ -27,39 +27,13 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
-import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
-import ca.uhn.fhir.util.ReflectionUtil;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Multimaps;
import org.apache.commons.lang3.Validate;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-import org.apache.commons.lang3.reflect.MethodUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
+import java.util.Optional;
-public class InterceptorService implements IInterceptorService, IInterceptorBroadcaster {
- private static final Logger ourLog = LoggerFactory.getLogger(InterceptorService.class);
- private final List myInterceptors = new ArrayList<>();
- private final ListMultimap myGlobalInvokers = ArrayListMultimap.create();
- private final ListMultimap myAnonymousInvokers = ArrayListMultimap.create();
- private final Object myRegistryMutex = new Object();
- private final ThreadLocal> myThreadlocalInvokers = new ThreadLocal<>();
- private String myName;
- private boolean myThreadlocalInvokersEnabled = true;
+public class InterceptorService extends BaseInterceptorService implements IInterceptorService, IInterceptorBroadcaster {
/**
* Constructor which uses a default name of "default"
@@ -74,28 +48,14 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
* @param theName The name for this registry (useful for troubleshooting)
*/
public InterceptorService(String theName) {
- super();
- myName = theName;
+ super(theName);
}
- /**
- * Are threadlocal interceptors enabled on this registry (defaults to true)
- */
- public boolean isThreadlocalInvokersEnabled() {
- return myThreadlocalInvokersEnabled;
+ @Override
+ protected Optional scanForHook(Method nextMethod) {
+ return findAnnotation(nextMethod, Hook.class).map(t -> new HookDescriptor(t.value(), t.order()));
}
- /**
- * Are threadlocal interceptors enabled on this registry (defaults to true)
- */
- public void setThreadlocalInvokersEnabled(boolean theThreadlocalInvokersEnabled) {
- myThreadlocalInvokersEnabled = theThreadlocalInvokersEnabled;
- }
-
- @VisibleForTesting
- List getGlobalInterceptorsForUnitTest() {
- return myInterceptors;
- }
@Override
@VisibleForTesting
@@ -103,309 +63,14 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
registerAnonymousInterceptor(thePointcut, Interceptor.DEFAULT_ORDER, theInterceptor);
}
- public void setName(String theName) {
- myName = theName;
- }
-
@Override
public void registerAnonymousInterceptor(Pointcut thePointcut, int theOrder, IAnonymousInterceptor theInterceptor) {
Validate.notNull(thePointcut);
Validate.notNull(theInterceptor);
- synchronized (myRegistryMutex) {
-
- myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder));
- if (!isInterceptorAlreadyRegistered(theInterceptor)) {
- myInterceptors.add(theInterceptor);
- }
- }
+ BaseInvoker invoker = new AnonymousLambdaInvoker(thePointcut, theInterceptor, theOrder);
+ registerAnonymousInterceptor(thePointcut, theInterceptor, invoker);
}
- @Override
- public List getAllRegisteredInterceptors() {
- synchronized (myRegistryMutex) {
- List retVal = new ArrayList<>();
- retVal.addAll(myInterceptors);
- return Collections.unmodifiableList(retVal);
- }
- }
-
- @Override
- @VisibleForTesting
- public void unregisterAllInterceptors() {
- synchronized (myRegistryMutex) {
- myAnonymousInvokers.clear();
- myGlobalInvokers.clear();
- myInterceptors.clear();
- }
- }
-
- @Override
- public void unregisterInterceptors(@Nullable Collection> theInterceptors) {
- if (theInterceptors != null) {
- theInterceptors.forEach(t -> unregisterInterceptor(t));
- }
- }
-
- @Override
- public void registerInterceptors(@Nullable Collection> theInterceptors) {
- if (theInterceptors != null) {
- theInterceptors.forEach(t -> registerInterceptor(t));
- }
- }
-
- @Override
- public void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction) {
- unregisterInterceptorsIf(theShouldUnregisterFunction, myGlobalInvokers);
- unregisterInterceptorsIf(theShouldUnregisterFunction, myAnonymousInvokers);
- }
-
- private void unregisterInterceptorsIf(Predicate theShouldUnregisterFunction, ListMultimap theGlobalInvokers) {
- theGlobalInvokers.entries().removeIf(t->theShouldUnregisterFunction.test(t.getValue().getInterceptor()));
- }
-
- @Override
- public boolean registerThreadLocalInterceptor(Object theInterceptor) {
- if (!myThreadlocalInvokersEnabled) {
- return false;
- }
- ListMultimap invokers = getThreadLocalInvokerMultimap();
- scanInterceptorAndAddToInvokerMultimap(theInterceptor, invokers);
- return !invokers.isEmpty();
-
- }
-
- @Override
- public void unregisterThreadLocalInterceptor(Object theInterceptor) {
- if (myThreadlocalInvokersEnabled) {
- ListMultimap invokers = getThreadLocalInvokerMultimap();
- invokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
- if (invokers.isEmpty()) {
- myThreadlocalInvokers.remove();
- }
- }
- }
-
- private ListMultimap getThreadLocalInvokerMultimap() {
- ListMultimap invokers = myThreadlocalInvokers.get();
- if (invokers == null) {
- invokers = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
- myThreadlocalInvokers.set(invokers);
- }
- return invokers;
- }
-
- @Override
- public boolean registerInterceptor(Object theInterceptor) {
- synchronized (myRegistryMutex) {
-
- if (isInterceptorAlreadyRegistered(theInterceptor)) {
- return false;
- }
-
- List addedInvokers = scanInterceptorAndAddToInvokerMultimap(theInterceptor, myGlobalInvokers);
- if (addedInvokers.isEmpty()) {
- ourLog.warn("Interceptor registered with no valid hooks - Type was: {}", theInterceptor.getClass().getName());
- return false;
- }
-
- // Add to the global list
- myInterceptors.add(theInterceptor);
- sortByOrderAnnotation(myInterceptors);
-
- return true;
- }
- }
-
- private boolean isInterceptorAlreadyRegistered(Object theInterceptor) {
- for (Object next : myInterceptors) {
- if (next == theInterceptor) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public boolean unregisterInterceptor(Object theInterceptor) {
- synchronized (myRegistryMutex) {
- boolean removed = myInterceptors.removeIf(t -> t == theInterceptor);
- removed |= myGlobalInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
- removed |= myAnonymousInvokers.entries().removeIf(t -> t.getValue().getInterceptor() == theInterceptor);
- return removed;
- }
- }
-
- private void sortByOrderAnnotation(List theObjects) {
- IdentityHashMap interceptorToOrder = new IdentityHashMap<>();
- for (Object next : theObjects) {
- Interceptor orderAnnotation = next.getClass().getAnnotation(Interceptor.class);
- int order = orderAnnotation != null ? orderAnnotation.order() : 0;
- interceptorToOrder.put(next, order);
- }
-
- theObjects.sort((a, b) -> {
- Integer orderA = interceptorToOrder.get(a);
- Integer orderB = interceptorToOrder.get(b);
- return orderA - orderB;
- });
- }
-
- @Override
- public Object callHooksAndReturnObject(Pointcut thePointcut, HookParams theParams) {
- assert haveAppropriateParams(thePointcut, theParams);
- assert thePointcut.getReturnType() != void.class;
-
- return doCallHooks(thePointcut, theParams, null);
- }
-
- @Override
- public boolean hasHooks(Pointcut thePointcut) {
- return myGlobalInvokers.containsKey(thePointcut)
- || myAnonymousInvokers.containsKey(thePointcut)
- || hasThreadLocalHooks(thePointcut);
- }
-
- private boolean hasThreadLocalHooks(Pointcut thePointcut) {
- ListMultimap hooks = myThreadlocalInvokersEnabled ? myThreadlocalInvokers.get() : null;
- return hooks != null && hooks.containsKey(thePointcut);
- }
-
- @Override
- public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
- assert haveAppropriateParams(thePointcut, theParams);
- assert thePointcut.getReturnType() == void.class || thePointcut.getReturnType() == boolean.class;
-
- Object retValObj = doCallHooks(thePointcut, theParams, true);
- return (Boolean) retValObj;
- }
-
- private Object doCallHooks(Pointcut thePointcut, HookParams theParams, Object theRetVal) {
- List invokers = getInvokersForPointcut(thePointcut);
-
- /*
- * Call each hook in order
- */
- for (BaseInvoker nextInvoker : invokers) {
- Object nextOutcome = nextInvoker.invoke(theParams);
- Class> pointcutReturnType = thePointcut.getReturnType();
- if (pointcutReturnType.equals(boolean.class)) {
- Boolean nextOutcomeAsBoolean = (Boolean) nextOutcome;
- if (Boolean.FALSE.equals(nextOutcomeAsBoolean)) {
- ourLog.trace("callHooks({}) for invoker({}) returned false", thePointcut, nextInvoker);
- theRetVal = false;
- break;
- }
- } else if (pointcutReturnType.equals(void.class) == false) {
- if (nextOutcome != null) {
- theRetVal = nextOutcome;
- break;
- }
- }
- }
-
- return theRetVal;
- }
-
- @VisibleForTesting
- List getInterceptorsWithInvokersForPointcut(Pointcut thePointcut) {
- return getInvokersForPointcut(thePointcut)
- .stream()
- .map(BaseInvoker::getInterceptor)
- .collect(Collectors.toList());
- }
-
- /**
- * Returns an ordered list of invokers for the given pointcut. Note that
- * a new and stable list is returned to.. do whatever you want with it.
- */
- private List getInvokersForPointcut(Pointcut thePointcut) {
- List invokers;
-
- synchronized (myRegistryMutex) {
- List globalInvokers = myGlobalInvokers.get(thePointcut);
- List anonymousInvokers = myAnonymousInvokers.get(thePointcut);
- List threadLocalInvokers = null;
- if (myThreadlocalInvokersEnabled) {
- ListMultimap pointcutToInvokers = myThreadlocalInvokers.get();
- if (pointcutToInvokers != null) {
- threadLocalInvokers = pointcutToInvokers.get(thePointcut);
- }
- }
- invokers = union(globalInvokers, anonymousInvokers, threadLocalInvokers);
- }
-
- return invokers;
- }
-
- /**
- * First argument must be the global invoker list!!
- */
- @SafeVarargs
- private final List union(List... theInvokersLists) {
- List haveOne = null;
- boolean haveMultiple = false;
- for (List nextInvokerList : theInvokersLists) {
- if (nextInvokerList == null || nextInvokerList.isEmpty()) {
- continue;
- }
-
- if (haveOne == null) {
- haveOne = nextInvokerList;
- } else {
- haveMultiple = true;
- }
- }
-
- if (haveOne == null) {
- return Collections.emptyList();
- }
-
- List retVal;
-
- if (haveMultiple == false) {
-
- // The global list doesn't need to be sorted every time since it's sorted on
- // insertion each time. Doing so is a waste of cycles..
- if (haveOne == theInvokersLists[0]) {
- retVal = haveOne;
- } else {
- retVal = new ArrayList<>(haveOne);
- retVal.sort(Comparator.naturalOrder());
- }
-
- } else {
-
- retVal = Arrays
- .stream(theInvokersLists)
- .filter(t -> t != null)
- .flatMap(t -> t.stream())
- .sorted()
- .collect(Collectors.toList());
-
- }
-
- return retVal;
- }
-
- /**
- * Only call this when assertions are enabled, it's expensive
- */
- boolean haveAppropriateParams(Pointcut thePointcut, HookParams theParams) {
- Validate.isTrue(theParams.getParamsForType().values().size() == thePointcut.getParameterTypes().size(), "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()));
-
- List wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
-
- ListMultimap, Object> givenTypes = theParams.getParamsForType();
- for (Class> nextTypeClass : givenTypes.keySet()) {
- String nextTypeName = nextTypeClass.getName();
- for (Object nextParamValue : givenTypes.get(nextTypeClass)) {
- 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);
- Validate.isTrue(wantedTypes.remove(nextTypeName), "Invalid params for pointcut %s - Wanted %s but found %s", thePointcut.name(), toErrorString(thePointcut.getParameterTypes()), nextTypeName);
- }
- }
-
- return true;
- }
private class AnonymousLambdaInvoker extends BaseInvoker {
private final IAnonymousInterceptor myHook;
@@ -424,191 +89,5 @@ public class InterceptorService implements IInterceptorService, IInterceptorBroa
}
}
- private abstract static class BaseInvoker implements Comparable {
-
- private final int myOrder;
- private final Object myInterceptor;
-
- BaseInvoker(Object theInterceptor, int theOrder) {
- myInterceptor = theInterceptor;
- myOrder = theOrder;
- }
-
- public Object getInterceptor() {
- return myInterceptor;
- }
-
- abstract Object invoke(HookParams theParams);
-
- @Override
- public int compareTo(BaseInvoker theInvoker) {
- return myOrder - theInvoker.myOrder;
- }
- }
-
- private static class HookInvoker extends BaseInvoker {
-
- private final Method myMethod;
- private final Class>[] myParameterTypes;
- private final int[] myParameterIndexes;
- private final Pointcut myPointcut;
-
- /**
- * Constructor
- */
- private HookInvoker(Hook theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod, int theOrder) {
- super(theInterceptor, theOrder);
- myPointcut = theHook.value();
- myParameterTypes = theHookMethod.getParameterTypes();
- myMethod = theHookMethod;
-
- Class> returnType = theHookMethod.getReturnType();
- if (myPointcut.getReturnType().equals(boolean.class)) {
- Validate.isTrue(boolean.class.equals(returnType) || void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
- } else if (myPointcut.getReturnType().equals(void.class)) {
- Validate.isTrue(void.class.equals(returnType), "Method does not return void: %s", theHookMethod);
- } else {
- Validate.isTrue(myPointcut.getReturnType().isAssignableFrom(returnType) || void.class.equals(returnType), "Method does not return %s or void: %s", myPointcut.getReturnType(), theHookMethod);
- }
-
- myParameterIndexes = new int[myParameterTypes.length];
- Map, AtomicInteger> typeToCount = new HashMap<>();
- for (int i = 0; i < myParameterTypes.length; i++) {
- AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
- myParameterIndexes[i] = counter.getAndIncrement();
- }
-
- myMethod.setAccessible(true);
- }
-
- @Override
- public String toString() {
- return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
- .append("method", myMethod)
- .toString();
- }
-
- public Pointcut getPointcut() {
- return myPointcut;
- }
-
- /**
- * @return Returns true/false if the hook method returns a boolean, returns true otherwise
- */
- @Override
- Object invoke(HookParams theParams) {
-
- Object[] args = new Object[myParameterTypes.length];
- for (int i = 0; i < myParameterTypes.length; i++) {
- Class> nextParamType = myParameterTypes[i];
- if (nextParamType.equals(Pointcut.class)) {
- args[i] = myPointcut;
- } else {
- int nextParamIndex = myParameterIndexes[i];
- Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
- args[i] = nextParamValue;
- }
- }
-
- // Invoke the method
- try {
- return myMethod.invoke(getInterceptor(), args);
- } catch (InvocationTargetException e) {
- Throwable targetException = e.getTargetException();
- if (myPointcut.isShouldLogAndSwallowException(targetException)) {
- ourLog.error("Exception thrown by interceptor: " + targetException.toString(), targetException);
- return null;
- }
-
- if (targetException instanceof RuntimeException) {
- throw ((RuntimeException) targetException);
- } else {
- throw new InternalErrorException("Failure invoking interceptor for pointcut(s) " + getPointcut(), targetException);
- }
- } catch (Exception e) {
- throw new InternalErrorException(e);
- }
-
- }
-
- }
-
- private static List scanInterceptorAndAddToInvokerMultimap(Object theInterceptor, ListMultimap theInvokers) {
- Class> interceptorClass = theInterceptor.getClass();
- int typeOrder = determineOrder(interceptorClass);
-
- List addedInvokers = scanInterceptorForHookMethods(theInterceptor, typeOrder);
-
- // Invoke the REGISTERED pointcut for any added hooks
- addedInvokers.stream()
- .filter(t -> Pointcut.INTERCEPTOR_REGISTERED.equals(t.getPointcut()))
- .forEach(t -> t.invoke(new HookParams()));
-
- // Register the interceptor and its various hooks
- for (HookInvoker nextAddedHook : addedInvokers) {
- Pointcut nextPointcut = nextAddedHook.getPointcut();
- if (nextPointcut.equals(Pointcut.INTERCEPTOR_REGISTERED)) {
- continue;
- }
- theInvokers.put(nextPointcut, nextAddedHook);
- }
-
- // Make sure we're always sorted according to the order declared in
- // @Order
- for (Pointcut nextPointcut : theInvokers.keys()) {
- List nextInvokerList = theInvokers.get(nextPointcut);
- nextInvokerList.sort(Comparator.naturalOrder());
- }
-
- return addedInvokers;
- }
-
- /**
- * @return Returns a list of any added invokers
- */
- private static List scanInterceptorForHookMethods(Object theInterceptor, int theTypeOrder) {
- ArrayList retVal = new ArrayList<>();
- for (Method nextMethod : ReflectionUtil.getDeclaredMethods(theInterceptor.getClass(), true)) {
- Optional hook = findAnnotation(nextMethod, Hook.class);
-
- if (hook.isPresent()) {
- int methodOrder = theTypeOrder;
- int methodOrderAnnotation = hook.get().order();
- if (methodOrderAnnotation != Interceptor.DEFAULT_ORDER) {
- methodOrder = methodOrderAnnotation;
- }
-
- retVal.add(new HookInvoker(hook.get(), theInterceptor, nextMethod, methodOrder));
- }
- }
-
- return retVal;
- }
-
- private static Optional findAnnotation(AnnotatedElement theObject, Class theHookClass) {
- T annotation;
- if (theObject instanceof Method) {
- annotation = MethodUtils.getAnnotation((Method) theObject, theHookClass, true, true);
- } else {
- annotation = theObject.getAnnotation(theHookClass);
- }
- return Optional.ofNullable(annotation);
- }
-
- private static int determineOrder(Class> theInterceptorClass) {
- int typeOrder = Interceptor.DEFAULT_ORDER;
- Optional typeOrderAnnotation = findAnnotation(theInterceptorClass, Interceptor.class);
- if (typeOrderAnnotation.isPresent()) {
- typeOrder = typeOrderAnnotation.get().order();
- }
- return typeOrder;
- }
-
- private static String toErrorString(List theParameterTypes) {
- return theParameterTypes
- .stream()
- .sorted()
- .collect(Collectors.joining(","));
- }
}