package com.netflix.fabricator.component;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.netflix.fabricator.ComponentConfigurationResolver;
import com.netflix.fabricator.ComponentType;
import com.netflix.fabricator.ConfigurationNode;
import com.netflix.fabricator.TypeConfigurationResolver;
import com.netflix.fabricator.annotations.Default;
import com.netflix.fabricator.component.exception.ComponentAlreadyExistsException;
import com.netflix.fabricator.component.exception.ComponentCreationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/**
* Implementation of a ComponentManager where each method is synchronized to guarantee
* thread safety. Uses guice MapBinder to specify the different types of component
* implementations.
*
* @author elandau
*
* @param <T>
*/
public class SynchronizedComponentManager<T> implements ComponentManager<T> {
private Logger LOG = LoggerFactory.getLogger(SynchronizedComponentManager.class);
private final ConcurrentMap<String, T> components = Maps.newConcurrentMap();
private final Map<String, ComponentFactory<T>> factories;
private final ComponentConfigurationResolver configResolver;
private final ComponentType<T> componentType;
@Default
@Inject(optional=true)
private ComponentFactory<T> defaultComponentFactory = null;
@Inject
public SynchronizedComponentManager(
ComponentType<T> type,
Map<String, ComponentFactory<T>> factories,
TypeConfigurationResolver config) {
this.factories = factories;
this.componentType = type;
this.configResolver = config.getConfigurationFactory(type.getType());
}
@Override
public synchronized T get(String id) throws ComponentCreationException, ComponentAlreadyExistsException {
Preconditions.checkNotNull(id, String.format("Component of type '%s' must have a id", componentType.getType()));
// Look for an existing component
T component = components.get(id);
if (component == null) {
// Get configuration context from default configuration
ConfigurationNode config = configResolver.getConfiguration(id);
if (config != null) {
// Create the object
component = getComponentFactory(config.getType()).create(config);
if (component == null) {
throw new ComponentCreationException(String.format("Error creating component of type '%s' with id '%s'", componentType.getType(), id));
}
addComponent(id, component);
}
else {
throw new ComponentCreationException(String.format("No config provided for component of type '%s' with id '%s'", componentType.getType(), id));
}
}
return component;
}
private void addComponent(String id, T component) throws ComponentCreationException{
try {
invokePostConstruct(component);
} catch (Exception e) {
throw new ComponentCreationException("Error creating component : " + id, e);
}
T oldComponent = components.put(id, component);
if (oldComponent != null) {
try {
invokePreDestroy(oldComponent);
} catch (Exception e) {
LOG.error("Error destroying component : " + id, e);
}
}
}
private static void fillAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annot, Map<String, Method> methods) {
if (clazz == null || clazz == Object.class) {
return;
}
for (Method method : clazz.getDeclaredMethods()) {
if (method.isSynthetic() || method.isBridge()) {
continue;
}
if (method.isAnnotationPresent(annot)) {
methods.putIfAbsent(method.getName(), method);
}
}
fillAnnotatedMethods(clazz.getSuperclass(), annot, methods);
for (Class<?> face : clazz.getInterfaces()) {
fillAnnotatedMethods(face, annot, methods);
}
}
private void invokePostConstruct(T component) throws Exception {
if (component == null)
return;
Map<String, Method> methods = new LinkedHashMap<>();
fillAnnotatedMethods(component.getClass(), PostConstruct.class, methods);
for (Method method : methods.values()) {
method.invoke(component, null);
}
}
private void invokePreDestroy(T component) throws Exception {
if (component == null)
return;
Map<String, Method> methods = new LinkedHashMap<>();
fillAnnotatedMethods(component.getClass(), PreDestroy.class, methods);
for (Method method : methods.values()) {
method.invoke(component, null);
}
}
private void removeComponent(String id, T component) throws Exception {
if (component == null)
return;
if (components.get(id) == component) {
components.remove(id);
invokePreDestroy(component);
}
}
@Override
public synchronized void add(String id, T component) throws ComponentAlreadyExistsException, ComponentCreationException {
Preconditions.checkNotNull(id, "Component must have a id");
Preconditions.checkNotNull(component, "Component cannot be null");
if (components.containsKey(id)) {
throw new ComponentAlreadyExistsException(id);
}
addComponent(id, component);
}
@Override
public synchronized Collection<String> getIds() {
return ImmutableSet.copyOf(components.keySet());
}
@Override
public synchronized T get(ConfigurationNode config) throws ComponentAlreadyExistsException, ComponentCreationException {
return load(config);
}
@Override
public synchronized T load(ConfigurationNode config) throws ComponentAlreadyExistsException, ComponentCreationException {
Preconditions.checkNotNull(config, "Configuration cannot be null");
Preconditions.checkNotNull(config.getId(), "Configuration must have an id");
if (config.getId() != null && components.containsKey(config.getId())) {
throw new ComponentAlreadyExistsException(config.getId());
}
T component = getComponentFactory(config.getType()).create(config);
if (component == null) {
throw new ComponentCreationException(String.format("Error creating component type '%s' with id '%s'", componentType.getType(), config.getId()));
}
addComponent(config.getId(), component);
return component;
}
@Override
public T create(ConfigurationNode config) throws ComponentCreationException, ComponentAlreadyExistsException {
T component = getComponentFactory(config.getType()).create(config);
if (component == null) {
throw new ComponentCreationException(String.format("Error creating component type '%s' with id '%s'", componentType.getType(), config.getId()));
}
try {
invokePostConstruct(component);
} catch (Exception e) {
throw new ComponentCreationException("Error creating component : " + config.getId(), e);
}
return component;
}
@Override
public synchronized void replace(String id, T component) throws ComponentAlreadyExistsException, ComponentCreationException {
Preconditions.checkNotNull(id, "Component must have a id");
Preconditions.checkNotNull(component, "Component cannot be null");
addComponent(id, component);
}
@Override
public synchronized void remove(String id) {
Preconditions.checkNotNull(id, "Component must have a id");
try {
removeComponent(id, components.get(id));
} catch (Exception e) {
LOG.error("Error shutting down component: " + id, e);
}
}
private ComponentFactory<T> getComponentFactory(String type) throws ComponentCreationException {
ComponentFactory<T> factory = null;
if (type != null) {
factory = factories.get(type);
}
if (factory == null) {
factory = defaultComponentFactory;
}
if (factory == null) {
throw new ComponentCreationException(
String.format("Failed to create component '%s'. Invalid implementation specified '%s'. Expecting one of '%s'.",
this.componentType.getType(), type, factories.keySet()));
}
return factory;
}
@Override
public synchronized void apply(Runnable operation) {
operation.run();
}
@Override
public synchronized T replace(ConfigurationNode config) throws ComponentCreationException {
Preconditions.checkNotNull(config, "Configuration cannot be null");
Preconditions.checkNotNull(config.getId(), "Configuration must have an id");
T component;
try {
component = getComponentFactory(config.getType()).create(config);
if (component == null) {
throw new ComponentCreationException(String.format("Error creating component type '%s' with id '%s'", componentType.getType(), config.getId()));
}
addComponent(config.getId(), component);
return component;
} catch (ComponentAlreadyExistsException e) {
// This can't really happen
throw new ComponentCreationException("Can't create component", e);
}
}
@Override
public synchronized T find(String id) {
return components.get(id);
}
@Override
public synchronized boolean contains(String id) {
return components.containsKey(id);
}
}