org.kalaider.desktop.scheduler.runner.Runner.java Source code

Java tutorial

Introduction

Here is the source code for org.kalaider.desktop.scheduler.runner.Runner.java

Source

/*
 * Copyright (C) 2016 Alexander Vasilevsky
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kalaider.desktop.scheduler.runner;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PatternOptionBuilder;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 *
 * @author Alexander Vasilevsky
 */
public class Runner {

    private static final Logger LOG = LogManager.getLogger("kScheduler-runner");

    public static final String CONFIG_OPTION = "config";
    public static final String PROFILE_OPTION = "profile";
    public static final String THREAD_NUMBER_OPTION = "nThreads";
    public static final String PORT_NUMBER_OPTION = "port";
    public static final String SHUTDOWN_OPTION = "shutdown";
    public static final String HELP_OPTION = "help";

    public static final int DEFAULT_PORT_NUMBER = 41256;

    private static final Options options;

    private static Object schedulerInstance;

    private static final Object lock = new Object();

    static {
        options = new Options();
        options.addOption(Option.builder().longOpt(CONFIG_OPTION).argName("file")
                .desc("A path to XML configuration file").type(PatternOptionBuilder.FILE_VALUE).hasArg().build());
        options.addOption(Option.builder().longOpt(PROFILE_OPTION).argName("string")
                .desc("A name of the profile specified in configuration file")
                .type(PatternOptionBuilder.STRING_VALUE).hasArg().build());
        options.addOption(Option.builder().longOpt(THREAD_NUMBER_OPTION).argName("integer")
                .desc("A number of threads available for scheduler").hasArg().build());
        options.addOption(Option.builder().longOpt(PORT_NUMBER_OPTION).argName("integer")
                .desc("A TCP/IP port number available for scheduler").hasArg().build());
        options.addOption(Option.builder().longOpt(SHUTDOWN_OPTION)
                .desc("Causes the shceduler running on the configured port to be shut down").build());
        options.addOption(Option.builder().longOpt(HELP_OPTION).desc("Print this help").build());
    }

    public static void main(String args[]) throws Exception {
        CommandLineParser parser = new DefaultParser();
        CommandLine parsed;
        try {
            parsed = parser.parse(options, args, false);
        } catch (ParseException ex) {
            help(ex);
            return;
        }
        if (parsed.hasOption(HELP_OPTION)) {
            help(null);
            return;
        }
        if (parsed.hasOption(SHUTDOWN_OPTION)) {
            stop(parsed);
        } else {
            start(parsed);
        }
    }

    private static void stop(CommandLine parsed) throws Exception {
        Number port = (Number) parsed.getParsedOptionValue(PORT_NUMBER_OPTION);
        if (port == null)
            port = DEFAULT_PORT_NUMBER;
        try (Socket socket = new Socket(InetAddress.getLocalHost(), port.intValue())) {
            socket.sendUrgentData(0);
        }
    }

    private static void start(CommandLine parsed) throws Exception {
        Number port = (Number) parsed.getParsedOptionValue(PORT_NUMBER_OPTION);
        if (port == null)
            port = DEFAULT_PORT_NUMBER;
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            shutdown();
        }, "ShutdownHook"));
        try (ServerSocket socket = new ServerSocket(port.intValue())) {
            File configFile = (File) parsed.getParsedOptionValue(CONFIG_OPTION);
            String profileName = (String) parsed.getParsedOptionValue(PROFILE_OPTION);
            Number threadCount = (Number) parsed.getParsedOptionValue(THREAD_NUMBER_OPTION);
            runScheduler(configFile.toPath(), profileName, threadCount == null ? 2 : threadCount.intValue());
            try (Socket accepted = socket.accept()) {
                shutdown();
            }
        }
    }

    private static void runScheduler(Path configFilePath, String profile, int nThreads) throws Exception {
        Path libs = Paths.get("./lib");
        Path plugins = Paths.get("./plugin");

        LOG.log(Level.INFO, "Libraries found: {}.", Arrays.asList(getUrlsInDirectory(libs)));
        LOG.log(Level.INFO, "Plugins found: {}.", Arrays.asList(getUrlsInDirectory(plugins)));

        ClassLoader libsLoader = URLClassLoader.newInstance(getUrlsInDirectory(libs),
                Thread.currentThread().getContextClassLoader());
        ClassLoader pluginsLoader = URLClassLoader.newInstance(getUrlsInDirectory(plugins), libsLoader);

        Thread newDaemon = new Thread(() -> {
            synchronized (lock) {
                Class<?> schedulerInstanceClass;

                try {
                    schedulerInstanceClass = pluginsLoader
                            .loadClass("org.kalaider.desktop.scheduler.SchedulerInstance");
                } catch (ClassNotFoundException ex) {
                    LOG.fatal("Class not found.", ex);
                    return;
                }

                Method getDefaultInstanceMethod;

                try {
                    getDefaultInstanceMethod = schedulerInstanceClass.getMethod("getDefaultInstance", Path.class,
                            String.class, int.class);
                } catch (NoSuchMethodException | SecurityException ex) {
                    LOG.fatal("Method not found.", ex);
                    return;
                }

                try {
                    schedulerInstance = getDefaultInstanceMethod.invoke(null, configFilePath, profile, nThreads);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    LOG.fatal("Illegal method call.", ex);
                    return;
                }

                Method startupMethod;

                try {
                    startupMethod = schedulerInstanceClass.getMethod("startup");
                } catch (NoSuchMethodException | SecurityException ex) {
                    LOG.fatal("Method not found.", ex);
                    return;
                }
                try {
                    startupMethod.invoke(schedulerInstance);
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                    LOG.fatal("Illegal method call.", ex);
                    return;
                }
            }
        });
        newDaemon.setName("SchedulerRunnerThread");
        newDaemon.setContextClassLoader(pluginsLoader);
        newDaemon.setDaemon(true);
        newDaemon.start();
    }

    private static URL[] getUrlsInDirectory(Path dir) throws Exception {
        try {
            dir = dir.toRealPath();
        } catch (IOException ex) {
            LOG.fatal("Unable to access directory.", ex);
            return new URL[0];
        }
        return Files.list(dir).collect(ArrayList::new, (list, path) -> {
            try {
                list.add(path.toUri().toURL());
            } catch (MalformedURLException ex) {
                LOG.warn("Unable to create URL from path.", ex);
            }
        }, ArrayList::addAll).toArray(new URL[0]);
    }

    private static void shutdown() {
        synchronized (lock) {

            if (schedulerInstance == null)
                return;

            Method shutdownMethod;

            try {
                shutdownMethod = schedulerInstance.getClass().getMethod("shutdown", boolean.class);
            } catch (NoSuchMethodException | SecurityException ex) {
                LOG.fatal("Method not found.", ex);
                return;
            }

            try {
                shutdownMethod.invoke(schedulerInstance, true);
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                LOG.fatal("Illegal method call.", ex);
                return;
            }

            schedulerInstance = null;
        }
    }

    private static void help(ParseException ex) {
        if (ex != null)
            LOG.error("Illegal command.", ex.getMessage());
        HelpFormatter fmt = new HelpFormatter();
        fmt.setLongOptPrefix("-");
        fmt.setOptPrefix("-");
        fmt.printHelp(" ", options);
    }
}