/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.service.servicemanager;
import com.foundationdb.server.error.CircularDependencyException;
import com.foundationdb.server.service.servicemanager.configuration.ServiceBinding;
import com.foundationdb.util.ArgumentValidation;
import com.foundationdb.util.Exceptions;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.ProvisionException;
import com.google.inject.Scopes;
import com.google.inject.internal.LinkedBindingImpl;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.InjectionPoint;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
public final class Guicer {
// Guicer interface
public Collection<Class<?>> directlyRequiredClasses() {
return directlyRequiredClasses;
}
public void stopAllServices(ServiceLifecycleActions<?> withActions) {
try {
stopServices(withActions, null);
} catch (Exception e) {
throw new RuntimeException("while stopping services", e);
}
}
public <T> T get(Class<T> serviceClass, ServiceLifecycleActions<?> withActions) {
if(!serviceClass.isInterface()) {
throw new IllegalArgumentException("Interface required");
}
final T instance = _injector.getInstance(serviceClass);
return startService(serviceClass, instance, withActions);
}
public boolean serviceIsStarted(Class<?> serviceClass) {
for (Object service : services) {
if (serviceClass.isInstance(service))
return true;
}
return false;
}
public boolean isRequired(Class<?> interfaceClass) {
return directlyRequiredClasses.contains(interfaceClass);
}
public boolean isBoundTo(Class<?> interfaceClass, Class<?> targetClass) {
Binding<?> existing = _injector.getExistingBinding(Key.get(interfaceClass));
if(existing instanceof LinkedBindingImpl) {
Key<?> key = ((LinkedBindingImpl<?>)existing).getLinkedKey();
return key.getTypeLiteral().getRawType() == targetClass;
}
return false;
}
/**
* <p>Builds and returns a list of dependencies for an instance of the specified class. The list will include an
* instance of the class itself, as well as any instances that root instance requires, directly or indirectly,
* according to Guice constructor injection.</p>
*
* <p>The order of the resulting list is fully deterministic and guarantees that for any element N in the list,
* if N depends on another element M, then M will also be in the list and will have an index greater than N's.
* In other words, traversing the list in reverse order will guarantee that you see any class's dependency
* before you see it.</p>
*
* <p>Each instance will appear exactly once; in other words, if there is an element N in the list,
* there will be no element M such that {@code N == M} or such that
* {@code N.getClass() == M.getClass()}</p>
*
* <p>More specifically, the order of the list is generated by doing a depth-first traversal of the dependency
* graph, with each element's dependents visited in alphabetical order of their class names, and then removing
* duplicates by doing a reverse traversal of the list. By that last point we mean that if N and M are duplicates,
* and N.index > M.index, then we would keep N and discard M.</p>
*
* <p>This method returns a new list with each invocation; the caller is free to modify the returned list.</p>
* @param rootClass the root class for which to get dependencies
* @return a mutable list of dependency instances, including the instance specified by the root class, in an order
* such that any element in the list always precedes all of its dependencies
* @throws CircularDependencyException if a circular dependency is found
*/
public List<?> dependenciesFor(Class<?> rootClass) {
LinkedHashMap<Class<?>,Object> result = new LinkedHashMap<>(16, .75f, true);
Deque<Object> dependents = new ArrayDeque<>();
buildDependencies(rootClass, result, dependents);
assert dependents.isEmpty() : dependents;
return new ArrayList<>(result.values());
}
// public class methods
public static Guicer forServices(Collection<ServiceBinding> serviceBindings)
throws ClassNotFoundException
{
return forServices(null, null, serviceBindings, Collections.<String>emptyList(),
Collections.<Module>emptyList());
}
public static <M extends ServiceManagerBase> Guicer forServices(Class<M> serviceManagerInterfaceClass,
M serviceManager,
Collection<ServiceBinding> serviceBindings,
List<String> priorities,
Collection<? extends Module> modules)
throws ClassNotFoundException
{
ArgumentValidation.notNull("bindings", serviceBindings);
if (serviceManagerInterfaceClass != null) {
if (!serviceManagerInterfaceClass.isInstance(serviceManager)) {
throw new IllegalArgumentException(serviceManager + " is not a "
+ serviceManagerInterfaceClass);
}
}
return new Guicer(serviceManagerInterfaceClass, serviceManager,
serviceBindings, priorities, modules);
}
// private methods
private Guicer(Class<? extends ServiceManagerBase> serviceManagerInterfaceClass, ServiceManagerBase serviceManager,
Collection<ServiceBinding> serviceBindings, List<String> priorities,
Collection<? extends Module> modules)
throws ClassNotFoundException
{
this.serviceManagerInterfaceClass = serviceManagerInterfaceClass;
List<Class<?>> localDirectlyRequiredClasses = new ArrayList<>();
List<ResolvedServiceBinding> resolvedServiceBindings = new ArrayList<>();
for (ServiceBinding serviceBinding : serviceBindings) {
ResolvedServiceBinding resolvedServiceBinding = new ResolvedServiceBinding(serviceBinding);
resolvedServiceBindings.add(resolvedServiceBinding);
if (serviceBinding.isDirectlyRequired()) {
localDirectlyRequiredClasses.add(resolvedServiceBinding.serviceInterfaceClass());
}
}
Collections.sort(localDirectlyRequiredClasses, BY_CLASS_NAME);
// Pull to front in reverse order.
for (int i = priorities.size() - 1; i >= 0; i--) {
Class<?> clazz = Class.forName(priorities.get(i));
if (localDirectlyRequiredClasses.remove(clazz)) {
localDirectlyRequiredClasses.add(0, clazz);
}
else {
throw new IllegalArgumentException("priority service " + priorities.get(i) + " is not a dependency");
}
}
directlyRequiredClasses = Collections.unmodifiableCollection(localDirectlyRequiredClasses);
this.services = Collections.synchronizedSet(new LinkedHashSet<>());
AbstractModule module = new ServiceBindingsModule(serviceManagerInterfaceClass, serviceManager,
resolvedServiceBindings);
List<Module> modulesList = new ArrayList<>(modules.size() + 1);
modulesList.add(module);
modulesList.addAll(modules);
_injector = Guice.createInjector(modulesList.toArray(new Module[modulesList.size()]));
}
private void buildDependencies(Class<?> forClass, LinkedHashMap<Class<?>,Object> results, Deque<Object> dependents) {
Object instance = _injector.getInstance(forClass);
if (dependents.contains(instance)) {
throw circularDependencyInjection(forClass, instance, dependents);
}
// Start building this object
dependents.addLast(instance);
Class<?> actualClass = instance.getClass();
Object oldInstance = results.put(actualClass, instance);
if (oldInstance != null) {
assert oldInstance == instance : oldInstance + " != " + instance;
}
// Build the dependency list
List<Class<?>> dependencyClasses = new ArrayList<>();
for (Dependency<?> dependency : InjectionPoint.forConstructorOf(actualClass).getDependencies()) {
dependencyClasses.add(dependency.getKey().getTypeLiteral().getRawType());
}
for (InjectionPoint injectionPoint : InjectionPoint.forInstanceMethodsAndFields(actualClass)) {
for (Dependency<?> dependency : injectionPoint.getDependencies()) {
dependencyClasses.add(dependency.getKey().getTypeLiteral().getRawType());
}
}
for (InjectionPoint injectionPoint : InjectionPoint.forStaticMethodsAndFields(actualClass)) {
for (Dependency<?> dependency : injectionPoint.getDependencies()) {
dependencyClasses.add(dependency.getKey().getTypeLiteral().getRawType());
}
}
// This dependency is already handled.
dependencyClasses.remove(serviceManagerInterfaceClass);
// Sort it and recursively invoke
Collections.sort(dependencyClasses, BY_CLASS_NAME);
for (Class<?> dependencyClass : dependencyClasses) {
buildDependencies(dependencyClass, results, dependents);
}
// Done building the object; pop the deque and confirm the instance
Object removed = dependents.removeLast();
assert removed == instance : removed + " != " + instance;
}
private CircularDependencyException circularDependencyInjection(Class<?> forClass, Object instance, Deque<Object> dependents) {
String forClassName = forClass.getSimpleName();
List<String> classNames = new ArrayList<>();
for (Object o : dependents) {
classNames.add(o.getClass().getSimpleName());
}
classNames.add(instance.getClass().getSimpleName());
return new CircularDependencyException (forClassName, classNames);
}
private <T,S> T startService(Class<T> serviceClass, T instance, ServiceLifecycleActions<S> withActions) {
// quick check; startServiceIfApplicable will do this too, but this way we can avoid finding dependencies
if (services.contains(instance)) {
return instance;
}
synchronized (services) {
for (Object dependency : reverse(dependenciesFor(serviceClass))) {
startServiceIfApplicable(dependency, withActions);
}
}
return instance;
}
private static <T> List<T> reverse(List<T> list) {
Collections.reverse(list);
return list;
}
private <T, S> void startServiceIfApplicable(T instance, ServiceLifecycleActions<S> withActions) {
if (services.contains(instance)) {
return;
}
if (withActions == null) {
services.add(instance);
return;
}
S service = withActions.castIfActionable(instance);
if (service != null) {
try {
withActions.onStart(service);
services.add(service);
} catch (Exception e) {
try {
stopServices(withActions, e);
} catch (Exception e1) {
e = e1;
}
throw new ProvisionException("While starting service " + instance.getClass(), e);
}
}
}
private void stopServices(ServiceLifecycleActions<?> withActions, Exception initialCause) throws Exception {
List<Throwable> exceptions = tryStopServices(withActions, initialCause);
if (!exceptions.isEmpty()) {
if (exceptions.size() == 1) {
throw Exceptions.throwAlways(exceptions, 0);
}
for (Throwable t : exceptions) {
t.printStackTrace();
}
Throwable cause = exceptions.get(0);
throw new Exception("Failure(s) while shutting down services: " + exceptions, cause);
}
}
private <S> List<Throwable> tryStopServices(ServiceLifecycleActions<S> withActions, Exception initialCause) {
ListIterator<?> reverseIter;
synchronized (services) {
reverseIter = new ArrayList<>(services).listIterator(services.size());
}
List<Throwable> exceptions = new ArrayList<>();
if (initialCause != null) {
exceptions.add(initialCause);
}
while (reverseIter.hasPrevious()) {
try {
Object serviceObject = reverseIter.previous();
services.remove(serviceObject);
if (withActions != null) {
S service = withActions.castIfActionable(serviceObject);
if (service != null) {
withActions.onShutdown(service);
}
}
} catch (Throwable t) {
exceptions.add(t);
}
}
// TODO because our dependency graph is created via Service.start() invocations, if service A uses service B
// in stop() but not start(), and service B has already been shut down, service B will be resurrected. Yuck.
// I don't know of a good way around this, other than by formalizing our dependency graph via constructor
// params (and thus removing ServiceManagerImpl.get() ). Until this is resolved, simplest is to just shrug
// our shoulders and not check
// synchronized (lock) {
// assert services.isEmpty() : services;
// }
return exceptions;
}
// object state
private final Class<? extends ServiceManagerBase> serviceManagerInterfaceClass;
private final Collection<Class<?>> directlyRequiredClasses;
private final Set<Object> services;
private final Injector _injector;
// consts
private static final Comparator<? super Class<?>> BY_CLASS_NAME = new Comparator<Class<?>>() {
@Override
public int compare(Class<?> o1, Class<?> o2) {
return o1.getName().compareTo(o2.getName());
}
};
public List<Class<?>> servicesClassesInStartupOrder() {
List<Class<?>> result = new ArrayList<>(services.size());
for (Object service : services) {
result.add(service.getClass());
}
return result;
}
// nested classes
private static final class ResolvedServiceBinding {
// ResolvedServiceBinding interface
public Class<?> serviceInterfaceClass() {
return serviceInterfaceClass;
}
public Class<?> serviceImplementationClass() {
return serviceImplementationClass;
}
public ResolvedServiceBinding(ServiceBinding serviceBinding) throws ClassNotFoundException {
ClassLoader loader = serviceBinding.getClassLoader();
this.serviceInterfaceClass = Class.forName(serviceBinding.getInterfaceName(), true, loader);
this.serviceImplementationClass = Class.forName(serviceBinding.getImplementingClassName(), true, loader);
if (!this.serviceInterfaceClass.isAssignableFrom(this.serviceImplementationClass)) {
throw new IllegalArgumentException(this.serviceInterfaceClass + " is not assignable from "
+ this.serviceImplementationClass);
}
}
// object state
private final Class<?> serviceInterfaceClass;
private final Class<?> serviceImplementationClass;
}
private static final class ServiceBindingsModule extends AbstractModule {
@Override
// we use unchecked, raw Class, relying on the invariant established by ResolvedServiceBinding's ctor
@SuppressWarnings("unchecked")
protected void configure() {
if (serviceManagerInterfaceClass != null)
bind((Class)serviceManagerInterfaceClass).toInstance(serviceManager);
for (ResolvedServiceBinding binding : bindings) {
Class unchecked = binding.serviceInterfaceClass();
bind(unchecked).to(binding.serviceImplementationClass()).in(Scopes.SINGLETON);
}
}
// ServiceBindingsModule interface
private ServiceBindingsModule(Class<? extends ServiceManagerBase> serviceManagerInterfaceClass, ServiceManagerBase serviceManager,
Collection<ResolvedServiceBinding> bindings)
{
this.serviceManagerInterfaceClass = serviceManagerInterfaceClass;
this.serviceManager = serviceManager;
this.bindings = bindings;
}
// object state
private final Class<? extends ServiceManagerBase> serviceManagerInterfaceClass;
private final ServiceManagerBase serviceManager;
private final Collection<ResolvedServiceBinding> bindings;
}
static interface ServiceLifecycleActions<T> {
void onStart(T service);
void onShutdown(T service);
/**
* Cast the given object to the actionable type if possible, or return {@code null} otherwise.
* @param object the object which may or may not be actionable
* @return the object reference, correctly casted; or null
*/
T castIfActionable(Object object);
}
}