package mireka.startup;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.GuardedBy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Collects objects which were created during the configuration phase and have a
* lifecycle annotation, and provide functions to execute these lifecycle
* methods later. Lifecycle annotations recognized are {@link PostConstruct} and
* {@link PreDestroy}.
*/
public class Lifecycle {
private static final Logger logger = LoggerFactory
.getLogger(Lifecycle.class);
public static final List<ManagedObject> managedObjects = new ArrayList<>();
/**
* Registers the object if it has at least one method which is marked with a
* lifecycle annotation. This function must be called for every object which
* were created during the configuration.
*
* @param object
* the object which may have a lifecycle annotation
*/
public static synchronized void addManagedObject(Object object) {
if (object == null)
throw new NullPointerException("Managed object must not be null");
if (alreadyRegisteredStartup(object))
throw new RuntimeException(
"Already registered for startup/shutdown: " + object);
if (hasLifecycleAnnotation(object))
managedObjects.add(new ManagedObject(object));
}
private static boolean alreadyRegisteredStartup(Object object) {
for (ManagedObject managedObject : managedObjects) {
if (managedObject.object == object)
return true;
}
return false;
}
private static boolean hasLifecycleAnnotation(Object object) {
for (Method method : object.getClass().getMethods()) {
if (method.isAnnotationPresent(PostConstruct.class))
return true;
if (method.isAnnotationPresent(PreDestroy.class))
return true;
}
return false;
}
/**
* Calls the methods of the registered objects which were marked with the
* {@link PostConstruct} annotation in the order of their registration.
*
* @throws InvocationTargetException
* thrown if a called method has thrown an exception
* @throws InvalidMethodSignatureException
* thrown if the method could not be called because it has
* arguments.
*/
public static synchronized void callPostConstructMethods()
throws InvocationTargetException, InvalidMethodSignatureException {
for (ManagedObject managedObject : managedObjects) {
for (Method method : managedObject.object.getClass().getMethods()) {
if (method.isAnnotationPresent(PostConstruct.class)) {
try {
method.invoke(managedObject.object);
logger.debug("Object started: {}", managedObject.object);
} catch (IllegalArgumentException e) {
throw new InvalidMethodSignatureException(
"@PostConstruct method must have an empty parameter list",
e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new InvocationTargetException(e,
"PostConstruct method failed: " + method
+ " on " + managedObject.object);
}
}
}
managedObject.initialized = true;
}
}
/**
* Calls the methods of the successfully initialized registered objects
* which were marked with the {@link PreDestroy} annotation in the opposite
* order of their registrations. The PreDestroy method is not called on an
* object
* <ul>
* <li>if it has a PostConstruct method which has thrown an exception during
* startup
* <li>another object which was registered earlier than this object has
* thrown an exception in its PostConstruct method.
* </ul>
*/
public static synchronized void callPreDestroyMethods() {
for (int i = managedObjects.size() - 1; i >= 0; i--) {
ManagedObject managedObject = managedObjects.get(i);
if (!managedObject.initialized)
continue;
for (Method method : managedObject.object.getClass().getMethods()) {
if (method.isAnnotationPresent(PreDestroy.class)) {
try {
method.invoke(managedObject.object);
logger.debug("Object stopped: {}", managedObject.object);
} catch (Exception e) {
logger.warn("PreDestroy function call failed on "
+ managedObject.object, e);
}
}
}
}
}
private static class ManagedObject {
final Object object;
@GuardedBy("Lifecycle.class")
boolean initialized;
public ManagedObject(Object object) {
this.object = object;
}
}
}