/*
* Helma License Notice
*
* The contents of this file are subject to the Helma License
* Version 2.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://adele.helma.org/download/helma/license.txt
*
* Copyright 1998-2003 Helma Software. All Rights Reserved.
*
* $RCSfile: Server.java,v $
* $Author: hannes $
* $Revision: 10004 $
* $Date: 2009-12-17 11:55:26 +0100 (Don, 17. Dez 2009) $
*/
package helma.main;
import helma.extensions.HelmaExtension;
import helma.framework.repository.FileResource;
import helma.framework.core.*;
import helma.objectmodel.db.DbSource;
import helma.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlrpc.*;
import java.io.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.*;
import java.net.*;
import helma.util.ResourceProperties;
/**
* Helma server main class.
*/
public class Server implements Runnable {
// version string
public static final String version = "1.7.0 (__builddate__)";
// static server instance
private static Server server;
// Server home directory
protected File hopHome;
// server-wide properties
ResourceProperties appsProps;
ResourceProperties dbProps;
ResourceProperties sysProps;
// our logger
private Log logger;
// are we using helma.util.Logging?
private boolean helmaLogging;
// server start time
public final long starttime;
// if paranoid == true we only accept XML-RPC connections from
// explicitly listed hosts.
public boolean paranoid;
private ApplicationManager appManager;
private Vector extensions;
private Thread mainThread;
// configuration
ServerConfig config;
// map of server-wide database sources
Hashtable dbSources;
// the embedded web server
// protected Serve websrv;
protected JettyServer jetty;
// the XML-RPC server
protected WebServer xmlrpc;
Thread shutdownhook;
/**
* Constructs a new Server instance with an array of command line options.
* TODO make this a singleton
* @param config the configuration
*/
public Server(ServerConfig config) {
server = this;
starttime = System.currentTimeMillis();
this.config = config;
hopHome = config.getHomeDir();
if (hopHome == null) {
throw new RuntimeException("helma.home property not set");
}
// create system properties
sysProps = new ResourceProperties();
if (config.hasPropFile()) {
sysProps.addResource(new FileResource(config.getPropFile()));
}
}
/**
* Static main entry point.
* @param args the command line arguments
*/
public static void main(String[] args) throws IOException {
loadServer(args);
// parse properties files etc
server.init();
// start the server main thread
server.start();
}
/**
* Entry point used by launcher.jar to load a server instance
* @param args the command line arguments
* @return the server instance
*/
public static Server loadServer(String[] args) {
checkJavaVersion();
ServerConfig config = null;
try {
config = getConfig(args);
} catch (Exception cex) {
printUsageError("error reading configuration: " + cex.getMessage());
System.exit(1);
}
checkRunning(config);
// create new server instance
server = new Server(config);
return server;
}
/**
* check if we are running on a Java 2 VM - otherwise exit with an error message
*/
public static void checkJavaVersion() {
String javaVersion = System.getProperty("java.version");
if ((javaVersion == null) || javaVersion.startsWith("1.3")
|| javaVersion.startsWith("1.2")
|| javaVersion.startsWith("1.1")
|| javaVersion.startsWith("1.0")) {
System.err.println("This version of Helma requires Java 1.4 or greater.");
if (javaVersion == null) { // don't think this will ever happen, but you never know
System.err.println("Your Java Runtime did not provide a version number. Please update to a more recent version.");
} else {
System.err.println("Your Java Runtime is version " + javaVersion +
". Please update to a more recent version.");
}
System.exit(1);
}
}
/**
* parse the command line arguments, read a given server.properties file
* and check the values given for server ports
* @return ServerConfig if successfull
* @throws Exception on any configuration error
*/
public static ServerConfig getConfig(String[] args) throws Exception {
ServerConfig config = new ServerConfig();
// get possible environment setting for helma home
if (System.getProperty("helma.home")!=null) {
config.setHomeDir(new File(System.getProperty("helma.home")));
}
parseArgs(config, args);
guessConfig(config);
// create system properties
ResourceProperties sysProps = new ResourceProperties();
sysProps.addResource(new FileResource(config.getPropFile()));
// check if there's a property setting for those ports not specified via command line
if (!config.hasWebsrvPort() && sysProps.getProperty("webPort") != null) {
try {
config.setWebsrvPort(getInetSocketAddress(sysProps.getProperty("webPort")));
} catch (Exception portx) {
throw new Exception("Error parsing web server port property from server.properties: " + portx);
}
}
if (!config.hasAjp13Port() && sysProps.getProperty("ajp13Port") != null) {
try {
config.setAjp13Port(getInetSocketAddress(sysProps.getProperty("ajp13Port")));
} catch (Exception portx) {
throw new Exception("Error parsing AJP1.3 server port property from server.properties: " + portx);
}
}
if (!config.hasXmlrpcPort() && sysProps.getProperty("xmlrpcPort") != null) {
try {
config.setXmlrpcPort(getInetSocketAddress(sysProps.getProperty("xmlrpcPort")));
} catch (Exception portx) {
throw new Exception("Error parsing XML-RPC server port property from server.properties: " + portx);
}
}
return config;
}
/**
* parse argument list from command line and store values
* in given ServerConfig object
* @throws Exception when argument can't be parsed into an InetAddrPort
* or invalid token is given.
*/
public static void parseArgs(ServerConfig config, String[] args) throws Exception {
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-h") && ((i + 1) < args.length)) {
config.setHomeDir(new File(args[++i]));
} else if (args[i].equals("-f") && ((i + 1) < args.length)) {
config.setPropFile(new File(args[++i]));
} else if (args[i].equals("-a") && ((i + 1) < args.length)) {
config.setApps(StringUtils.split(args[++i]));
} else if (args[i].equals("-x") && ((i + 1) < args.length)) {
try {
config.setXmlrpcPort(getInetSocketAddress(args[++i]));
} catch (Exception portx) {
throw new Exception("Error parsing XML-RPC server port property: " + portx);
}
} else if (args[i].equals("-w") && ((i + 1) < args.length)) {
try {
config.setWebsrvPort(getInetSocketAddress(args[++i]));
} catch (Exception portx) {
throw new Exception("Error parsing web server port property: " + portx);
}
} else if (args[i].equals("-jk") && ((i + 1) < args.length)) {
try {
config.setAjp13Port(getInetSocketAddress(args[++i]));
} catch (Exception portx) {
throw new Exception("Error parsing AJP1.3 server port property: " + portx);
}
} else if (args[i].equals("-c") && ((i + 1) < args.length)) {
config.setConfigFile(new File(args[++i]));
} else if (args[i].equals("-i") && ((i + 1) < args.length)) {
// eat away the -i parameter which is meant for helma.main.launcher.Main
i++;
} else {
throw new Exception("Unknown command line token: " + args[i]);
}
}
}
/**
* get main property file from home dir or vice versa,
* depending on what we have
*/
public static void guessConfig(ServerConfig config) throws Exception {
// get property file from hopHome:
if (!config.hasPropFile()) {
if (config.hasHomeDir()) {
config.setPropFile(new File(config.getHomeDir(), "server.properties"));
} else {
config.setPropFile(new File("server.properties"));
}
}
// create system properties
ResourceProperties sysProps = new ResourceProperties();
sysProps.addResource(new FileResource(config.getPropFile()));
// try to get hopHome from property file
if (!config.hasHomeDir() && sysProps.getProperty("hophome") != null) {
config.setHomeDir(new File(sysProps.getProperty("hophome")));
}
// use the directory where server.properties is located:
if (!config.hasHomeDir() && config.hasPropFile()) {
config.setHomeDir(config.getPropFile().getAbsoluteFile().getParentFile());
}
if (!config.hasPropFile()) {
throw new Exception ("no server.properties found");
}
if (!config.hasHomeDir()) {
throw new Exception ("couldn't determine helma directory");
}
}
/**
* print the usage hints and prefix them with a message.
*/
public static void printUsageError(String msg) {
System.out.println(msg);
printUsageError();
}
/**
* print the usage hints
*/
public static void printUsageError() {
System.out.println("");
System.out.println("Usage: java helma.main.Server [options]");
System.out.println("Possible options:");
System.out.println(" -a app[,...] Specify applications to start");
System.out.println(" -h dir Specify hop home directory");
System.out.println(" -f file Specify server.properties file");
System.out.println(" -c jetty.xml Specify Jetty XML configuration file");
System.out.println(" -w [ip:]port Specify embedded web server address/port");
System.out.println(" -x [ip:]port Specify XML-RPC address/port");
System.out.println(" -jk [ip:]port Specify AJP13 address/port");
System.out.println("");
System.out.println("Supported formats for server ports:");
System.out.println(" <port-number>");
System.out.println(" <ip-address>:<port-number>");
System.out.println(" <hostname>:<port-number>");
System.out.println("");
System.err.println("Usage Error - exiting");
System.out.println("");
}
/**
* Check wheter a server is already running on any of the given ports
* - otherwise exit with an error message
*/
public static void checkRunning(ServerConfig config) {
// check if any of the specified server ports is in use already
try {
if (config.hasWebsrvPort()) {
checkPort(config.getWebsrvPort());
}
if (config.hasXmlrpcPort()) {
checkPort(config.getXmlrpcPort());
}
if (config.hasAjp13Port()) {
checkPort(config.getAjp13Port());
}
} catch (Exception running) {
System.out.println(running.getMessage());
System.exit(1);
}
}
/**
* Check whether a server port is available by trying to open a server socket
*/
private static void checkPort(InetSocketAddress endpoint) throws IOException {
try {
ServerSocket sock = new ServerSocket();
sock.bind(endpoint);
sock.close();
} catch (IOException x) {
throw new IOException("Error binding to " + endpoint + ": " + x.getMessage());
}
}
/**
* initialize the server
*/
public void init() throws IOException {
// set the log factory property
String logFactory = sysProps.getProperty("loggerFactory",
"helma.util.Logging");
helmaLogging = "helma.util.Logging".equals(logFactory);
System.setProperty("org.apache.commons.logging.LogFactory", logFactory);
// set the current working directory to the helma home dir.
// note that this is not a real cwd, which is not supported
// by java. It makes sure relative to absolute path name
// conversion is done right, so for Helma code, this should work.
System.setProperty("user.dir", hopHome.getPath());
// from now on it's safe to call getLogger() because hopHome is set up
getLogger();
String startMessage = "Starting Helma " + version + " on Java " +
System.getProperty("java.version");
logger.info(startMessage);
// also print a msg to System.out
System.out.println(startMessage);
logger.info("Setting Helma Home to " + hopHome);
// read db.properties file in helma home directory
String dbPropfile = sysProps.getProperty("dbPropFile");
File file;
if ((dbPropfile != null) && !"".equals(dbPropfile.trim())) {
file = new File(dbPropfile);
} else {
file = new File(hopHome, "db.properties");
}
dbProps = new ResourceProperties();
dbProps.setIgnoreCase(false);
dbProps.addResource(new FileResource(file));
DbSource.setDefaultProps(dbProps);
// read apps.properties file
String appsPropfile = sysProps.getProperty("appsPropFile");
if ((appsPropfile != null) && !"".equals(appsPropfile.trim())) {
file = new File(appsPropfile);
} else {
file = new File(hopHome, "apps.properties");
}
appsProps = new ResourceProperties();
appsProps.setIgnoreCase(true);
appsProps.addResource(new FileResource(file));
paranoid = "true".equalsIgnoreCase(sysProps.getProperty("paranoid"));
String language = sysProps.getProperty("language");
String country = sysProps.getProperty("country");
String timezone = sysProps.getProperty("timezone");
if ((language != null) && (country != null)) {
Locale.setDefault(new Locale(language, country));
}
if (timezone != null) {
TimeZone.setDefault(TimeZone.getTimeZone(timezone));
}
// logger.debug("Locale = " + Locale.getDefault());
// logger.debug("TimeZone = " +
// TimeZone.getDefault().getDisplayName(Locale.getDefault()));
dbSources = new Hashtable();
// try to load the extensions
extensions = new Vector();
if (sysProps.getProperty("extensions") != null) {
initExtensions();
}
jetty = JettyServer.init(this, config);
}
/**
* initialize extensions
*/
private void initExtensions() {
StringTokenizer tok = new StringTokenizer(sysProps.getProperty("extensions"), ",");
while (tok.hasMoreTokens()) {
String extClassName = tok.nextToken().trim();
try {
Class extClass = Class.forName(extClassName);
HelmaExtension ext = (HelmaExtension) extClass.newInstance();
ext.init(this);
extensions.add(ext);
logger.info("Loaded: " + extClassName);
} catch (Throwable e) {
logger.error("Error loading extension " + extClassName + ": " + e.toString());
}
}
}
public void start() {
// Start running, finishing setup and then entering a loop to check changes
// in the apps.properties file.
mainThread = new Thread(this);
mainThread.start();
}
public void stop() {
mainThread = null;
appManager.stopAll();
}
public void shutdown() {
getLogger().info("Shutting down Helma");
if (jetty != null) {
try {
jetty.stop();
jetty.destroy();
} catch (Exception x) {
// exception in jettx stop. ignore.
}
}
if (xmlrpc != null) {
try {
xmlrpc.shutdown();
} catch (Exception x) {
// exception in xmlrpc server shutdown, ignore.
}
}
if (helmaLogging) {
Logging.shutdown();
}
server = null;
try {
Runtime.getRuntime().removeShutdownHook(shutdownhook);
// HACK: running the shutdownhook seems to be necessary in order
// to prevent it from blocking garbage collection of helma
// classes/classloaders. Since we already set server to null it
// won't do anything anyhow.
shutdownhook.start();
shutdownhook = null;
} catch (Exception x) {
// invalid shutdown hook or already shutting down. ignore.
}
}
/**
* The main method of the Server. Basically, we set up Applications and than
* periodically check for changes in the apps.properties file, shutting down
* apps or starting new ones.
*/
public void run() {
try {
if (config.hasXmlrpcPort()) {
InetSocketAddress xmlrpcPort = config.getXmlrpcPort();
String xmlparser = sysProps.getProperty("xmlparser");
if (xmlparser != null) {
XmlRpc.setDriver(xmlparser);
}
if (xmlrpcPort.getAddress() != null) {
xmlrpc = new WebServer(xmlrpcPort.getPort(), xmlrpcPort.getAddress());
} else {
xmlrpc = new WebServer(xmlrpcPort.getPort());
}
if (paranoid) {
xmlrpc.setParanoid(true);
String xallow = sysProps.getProperty("allowXmlRpc");
if (xallow != null) {
StringTokenizer st = new StringTokenizer(xallow, " ,;");
while (st.hasMoreTokens())
xmlrpc.acceptClient(st.nextToken());
}
}
xmlrpc.start();
logger.info("Starting XML-RPC server on port " + (xmlrpcPort));
}
appManager = new ApplicationManager(appsProps, this);
if (xmlrpc != null) {
xmlrpc.addHandler("$default", appManager);
}
// add shutdown hook to close running apps and servers on exit
shutdownhook = new HelmaShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownhook);
} catch (Exception x) {
throw new RuntimeException("Error setting up Server", x);
}
// set the security manager.
// the default implementation is helma.main.HelmaSecurityManager.
try {
String secManClass = sysProps.getProperty("securityManager");
if (secManClass != null) {
SecurityManager secMan = (SecurityManager) Class.forName(secManClass)
.newInstance();
System.setSecurityManager(secMan);
logger.info("Setting security manager to " + secManClass);
}
} catch (Exception x) {
logger.error("Error setting security manager", x);
}
// start embedded web server
if (jetty != null) {
try {
jetty.start();
} catch (Exception m) {
throw new RuntimeException("Error starting embedded web server", m);
}
}
// start applications
appManager.startAll();
while (Thread.currentThread() == mainThread) {
try {
Thread.sleep(3000L);
} catch (InterruptedException ie) {
}
try {
appManager.checkForChanges();
} catch (Exception x) {
logger.warn("Caught in app manager loop: " + x);
}
}
}
/**
* Make sure this server has an ApplicationManager (e.g. used when
* accessed from CommandlineRunner)
*/
public void checkAppManager() {
if (appManager == null) {
appManager = new ApplicationManager(appsProps, this);
}
}
/**
* Get an Iterator over the applications currently running on this Server.
*/
public Object[] getApplications() {
return appManager.getApplications();
}
/**
* Get an Application by name
*/
public Application getApplication(String name) {
return appManager.getApplication(name);
}
/**
* Get a logger to use for output in this server.
*/
public Log getLogger() {
if (logger == null) {
if (helmaLogging) {
// set up system properties for helma.util.Logging
String logDir = sysProps.getProperty("logdir", "log");
if (!"console".equals(logDir)) {
// try to get the absolute logdir path
// set up helma.logdir system property
File dir = new File(logDir);
if (!dir.isAbsolute()) {
dir = new File(hopHome, logDir);
}
logDir = dir.getAbsolutePath();
}
System.setProperty("helma.logdir", logDir);
}
logger = LogFactory.getLog("helma.server");
}
return logger;
}
/**
* Get the Home directory of this server.
*/
public File getHopHome() {
return hopHome;
}
/**
* Get the explicit list of apps if started with -a option
* @return
*/
public String[] getApplicationsOption() {
return config.getApps();
}
/**
* Get the main Server instance.
*/
public static Server getServer() {
return server;
}
/**
* Get the Server's XML-RPC web server.
*/
public static WebServer getXmlRpcServer() {
return server.xmlrpc;
}
/**
*
*
* @param key ...
*
* @return ...
*/
public String getProperty(String key) {
return (String) sysProps.get(key);
}
/**
* Return the server.properties for this server
* @return the server.properties
*/
public ResourceProperties getProperties() {
return sysProps;
}
/**
* Return the server-wide db.properties
* @return the server-wide db.properties
*/
public ResourceProperties getDbProperties() {
return dbProps;
}
/**
* Return the apps.properties entries for a given application
* @param appName the app name
* @return the apps.properties subproperties for the given app
*/
public ResourceProperties getAppsProperties(String appName) {
if (appName == null) {
return appsProps;
} else {
return appsProps.getSubProperties(appName + ".");
}
}
/**
*
*
* @return ...
*/
public File getAppsHome() {
String appHome = sysProps.getProperty("appHome", "");
if (appHome.trim().length() != 0) {
return new File(appHome);
} else {
return new File(hopHome, "apps");
}
}
/**
*
*
* @return ...
*/
public File getDbHome() {
String dbHome = sysProps.getProperty("dbHome", "");
if (dbHome.trim().length() != 0) {
return new File(dbHome);
} else {
return new File(hopHome, "db");
}
}
/**
*
*
* @return ...
*/
public Vector getExtensions() {
return extensions;
}
/**
*
*
* @param name ...
*/
public void startApplication(String name) {
appManager.start(name);
appManager.register(name);
}
/**
*
*
* @param name ...
*/
public void stopApplication(String name) {
appManager.stop(name);
}
private static InetSocketAddress getInetSocketAddress(String inetAddrPort)
throws UnknownHostException {
InetAddress addr = null;
int c = inetAddrPort.indexOf(':');
if (c >= 0) {
String a = inetAddrPort.substring(0, c);
if (a.indexOf('/') > 0)
a = a.substring(a.indexOf('/') + 1);
inetAddrPort = inetAddrPort.substring(c + 1);
if (a.length() > 0 && !"0.0.0.0".equals(a)) {
addr = InetAddress.getByName(a);
}
}
int port = Integer.parseInt(inetAddrPort);
return new InetSocketAddress(addr, port);
}
}