package com.netflix.governator.internal; import java.lang.annotation.Annotation; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.netflix.governator.LifecycleAction; public class JSR250LifecycleAction implements LifecycleAction { private static final Lookup METHOD_HANDLE_LOOKUP = MethodHandles.lookup(); public enum ValidationMode { STRICT, LAX } private static final Logger LOG = LoggerFactory.getLogger(JSR250LifecycleAction.class); private final Method method; private final String description; private MethodHandle mh; public JSR250LifecycleAction(Class<? extends Annotation> annotationClass, Method method) { this(annotationClass, method, ValidationMode.STRICT); } public JSR250LifecycleAction(Class<? extends Annotation> annotationClass, Method method, ValidationMode validationMode) { validateAnnotationUsage(annotationClass, method, validationMode); if (!method.isAccessible()) { method.setAccessible(true); } this.method = method; try { this.mh = METHOD_HANDLE_LOOKUP.unreflect(method); } catch (IllegalAccessException e) { // that's ok we'll use reflected method.invoke() } this.description = String.format("%s@%d[%s.%s()]", annotationClass.getSimpleName(), System.identityHashCode(this), method.getDeclaringClass().getSimpleName(), method.getName()); } private void validateAnnotationUsage(Class<? extends Annotation> annotationClass, Method method, ValidationMode validationMode) { LOG.debug("method validationMode is " + validationMode); if (Modifier.isStatic(method.getModifiers())) { throw new IllegalArgumentException("method must not be static"); } else if (method.getParameterCount() > 0) { throw new IllegalArgumentException("method parameter count must be zero"); } else if (Void.TYPE != method.getReturnType() && validationMode == ValidationMode.STRICT) { throw new IllegalArgumentException("method must have void return type"); } else if (method.getExceptionTypes().length > 0 && validationMode == ValidationMode.STRICT) { for (Class<?> e : method.getExceptionTypes()) { if (!RuntimeException.class.isAssignableFrom(e)) { throw new IllegalArgumentException( "method must must not throw checked exception: " + e.getSimpleName()); } } } else { int annotationCount = 0; for (Method m : method.getDeclaringClass().getDeclaredMethods()) { if (m.isAnnotationPresent(annotationClass)) { annotationCount++; } } if (annotationCount > 1 && validationMode == ValidationMode.STRICT) { throw new IllegalArgumentException( "declaring class must not contain multiple @" + annotationClass.getSimpleName() + " methods"); } } } @Override public void call(Object obj) throws InvocationTargetException { LOG.debug("calling action {} on instance {}", description, obj); if (mh != null) { try { mh.invoke(obj); } catch( Throwable throwable) { if (throwable instanceof RuntimeException) { throw (RuntimeException) throwable; } else if (throwable instanceof Error) { throw (Error) throwable; } throw new InvocationTargetException(throwable, "invoke-dynamic"); } } else { try { method.invoke(obj); } catch (InvocationTargetException ite) { Throwable cause = ite.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw ite; } catch (Throwable e) { // extremely unlikely, as constructor sets the method to 'accessible' and validates that it takes no parameters throw new RuntimeException("unexpected exception in method invocation", e); } } } @Override public String toString() { return description; } }