/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss;
import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;
import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import org.jboss.bootstrap.ServerLoader;
import org.jboss.bootstrap.spi.Server;
import org.jboss.bootstrap.spi.ServerConfig;
import org.jboss.bootstrap.spi.util.ServerConfigUtil;
/**
* Provides a command line interface to start the JBoss server.
*
* <p>
* To enable debug or trace messages durring boot change the Log4j
* configuration to use either <tt>log4j-debug.properties</tt>
* <tt>log4j-trace.properties</tt> by setting the system property
* <tt>log4j.configuration</tt>:
*
* <pre>
* ./run.sh -Dlog4j.configuration=log4j-debug.properties
* </pre>
* TODO: Should jdk logging be the default
*
* @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
* @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
* @author <a href="mailto:adrian.brock@happeningtimes.com">Adrian Brock</a>
* @author <a href="mailto:scott.stark@jboss.org">Scott Stark</a>
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @version $Revision: 88978 $
*/
public class Main
{
/** EDU.oswego.cs.dl.util.concurrent */
private String concurrentLib = "concurrent.jar";
/** A URL for obtaining microkernel patches */
private URL bootURL;
/** Extra jars from the /lib location that are added to the start of the boot
classpath. This can be used to override jboss /lib boot classes.
*/
private List<String> bootLibraries = new LinkedList<String>();
/** Extra libraries to load the server with .*/
private List<String> extraLibraries = new LinkedList<String>();
/** Extra classpath URLS to load the server with .*/
private List<URL> extraClasspath = new LinkedList<URL>();
/**
* Server properties. This object holds all of the required
* information to get the server up and running. Use System
* properties for defaults.
*/
private Properties props = new Properties(System.getProperties());
/** The booted server instance */
private Server server;
/**
* Explicit constructor.
*/
public Main()
{
super();
}
/**
* Access the booted server.
* @return the Server instance.
*/
public Server getServer()
{
return server;
}
/**
* Boot up JBoss.
*
* @param args The command line arguments.
*
* @throws Exception Failed to boot.
*/
public void boot(final String[] args) throws Exception
{
// remove this when JBAS-6744 is fixed
String useUnorderedSequence = System.getProperty("xb.builder.useUnorderedSequence");
if(useUnorderedSequence == null)
System.setProperty("xb.builder.useUnorderedSequence", "true");
// First process the command line to pickup custom props/settings
processCommandLine(args);
// Initialize the JDK logmanager
String name = System.getProperty("java.util.logging.manager");
if (name == null) {
System.setProperty("java.util.logging.manager",
"org.jboss.logmanager.LogManager");
}
// Auto set HOME_DIR to ../bin/run.jar if not set
String homeDir = props.getProperty(ServerConfig.HOME_DIR);
if (homeDir == null)
{
String path = Main.class.getProtectionDomain().getCodeSource().getLocation().getFile();
/* The 1.4 JDK munges the code source file with URL encoding so run
* this path through the decoder so that is JBoss starts in a path with
* spaces we don't come crashing down.
*/
path = URLDecoder.decode(path, "UTF-8");
File runJar = new File(path);
File homeFile = runJar.getParentFile().getParentFile();
homeDir = homeFile.getCanonicalPath();
}
props.setProperty(ServerConfig.HOME_DIR, homeDir);
// Setup HOME_URL too, ServerLoader needs this
String homeURL = props.getProperty(ServerConfig.HOME_URL);
if (homeURL == null)
{
File file = new File(homeDir);
homeURL = file.toURI().toURL().toString();
props.setProperty(ServerConfig.HOME_URL, homeURL);
}
// Load the server instance
ServerLoader loader = new ServerLoader(props);
/* If there is a patch dir specified make it the first element of the
loader bootstrap classpath. If its a file url pointing to a dir, then
add the dir and its contents.
*/
if (bootURL != null)
{
if (bootURL.getProtocol().equals("file"))
{
File dir = new File(bootURL.getFile());
if (dir.exists())
{
// Add the local file patch directory
loader.addURL(dir.toURL());
// Add the contents of the directory too
File[] jars = dir.listFiles(new JarFilter());
for (int j = 0; jars != null && j < jars.length; j++)
{
loader.addURL(jars[j].getCanonicalFile().toURL());
}
}
}
else
{
loader.addURL(bootURL);
}
}
// Add any extra libraries
for (int i = 0; i < bootLibraries.size(); i++)
{
loader.addLibrary(bootLibraries.get(i));
}
// Add the jars from the endorsed dir
loader.addEndorsedJars();
// jmx UnifiedLoaderRepository needs a concurrent class...
loader.addLibrary(concurrentLib);
// Add any extra libraries after the boot libs
for (int i = 0; i < extraLibraries.size(); i++)
{
loader.addLibrary(extraLibraries.get(i));
}
// Add any extra classapth URLs
for (int i = 0; i < extraClasspath.size(); i++)
{
loader.addURL(extraClasspath.get(i));
}
// Load the server
ClassLoader parentCL = Thread.currentThread().getContextClassLoader();
server = loader.load(parentCL);
// Initialize the server
server.init(props);
// Start 'er up mate!
server.start();
}
/**
* Shutdown the booted Server instance.
*
*/
public void shutdown()
{
server.shutdown();
}
private URL makeURL(String urlspec) throws MalformedURLException
{
urlspec = urlspec.trim();
URL url;
try
{
url = new URL(urlspec);
if (url.getProtocol().equals("file"))
{
// make sure the file is absolute & canonical file url
File file = new File(url.getFile()).getCanonicalFile();
url = file.toURL();
}
}
catch (Exception e)
{
// make sure we have a absolute & canonical file url
try
{
File file = new File(urlspec).getCanonicalFile();
url = file.toURL();
}
catch (Exception n)
{
throw new MalformedURLException(n.toString());
}
}
return url;
}
/** Process the command line... */
private void processCommandLine(final String[] args) throws Exception
{
// set this from a system property or default to jboss
String programName = System.getProperty("program.name", "jboss");
String sopts = "-:hD:d:p:n:c:Vj::B:L:C:P:b:g:u:m:l:";
LongOpt[] lopts =
{
new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'),
new LongOpt("bootdir", LongOpt.REQUIRED_ARGUMENT, null, 'd'),
new LongOpt("patchdir", LongOpt.REQUIRED_ARGUMENT, null, 'p'),
new LongOpt("netboot", LongOpt.REQUIRED_ARGUMENT, null, 'n'),
new LongOpt("configuration", LongOpt.REQUIRED_ARGUMENT, null, 'c'),
new LongOpt("version", LongOpt.NO_ARGUMENT, null, 'V'),
new LongOpt("jaxp", LongOpt.REQUIRED_ARGUMENT, null, 'j'),
new LongOpt("bootlib", LongOpt.REQUIRED_ARGUMENT, null, 'B'),
new LongOpt("library", LongOpt.REQUIRED_ARGUMENT, null, 'L'),
new LongOpt("classpath", LongOpt.REQUIRED_ARGUMENT, null, 'C'),
new LongOpt("properties", LongOpt.REQUIRED_ARGUMENT, null, 'P'),
new LongOpt("host", LongOpt.REQUIRED_ARGUMENT, null, 'b'),
new LongOpt("partition", LongOpt.REQUIRED_ARGUMENT, null, 'g'),
new LongOpt("udp", LongOpt.REQUIRED_ARGUMENT, null, 'u'),
new LongOpt("mcast_port", LongOpt.REQUIRED_ARGUMENT, null, 'm'),
new LongOpt("log", LongOpt.REQUIRED_ARGUMENT, null, 'l'),
};
Getopt getopt = new Getopt(programName, args, sopts, lopts);
int code;
String arg;
if (System.getProperty(ServerConfig.SERVER_BIND_ADDRESS) == null)
{
// ServerConfig.SERVER_BIND_ADDRESS could have been defined via
// run.conf and so we don't wanna override that.
props.setProperty(ServerConfig.SERVER_BIND_ADDRESS, "127.0.0.1");
System.setProperty(ServerConfig.SERVER_BIND_ADDRESS, "127.0.0.1");
}
while ((code = getopt.getopt()) != -1)
{
switch (code)
{
case ':':
case '?':
// for now both of these should exit with error status
System.exit(1);
break; // for completeness
case 1:
// this will catch non-option arguments
// (which we don't currently care about)
System.err.println(programName +
": unused non-option argument: " +
getopt.getOptarg());
break; // for completeness
case 'h':
// show command line help
System.out.println("usage: " + programName + " [options]");
System.out.println();
System.out.println("options:");
System.out.println(" -h, --help Show this help message");
System.out.println(" -V, --version Show version information");
System.out.println(" -- Stop processing options");
System.out.println(" -D<name>[=<value>] Set a system property");
System.out.println(" -d, --bootdir=<dir> Set the boot patch directory; Must be absolute or url");
System.out.println(" -p, --patchdir=<dir> Set the patch directory; Must be absolute or url");
System.out.println(" -n, --netboot=<url> Boot from net with the given url as base");
System.out.println(" -c, --configuration=<name> Set the server configuration name");
System.out.println(" -B, --bootlib=<filename> Add an extra library to the front bootclasspath");
System.out.println(" -L, --library=<filename> Add an extra library to the loaders classpath");
System.out.println(" -C, --classpath=<url> Add an extra url to the loaders classpath");
System.out.println(" -P, --properties=<url> Load system properties from the given url");
System.out.println(" -b, --host=<host or ip> Bind address for all JBoss services");
System.out.println(" -g, --partition=<name> HA Partition name (default=DefaultDomain)");
System.out.println(" -m, --mcast_port=<ip> UDP multicast port; only used by JGroups");
System.out.println(" -u, --udp=<ip> UDP multicast address");
System.out.println(" -l, --log=<log4j|jdk> Specify the logger plugin type");
System.out.println();
System.exit(0);
break; // for completeness
case 'D':
{
// set a system property
arg = getopt.getOptarg();
String name, value;
int i = arg.indexOf("=");
if (i == -1)
{
name = arg;
value = "true";
}
else
{
name = arg.substring(0, i);
value = arg.substring(i + 1, arg.length());
}
System.setProperty(name, value);
// Ensure setting the old bind.address property also sets the new
// jgroups.bind_addr property, otherwise jgroups may ignore it
if ("bind.address".equals(name))
{
// Wildcard address is not valid for JGroups
String addr = ServerConfigUtil.fixRemoteAddress(value);
System.setProperty("jgroups.bind_addr", addr);
}
else if ("jgroups.bind_addr".equals(name))
{
// Wildcard address is not valid for JGroups
String addr = ServerConfigUtil.fixRemoteAddress(value);
System.setProperty("jgroups.bind_addr", addr);
}
break;
}
case 'd':
// set the boot patch URL
bootURL = makeURL(getopt.getOptarg());
break;
case 'p':
{
// set the patch URL
URL patchURL = makeURL(getopt.getOptarg());
props.put(ServerConfig.PATCH_URL, patchURL.toString());
break;
}
case 'n':
// set the net boot url
arg = getopt.getOptarg();
// make sure there is a trailing '/'
if (!arg.endsWith("/")) arg += "/";
props.put(ServerConfig.HOME_URL, new URL(arg).toString());
break;
case 'c':
// set the server name
arg = getopt.getOptarg();
props.put(ServerConfig.SERVER_NAME, arg);
break;
case 'V':
{
// Package information for org.jboss
Package jbossPackage = Package.getPackage("org.jboss");
// show version information
System.out.println("JBoss " + jbossPackage.getImplementationVersion());
System.out.println();
System.out.println("Distributable under LGPL license.");
System.out.println("See terms of license at gnu.org.");
System.out.println();
System.exit(0);
break; // for completness
}
case 'j':
// Show an error and exit
System.err.println(programName + ": option '-j, --jaxp' no longer supported");
System.exit(1);
break; // for completness
case 'B':
arg = getopt.getOptarg();
bootLibraries.add(arg);
break;
case 'L':
arg = getopt.getOptarg();
extraLibraries.add(arg);
break;
case 'C':
{
URL url = makeURL(getopt.getOptarg());
extraClasspath.add(url);
break;
}
case 'P':
{
// Set system properties from url/file
URL url = makeURL(getopt.getOptarg());
Properties props = System.getProperties();
props.load(url.openConnection().getInputStream());
break;
}
case 'b':
arg = getopt.getOptarg();
props.put(ServerConfig.SERVER_BIND_ADDRESS, arg);
System.setProperty(ServerConfig.SERVER_BIND_ADDRESS, arg);
// used by JGroups; only set if not set via -D so users
// can use a different interface for cluster communication
// There are 2 versions of this property, deprecated bind.address
// and the new version, jgroups.bind_addr
String bindAddress = System.getProperty("bind.address");
if (bindAddress == null)
{
// Wildcard address is not valid for JGroups
bindAddress = ServerConfigUtil.fixRemoteAddress(arg);
System.setProperty("bind.address", bindAddress);
}
bindAddress = System.getProperty("jgroups.bind_addr");
if (bindAddress == null)
{
// Wildcard address is not valid for JGroups
bindAddress = ServerConfigUtil.fixRemoteAddress(arg);
System.setProperty("jgroups.bind_addr", bindAddress);
}
// Set the java.rmi.server.hostname if not set
String rmiHost = System.getProperty("java.rmi.server.hostname");
if( rmiHost == null )
{
rmiHost = ServerConfigUtil.fixRemoteAddress(arg);
System.setProperty("java.rmi.server.hostname", rmiHost);
}
break;
case 'g':
arg = getopt.getOptarg();
props.put(ServerConfig.PARTITION_NAME_PROPERTY, arg);
System.setProperty(ServerConfig.PARTITION_NAME_PROPERTY, arg);
break;
case 'u':
arg = getopt.getOptarg();
props.put(ServerConfig.PARTITION_UDP_PROPERTY, arg);
System.setProperty(ServerConfig.PARTITION_UDP_PROPERTY, arg);
// the new jgroups property name
System.setProperty("jgroups.udp.mcast_addr", arg);
break;
case 'm':
arg = getopt.getOptarg();
props.put(ServerConfig.PARTITION_UDP_PORT_PROPERTY, arg);
System.setProperty(ServerConfig.PARTITION_UDP_PORT_PROPERTY, arg);
break;
case 'l':
{
arg = getopt.getOptarg();
String logPlugin = arg;
if( arg.equalsIgnoreCase("log4j") )
logPlugin = "org.jboss.logging.Log4jLoggerPlugin";
else if( arg.equalsIgnoreCase("jdk") )
logPlugin = "org.jboss.logging.jdk.JDK14LoggerPlugin";
System.setProperty("org.jboss.logging.Logger.pluginClass", logPlugin);
break;
}
default:
// this should not happen,
// if it does throw an error so we know about it
throw new Error("unhandled option code: " + code);
}
}
// Fix up other bind addresses
String bindAddress = System.getProperty(ServerConfig.SERVER_BIND_ADDRESS);
if (System.getProperty("java.rmi.server.hostname") == null)
System.setProperty("java.rmi.server.hostname", bindAddress);
if (System.getProperty("jgroups.bind_addr") == null)
System.setProperty("jgroups.bind_addr", bindAddress);
// Enable jboss.vfs.forceCopy by default, if unspecified
if (System.getProperty("jboss.vfs.forceCopy") == null)
System.setProperty("jboss.vfs.forceCopy", "true");
}
/**
* This is where the magic begins.
*
* <P>Starts up inside of a "jboss" thread group to allow better
* identification of JBoss threads.
*
* @param args The command line arguments.
* @throws Exception for any error
*/
public static void main(final String[] args) throws Exception
{
Runnable worker = new Runnable() {
public void run()
{
try
{
Main main = new Main();
main.boot(args);
}
catch (Exception e)
{
System.err.println("Failed to boot JBoss:");
e.printStackTrace();
}
}
};
ThreadGroup threads = new ThreadGroup("jboss");
new Thread(threads, worker, "main").start();
}
/**
* This method is here so that if JBoss is running under
* Alexandria (An NT Service Installer), Alexandria can shutdown
* the system down correctly.
*
* @param argv the arguments
*/
public static void systemExit(String argv[])
{
System.exit(0);
}
static class JarFilter implements FilenameFilter
{
public boolean accept(File dir, String name)
{
return name.endsWith(".jar");
}
}
}