package com.netflix.governator;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.ProvisionException;
import com.google.inject.matcher.Matchers;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.multibindings.MultibindingsScanner;
import com.google.inject.spi.ProvisionListener;
import com.netflix.governator.annotations.SuppressLifecycleUninitialized;
import com.netflix.governator.internal.BinaryConstant;
import com.netflix.governator.internal.GovernatorFeatureSet;
import com.netflix.governator.internal.JSR250LifecycleAction.ValidationMode;
import com.netflix.governator.internal.PostConstructLifecycleFeature;
import com.netflix.governator.internal.PreDestroyLifecycleFeature;
import com.netflix.governator.internal.PreDestroyMonitor;
/**
* Adds support for standard lifecycle annotations @PostConstruct and @PreDestroy to Guice.
*
* <code>
* public class MyService {
* {@literal @}PostConstruct
* public void init() {
* }
*
* {@literal @}PreDestroy
* public void shutdown() {
* }
* }
* </code>
*
* To use simply add LifecycleModule to guice when creating the injector
*
* See {@link LifecycleInjector} for different scenarios for shutting down the LifecycleManager.
*/
public final class LifecycleModule extends AbstractModule {
private static final Logger LOG = LoggerFactory.getLogger(LifecycleModule.class);
private LifecycleProvisionListener provisionListener = new LifecycleProvisionListener();
/**
* Holder of actions for a specific type.
*/
static class TypeLifecycleActions {
final List<LifecycleAction> postConstructActions = new ArrayList<LifecycleAction>();
final List<LifecycleAction> preDestroyActions = new ArrayList<>();
}
@Singleton
@SuppressLifecycleUninitialized
static class LifecycleProvisionListener extends AbstractLifecycleListener implements ProvisionListener {
private final ConcurrentMap<Class<?>, TypeLifecycleActions> cache = new ConcurrentHashMap<>(BinaryConstant.I12_4096);
private Set<LifecycleFeature> features;
private final AtomicBoolean isShutdown = new AtomicBoolean();
private PostConstructLifecycleFeature postConstructFeature;
private PreDestroyLifecycleFeature preDestroyFeature;
private PreDestroyMonitor preDestroyMonitor;
private boolean shutdownOnFailure = true;
@SuppressLifecycleUninitialized
@Singleton
static class OptionalArgs {
@com.google.inject.Inject(optional = true)
GovernatorFeatureSet governatorFeatures;
boolean hasShutdownOnFailure() {
return governatorFeatures == null ? true : governatorFeatures.get(GovernatorFeatures.SHUTDOWN_ON_ERROR);
}
ValidationMode getJsr250ValidationMode() {
return governatorFeatures == null ? ValidationMode.LAX : governatorFeatures.get(GovernatorFeatures.STRICT_JSR250_VALIDATION) ? ValidationMode.STRICT : ValidationMode.LAX;
}
}
@Inject
public static void initialize(
final Injector injector,
OptionalArgs args,
LifecycleManager manager,
LifecycleProvisionListener provisionListener,
Set<LifecycleFeature> features) {
provisionListener.features = features;
provisionListener.shutdownOnFailure = args.hasShutdownOnFailure();
ValidationMode validationMode = args.getJsr250ValidationMode();
provisionListener.postConstructFeature = new PostConstructLifecycleFeature(validationMode);
provisionListener.preDestroyFeature = new PreDestroyLifecycleFeature(validationMode);
provisionListener.preDestroyMonitor = new PreDestroyMonitor(injector.getScopeBindings());
LOG.debug("LifecycleProvisionListener initialized with features {}", features);
}
public TypeLifecycleActions getOrCreateActions(Class<?> type) {
TypeLifecycleActions actions = cache.get(type);
if (actions == null) {
actions = new TypeLifecycleActions();
// Ordered set of actions to perform before PostConstruct
for (LifecycleFeature feature : features) {
actions.postConstructActions.addAll(feature.getActionsForType(type));
}
// Finally, add @PostConstruct methods
actions.postConstructActions.addAll(postConstructFeature.getActionsForType(type));
// Determine @PreDestroy methods
actions.preDestroyActions.addAll(preDestroyFeature.getActionsForType(type));
TypeLifecycleActions existing = cache.putIfAbsent(type, actions);
if (existing != null) {
return existing;
}
}
return actions;
}
/**
* Invoke all shutdown actions
*/
@Override
public synchronized void onStopped(Throwable optionalFailureReason) {
if (shutdownOnFailure || optionalFailureReason == null) {
if (isShutdown.compareAndSet(false, true)) {
try {
preDestroyMonitor.close();
} catch (Exception e) {
LOG.error("failed closing preDestroyMonitor", e);
}
}
}
}
@Override
public String toString() {
return "LifecycleProvisionListener@" + System.identityHashCode(this);
}
@Override
public <T> void onProvision(ProvisionInvocation<T> provision) {
final T injectee = provision.provision();
if (injectee == null) {
return;
}
if (features == null) {
if (!injectee.getClass().isAnnotationPresent(SuppressLifecycleUninitialized.class)) {
LOG.debug("LifecycleProvisionListener not initialized yet : {}", injectee.getClass());
}
// TODO: Add to PreDestroy list
return;
}
final TypeLifecycleActions actions = getOrCreateActions(injectee.getClass());
// Call all postConstructActions for this injectee
if (!actions.postConstructActions.isEmpty()) {
try {
new ManagedInstanceAction(injectee, actions.postConstructActions).call();
} catch (Exception e) {
throw new ProvisionException("postConstruct failed", e);
}
}
// Add any PreDestroy methods to the shutdown list of actions
if (!actions.preDestroyActions.isEmpty()) {
if (isShutdown.get() == false) {
preDestroyMonitor.register(injectee, provision.getBinding(), actions.preDestroyActions);
}
else {
LOG.warn("Already shutting down. Shutdown methods {} on {} will not be invoked", actions.preDestroyActions, injectee.getClass().getName());
}
}
}
}
@Override
protected void configure() {
requestStaticInjection(LifecycleProvisionListener.class);
bind(LifecycleProvisionListener.class).toInstance(provisionListener);
bindListener(Matchers.any(), provisionListener);
Multibinder.newSetBinder(binder(), LifecycleFeature.class);
install(MultibindingsScanner.asModule());
}
@Override
public boolean equals(Object obj) {
return getClass().equals(obj.getClass());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public String toString() {
return "LifecycleModule@" + System.identityHashCode(this);
}
}