All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dtolabs.rundeck.ExpandRunServer Maven / Gradle / Ivy

There is a newer version: 2.11.14
Show newest version
/*
 * Copyright 2016 SimplifyOps, Inc. (http://simplifyops.com)
 *
 * 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.dtolabs.rundeck;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/*
 * Note that any non java/javax from the JavaSE runtime that are included in this file must also
 * be copied into the resulting rundeck-lanucher.jar via rundeckapp/scripts/BuildLauncher.groovy.
 */

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 * ExpandRunServer extracts some contents to particular locations, generates config files based on internal templates,
 * and then loads and starts the jetty server configured for the application.
 */
public class ExpandRunServer {

    
    
    private static final String CONFIG_DEFAULTS_PROPERTIES = "config-defaults.properties";
    //system props for launcher config
    private static final String SYS_PROP_RUNDECK_LAUNCHER_DEBUG = "rundeck.launcher.debug";
    private static final String SYS_PROP_RUNDECK_LAUNCHER_REWRITE = "rundeck.launcher.rewrite";
    private static final String SYS_PROP_RUNDECK_JAASLOGIN = "rundeck.jaaslogin";
    private static final String RUN_SERVER_CLASS = "com.dtolabs.rundeck.RunServer";

    private static final String PROP_LOGINMODULE_NAME = "loginmodule.name";
    private static final String SERVER_DATASTORE_PATH = "server.datastore.path";
    private static final String RUNDECK_SERVER_CONFIG_DIR = "rundeck.server.configDir";
    
    /**
     * Config properties are defaulted in config-defaults.properties, but can be overridden by system properties
     */
    final static String[] configProperties = {
        "server.http.port",
        "server.https.port",
        "server.hostname",
        "server.web.context",
        "rdeck.base",
        SERVER_DATASTORE_PATH,
        "default.user.name",
        "default.user.password",
        PROP_LOGINMODULE_NAME,
        "loginmodule.conf.name",
        "rundeck.config.name"
    };
    /**
     * line separator
     */
    private static final String LINESEP = System.getProperty("line.separator");
    public static final String FLAG_INSTALLONLY = "installonly";
    public static final String FLAG_SKIPINSTALL = "skipinstall";

    //members
    String basedir;

    File serverdir;
    File configDir;
    File datadir;
    File thisJar;
    String coreJarName;
    boolean debug = false;
    boolean rewrite = false;
    boolean useJaas = true;
    String versionString;
    private String runClassName;
    private String jettyLibsString;
    private String jettyLibPath;
    private static final String RUNDECK_START_CLASS = "Rundeck-Start-Class";
    private static final String RUNDECK_JETTY_LIBS = "Rundeck-Jetty-Libs";
    private static final String RUNDECK_JETTY_LIB_PATH = "Rundeck-Jetty-Lib-Path";
    private static final String RUNDECK_VERSION = "Rundeck-Version";
    
    private final Options options = new Options();

    public static void main(final String[] args) throws Exception {
        int result = new ExpandRunServer().run(args);
        System.exit(result);
    }

    @SuppressWarnings("static-access")
    public ExpandRunServer() {
        
        Option baseDir =    OptionBuilder.withLongOpt("basedir")
                                         .hasArg()
                                         .withDescription("The basedir")
                                         .withArgName("PATH")
                                         .create('b');
        
        Option serverDir =  OptionBuilder.withLongOpt("serverdir")
                                         .hasArg()
                                         .withDescription("The base directory for the server")
                                         .withArgName("PATH")
                                         .create();
        
        Option binDir =     OptionBuilder.withLongOpt("bindir")
                                         .hasArg()
                                         .withArgName("PATH")
                                         .withDescription("The install directory for the tools used by users.")
                                         .create('x');
        
        Option sbinDir =    OptionBuilder.withLongOpt("sbindir")
                                         .hasArg()
                                         .withArgName("PATH")
                                         .withDescription("The install directory for the tools used by administrators.")
                                         .create('s');
        
        Option configDir =  OptionBuilder.withLongOpt("configdir")
                                         .hasArg()
                                         .withArgName("PATH")
                                         .withDescription("The location of the configuration.")
                                         .create('c');
        
        Option dataDir =    OptionBuilder.withLongOpt("datadir")
                                         .hasArg()
                                         .withArgName("PATH")
                                         .withDescription("The location of Rundeck's runtime data.")
                                         .create();
        
        Option projectDir =    OptionBuilder.withLongOpt("projectdir")
                                            .hasArg()
                                            .withArgName("PATH")
                                            .withDescription("The location of Rundeck's project data.")
                                            .create('p');
        
        Option help =       OptionBuilder.withLongOpt("help")
                                         .withDescription("Display this message.")
                                         .create('h');
        
        Option debugFlag =  OptionBuilder.withDescription("Show debug information")
                                         .create('d');
        
        Option skipInstall = OptionBuilder.withLongOpt(FLAG_SKIPINSTALL)
                                          .withDescription("Skip the extraction of the utilities from the launcher.")
                                          .create();

        Option installonly = OptionBuilder.withLongOpt(FLAG_INSTALLONLY)
                                          .withDescription("Perform installation only and do not start the server.")
                                          .create();
        
        options.addOption(baseDir);
        options.addOption(dataDir);
        options.addOption(serverDir);
        options.addOption(binDir);
        options.addOption(sbinDir);
        options.addOption(configDir);
        options.addOption(help);
        options.addOption(debugFlag);
        options.addOption(skipInstall);
        options.addOption(installonly);
        options.addOption(projectDir);
        
        debug = Boolean.getBoolean(SYS_PROP_RUNDECK_LAUNCHER_DEBUG);
        rewrite = Boolean.getBoolean(SYS_PROP_RUNDECK_LAUNCHER_REWRITE);
        useJaas = null == System.getProperty(SYS_PROP_RUNDECK_JAASLOGIN) || Boolean.getBoolean(
            SYS_PROP_RUNDECK_JAASLOGIN);
        runClassName = RUN_SERVER_CLASS;
        thisJar = thisJarFile();
        //load jar attributes
        final Attributes mainAttributes;
        try {
            mainAttributes = getJarMainAttributes();
        } catch (IOException e) {
            throw new RuntimeException("Unable to load attributes", e);
        }

        versionString = mainAttributes.getValue(RUNDECK_VERSION);
        if (null != versionString) {
            DEBUG("Rundeck version: " + versionString);
        } else {
            throw new RuntimeException("Jar file attribute not found: " + RUNDECK_VERSION);
        }
        runClassName = mainAttributes.getValue(RUNDECK_START_CLASS);
        if (null == runClassName) {
            throw new RuntimeException("Jar file attribute not found: " + RUNDECK_START_CLASS);
        }
        jettyLibsString = mainAttributes.getValue(RUNDECK_JETTY_LIBS);
        if (null == jettyLibsString) {
            throw new RuntimeException("Jar file attribute not found: " + RUNDECK_JETTY_LIBS);
        }
        jettyLibPath = mainAttributes.getValue(RUNDECK_JETTY_LIB_PATH);
        if (null == jettyLibPath) {
            throw new RuntimeException("Jar file attribute not found: " + RUNDECK_JETTY_LIB_PATH);
        }
    }

    public int run(final String[] args) throws Exception {
        
        final CommandLineParser parser = new GnuParser();
        
        final CommandLine cl;
        try {
            cl = parser.parse(this.options, args);
            
            if(cl.hasOption('h')) {
                printUsage();
                return 0;
            }
            if(cl.hasOption(FLAG_INSTALLONLY) && cl.hasOption(FLAG_SKIPINSTALL)) {
                ERR("--" + FLAG_INSTALLONLY + " and --" + FLAG_SKIPINSTALL + " are mutually exclusive");
                printUsage();
                return 1;
            }
            
        } catch (ParseException e) {
            // oops, something went wrong
            System.err.println( "Parsing failed.  Reason: " + e.getMessage() );
            return 1;
        }
        debug = debug || cl.hasOption('d');
        DEBUG("Debugging is turned on.");
        //nb: absolutePath called twice because a relative file will return null for getParentFile
        this.basedir = cl.getOptionValue('b', new File(thisJar.getAbsolutePath()).getParentFile().getAbsolutePath());
        this.serverdir = new File(cl.getOptionValue("serverdir", basedir+"/server"));
        this.configDir = new File(cl.getOptionValue("c", serverdir + "/config"));
        this.datadir = new File(cl.getOptionValue("datadir", serverdir + "/data"));
        DEBUG("configDir is " + configDir.getAbsolutePath());
        final File toolsdir = new File(basedir, "tools");
        final File toolslibdir = new File(toolsdir, "lib");
        final File bindir = new File(cl.getOptionValue('x', toolsdir.getAbsolutePath() + "/bin"));


        initArgs();
        this.coreJarName = "rundeck-core-" + versionString + ".jar";

        if (null != basedir) {
            System.setProperty("rdeck.base", forwardSlashPath(basedir));
        }

        final Properties defaults = loadDefaults(CONFIG_DEFAULTS_PROPERTIES);
        final Properties configuration = createConfiguration(defaults);
        configuration.put("realm.properties.location", forwardSlashPath(configDir.getAbsolutePath())
                                                       + "/realm.properties");
        DEBUG("Runtime configuration properties: " + configuration);
        
        if(!cl.hasOption(FLAG_SKIPINSTALL)) {
            final File libdir = new File(serverdir, "lib");
            DEBUG("Extracting libs to: " + libdir.getAbsolutePath() + " ... ");
            deleteExistingJarsInDir(libdir, "^rundeck.*");
            deleteExistingJarsInDir(libdir, "^jetty-all-7\\.6\\.0.*");
            deleteExistingJarsInDir(libdir, "^servlet-api-2\\.5.*");
            extractLibs(libdir);
            extractJettyLibs(libdir);
            final File expdir = new File(serverdir, "exp");
            DEBUG("Extracting webapp to: " + expdir.getAbsolutePath() + " ... ");

            deleteExistingJarsInDir(new File(expdir, "webapp/WEB-INF/lib"), "^rundeck.*");
            deleteExistingJarsInDir(new File(expdir, "webapp/WEB-INF/lib"), "^h2-.*");
            extractWar(expdir);
            
            DEBUG("Extracting bin scripts to: " + bindir.getAbsolutePath() + " ... ");

            extractBin(bindir, new File(serverdir, "exp/webapp/WEB-INF/lib/" + coreJarName));
            deleteExistingJarsInDir(toolslibdir, "^rundeck.*");
            copyToolLibs(toolslibdir, new File(serverdir, "exp/webapp/WEB-INF/lib/" + coreJarName));
        
            expandTemplates(configuration, serverdir, rewrite);
            setScriptFilesExecutable(new File(serverdir, "sbin"));
//            extractLauncherContents(new File(basedir, "docs"), "docs", "docs/");
//            extractLauncherContents(new File(basedir, "libext"), "libext", "libext/");
        }else{
            DEBUG("--" + FLAG_SKIPINSTALL + ": Not extracting.");
        }
        
        if(cl.hasOption('p')) {
            System.setProperty("rdeck.projects",cl.getOptionValue('p'));
        }

        if (cl.hasOption(FLAG_INSTALLONLY)) {
            DEBUG("Done. --"+FLAG_INSTALLONLY+": Not starting server.");
            return 0;
        } else {
            return execute(cl.getArgs(), configDir, new File(basedir), serverdir, configuration);
        }
    }

    private void printUsage() {
        // automatically generate the help statement
        final HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp( "java [JAVA_OPTIONS] -jar rundeck-launcher.jar ", "\nRun the rundeck server, installing the " +
                "necessary components if they do not exist.\n", options,
                "\nhttp://rundeck.org\n", true );
    }

    private void extractLauncherContents(final File targetdir, final String prefix, final String stripPrefix) throws IOException {
        if(!targetdir.exists() && !targetdir.mkdirs()) {
            ERR("Unable to create dir: " + targetdir);
        }
        //extract launcher contents dir into the targetdir
        ZipUtil.extractZip(thisJar.getAbsolutePath(), targetdir, prefix, stripPrefix);
    }

    /**
     * Set executable bit on any script files in the directory if it exists
     * @param sbindir
     */
    private void setScriptFilesExecutable(final File sbindir) {
        //set executable on files
        if (sbindir.exists()) {
            for (final String s : sbindir.list()) {
                final File script = new File(sbindir, s);
                if (script.isFile() && !script.setExecutable(true)) {
                    ERR("Unable to set executable permissions for file: " + script.getAbsolutePath());
                }
            }
        }
    }

    private void copyToolLibs(final File toolslibdir, final File coreJar) throws IOException {
        if (!toolslibdir.isDirectory()) {
            if (!toolslibdir.mkdirs()) {
                ERR("Couldn't create bin dir: " + toolslibdir.getAbsolutePath());
                return;
            }
        }
        //get dependencies info
        final String depslist;
        final String[] jars;
        final String jarpath;
        final File destfile;
        try (JarFile zf = new JarFile(coreJar)) {
            depslist = zf.getManifest().getMainAttributes().getValue("Rundeck-Tools-Dependencies");
        }
        if (null == depslist) {
            throw new RuntimeException(
                "Rundeck Core jar file manifest attribute \"Rundeck-Tools-Dependencies\" was not found: " + coreJar
                    .getAbsolutePath());
        }
        jars = depslist.split(" ");

        //copy jars from list to toolslibdir

        jarpath = jettyLibPath;
        for (final String jarName : jars) {
            ZipUtil.extractZip(thisJar.getAbsolutePath(), toolslibdir, jarpath + "/" + jarName, jarpath + "/");
            if(!new File(toolslibdir,jarName).exists()) {
                ERR("Failed to extract dependent jar for tools into "+ toolslibdir.getAbsolutePath()+": " + jarName);
            }
        }

        //finally, copy corejar to toolslibdir
        destfile = new File(toolslibdir, coreJarName);
        if(!destfile.exists()) {
            if(!destfile.createNewFile()) {
                ERR("Unable to create file: " + destfile.createNewFile());
            }
        }
        ZipUtil.copyStream(new FileInputStream(coreJar), new FileOutputStream(destfile));
    }

    /**
     * Extract scripts to bin dir
     *
     * @param destDir
     */
    private void extractBin(final File destDir, final File coreJar) throws IOException {
        if (!destDir.isDirectory()) {
            if (!destDir.mkdirs()) {
                ERR("Couldn't create bin dir: " + destDir.getAbsolutePath());
                return;
            }
        }
        ZipUtil.extractZip(coreJar.getAbsolutePath(), destDir, "com/dtolabs/rundeck/core/cli/templates",
            "com/dtolabs/rundeck/core/cli/templates/");

        //set executable on shell scripts
        for (final String s : destDir.list(new FilenameFilter() {
            public boolean accept(final File file, final String s) {
                return !s.endsWith(".bat");
            }
        })) {
            final File script = new File(destDir, s);
            if(!script.setExecutable(true)) {
                ERR("Unable to set executable permissions for file: " + script.getAbsolutePath());
            }
        }
    }

    /**
     * Look for *.template files in the directory, duplicate to file "name" and expand properties
     *
     * @param props
     * @param directory
     * @param overwrite
     */
    private void expandTemplates(final Properties props, final File directory, final boolean overwrite) throws
            IOException
    {
        if (overwrite) {
            DEBUG("Configuration overwrite is TRUE");
        }
        final String tmplPrefix = "templates/";
        final String tmplSuffix = ".template";
        if (!directory.isDirectory() && !directory.mkdirs()) {
            throw new RuntimeException("Unable to create config dir: " + directory.getAbsolutePath());
        }

        /**
         * rename by removing suffix and prefix dir
         */
        final ZipUtil.renamer renamer = new ZipUtil.renamer() {
            public String rename(String input) {
                if (input.endsWith(tmplSuffix)) {
                    input = input.substring(0, input.length() - tmplSuffix.length());
                }
                if (input.startsWith(tmplPrefix)) {
                    input = input.substring(tmplPrefix.length());
                }
                return input;
            }
        };
        List origNames = new ArrayList<>();
        List partNames = new ArrayList<>();
        /**
         * accept .template files in templates/ directory
         * and only accept if destination file doesn't exist, or overwrite==true
         */
        final FilenameFilter filenameFilter = new FilenameFilter() {
            public boolean accept(final File file, final String name) {
                final String destName = renamer.rename(name);
                final File destFile;
                if (null != props.getProperty(destName + ".location")) {
                    destFile = new File(props.getProperty(destName + ".location"));
                } else {
                    destFile = new File(file, destName);
                }

                final boolean accept = (overwrite || !destFile.isFile())
                                       && name.startsWith(tmplPrefix)
                                       && name.endsWith(tmplSuffix);
                if (accept) {
                    DEBUG("Writing config file: " + destFile.getAbsolutePath());
                    if (!destFile.getName().contains("._")) {
                        origNames.add(destFile);
                    } else {
                        partNames.add(destFile);
                    }
                }
                return accept;
            }
        };
        ZipUtil.extractZip(thisJar.getAbsolutePath(), directory,
                           filenameFilter,
                           renamer,
                           //expand properties in-place
                           new propertyExpander(props)
        );
        Set parts = processFileParts(origNames);
        partNames.removeAll(parts);
        for (File part : parts) {
            //unprocessed
            part.delete();
        }
    }

    private Set processFileParts(final List origNames) throws IOException {
        //process appending file parts
        Set parts = new HashSet<>();
        for (File origName : origNames) {
            int i = 1;
            File test = new File(origName.getParentFile(), origName.getName() + "._" + i);
            while (test.exists()) {
                //append to original
                appendFile(test, origName);
                test.delete();
                parts.add(test);
                i++;
                test = new File(origName.getParentFile(), origName.getName() + "._" + i);
            }
        }
        return parts;
    }

    private void appendFile(final File test, final File origName) throws IOException {

        try (
                FileChannel inc = FileChannel.open(test.toPath(), StandardOpenOption.READ);
                FileChannel outc = FileChannel.open(
                        origName.toPath(),
                        StandardOpenOption.WRITE,
                        StandardOpenOption.APPEND
                );
        ) {
            inc.transferTo(0, inc.size(), outc);
        }
    }

    private static class propertyExpander implements ZipUtil.streamCopier {
        Properties properties;

        public propertyExpander(final Properties properties) {
            this.properties = properties;
        }

        public void copyStream(final InputStream in, final OutputStream out) throws IOException {
            expandTemplate(in, out, properties);
        }
    }

    /**
     * Copy from file to toFile, expanding properties in the contents
     *
     * @param inputStream  input stream
     * @param outputStream output stream
     * @param props        properties
     */
    private static void expandTemplate(final InputStream inputStream, final OutputStream outputStream,
                                       final Properties props) throws IOException {

        final BufferedReader read = new BufferedReader(new InputStreamReader(inputStream));
        final BufferedWriter write = new BufferedWriter(new OutputStreamWriter(outputStream));
        String line = read.readLine();
        while (null != line) {
            write.write(expandProperties(props, line));
            write.write(LINESEP);
            line = read.readLine();
        }
        write.flush();
        write.close();
        read.close();
    }

    /**
     * Load properties file with default values in the jar
     *
     * @param path
     *
     * @return
     */
    private Properties loadDefaults(final String path) {
        final Properties properties = new Properties();
        try {
            final InputStream is;
            try (ZipFile jar = new ZipFile(thisJar)) {
                is = jar.getInputStream(new ZipEntry(CONFIG_DEFAULTS_PROPERTIES));
                if (null == is) {
                    throw new RuntimeException("Unable to read config-defaults.properties from jar");
                }
                properties.load(is);
            }
        } catch (IOException e) {
            throw new RuntimeException("Unable to load config defaults: " + path + ": " + e.getMessage(), e);
        }
        return properties;
    }


    /**
     * Create properties for template expansion
     *
     * @return
     */
    private Properties createConfiguration(final Properties defaults) throws UnknownHostException {
        final Properties properties = new Properties();
        properties.putAll(defaults);
        final String localhostname = getHostname();
        if (null != localhostname) {
            properties.put("server.hostname", localhostname);
        }
        properties.put("rdeck.base", forwardSlashPath(basedir));
        properties.put(SERVER_DATASTORE_PATH, forwardSlashPath(datadir.getAbsolutePath()) + "/grailsdb");
        properties.put("rundeck.log.dir", forwardSlashPath(serverdir.getAbsolutePath()) + "/logs");
        properties.put("rundeck.launcher.jar.location", forwardSlashPath(thisJar.getAbsolutePath()));
        properties.put(RUNDECK_SERVER_CONFIG_DIR, forwardSlashPath(this.configDir.getAbsolutePath()));
        for (final String configProperty : configProperties) {
            if (null != System.getProperty(configProperty)) {
                properties.put(configProperty, forwardSlashPath(System.getProperty(configProperty)));
            }
        }

        return properties;
    }
    public static String forwardSlashPath(final String input) {
        if (System.getProperties().get("file.separator").equals("\\")) {
            return input.replaceAll("\\\\", "/");
        }
        return input;
    }

    private String getHostname() {
        String name = null;
        try {
            name = InetAddress.getLocalHost().getHostName();
            DEBUG("Determined hostname: " + name);
        } catch (UnknownHostException ignored) {
        }
        return name;
    }

    /**
     * Extract any jars in the lib/ resource dir to the destination
     *
     * @param libdir
     *
     * @throws IOException
     */
    private void extractLibs(final File libdir) throws IOException {
        //expand contents
        ZipUtil.extractZip(thisJar.getAbsolutePath(), libdir, "lib/", "lib/");
    }

    /**
     * Use the jar attributes to extract selective libs for jetty dependencies
     *
     * @param libdir
     *
     * @throws IOException
     */
    private void extractJettyLibs(final File libdir) throws IOException {
        //expand contents
        final String[] jarNames = jettyLibsString.split(" ");

        final String jarpath = jettyLibPath;
        for (final String jarName : jarNames) {
            ZipUtil.extractZip(thisJar.getAbsolutePath(), libdir, jarpath + "/" + jarName, jarpath + "/");
        }
    }

    private void extractWar(final File expdir) throws IOException {
        //expand contents
        ZipUtil.extractZip(thisJar.getAbsolutePath(), expdir, "pkgs", "pkgs/");
    }

    /**
     * Remove any jar files whose names match the pattern that exist in the directory, if the directory
     * exists.
     * @param dir directory
     * @param fileMatch regex to match files to delete
     */
    private void deleteExistingJarsInDir(final File dir, final String fileMatch) {
        if(dir.isDirectory()){
            final File[] rundeckJars = dir.listFiles(new FilenameFilter() {
                public boolean accept(final File file, final String s) {
                    return s.matches(fileMatch) && s.endsWith(".jar");
                }
            });
            for (final File rundeckJar : rundeckJars) {
                DEBUG("Delete existing jar file: " + rundeckJar.getAbsolutePath());
                if (!rundeckJar.delete()) {
                    ERR("Unable to remove existing jar file: " + rundeckJar);
                }
            }
        }
    }

    private int execute(
            final String[] args, final File configDir, final File baseDir, final File serverDir,
            final Properties configuration
    ) throws
        IOException {
        //set some system properties used by the RunServer class
        System.setProperty("server.http.port", configuration.getProperty("server.http.port"));
        System.setProperty(RUNDECK_SERVER_CONFIG_DIR, configDir.getAbsolutePath());
        System.setProperty("rundeck.server.serverDir", serverDir.getAbsolutePath());
        System.setProperty("rundeck.config.location", new File(configDir, configuration.getProperty(
            "rundeck.config.name")).getAbsolutePath());
        if (useJaas) {
            System.setProperty("java.security.auth.login.config", new File(configDir,
                configuration.getProperty("loginmodule.conf.name")).getAbsolutePath());
            System.setProperty(PROP_LOGINMODULE_NAME, configuration.getProperty(PROP_LOGINMODULE_NAME));
        }

        //configure commandline arguments
        final ArrayList execargs = new ArrayList();
        execargs.add(baseDir.getAbsolutePath());
        if (args.length > 1) {
            execargs.addAll(Arrays.asList(Arrays.copyOfRange(args.clone(), 1, args.length)));
        }

        //execute the RunServer.main method
        int result = 500;
        try {
            invokeMain(runClassName, execargs.toArray(new String[execargs.size()]), new File(
                serverdir, "lib"));
            result = 0;//success
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        DEBUG("Finished, exit code: " + result);
        return result;
    }

    /**
     * Invoke the main method on the given class, using the specified args, with a classloader including all jars in the
     * specified libdir
     *
     * @param CLASSNAME class to invoke
     * @param args      arguments to pass to main method
     * @param libdir    dir containing required jar files
     *
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     * @throws MalformedURLException
     */
    public void invokeMain(final String CLASSNAME, final String[] args, final File libdir) throws NoSuchMethodException,
        InvocationTargetException,
        IllegalAccessException, ClassNotFoundException, MalformedURLException {
        final ClassLoaderUtil clload = new ClassLoaderUtil(libdir);
        // load the class
        final ClassLoader loader = clload.getClassLoader(ClassLoader.getSystemClassLoader());

        final Class cls = Class.forName(CLASSNAME, true, loader);
        // invokde the main method via reflection
        final Method mainMethod = ClassLoaderUtil.findMain(cls);

        Thread.currentThread().setContextClassLoader(loader);
        DEBUG("Start server with " + CLASSNAME + ".main(" + Arrays.toString(args) + ")");
        mainMethod.invoke(null, new Object[]{args});
    }


    /**
     * Initialize field values based on parsed args and system properties.  Sets the basedir to parent dir of the
     * launcher jar if unset, and loads necessary manifest attributes for extracting the launcher jar contents.
     */
    private void initArgs() {
        if (null == basedir) {
            //locate basedir based on this jar's location
            //set basedir to the dir containing
            final File base = new File(thisJar.getAbsolutePath()).getParentFile();
            basedir = base.getAbsolutePath();
            LOG("Rundeck basedir: " + basedir);
        }

    }


    /**
     * Load the manifest main attributes from the enclosing jar
     *
     * @return
     */
    private static Attributes getJarMainAttributes() throws IOException {
        Attributes mainAttributes = null;
        final File file = thisJarFile();
        try (JarFile jarFile = new JarFile(file)) {
            mainAttributes = jarFile.getManifest().getMainAttributes();
        }
        return mainAttributes;
    }

    /**
     * Return file for the enclosing jar
     *
     * @return
     *
     * @throws URISyntaxException
     */
    private static File thisJarFile() {
        final ProtectionDomain protectionDomain = ExpandRunServer.class.getProtectionDomain();
        final URL location = protectionDomain.getCodeSource().getLocation();
        try {
            return new File(location.toURI());
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Print log message
     *
     * @param s
     */
    private void LOG(final String s) {
        System.out.println(s);
    }
    /**
     * Print err message
     *
     * @param s
     */
    private void ERR(final String s) {
        System.err.println("ERROR: " + s);
    }


    /**
     * Print debug message if debug is enabled
     *
     * @param msg
     */
    private void DEBUG(final String msg) {
        if (debug) {
            System.err.println("VERBOSE: " + msg);
        }
    }

    private static final String PROPERTY_PATTERN = "\\$\\{([^\\}]+?)\\}";

    /**
     * Return the input with embedded property references expanded
     *
     * @param properties the properties to select form
     * @param input      the input
     *
     * @return string with references expanded
     */
    public static String expandProperties(final Properties properties, final String input) {
        final Pattern pattern = Pattern.compile(PROPERTY_PATTERN);
        final Matcher matcher = pattern.matcher(input);
        final StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            final String match = matcher.group(1);
            if (null != properties.get(match)) {
                matcher.appendReplacement(sb, Matcher.quoteReplacement(properties.getProperty(match)));
            } else {
                matcher.appendReplacement(sb, Matcher.quoteReplacement(matcher.group(0)));
            }
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy