/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.core.impl; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.everrest.core.LifecycleMethodStrategy; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import static java.util.concurrent.TimeUnit.MINUTES; /** * Implementation of LifecycleComponent.LifecycleMethodStrategy that uses {@link PostConstruct} and {@link PreDestroy} * annotation to find "initialize" and "destroy" methods. */ public final class AnnotatedLifecycleMethodStrategy implements LifecycleMethodStrategy { private static class MethodFilter { private final Class<? extends Annotation> annotation; MethodFilter(Class<? extends Annotation> annotation) { this.annotation = annotation; } /** * Check is method may be used as PostConstruct/PreDestroy method. There are some limitations according to * requirements usage of {@link PostConstruct} and {@link PreDestroy} annotation : * <ul> * <li>Method is annotated with {@link #annotation}.</li> * <li>Method must not be static.</li> * <li>Method has not have any parameters.</li> * <li>The return type of the method must be void.</li> * <li>Method must not throw checked exception.</li> * </ul> * * @param method * the method * @return <code>true</code> if method is matched to requirements above and false otherwise * @see PostConstruct * @see PreDestroy */ boolean accept(Method method) { return (!Modifier.isStatic(method.getModifiers())) && (method.getReturnType() == void.class || method.getReturnType() == Void.class) && method.getParameterTypes().length == 0 && noCheckedException(method) && method.getAnnotation(annotation) != null; } private boolean noCheckedException(Method method) { Class<?>[] exceptions = method.getExceptionTypes(); if (exceptions.length > 0) { for (Class<?> exception : exceptions) { if (!RuntimeException.class.isAssignableFrom(exception)) { return false; } } } return true; } } private static final MethodFilter POST_CONSTRUCT_METHOD_FILTER = new MethodFilter(PostConstruct.class); private static final MethodFilter PRE_DESTROY_METHOD_FILTER = new MethodFilter(PreDestroy.class); private final LoadingCache<Class<?>, Method[]> initializeMethodsCache; private final LoadingCache<Class<?>, Method[]> destroyMethodsCache; public AnnotatedLifecycleMethodStrategy() { initializeMethodsCache = CacheBuilder.newBuilder() .concurrencyLevel(8) .maximumSize(256) .expireAfterAccess(10, MINUTES) .build(new CacheLoader<Class<?>, Method[]>() { @Override public Method[] load(Class<?> aClass) { return getLifecycleMethods(aClass, POST_CONSTRUCT_METHOD_FILTER); } }); destroyMethodsCache = CacheBuilder.newBuilder() .concurrencyLevel(8) .maximumSize(256) .expireAfterAccess(10, MINUTES) .build(new CacheLoader<Class<?>, Method[]>() { @Override public Method[] load(Class<?> aClass) { return getLifecycleMethods(aClass, PRE_DESTROY_METHOD_FILTER); } }); } /** @see LifecycleMethodStrategy#invokeInitializeMethods(java.lang.Object) */ @Override public void invokeInitializeMethods(Object o) { final Class<?> aClass = o.getClass(); Method[] initMethods = null; try { initMethods = initializeMethodsCache.get(aClass); } catch (ExecutionException e) { Throwables.propagate(e); } if (initMethods != null && initMethods.length > 0) { doInvokeLifecycleMethods(o, initMethods); } } /** @see LifecycleMethodStrategy#invokeDestroyMethods(java.lang.Object) */ @Override public void invokeDestroyMethods(Object o) { final Class<?> aClass = o.getClass(); Method[] destroyMethods = null; try { destroyMethods = destroyMethodsCache.get(aClass); } catch (ExecutionException e) { Throwables.propagate(e); } if (destroyMethods != null && destroyMethods.length > 0) { doInvokeLifecycleMethods(o, destroyMethods); } } private Method[] getLifecycleMethods(Class<?> cl, MethodFilter filter) { try { List<Method> result = new LinkedList<>(); Set<String> names = new HashSet<>(); for (; cl != Object.class; cl = cl.getSuperclass()) { Method[] methods = cl.getDeclaredMethods(); for (Method method : methods) { if (filter.accept(method) && names.add(method.getName())) { if (!Modifier.isPublic(method.getModifiers())) { method.setAccessible(true); } result.add(method); } } } return result.toArray(new Method[result.size()]); } catch (SecurityException e) { throw new InternalException(e); } } private void doInvokeLifecycleMethods(Object o, Method[] lifecycleMethods) { for (Method method : lifecycleMethods) { try { method.invoke(o); } catch (InvocationTargetException e) { Throwable t = e.getTargetException(); throw new InternalException(t); } catch (Exception e) { throw new InternalException(e); } } } }