/*
* 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.I10_1024;
import static com.netflix.governator.internal.BinaryConstant.I15_32768;
import static com.netflix.governator.internal.BinaryConstant.I16_65536;
import java.io.Closeable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MapMaker;
import com.google.inject.Binding;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.netflix.governator.LifecycleAction;
import com.netflix.governator.annotations.PreConfiguration;
import com.netflix.governator.annotations.WarmUp;
import com.netflix.governator.configuration.ConfigurationColumnWriter;
import com.netflix.governator.configuration.ConfigurationDocumentation;
import com.netflix.governator.configuration.ConfigurationMapper;
import com.netflix.governator.configuration.ConfigurationProvider;
import com.netflix.governator.guice.PostInjectorAction;
import com.netflix.governator.internal.JSR250LifecycleAction.ValidationMode;
import com.netflix.governator.internal.PreDestroyLifecycleFeature;
import com.netflix.governator.internal.PreDestroyMonitor;
/**
* Main instance management container
*/
@Singleton
public class LifecycleManager implements Closeable, PostInjectorAction
{
private enum State
{
LATENT,
STARTING,
STARTED,
CLOSED
}
private final Logger log = LoggerFactory.getLogger(getClass());
private final ConcurrentMap<Object, LifecycleStateWrapper> objectStates = new MapMaker().weakKeys().initialCapacity(I16_65536).concurrencyLevel(I10_1024).makeMap();
private final PreDestroyLifecycleFeature preDestroyLifecycleFeature = new PreDestroyLifecycleFeature(ValidationMode.LAX);
private final ConcurrentMap<Class<?>, List<LifecycleAction>> preDestroyActionCache = new ConcurrentHashMap<Class<?>, List<LifecycleAction>>(I15_32768);
private final AtomicReference<State> state = new AtomicReference<State>(State.LATENT);
private final ConfigurationDocumentation configurationDocumentation;
private final ConfigurationProvider configurationProvider;
private final ConfigurationMapper configurationMapper;
private final ResourceMapper resourceMapper;
final LifecycleListener[] listeners;
private final PreDestroyMonitor preDestroyMonitor;
private com.netflix.governator.LifecycleManager newLifecycleManager;
public LifecycleManager()
{
this(new LifecycleManagerArguments(), null);
}
public LifecycleManager(LifecycleManagerArguments arguments)
{
this(arguments, null);
}
@Inject
public LifecycleManager(LifecycleManagerArguments arguments, Injector injector)
{
if (injector != null) {
preDestroyMonitor = new PreDestroyMonitor(injector.getScopeBindings());
}
else {
preDestroyMonitor = null;
}
configurationMapper = arguments.getConfigurationMapper();
newLifecycleManager = arguments.getLifecycleManager();
listeners = arguments.getLifecycleListeners().toArray(new LifecycleListener[0]);
resourceMapper = new ResourceMapper(injector, ImmutableSet.copyOf(arguments.getResourceLocators()));
configurationDocumentation = arguments.getConfigurationDocumentation();
configurationProvider = arguments.getConfigurationProvider();
}
/**
* Return the lifecycle listener if any
*
* @return listener or null
*/
public Collection<LifecycleListener> getListeners()
{
return Arrays.asList(listeners);
}
/**
* Add the objects to the container. Their assets will be loaded, post construct methods called, etc.
*
* @param objects objects to add
* @throws Exception errors
*/
@Deprecated
public void add(Object... objects) throws Exception
{
for ( Object obj : objects )
{
add(obj);
}
}
/**
* Add the object to the container. Its assets will be loaded, post construct methods called, etc.
*
* @param obj object to add
* @throws Exception errors
*/
@Deprecated
public void add(Object obj) throws Exception
{
add(obj, null, new LifecycleMethods(obj.getClass()));
}
/**
* Add the object to the container. Its assets will be loaded, post construct methods called, etc.
* This version helps performance when the lifecycle methods have already been calculated
*
* @param obj object to add
* @param methods calculated lifecycle methods
* @throws Exception errors
*/
@Deprecated
public void add(Object obj, LifecycleMethods methods) throws Exception
{
add(obj, null, methods);
}
/**
* Add the object to the container. Its assets will be loaded, post construct methods called, etc.
* This version helps performance when the lifecycle methods have already been calculated
*
* @param obj object to add
* @param methods calculated lifecycle methods
* @throws Exception errors
*/
public <T> void add(T obj, Binding<T> binding, LifecycleMethods methods) throws Exception
{
State managerState = state.get();
if (managerState != State.CLOSED) {
startInstance(obj, binding, methods);
if ( managerState == State.STARTED )
{
initializeObjectPostStart(obj);
}
}
else {
throw new IllegalStateException("LifecycleManager is closed");
}
}
/**
* Returns true if the lifecycle has started (i.e. {@link #start()} has been called).
*
* @return true/false
*/
public boolean hasStarted()
{
return state.get() == State.STARTED;
}
/**
* Return the current state of the given object or LATENT if unknown
*
* @param obj object to check
* @return state
*/
public LifecycleState getState(Object obj)
{
LifecycleStateWrapper lifecycleState = objectStates.get(obj);
if ( lifecycleState == null )
{
return hasStarted() ? LifecycleState.ACTIVE : LifecycleState.LATENT;
}
else {
synchronized(lifecycleState) {
return lifecycleState.get();
}
}
}
/**
* The manager MUST be started. Note: this method
* waits indefinitely for warm up methods to complete
*
* @throws Exception errors
*/
public void start() throws Exception
{
Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTING), "Already started");
new ConfigurationColumnWriter(configurationDocumentation).output(log);
if (newLifecycleManager != null) {
newLifecycleManager.notifyStarted();
}
state.set(State.STARTED);
}
/**
* The manager MUST be started. This version of start() has a maximum
* wait period for warm up methods.
*
* @param maxWait maximum wait time for warm up methods - if the time elapses, the warm up methods are interrupted
* @param unit time unit
* @return true if warm up methods successfully executed, false if the time elapses
* @throws Exception errors
*/
@Deprecated
public boolean start(long maxWait, TimeUnit unit) throws Exception
{
start();
return true;
}
@SuppressWarnings("deprecation")
private <T> void startInstance(T obj, Binding<T> binding, LifecycleMethods methods) throws Exception
{
final Class<?> instanceType = obj.getClass();
log.debug("Starting {}", instanceType.getName());
final LifecycleStateWrapper lifecycleState = initState(obj, LifecycleState.PRE_CONFIGURATION);
methods.methodInvoke(PreConfiguration.class, obj);
lifecycleState.set(obj, LifecycleState.SETTING_CONFIGURATION);
configurationMapper.mapConfiguration(configurationProvider, configurationDocumentation, obj, methods);
lifecycleState.set(obj, LifecycleState.SETTING_RESOURCES);
resourceMapper.map(obj, methods);
lifecycleState.set(obj, LifecycleState.POST_CONSTRUCTING);
methods.methodInvoke(PostConstruct.class, obj);
Method[] warmUpMethods = methods.annotatedMethods(WarmUp.class);
if (warmUpMethods.length > 0) {
Method[] postConstructMethods = methods.annotatedMethods(PostConstruct.class);
for ( Method warmupMethod : warmUpMethods)
{
boolean skipWarmup = false;
// assuming very few methods in both WarmUp and PostConstruct
for (Method postConstruct : postConstructMethods) {
if (postConstruct == warmupMethod) {
skipWarmup = true;
break;
}
}
if (!skipWarmup) {
log.debug("\t{}()", warmupMethod.getName());
LifecycleMethods.methodInvoke(warmupMethod, obj);
}
}
}
List<LifecycleAction> preDestroyActions;
if (preDestroyActionCache.containsKey(instanceType)) {
preDestroyActions = preDestroyActionCache.get(instanceType);
}
else {
preDestroyActions = preDestroyLifecycleFeature.getActionsForType(instanceType);
preDestroyActionCache.put(instanceType, preDestroyActions);
}
if ( !preDestroyActions.isEmpty() )
{
if (binding != null) {
preDestroyMonitor.register(obj, binding, preDestroyActions);
}
else {
preDestroyMonitor.register(obj, "legacy", preDestroyActions);
}
}
}
class LifecycleStateWrapper {
LifecycleState state;
public void set(Object managedInstance, LifecycleState state) {
this.state = state;
for ( LifecycleListener listener : listeners )
{
listener.stateChanged(managedInstance, state);
}
}
public LifecycleState get() {
return state;
}
}
private LifecycleStateWrapper initState(Object obj, LifecycleState state) {
LifecycleStateWrapper stateWrapper = new LifecycleStateWrapper();
objectStates.put(obj, stateWrapper);
stateWrapper.set(obj, state);
return stateWrapper;
}
@Override
public synchronized void close()
{
if ( state.compareAndSet(State.STARTING, State.CLOSED) || state.compareAndSet(State.STARTED, State.CLOSED) )
{
try
{
if (newLifecycleManager != null) {
newLifecycleManager.notifyShutdown();
}
preDestroyMonitor.close();
}
catch ( Exception e )
{
log.error("While stopping instances", e);
}
finally
{
objectStates.clear();
preDestroyActionCache.clear();
}
}
}
private void initializeObjectPostStart(Object obj)
{
}
@Override
public void call(Injector injector) {
this.resourceMapper.setInjector(injector);
this.preDestroyMonitor.addScopeBindings(injector.getScopeBindings());
}
}