com.dtolabs.rundeck.ExpandRunServer Maven / Gradle / Ivy
/*
* 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