/*
* Copyright 2010 Proofpoint, 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 io.airlift.bootstrap;
import io.airlift.log.Logger;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
* Manages PostConstruct and PreDestroy life cycles
*/
public final class LifeCycleManager
{
private final Logger log = Logger.get(getClass());
private final AtomicReference<State> state = new AtomicReference<>(State.LATENT);
private final Queue<Object> managedInstances = new ConcurrentLinkedQueue<>();
private final LifeCycleMethodsMap methodsMap;
private enum State
{
LATENT,
STARTING,
STARTED,
STOPPING,
STOPPED
}
/**
* @param managedInstances list of objects that have life cycle annotations
* @param methodsMap existing or new methods map
* @throws Exception exceptions starting instances (depending on mode)
*/
public LifeCycleManager(List<Object> managedInstances, LifeCycleMethodsMap methodsMap)
throws Exception
{
this.methodsMap = (methodsMap != null) ? methodsMap : new LifeCycleMethodsMap();
for (Object instance : managedInstances) {
addInstance(instance);
}
}
/**
* Returns the number of managed instances
*
* @return qty
*/
public int size()
{
return managedInstances.size();
}
/**
* Start the life cycle - all instances will have their {@link javax.annotation.PostConstruct} method(s) called
*
* @throws Exception errors
*/
public void start()
throws Exception
{
if (!state.compareAndSet(State.LATENT, State.STARTING)) {
throw new Exception("System already starting");
}
log.info("Life cycle starting...");
for (Object obj : managedInstances) {
LifeCycleMethods methods = methodsMap.get(obj.getClass());
if (!methods.hasFor(PreDestroy.class)) {
managedInstances.remove(obj); // remove reference to instances that aren't needed anymore
}
}
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
try {
LifeCycleManager.this.stop();
}
catch (Exception e) {
log.error(e, "Trying to shut down");
}
}
});
state.set(State.STARTED);
log.info("Life cycle startup complete. System ready.");
}
/**
* Stop the life cycle - all instances will have their {@link javax.annotation.PreDestroy} method(s) called
*
* @throws Exception errors
*/
public void stop()
throws Exception
{
if (!state.compareAndSet(State.STARTED, State.STOPPING)) {
return;
}
log.info("Life cycle stopping...");
List<Object> reversedInstances = new ArrayList<>(managedInstances);
Collections.reverse(reversedInstances);
for (Object obj : reversedInstances) {
log.debug("Stopping %s", obj.getClass().getName());
LifeCycleMethods methods = methodsMap.get(obj.getClass());
for (Method preDestroy : methods.methodsFor(PreDestroy.class)) {
log.debug("\t%s()", preDestroy.getName());
preDestroy.invoke(obj);
}
}
state.set(State.STOPPED);
log.info("Life cycle stopped.");
}
/**
* Add an additional managed instance
*
* @param instance instance to add
* @throws Exception errors
*/
public void addInstance(Object instance)
throws Exception
{
State currentState = state.get();
if ((currentState == State.STOPPING) || (currentState == State.STOPPED)) {
throw new IllegalStateException();
}
else {
startInstance(instance);
if (methodsMap.get(instance.getClass()).hasFor(PreDestroy.class)) {
managedInstances.add(instance);
}
}
}
private void startInstance(Object obj)
throws IllegalAccessException, InvocationTargetException
{
log.debug("Starting %s", obj.getClass().getName());
LifeCycleMethods methods = methodsMap.get(obj.getClass());
for (Method postConstruct : methods.methodsFor(PostConstruct.class)) {
log.debug("\t%s()", postConstruct.getName());
postConstruct.invoke(obj);
}
}
}