Package com.foundationdb.server.service.servicemanager

Source Code of com.foundationdb.server.service.servicemanager.GuicedServiceManager

/**
* 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.sql.LayerInfoInterface;
import com.foundationdb.server.error.ServiceStartupException;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.ServiceManager;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.server.service.dxl.DXLService;
import com.foundationdb.server.service.monitor.MonitorService;
import com.foundationdb.server.service.jmx.JmxManageable;
import com.foundationdb.server.service.jmx.JmxRegistryService;
import com.foundationdb.server.service.plugins.Plugin;
import com.foundationdb.server.service.plugins.PluginsFinder;
import com.foundationdb.server.service.servicemanager.configuration.BindingsConfigurationLoader;
import com.foundationdb.server.service.servicemanager.configuration.DefaultServiceConfigurationHandler;
import com.foundationdb.server.service.servicemanager.configuration.ServiceBinding;
import com.foundationdb.server.service.servicemanager.configuration.ServiceConfigurationHandler;
import com.foundationdb.server.service.servicemanager.configuration.yaml.YamlConfiguration;
import com.foundationdb.server.service.session.SessionService;
import com.foundationdb.server.service.stats.StatisticsService;
import com.foundationdb.server.store.SchemaManager;
import com.foundationdb.server.store.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

public final class GuicedServiceManager implements ServiceManager, JmxManageable {
    // ServiceManager interface

    @Override
    public State getState() {
        return state;
    }

    @Override
    public synchronized void startServices() {
        logger.info("Starting services.");
        state = State.STARTING;
        getJmxRegistryService().register(this);
        boolean ok = false;
        try {
            for (Class<?> directlyRequiredClass : guicer.directlyRequiredClasses()) {
                guicer.get(directlyRequiredClass, STANDARD_SERVICE_ACTIONS);
            }
            ok = true;
        }
        finally {
            if (!ok)
                state = State.ERROR_STARTING;
        }
        state = State.ACTIVE;
        LayerInfoInterface layerInfo = getLayerInfo();
        logger.info("{} {} ready.", layerInfo.getServerName(), layerInfo.getVersionInfo().versionLong);
    }

    @Override
    public synchronized void stopServices() throws Exception {
        logger.info("Stopping services normally.");
        state = State.STOPPING;
        try {
            guicer.stopAllServices(STANDARD_SERVICE_ACTIONS);
        }
        finally {
            state = State.IDLE;
        }
        logger.info("Services stopped.");
    }

    @Override
    public synchronized void crashServices() throws Exception {
        logger.info("Stopping services abnormally.");
        state = State.STOPPING;
        try {
            guicer.stopAllServices(CRASH_SERVICES);
        }
        finally {
            state = State.IDLE;
        }
        logger.info("Services stopped.");
    }

    @Override
    public ConfigurationService getConfigurationService() {
        return getServiceByClass(ConfigurationService.class);
    }

    @Override
    public LayerInfoInterface getLayerInfo() {
        return getServiceByClass(LayerInfoInterface.class);
    }

    @Override
    public Store getStore() {
        return getServiceByClass(Store.class);
    }

    @Override
    public SchemaManager getSchemaManager() {
        return getServiceByClass(SchemaManager.class);
    }

    @Override
    public JmxRegistryService getJmxRegistryService() {
        return getServiceByClass(JmxRegistryService.class);
    }

    @Override
    public StatisticsService getStatisticsService() {
        return getServiceByClass(StatisticsService.class);
    }

    @Override
    public SessionService getSessionService() {
        return getServiceByClass(SessionService.class);
    }

    @Override
    public synchronized <T> T getServiceByClass(Class<T> serviceClass) {
        return guicer.get(serviceClass, STANDARD_SERVICE_ACTIONS);
    }

    @Override
    public DXLService getDXL() {
        return getServiceByClass(DXLService.class);
    }

    @Override
    public MonitorService getMonitorService() {
        return getServiceByClass(MonitorService.class);
    }

    @Override
    public boolean serviceIsBoundTo(Class<?> serviceClass, Class<?> implClass) {
        return guicer.isBoundTo(serviceClass, implClass);
    }

    @Override
    public boolean serviceIsStarted(Class<?> serviceClass) {
        return guicer.serviceIsStarted(serviceClass);
    }

    // JmxManageable interface

    @Override
    public JmxObjectInfo getJmxObjectInfo() {
        return new JmxObjectInfo("Services", bean, ServiceManagerMXBean.class);
    }


    // GuicedServiceManager interface

    public GuicedServiceManager() {
        this(standardUrls());
    }

    public GuicedServiceManager(BindingsConfigurationProvider bindingsConfigurationProvider) {
        DefaultServiceConfigurationHandler configurationHandler = new DefaultServiceConfigurationHandler();

        // Install the default, no-op JMX registry; this is a special case, since we want to use it
        // as we start each service.
        configurationHandler.bind(JmxRegistryService.class.getName(), NoOpJmxRegistry.class.getName(), null);

        // Next, load each element in the provider...
        for (BindingsConfigurationLoader loader : bindingsConfigurationProvider.loaders()) {
            loader.loadInto(configurationHandler);
        }

        // ... followed by the configured or default file, if either exists
        URL configFile = findServiceConfigFile();
        if (configFile != null) {
            new YamlBindingsUrl(configFile).loadInto(configurationHandler);
        }

        // ... followed by any command-line overrides.
        new PropertyBindings(System.getProperties()).loadInto(configurationHandler);

        Collection<ServiceBinding> bindings = configurationHandler.serviceBindings(false);
        BindingsConfigurationLoader pluginsConfigLoader = getPluginsConfigurationLoader(bindings);
        pluginsConfigLoader.loadInto(configurationHandler);

        bindings = configurationHandler.serviceBindings(true);

        try {
            guicer = Guicer.forServices(ServiceManager.class, this,
                                        bindings, configurationHandler.priorities(), configurationHandler.getModules());
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    // private methods

    private BindingsConfigurationLoader getPluginsConfigurationLoader(Collection<ServiceBinding> bindings) {
        ServiceBinding pluginsFinderBinding = null;
        for (ServiceBinding binding : bindings) {
            if (PluginsFinder.class.getCanonicalName().equals(binding.getInterfaceName())) {
                if (pluginsFinderBinding != null)
                    throw new ServiceStartupException("multiple bindings found for " + PluginsFinder.class);
                pluginsFinderBinding = binding;
            }
        }
        if (pluginsFinderBinding == null)
            return emptyConfigurationLoader;
        String pluginsFinderClassName = pluginsFinderBinding.getImplementingClassName();
        Class<?> pluginsFinderClass;
        try {
            pluginsFinderClass = Class.forName(pluginsFinderClassName);
        }
        catch (ClassNotFoundException e) {
            throw new ServiceStartupException("couldn't get Class object for " + pluginsFinderClassName);
        }
        PluginsFinder pluginsFinder;
        try {
            pluginsFinder = (PluginsFinder) pluginsFinderClass.newInstance();
        }
        catch (Exception e) {
            logger.error("while instantiating plugins finder", e);
            logger.error("plugins finder must have a no-arg constructor, though there may be something else wrong");
            throw new ServiceStartupException("error while instantiating plugins finder. please check logs");
        }
        CompositeConfigurationLoader compositeLoader = new CompositeConfigurationLoader();
        Collection<? extends Plugin> plugins = pluginsFinder.get();
        List<URL> pluginUrls = new ArrayList<>(plugins.size());
        for (Plugin plugin : plugins) {
            URL url = plugin.getClassLoaderURL();
            if (url != null) {
                pluginUrls.add(url);
            }
        }
        ClassLoader pluginsClassloader = null;
        if (!pluginUrls.isEmpty()) {
            pluginsClassloader = new URLClassLoader(pluginUrls.toArray(new URL[pluginUrls.size()]));
        }
        for (Plugin plugin : plugins) {
            try {
                YamlConfiguration pluginConfig = new YamlConfiguration(
                        plugin.toString(),
                        plugin.getServiceConfigsReader(),
                        pluginsClassloader);
                compositeLoader.add(pluginConfig);
            }
            catch (IOException e) {
                logger.error("while reading services config for " + plugin, e);
                throw new ServiceStartupException("error while reading services config for " + plugin);
            }
        }
        return compositeLoader;
    }

    boolean isRequired(Class<?> theClass) {
        return guicer.isRequired(theClass);
    }

    // static methods

    public static BindingsConfigurationProvider standardUrls() {
        BindingsConfigurationProvider provider = new BindingsConfigurationProvider();
        provider.define(GuicedServiceManager.class.getResource("default-services.yaml"));
        return provider;
    }

    public static BindingsConfigurationProvider testUrls() {
        BindingsConfigurationProvider provider = standardUrls();
        provider.define(GuicedServiceManager.class.getResource("test-services.yaml"));
        provider.overrideRequires(GuicedServiceManager.class.getResource("test-services-requires.yaml"));
        return provider;
    }

    /**
     * <ul>
     *     <li>
     *         if {@link #SERVICES_CONFIG_PROPERTY} is defined,
     *            return {@link URL}
     *     </li>
     *     <li>
     *         if {@link #CONFIG_DIR_PROPERTY} is defined and
     *         <ul>
     *             <li>contains {@link #DEFAULT_CONFIG_FILE_NAME}, return {@link File}</li>
     *         </ul>
     *     </li>
     *     <li>
     *         return <code>null</code>
     *     </li>
     * </ul>
     */
    private static URL findServiceConfigFile() {
        try {
            String servicesConfigFile = System.getProperty(SERVICES_CONFIG_PROPERTY);
            if(servicesConfigFile != null) {
                return new URL(new File(".").toURI().toURL(), // Default to local file.
                               servicesConfigFile);
            }
            String configDir = System.getProperty(CONFIG_DIR_PROPERTY);
            if(configDir != null) {
                File configFile = new File(configDir, DEFAULT_CONFIG_FILE_NAME);
                if(configFile.isFile()) {
                    return configFile.toURI().toURL();
                }
            }
        } catch (MalformedURLException e) {
            throw new RuntimeException("couldn't convert config file to URL", e);
        }
        return null;
    }

    // object state

    private State state = State.IDLE;
    private final Guicer guicer;

    private final ServiceManagerMXBean bean = new ServiceManagerMXBean() {
        @Override
        public List<String> getStartedDependencies() {
            boolean fullNames = isFullClassNames();
            List<String> result = new ArrayList<>();
            for (Class<?> requiredClass : guicer.directlyRequiredClasses()) {
                List<?> dependencies = guicer.dependenciesFor(requiredClass);
                List<String> dependenciesClasses = new ArrayList<>();
                for (Object dependency : dependencies) {
                    Class<?> depClass = dependency.getClass();
                    dependenciesClasses.add(fullNames ? depClass.getName() : depClass.getSimpleName());
                }
                result.add(dependenciesClasses.toString());
            }
            return result;
        }

        @Override
        public boolean isFullClassNames() {
            return fullClassNames.get();
        }

        @Override
        public void setFullClassNames(boolean value) {
            fullClassNames.set(value);
        }

        @Override
        public List<String> getServicesInStartupOrder() {
            List<String> result = new ArrayList<>();
            for (Class<?> serviceClass : guicer.servicesClassesInStartupOrder()) {
                result.add(isFullClassNames() ? serviceClass.getName() : serviceClass.getSimpleName() );
            }
            return result;
        }

        private final AtomicBoolean fullClassNames = new AtomicBoolean(false);
    };

    final Guicer.ServiceLifecycleActions<Service> STANDARD_SERVICE_ACTIONS
            = new Guicer.ServiceLifecycleActions<Service>()
    {
        private Map<Class<? extends JmxManageable>,ObjectName> jmxNames
                = Collections.synchronizedMap(new HashMap<Class<? extends JmxManageable>, ObjectName>());

        @Override
        public void onStart(Service service) {
            final Thread currentThread = Thread.currentThread();
            final ClassLoader oldContextCl = currentThread.getContextClassLoader();
            ClassLoader contextClassloader = service.getClass().getClassLoader();
            boolean setContextCl = (contextClassloader != null && contextClassloader != oldContextCl);
            try {
                if (setContextCl)
                    currentThread.setContextClassLoader(contextClassloader);
                service.start();
                if (service instanceof JmxManageable && isRequired(JmxRegistryService.class)) {
                    JmxRegistryService registry = (service instanceof JmxRegistryService)
                            ? (JmxRegistryService) service
                            : getJmxRegistryService();
                    JmxManageable manageable = (JmxManageable)service;
                    ObjectName objectName = registry.register(manageable);
                    jmxNames.put(manageable.getClass(), objectName);
                    // 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
    //                assert (ObjectName)old == null : objectName + " has displaced " + old;
                }
            }
            finally {
                if (setContextCl)
                    currentThread.setContextClassLoader(oldContextCl);
            }
        }

        @Override
        public void onShutdown(Service service) {
            if (service instanceof JmxManageable && isRequired(JmxRegistryService.class)) {
                JmxRegistryService registry = (service instanceof JmxRegistryService)
                        ? (JmxRegistryService) service
                        : getJmxRegistryService();
                JmxManageable manageable = (JmxManageable) service;
                ObjectName objectName = jmxNames.get(manageable.getClass());
                if (objectName == null) {
                    throw new NullPointerException("service not registered: " + manageable.getClass());
                }
                registry.unregister(objectName);
            }
            service.stop();
        }

        @Override
        public Service castIfActionable(Object object) {
            return (object instanceof Service) ? (Service)object : null;
        }
    };

    // consts

    private static final String CONFIG_DIR_PROPERTY = "fdbsql.config_dir"; // Note: ConfigurationServiceImpl
    private static final String DEFAULT_CONFIG_FILE_NAME = "services-config.yaml";
    private static final String SERVICES_CONFIG_PROPERTY = "services.config";

    private static final Guicer.ServiceLifecycleActions<Service> CRASH_SERVICES
            = new Guicer.ServiceLifecycleActions<Service>() {
        @Override
        public void onStart(Service service) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void onShutdown(Service service){
            service.crash();
        }

        @Override
        public Service castIfActionable(Object object) {
            return (object instanceof Service) ? (Service) object : null;
        }
    };

    private static final Logger LOG = LoggerFactory.getLogger(GuicedServiceManager.class);

    // nested classes

    /**
     * Definition of URLs to use for defining service bindings. There are two sections of URls: the defines
     * and requires. You can have as many defines as you want, but only one requires. When parsing the resources,
     * the defines will be processed (in order) before the requires resource.
     */
    public static final class BindingsConfigurationProvider {

        // BindingsConfigurationProvider interface

        /**
         * Adds a URL to the the internal list.
         * @param url the url to add
         * @return this instance; useful for chaining
         */
        public BindingsConfigurationProvider define(URL url) {
            elements.add(new YamlBindingsUrl(url));
            return this;
        }

        /**
         * Adds a service binding to the internal list. This is equivalent to a yaml segment of
         * {@code bind: {theInteface : theImplementation}}. For instance, it does not affect locking, and if the
         * interface is locked, this will fail at run time.
         * @param anInterface the interface to bind to
         * @param anImplementation the implementing class
         * @param <T> the interface's type
         * @return this instance; useful for chaining
         */
        public <T> BindingsConfigurationProvider bind(Class<T> anInterface, Class<? extends T> anImplementation) {
            elements.add(new ManualServiceBinding(anInterface.getName(), anImplementation.getName(), false));
            return this;
        }

        /**
         * Adds a service binding to the internal list. This is equivalent to a yaml segment of
         * {@code bind: {theInteface : theImplementation}}. For instance, it does not affect locking, and if the
         * interface is locked, this will fail at run time.
         * @param anInterface the interface to bind to
         * @param anImplementation the implementing class
         * @param <T> the interface's type
         * @return this instance; useful for chaining
         */
        public <T> BindingsConfigurationProvider bindAndRequire(Class<T> anInterface, Class<? extends T> anImplementation) {
            elements.add(new ManualServiceBinding(anInterface.getName(), anImplementation.getName(), true));
            return this;
        }

        /**
         * Overrides the "requires" section of the URL definitions. This replaces the old requires URL.
         * @param url the new requires URL
         * @return this instance; useful for chaining
         */
        public BindingsConfigurationProvider overrideRequires(URL url) {
            requires = url;
            return this;
        }

        // for use in this package

        public Collection<BindingsConfigurationLoader> loaders() {
            List<BindingsConfigurationLoader> urls = new ArrayList<>(elements);
            if (requires != null) {
                urls.add(new YamlBindingsUrl(requires));
            }
            return urls;
        }


        // object state

        private final List<BindingsConfigurationLoader> elements = new ArrayList<>();
        private URL requires = null;
    }

    private static class YamlBindingsUrl implements BindingsConfigurationLoader {
        @Override
        public void loadInto(ServiceConfigurationHandler config) {
            final InputStream defaultServicesStream;
            try {
                defaultServicesStream = url.openStream();
            } catch(IOException e) {
                throw new RuntimeException("no resource " + url, e);
            }
            final Reader defaultServicesReader;
            try {
                defaultServicesReader = new InputStreamReader(defaultServicesStream, "UTF-8");
            } catch (Exception e) {
                try {
                    defaultServicesStream.close();
                } catch (IOException ioe) {
                    LOG.error("while closing stream error", ioe);
                }
                throw new RuntimeException("while opening default services reader", e);
            }
            RuntimeException exception = null;
            try {
                new YamlConfiguration(url.toString(), defaultServicesReader, null).loadInto(config);
            } catch (RuntimeException e) {
                exception = e;
            } finally {
                try {
                    defaultServicesReader.close();
                } catch (IOException e) {
                    if (exception == null) {
                        exception = new RuntimeException("while closing " + url, e);
                    }
                    else {
                        LOG.error("while closing url after exception " + exception, e);
                    }
                }
            }
            if (exception != null) {
                throw exception;
            }
        }

        private YamlBindingsUrl(URL url) {
            this.url = url;
        }

        private final URL url;
    }

    private static final BindingsConfigurationLoader emptyConfigurationLoader = new BindingsConfigurationLoader() {
        @Override
        public void loadInto(ServiceConfigurationHandler config) {}
    };

    private static class CompositeConfigurationLoader implements BindingsConfigurationLoader {

        public void add(BindingsConfigurationLoader loader) {
            loaders.add(loader);
        }

        @Override
        public void loadInto(ServiceConfigurationHandler config) {
            for (BindingsConfigurationLoader loader : loaders)
                loader.loadInto(config);
        }

        private final List<BindingsConfigurationLoader> loaders = new ArrayList<>();
    }

    private static class ManualServiceBinding implements BindingsConfigurationLoader {

        // BindingsConfigurationElement interface

        @Override
        public void loadInto(ServiceConfigurationHandler config) {
            config.bind(interfaceName, implementationName, null);
            if (required)
                config.require(interfaceName);
        }


        // ManualServiceBinding interface

        private ManualServiceBinding(String interfaceName, String implementationName, boolean required) {
            this.interfaceName = interfaceName;
            this.implementationName = implementationName;
            this.required = required;
        }

        // object state

        private final String interfaceName;
        private final String implementationName;
        private final boolean required;
    }

    static class PropertyBindings implements BindingsConfigurationLoader {
        // BindingsConfigurationElement interface

        @Override
        public void loadInto(ServiceConfigurationHandler config) {
            for (String property : properties.stringPropertyNames()) {
                if (property.startsWith(BIND)) {
                    String theInterface = property.substring(BIND.length());
                    String theImpl = properties.getProperty(property);
                    if (theInterface.length() == 0) {
                        throw new IllegalArgumentException("empty -Dbind: property found");
                    }
                    if (theImpl.length() == 0) {
                        throw new IllegalArgumentException("-D" + property + " doesn't have a valid value");
                    }
                    config.bind(theInterface, theImpl, null);
                } else if (property.startsWith(REQUIRE)) {
                    String theInterface = property.substring(REQUIRE.length());
                    String value = properties.getProperty(property);
                    if (value.length() != 0) {
                        throw new IllegalArgumentException(
                                String.format("-Drequire tags may not have values: %s = %s", theInterface, value)
                        );
                    }
                    config.require(theInterface);
                } else if (property.startsWith(PRIORITIZE)) {
                    String theInterface = property.substring(PRIORITIZE.length());
                    String value = properties.getProperty(property);
                    if (value.length() != 0) {
                        throw new IllegalArgumentException(
                                String.format("-Dprioritize tags may not have values: %s = %s", theInterface, value)
                        );
                    }
                    config.prioritize(theInterface);
                }
            }
        }

        // PropertyBindings interface

        PropertyBindings(Properties properties) {
            this.properties = properties;
        }

        // for use in unit tests

        // object state

        private final Properties properties;

        // consts

        private static final String BIND = "bind:";
        private static final String REQUIRE = "require:";
        private static final String PRIORITIZE = "prioritize:";
    }

    public static class NoOpJmxRegistry implements JmxRegistryService {
        @Override
        public ObjectName register(JmxManageable service) {
            try {
                return new ObjectName("com.foundationdb:type=DummyPlaceholder" + counter.incrementAndGet());
            } catch (MalformedObjectNameException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void unregister(ObjectName registeredObject) {
        }

        @Override
        public void unregister(String serviceName) {
        }

        // object state
        private final AtomicInteger counter = new AtomicInteger();
    }
}
TOP

Related Classes of com.foundationdb.server.service.servicemanager.GuicedServiceManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.