com.dell.doradus.core.DoradusServer.java Source code

Java tutorial

Introduction

Here is the source code for com.dell.doradus.core.DoradusServer.java

Source

/*
 * Copyright (C) 2014 Dell, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dell.doradus.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Set;

import org.eclipse.jgit.api.DescribeCommand;
import org.eclipse.jgit.api.Git;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dell.doradus.common.ConfigurationException;
import com.dell.doradus.common.Utils;
import com.dell.doradus.service.Service;
import com.dell.doradus.service.StorageService;
import com.dell.doradus.service.rest.RESTCallback;
import com.dell.doradus.service.rest.RESTService;

/**
 * The container and entrypoint for the Doradus Server. Contains methods to start the
 * server as a console application or as a service, e.g., via procrun. Initializes
 * all services, watches for all a shutdown signal, then shuts down all services.
 */
public final class DoradusServer {
    // Singleton object:
    private static final DoradusServer INSTANCE = new DoradusServer();

    // True after init() and start() have been called, respectively:
    private boolean m_bInitialized;
    private boolean m_bRunning;

    // Logging interface:
    private final Logger m_logger = LoggerFactory.getLogger(getClass().getSimpleName());

    // Services required in every start (in addition to 1 storage service):
    private static final String[] REQUIRED_SERVICES = new String[] {
            com.dell.doradus.service.db.DBManagerService.class.getName(),
            com.dell.doradus.service.schema.SchemaService.class.getName(),
            com.dell.doradus.service.tenant.TenantService.class.getName() };

    // List of initialized services:
    private final List<Service> m_initializedServices = new ArrayList<>();

    // List of initialized/started StorageServices:
    private final List<StorageService> m_storageServices = new ArrayList<>();

    // List of all started services (the ones we must call "stop" on):
    private final List<Service> m_startedServices = new ArrayList<>();

    // System commands supported directly by the DoradusServer:
    private static final List<Class<? extends RESTCallback>> CMD_CLASSES = Arrays.asList(ThreadDumpCmd.class,
            LogDumpCmd.class, GetConfigCmd.class);

    private static final String VERSION_FILE = "doradus.ver";

    ///// Public methods

    /**
     * Get the singleton instance of the DoradusServer. The instance may or may not yet be
     * initialized.
     * 
     * @return  The singleton instance of the DoradusServer.
     */
    public static DoradusServer instance() {
        return INSTANCE;
    } // instance

    /**
     * Start the Doradus Server in stand-alone mode, overriding doradus.yaml file options
     * with the given options. All required services plus default_services and
     * storage_services configured in doradus.yaml are started. The process blocks until a
     * shutdown signal is received via Ctrl-C or until {@link #shutdown(String[])} is
     * called.
     * 
     * @param args  Optional arguments that override doradus.yaml file options. Arguments
     *              should be provided in the form "-option value" where "option" is a
     *              doradus.yaml option name and "value" is the overriding value. For
     *              example: "-restport 1223" sets the REST API listening port to 1223.
     */
    public static void main(String[] args) {
        try {
            instance().initStandAlone(args);
            instance().start();
            instance().waitForShutdown();
        } catch (Throwable e) {
            instance().m_logger.error("Abnormal shutdown", e);
            System.exit(1); // invokes shutdown hooks
        }
    } // main

    /**
     * Entrypoint method when Doradus is run as a Windows service. Currently, does the
     * same thing as {@link #main(String[])}. This method blocks until a shutdown is
     * received via Ctrl-C or {@link #shutDown()} is called in another thread.
     * 
     * @param args  See {@link #main(String[])} for examples.
     */
    public static void startServer(String[] args) {
        main(args);
    } // startServer

    /**
     * Start the Doradus Server in stand-alone mode but return as soon as all internal
     * services are started. doradus.yaml file options are overridden by the args, if any.
     * All required services plus default_services and storage_services configured in
     * doradus.yaml are started. When the method returns, all internal services are
     * started by may not be ready to use.
     * 
     * @param args  Optional arguments that override doradus.yaml file options. Arguments
     *              should be provided in the form "-option value" where "option" is a
     *              doradus.yaml option name and "value" is the overriding value. For
     *              example: "-restport 1223" sets the REST API listening port to 1223.
     */
    public static void startServerUnblocked(String[] args) {
        instance().initStandAlone(args);
        instance().start();
    } // startServerUnblocked

    /**
     * Entrypoint method to embed a Doradus server in an application. The args parameter
     * is the same as {@link #startServer(String[])} and {@link #main(String[])}, which
     * override doradus.yaml file defaults. However, instead of starting all services,
     * only those in the given services parameter plus "required" services are started. At
     * least one storage service must be given, otherwise an exception is thrown. Once all
     * services have been started, this method returns, allowing the application to use
     * the now-running embedded server. When the application is done, it should call
     * {@link #shutDown()} or {@link #stopServer(String[])} to gracefully shut down the
     * server.
     * 
     * @param args      See {@link #main(String[])} for more details.
     * @param services  List of service modules to start. The full package name of each
     *                  must be provided.
     */
    public static void startEmbedded(String[] args, String[] services) {
        instance().initEmbedded(args, services);
        instance().start();
    } // startEmbedded

    /**
     * Shutdown the Doradus Server by stopping all services and calling System.exit(). The
     * given args are ignored: they are present for compatibility with Apache's procrun
     * application.
     * 
     * @param args  Not currently used (ignored).
     */
    public static void stopServer(String[] args) {
        instance().stop();
        System.exit(0);
    } // stopServer

    /**
     * Shutdown the Doradus Server by stopping all services. Compared to
     * {@link #stopServer(String[])}, this method does not call System.exit(). 
     */
    public static void shutDown() {
        instance().stop();
    } // shutDown

    /**
     * Get the name of the default storage-service for this server. This is either the
     * only {@link StorageService} that has been started, or if there are more than one,
     * the first one configured in the yaml file.
     * 
     * @return  The name of the default storage-service for this server.
     */
    public String getDefaultStorageService() {
        assert m_storageServices.size() > 0;
        return m_storageServices.get(0).getClass().getSimpleName();
    } // getDefaultStorageService

    /**
     * Find a registered storage service with the given name. If there is no storage
     * service with the registered name, null is returned. This method can only be called
     * after the DoradusServer has initialized.
     * 
     * @param serviceName   Name of the storage service to find. This is the same as the
     *                      class's "simple" name. For example, if the class name is
     *                      "com.dell.doradus.service.spider.SpiderService", the service
     *                      name is just "SpiderService".
     * @return              Singleton instance of the given service name if it is
     *                      registered, otherwise null.
     */
    public StorageService findStorageService(String serviceName) {
        Utils.require(m_bInitialized, "DoradusService has not yet initialized");
        for (StorageService service : m_storageServices) {
            if (service.getClass().getSimpleName().equals(serviceName)) {
                return service;
            }
        }
        return null;
    } // findStorageService

    /**
     * Get Doradus Version from git repo if it exists; otherwise get it from the local doradus.ver file
     * @return version
     */
    public static String getDoradusVersion() {
        String version = null;
        try {
            //first read from the local git repository
            Git git = Git.open(new File("../.git"));
            String url = git.getRepository().getConfig().getString("remote", "origin", "url");
            instance().m_logger.info("Remote.origin.url: {}", url);
            if (!Utils.isEmpty(url) && url.contains("dell-oss/Doradus.git")) {
                DescribeCommand cmd = git.describe();
                version = cmd.call();
                instance().m_logger.info("Doradus version found from git repo: {}", version);
                writeVersionToVerFile(version);
            }
        } catch (Throwable e) {
            instance().m_logger.info("failed to read version from git repo");
        }
        //if not found, reading from local file
        if (Utils.isEmpty(version)) {
            try {
                version = getVersionFromVerFile();
                instance().m_logger.info("Doradus version found from doradus.ver file {}", version);
            } catch (IOException e1) {
                version = null;
            }
        }
        return version;
    }

    // Write version to local file
    private static void writeVersionToVerFile(String version) throws IOException {
        //declared in a try-with-resource statement, it will be closed regardless of it completes normally or not      
        try (PrintWriter writer = new PrintWriter(
                new File(DoradusServer.class.getResource("/" + VERSION_FILE).getPath()))) {
            writer.write(version);
        }

    }

    // Get version from local file
    private static String getVersionFromVerFile() throws IOException {
        //declared in a try-with-resource statement, it will be closed regardless of it completes normally or not
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(DoradusServer.class.getResourceAsStream("/" + VERSION_FILE), "UTF-8"))) {
            return br.readLine();
        }

    }

    ///// Private methods

    // Construction via the instance() method only.
    private DoradusServer() {
    }

    // Add configured storage_services to the given set. 
    private void addConfiguredStorageServices(Set<String> serviceSet) {
        List<String> ssList = ServerParams.instance().getModuleParamList("DoradusServer", "storage_services");
        if (ssList != null) {
            serviceSet.addAll(ssList);
        }
    } // addConfiguredStorageServices

    // Add configured default_services to the given set. 
    private void addDefaultServices(Set<String> serviceSet) {
        List<String> defaultServices = ServerParams.instance().getModuleParamList("DoradusServer",
                "default_services");
        if (defaultServices != null) {
            serviceSet.addAll(defaultServices);
        }
    } // addDefaultServices

    // Hook the JVM shutdown hook so we get notified for Ctrl-C.
    private void hookShutdownEvent() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                instance().stop();
            }
        });
    } // hookShutdownEvent

    // Initialize server configuration and given+required services for embedded running.
    private void initEmbedded(String[] args, String[] services) {
        if (m_bInitialized) {
            m_logger.warn("initEmbedded: Already initialized -- ignoring");
            return;
        }
        m_logger.info("Initializing embedded mode");
        initConfig(args);
        initEmbeddedServices(services);
        RESTService.instance().registerCommands(CMD_CLASSES);
        m_bInitialized = true;
    } // initEmbedded

    // Initialize services required for embedded start.
    private void initEmbeddedServices(String[] requestedServices) {
        Set<String> serviceSet = new LinkedHashSet<>();
        if (requestedServices != null) {
            serviceSet.addAll(Arrays.asList(requestedServices));
        }
        addRequiredServices(serviceSet);
        initServices(serviceSet);
    } // initEmbeddedServices

    // Initialize server configuration and all services for stand-alone running.
    private void initStandAlone(String[] args) {
        if (m_bInitialized) {
            m_logger.warn("initStandAlone: Already initialized -- ignoring");
            return;
        }
        m_logger.info("Initializing standalone mode");
        initConfig(args);
        initStandaAloneServices();
        RESTService.instance().registerCommands(CMD_CLASSES);
        m_bInitialized = true;
    } // initStandAlone

    // Initialize services configured+needed for stand-alone operation.
    private void initStandaAloneServices() {
        Set<String> serviceSet = new LinkedHashSet<>();
        addDefaultServices(serviceSet);
        addRequiredServices(serviceSet);
        addConfiguredStorageServices(serviceSet);
        initServices(serviceSet);
    } // initStandaAloneServices

    // Initialize the ServerParams module, which loads the doradus.yaml file.
    private void initConfig(String[] args) {
        try {
            ServerParams.load(args);
            if (Utils.isEmpty(ServerParams.instance().getModuleParamString("DoradusServer", "super_user"))) {
                m_logger.warn("'DoradusServer.super_user' parameter is not defined. "
                        + "Privileged commands will be available without authentication.");
            }
        } catch (ConfigurationException e) {
            throw new RuntimeException("Failed to initialize server configuration", e);
        }
    } // initConfig

    // Initialize the given list of services. Register initialized Service and
    // StorageService objects. Throw if a storage service is not requested.
    private void initServices(Set<String> serviceSet) {
        for (String serviceName : serviceSet) {
            Service service = initService(serviceName);
            m_initializedServices.add(service);
            if (service instanceof StorageService) {
                m_storageServices.add((StorageService) service);
            }
        }
        if (m_storageServices.size() == 0) {
            throw new RuntimeException("No storage services were configured");
        }
    } // initServices

    // Initialize the service with the given package name.
    private Service initService(String serviceName) {
        m_logger.debug("Initializing service: " + serviceName);
        try {
            @SuppressWarnings("unchecked")
            Class<Service> serviceClass = (Class<Service>) Class.forName(serviceName);
            Method instanceMethod = serviceClass.getMethod("instance", (Class<?>[]) null);
            Service instance = (Service) instanceMethod.invoke(null, (Object[]) null);
            instance.initialize();
            return instance;
        } catch (Exception e) {
            throw new RuntimeException("Error initializing service: " + serviceName, e);
        }
    } // initService

    // Add required services, if missing, to the given list and return.
    private void addRequiredServices(Set<String> serviceSet) {
        serviceSet.addAll(Arrays.asList(REQUIRED_SERVICES));
    } // addRequiredServices

    // Start the DoradusServer services.
    private void start() {
        if (m_bRunning) {
            m_logger.warn("start: Already started -- ignoring");
            return;
        }
        Locale.setDefault(Locale.ROOT);
        m_logger.info("Doradus Version: {}", getDoradusVersion());
        hookShutdownEvent();
        startServices();
        m_bRunning = true;
    } // start

    // Start all registered services.
    private void startServices() {
        m_logger.info("Starting services: {}", simpleServiceNames(m_initializedServices));
        for (Service service : m_initializedServices) {
            m_logger.debug("Starting service: " + service.getClass().getSimpleName());
            service.start();
            m_startedServices.add(service);
        }
    } // startServices

    // Get simple service names as a comma-separated list. 
    private String simpleServiceNames(Collection<Service> services) {
        StringBuilder buffer = new StringBuilder();
        for (Service service : services) {
            if (buffer.length() > 0) {
                buffer.append(",");
            }
            buffer.append(service.getClass().getSimpleName());
        }
        return buffer.toString();
    } // simpleServiceNames

    // Stop all registered services.
    private void stopServices() {
        // Stop services in reverse order of starting.
        m_logger.debug("Stopping all services");
        ListIterator<Service> iter = m_startedServices.listIterator(m_startedServices.size());
        while (iter.hasPrevious()) {
            Service service = iter.previous();
            m_logger.debug("Stopping service: " + service.getClass().getSimpleName());
            service.stop();
            iter.remove();
        }
        m_initializedServices.clear();
        m_storageServices.clear();
    } // stopServices

    // Shutdown all services and terminate.
    private void stop() {
        if (m_bRunning) {
            instance().m_logger.info("Doradus Server shutting down");
            stopServices();
            ServerParams.unload();
            m_bRunning = false;
            m_bInitialized = false;
        }
    } // stop

    // Loop forever without consuming resources.
    private void waitForShutdown() {
        m_logger.info("Main thread waiting for shutdown notice");
        synchronized (this) {
            while (true) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // Ignore
                }
            }
        }
    } // waitForShutdown

} // class DoradusServer