/*
* Copyright 2012 Netflix, 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.
*/
package com.netflix.governator.lifecycle;
import static com.netflix.governator.internal.BinaryConstant.I15_32768;
import static com.netflix.governator.internal.BinaryConstant.I2_4;
import static com.netflix.governator.internal.BinaryConstant.I3_8;
import static com.netflix.governator.internal.BinaryConstant.I4_16;
import static com.netflix.governator.internal.BinaryConstant.I5_32;
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.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.annotation.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.netflix.governator.annotations.Configuration;
import com.netflix.governator.annotations.ConfigurationVariable;
import com.netflix.governator.annotations.PreConfiguration;
import com.netflix.governator.annotations.WarmUp;
/**
* Used internally to hold the methods important to the LifecycleManager
*/
public class LifecycleMethods {
private static final Field[] EMPTY_FIELDS = new Field[0];
private static final Method[] EMPTY_METHODS = new Method[0];
private static final Lookup METHOD_HANDLE_LOOKUP = MethodHandles.lookup();
private static final Logger log = LoggerFactory.getLogger(LifecycleMethods.class);
private boolean hasValidations = false;
private final boolean hasResources;
final static Map<Method, MethodHandle> methodHandlesMap = new ConcurrentHashMap<>(I15_32768);
final static Map<Field, MethodHandle[]> fieldHandlesMap = new ConcurrentHashMap<>(I15_32768);
static class LifecycleMethodsBuilder {
private static final Logger log = LoggerFactory.getLogger(LifecycleMethodsBuilder.class);
private static final Collection<Class<? extends Annotation>> fieldAnnotations;
private static final Collection<Class<? extends Annotation>> methodAnnotations;
private static final Collection<Class<? extends Annotation>> classAnnotations;
static {
ImmutableSet.Builder<Class<? extends Annotation>> methodAnnotationsBuilder = ImmutableSet.builder();
methodAnnotationsBuilder.add(PreConfiguration.class);
methodAnnotationsBuilder.add(PostConstruct.class);
methodAnnotationsBuilder.add(PreDestroy.class);
methodAnnotationsBuilder.add(Resource.class);
methodAnnotationsBuilder.add(Resources.class);
methodAnnotationsBuilder.add(WarmUp.class);
methodAnnotations = methodAnnotationsBuilder.build();
ImmutableSet.Builder<Class<? extends Annotation>> fieldAnnotationsBuilder = ImmutableSet.builder();
fieldAnnotationsBuilder.add(Configuration.class);
fieldAnnotationsBuilder.add(Resource.class);
fieldAnnotationsBuilder.add(Resources.class);
fieldAnnotationsBuilder.add(ConfigurationVariable.class);
fieldAnnotations = fieldAnnotationsBuilder.build();
ImmutableSet.Builder<Class<? extends Annotation>> classAnnotationsBuilder = ImmutableSet.builder();
classAnnotationsBuilder.add(Resource.class);
classAnnotationsBuilder.add(Resources.class);
classAnnotations = classAnnotationsBuilder.build();
}
private boolean hasValidations = false;
private boolean hasResources;
private final Multimap<Class<? extends Annotation>, Field> fieldMap = ArrayListMultimap.create(I3_8, I5_32);
private final Multimap<Class<? extends Annotation>, Method> methodMap = ArrayListMultimap.create(I4_16, I5_32);
private final Multimap<Class<? extends Annotation>, Annotation> classMap = ArrayListMultimap.create(I2_4, I3_8);
public LifecycleMethodsBuilder( Class<?> clazz, Multimap<Class<? extends Annotation>, String> usedNames) {
addLifeCycleMethods(clazz, ArrayListMultimap.<Class<? extends Annotation>, String> create());
this.hasResources = fieldMap.containsKey(Resource.class) ||
fieldMap.containsKey(Resources.class) ||
methodMap.containsKey(Resource.class) ||
methodMap.containsKey(Resources.class) ||
classMap.containsKey(Resource.class) ||
classMap.containsKey(Resources.class);
this.hasValidations = this.hasValidations || !methodMap.isEmpty() || !fieldMap.isEmpty();
}
void addLifeCycleMethods(Class<?> clazz, Multimap<Class<? extends Annotation>, String> usedNames) {
if (clazz == null) {
return;
}
for (Class<? extends Annotation> annotationClass : classAnnotations) {
if (clazz.isAnnotationPresent(annotationClass)) {
classMap.put(annotationClass, clazz.getAnnotation(annotationClass));
}
}
for (Field field : getDeclaredFields(clazz)) {
if (field.isSynthetic()) {
continue;
}
for (Class<? extends Annotation> annotationClass : fieldAnnotations) {
processField(field, annotationClass, usedNames);
}
}
for (Method method : getDeclaredMethods(clazz)) {
if (method.isSynthetic() || method.isBridge()) {
continue;
}
for (Class<? extends Annotation> annotationClass : methodAnnotations) {
processMethod(method, annotationClass, usedNames);
}
}
addLifeCycleMethods(clazz.getSuperclass(), usedNames);
for (Class<?> face : clazz.getInterfaces()) {
addLifeCycleMethods(face, usedNames);
}
}
private Method[] getDeclaredMethods(Class<?> clazz) {
try {
return clazz.getDeclaredMethods();
} catch (Throwable e) {
handleReflectionError(clazz, e);
}
return EMPTY_METHODS;
}
private Field[] getDeclaredFields(Class<?> clazz) {
try {
return clazz.getDeclaredFields();
} catch (Throwable e) {
handleReflectionError(clazz, e);
}
return EMPTY_FIELDS;
}
private void handleReflectionError(Class<?> clazz, Throwable e) {
if (e != null) {
if ((e instanceof NoClassDefFoundError) || (e instanceof ClassNotFoundException)) {
log.debug(String.format(
"Class %s could not be resolved because of a class path error. Governator cannot further process the class.",
clazz.getName()), e);
return;
}
handleReflectionError(clazz, e.getCause());
}
}
private void processField(Field field, Class<? extends Annotation> annotationClass,
Multimap<Class<? extends Annotation>, String> usedNames) {
if (field.isAnnotationPresent(annotationClass)) {
String fieldName = field.getName();
if (!usedNames.get(annotationClass).contains(fieldName)) {
field.setAccessible(true);
usedNames.put(annotationClass, fieldName);
fieldMap.put(annotationClass, field);
try {
fieldHandlesMap.put(field, new MethodHandle[] {
METHOD_HANDLE_LOOKUP.unreflectGetter(field),
METHOD_HANDLE_LOOKUP.unreflectSetter(field)
});
} catch (IllegalAccessException e) {
// that's ok, will use reflected method
}
}
}
}
private void processMethod(Method method, Class<? extends Annotation> annotationClass,
Multimap<Class<? extends Annotation>, String> usedNames) {
if (method.isAnnotationPresent(annotationClass)) {
String methodName = method.getName();
if (!usedNames.get(annotationClass).contains(methodName)) {
method.setAccessible(true);
usedNames.put(annotationClass, methodName);
methodMap.put(annotationClass, method);
try {
methodHandlesMap.put(method, METHOD_HANDLE_LOOKUP.unreflect(method));
} catch (IllegalAccessException e) {
// that's ok, will use reflected method
}
}
}
}
}
Map<Class<? extends Annotation>, Method[]> methodMap;
Map<Class<? extends Annotation>, Field[]> fieldMap;
Map<Class<? extends Annotation>, Annotation[]> classMap;
public LifecycleMethods(Class<?> clazz) {
LifecycleMethodsBuilder builder = new LifecycleMethodsBuilder(clazz, ArrayListMultimap.<Class<? extends Annotation>, String> create());
this.hasResources = builder.hasResources;
this.hasValidations = builder.hasValidations;
methodMap = new HashMap<>();
for (Map.Entry<Class<? extends Annotation>, Collection<Method>> entry : builder.methodMap.asMap().entrySet()) {
methodMap.put(entry.getKey(), entry.getValue().toArray(EMPTY_METHODS));
}
fieldMap = new HashMap<>();
for (Map.Entry<Class<? extends Annotation>, Collection<Field>> entry : builder.fieldMap.asMap().entrySet()) {
fieldMap.put(entry.getKey(), entry.getValue().toArray(EMPTY_FIELDS));
}
classMap = new HashMap<>();
for (Class<? extends Annotation> annotationClass : LifecycleMethodsBuilder.classAnnotations) {
Annotation[] annotationsArray = (Annotation[])Array.newInstance(annotationClass, 0);
Collection<Annotation> annotations = builder.classMap.get(annotationClass);
if (annotations != null) {
annotationsArray = annotations.toArray(annotationsArray);
}
classMap.put(annotationClass, annotationsArray);
}
}
public boolean hasLifecycleAnnotations() {
return hasValidations;
}
public boolean hasResources() {
return hasResources;
}
@Deprecated
public Collection<Method> methodsFor(Class<? extends Annotation> annotation) {
return Arrays.asList(annotatedMethods(annotation));
}
@Deprecated
public Collection<Field> fieldsFor(Class<? extends Annotation> annotation) {
return Arrays.asList(annotatedFields(annotation));
}
@Deprecated
public <T extends Annotation> Collection<T> classAnnotationsFor(Class<T> annotation) {
return Arrays.asList(classAnnotations(annotation));
}
public Method[] annotatedMethods(Class<? extends Annotation> annotation) {
Method[] methods = methodMap.get(annotation);
return (methods != null) ? methods : EMPTY_METHODS;
}
public Field[] annotatedFields(Class<? extends Annotation> annotation) {
Field[] fields = fieldMap.get(annotation);
return (fields != null) ? fields : EMPTY_FIELDS;
}
@SuppressWarnings("unchecked")
public <T extends Annotation> T[] classAnnotations(Class<T> annotation) {
Annotation[] annotations = classMap.get(annotation);
return (annotations != null) ? (T[])annotations : (T[])Array.newInstance(annotation, 0);
}
public void methodInvoke(Class<? extends Annotation> annotation, Object obj) throws Exception {
if (methodMap.containsKey(annotation)) {
for (Method m : methodMap.get(annotation)) {
methodInvoke(m, obj);
}
}
}
public static void methodInvoke(Method method, Object target) throws InvocationTargetException, IllegalAccessException {
MethodHandle handler = methodHandlesMap.get(method);
if (handler != null) {
try {
if (Modifier.isStatic(method.getModifiers())) {
log.warn("static lifecycle method: {}, target={}", method, target);
handler.invoke();
}
else {
handler.invoke(target);
}
} catch (Throwable e) {
throw new InvocationTargetException(e, "invokedynamic: method=" + method + ", target=" + target);
}
}
else {
// fall through to reflected invocation
method.invoke(target);
}
}
public static <T> T fieldGet(Field field, Object obj) throws InvocationTargetException, IllegalAccessException {
MethodHandle[] fieldHandler = fieldHandlesMap.get(field);
if (fieldHandler == null) {
fieldHandler = updateFieldHandles(field);
}
if (fieldHandler != EMPTY_FIELD_HANDLES) {
try {
return Modifier.isStatic(field.getModifiers()) ? (T)fieldHandler[0].invoke() : (T)fieldHandler[0].bindTo(obj).invoke();
} catch (Throwable e) {
throw new InvocationTargetException(e, "invokedynamic: field=" + field + ", object=" + obj);
}
}
else {
return (T)field.get(obj);
}
}
public static void fieldSet(Field field, Object object, Object value) throws InvocationTargetException, IllegalAccessException {
MethodHandle[] fieldHandler = fieldHandlesMap.get(field);
if (fieldHandler == null) {
fieldHandler = updateFieldHandles(field);
}
if (fieldHandler != EMPTY_FIELD_HANDLES) {
try {
if (Modifier.isStatic(field.getModifiers())) {
fieldHandler[1].invoke(value);
}
else {
fieldHandler[1].bindTo(object).invoke(value);
}
} catch (Throwable e) {
throw new InvocationTargetException(e, "invokedynamic: field=" + field + ", object=" + object);
}
}
else {
field.set(object, value);
}
}
private final static MethodHandle[] EMPTY_FIELD_HANDLES = new MethodHandle[0];
private static MethodHandle[] updateFieldHandles(Field field) {
synchronized(fieldHandlesMap) {
MethodHandle[] handles = EMPTY_FIELD_HANDLES;
try {
handles = new MethodHandle[] {
METHOD_HANDLE_LOOKUP.unreflectGetter(field),
METHOD_HANDLE_LOOKUP.unreflectSetter(field)
};
} catch (IllegalAccessException e) {
// that's ok, will use reflected method
}
fieldHandlesMap.put(field, handles);
return handles;
}
}
}