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