/** * 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); } }