// ========================================================================
// Copyright 2008 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// 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 org.mortbay.jetty.runner;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import javax.transaction.UserTransaction;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.plus.jndi.Transaction;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ShutdownMonitor;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.StatisticsServlet;
import org.eclipse.jetty.util.RolloverFileOutputStream;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
public class Runner
{
private static final Logger LOG = Log.getLogger(Runner.class);
public static final String[] __plusConfigurationClasses = new String[] {
org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName(),
org.eclipse.jetty.webapp.TagLibConfiguration.class.getCanonicalName()
};
public static final String __containerIncludeJarPattern = ".*/.*jsp-api-[^/]*\\.jar$|.*/.*jsp-[^/]*\\.jar$|.*/.*taglibs[^/]*\\.jar$|.*/.*jstl[^/]*\\.jar$|.*/.*jsf-impl-[^/]*\\.jar$|.*/.*javax.faces-[^/]*\\.jar$|.*/.*myfaces-impl-[^/]*\\.jar$|.*/.*jetty-runner-[^/]*\\.jar$";
protected Server _server;
protected Monitor _monitor;
protected URLClassLoader _classLoader;
protected List<URL> _classpath=new ArrayList<URL>();
protected ContextHandlerCollection _contexts;
protected RequestLogHandler _logHandler;
protected String _logFile;
protected String _configFile;
protected UserTransaction _ut;
protected String _utId;
protected String _txMgrPropertiesFile;
protected Random _random = new Random();
protected boolean _isTxServiceAvailable=false;
protected boolean _enableStatsGathering=false;
protected String _statsPropFile;
protected boolean _clusteredSessions=true;
public Runner()
{
}
public void usage(String error)
{
if (error!=null)
System.err.println("ERROR: "+error);
System.err.println("Usage: java [-DDEBUG] [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
System.err.println("Server Options:");
System.err.println(" --version - display version and exit");
System.err.println(" --log file - request log filename (with optional 'yyyy_mm_dd' wildcard");
System.err.println(" --out file - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
System.err.println(" --port n - port to listen on (default 8080)");
System.err.println(" --stop-port n - port to listen for stop command");
System.err.println(" --stop-key n - security string for stop command (required if --stop-port is present)");
System.err.println(" --jar file - a jar to be added to the classloader");
System.err.println(" --jdbc classname properties jndiname - classname of XADataSource or driver; properties string; name to register in jndi");
System.err.println(" --lib dir - a directory of jars to be added to the classloader");
System.err.println(" --classes dir - a directory of classes to be added to the classloader");
System.err.println(" --txFile - override properties file for Atomikos");
System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
System.err.println(" --config file - a jetty xml config file to use instead of command line options");
System.err.println("Context Options:");
System.err.println(" --path /path - context path (default /)");
System.err.println(" context - WAR file, web app dir or context.xml file");
System.exit(1);
}
public void configure(String[] args) throws Exception
{
// handle classpath bits first so we can initialize the log mechanism.
for (int i=0;i<args.length;i++)
{
if ("--version".equals(args[i]))
{
}
if ("--lib".equals(args[i]))
{
Resource lib = Resource.newResource(args[++i]);
if (!lib.exists() || !lib.isDirectory())
usage("No such lib directory "+lib);
expandJars(lib);
}
else if ("--jar".equals(args[i]))
{
Resource jar = Resource.newResource(args[++i]);
if (!jar.exists() || jar.isDirectory())
usage("No such jar "+jar);
_classpath.add(jar.getURL());
}
else if ("--classes".equals(args[i]))
{
Resource classes = Resource.newResource(args[++i]);
if (!classes.exists() || !classes.isDirectory())
usage("No such classes directory "+classes);
_classpath.add(classes.getURL());
}
else if (args[i].startsWith("--"))
i++;
}
initClassLoader();
try
{
if (Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.icatch.jta.UserTransactionImp")!=null)
_isTxServiceAvailable=true;
}
catch (ClassNotFoundException e)
{
_isTxServiceAvailable=false;
}
if (System.getProperties().containsKey("DEBUG"))
Log.getLog().setDebugEnabled(true);
LOG.info("Runner");
LOG.debug("Runner classpath {}",_classpath);
String contextPath="/";
boolean contextPathSet=false;
int port=8080;
int stopPort=0;
String stopKey=null;
boolean transactionManagerProcessed = false;
boolean runnerServerInitialized = false;
for (int i=0;i<args.length;i++)
{
if ("--port".equals(args[i]))
port=Integer.parseInt(args[++i]);
else if ("--stop-port".equals(args[i]))
stopPort=Integer.parseInt(args[++i]);
else if ("--stop-key".equals(args[i]))
stopKey=args[++i];
else if ("--log".equals(args[i]))
_logFile=args[++i];
else if ("--out".equals(args[i]))
{
String outFile=args[++i];
PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile,true,-1));
LOG.info("Redirecting stderr/stdout to "+outFile);
System.setErr(out);
System.setOut(out);
}
else if ("--path".equals(args[i]))
{
contextPath=args[++i];
contextPathSet=true;
}
else if ("--config".equals(args[i]))
{
_configFile=args[++i];
}
else if ("--lib".equals(args[i]))
{
++i;//skip
}
else if ("--jar".equals(args[i]))
{
++i; //skip
}
else if ("--classes".equals(args[i]))
{
++i;//skip
}
else if ("--stats".equals( args[i]))
{
_enableStatsGathering = true;
_statsPropFile = args[++i];
_statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile)?null:_statsPropFile);
}
else if ("--txFile".equals(args[i]))
{
_txMgrPropertiesFile=args[++i];
}
else if ("--jdbc".equals(args[i]))
{
i=configJDBC(args,i);
}
else // process contexts
{
if ( !transactionManagerProcessed ) // to be executed once upon starting to process contexts
{
processTransactionManagement();
transactionManagerProcessed = true;
}
if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
{
if (_server == null) // server not initialized yet
{
// build the server
_server = new Server();
}
//set up default configuration classes to apply to webapps
_server.setAttribute("org.eclipse.jetty.webapp.configuration", __plusConfigurationClasses);
//apply a config file if there is one
if (_configFile != null)
{
XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.newResource(_configFile).getURL());
xmlConfiguration.configure(_server);
}
//check that everything got configured, and if not, make the handlers
HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
if (handlers == null)
{
handlers = new HandlerCollection();
_server.setHandler(handlers);
}
//check if contexts already configured
_contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
if (_contexts == null)
{
_contexts = new ContextHandlerCollection();
prependHandler(_contexts, handlers);
}
if (_enableStatsGathering)
{
//if no stats handler already configured
if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null)
{
StatisticsHandler statsHandler = new StatisticsHandler();
prependHandler(statsHandler,handlers);
ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
statsContext.setSessionHandler(new SessionHandler());
if (_statsPropFile != null)
{
HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
Constraint constraint = new Constraint();
constraint.setName("Admin Only");
constraint.setRoles(new String[]{"admin"});
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setLoginService(loginService);
securityHandler.setConstraintMappings(Collections.singletonList(cm));
securityHandler.setAuthenticator(new BasicAuthenticator());
statsContext.setSecurityHandler(securityHandler);
}
}
}
//ensure a DefaultHandler is present
if (handlers.getChildHandlerByClass(DefaultHandler.class) == null)
{
handlers.addHandler(new DefaultHandler());
}
//ensure a log handler is present
_logHandler = (RequestLogHandler)handlers.getChildHandlerByClass( RequestLogHandler.class );
if ( _logHandler == null )
{
_logHandler = new RequestLogHandler();
handlers.addHandler( _logHandler );
}
//check a connector is configured to listen on
Connector[] connectors = _server.getConnectors();
if (connectors == null || connectors.length == 0)
{
Connector connector = new SelectChannelConnector();
connector.setPort(port);
_server.addConnector(connector);
if (_enableStatsGathering)
connector.setStatsOn(true);
}
else
{
if (_enableStatsGathering)
{
for (int j=0; j<connectors.length; j++)
{
connectors[j].setStatsOn(true);
}
}
}
runnerServerInitialized = true;
}
// Create a context
Resource ctx = Resource.newResource(args[i]);
if (!ctx.exists())
usage("Context '"+ctx+"' does not exist");
// Configure the context
if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml"))
{
// It is a context config file
XmlConfiguration xmlConfiguration=new XmlConfiguration(ctx.getURL());
xmlConfiguration.getIdMap().put("Server",_server);
ContextHandler handler=(ContextHandler)xmlConfiguration.configure();
_contexts.addHandler(handler);
if (contextPathSet)
handler.setContextPath(contextPath);
handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
__containerIncludeJarPattern);
}
else
{
// assume it is a WAR file
if (contextPathSet && !(contextPath.startsWith("/")))
contextPath = "/"+contextPath;
LOG.info("Deploying "+ctx.toString()+" @ "+contextPath);
WebAppContext webapp = new WebAppContext(_contexts,ctx.toString(),contextPath);
webapp.setConfigurationClasses(__plusConfigurationClasses);
webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
__containerIncludeJarPattern);
}
}
}
if (_server==null)
usage("No Contexts defined");
_server.setStopAtShutdown(true);
switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
{
case 1:
usage("Must specify --stop-key when --stop-port is specified");
break;
case 2:
usage("Must specify --stop-port when --stop-key is specified");
break;
case 3:
ShutdownMonitor monitor = ShutdownMonitor.getInstance();
monitor.setPort(stopPort);
monitor.setKey(stopKey);
monitor.setExitVm(true);
break;
}
if (_logFile!=null)
{
NCSARequestLog requestLog = new NCSARequestLog(_logFile);
requestLog.setExtended(false);
_logHandler.setRequestLog(requestLog);
}
}
protected void prependHandler (Handler handler, HandlerCollection handlers)
{
if (handler == null || handlers == null)
return;
Handler[] existing = handlers.getChildHandlers();
Handler[] children = new Handler[existing.length + 1];
children[0] = handler;
System.arraycopy(existing, 0, children, 1, existing.length);
handlers.setHandlers(children);
}
protected int configJDBC(String[] args,int i) throws Exception
{
String jdbcClass=null;
String jdbcProperties=null;
String jdbcJndiName=null;
if (!_isTxServiceAvailable)
{
LOG.warn("JDBC TX support not found on classpath");
i+=3;
}
else
{
jdbcClass=args[++i];
jdbcProperties=args[++i];
jdbcJndiName=args[++i];
//check for jdbc resources to register
if (jdbcClass!=null)
{
if (isXADataSource(jdbcClass))
{
Class simpleDataSourceBeanClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.SimpleDataSourceBean");
Object o = simpleDataSourceBeanClass.newInstance();
simpleDataSourceBeanClass.getMethod("setXaDataSourceClassName", new Class[] {String.class}).invoke(o, new Object[] {jdbcClass});
simpleDataSourceBeanClass.getMethod("setXaDataSourceProperties", new Class[] {String.class}).invoke(o, new Object[] {jdbcProperties});
simpleDataSourceBeanClass.getMethod("setUniqueResourceName", new Class[] {String.class}).invoke(o, new Object[] {jdbcJndiName});
org.eclipse.jetty.plus.jndi.Resource jdbcResource = new org.eclipse.jetty.plus.jndi.Resource(jdbcJndiName, o);
}
else
{
String[] props = jdbcProperties.split(";");
String user=null;
String password=null;
String url=null;
for (int j=0;props!=null && j<props.length;j++)
{
String[] pair = props[j].split("=");
if (pair!=null && pair[0].equalsIgnoreCase("user"))
user=pair[1];
else if (pair!=null && pair[0].equalsIgnoreCase("password"))
password=pair[1];
else if (pair!=null && pair[0].equalsIgnoreCase("url"))
url=pair[1];
}
Class nonXADataSourceBeanClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.nonxa.NonXADataSourceBean");
Object o = nonXADataSourceBeanClass.newInstance();
nonXADataSourceBeanClass.getMethod("setDriverClassName", new Class[] {String.class}).invoke(o, new Object[] {jdbcClass});
nonXADataSourceBeanClass.getMethod("setUniqueResourceName", new Class[] {String.class}).invoke(o, new Object[] {jdbcJndiName});
nonXADataSourceBeanClass.getMethod("setUrl", new Class[] {String.class}).invoke(o, new Object[] {url});
nonXADataSourceBeanClass.getMethod("setUser", new Class[] {String.class}).invoke(o, new Object[] {user});
nonXADataSourceBeanClass.getMethod("setPassword", new Class[] {String.class}).invoke(o, new Object[] {password});
org.eclipse.jetty.plus.jndi.Resource jdbcResource = new org.eclipse.jetty.plus.jndi.Resource(jdbcJndiName, o);
}
}
}
return i;
}
public void run() throws Exception
{
if (_monitor != null)
{
_monitor.start();
}
_server.start();
_server.join();
}
protected void expandJars(Resource lib) throws IOException
{
String[] list = lib.list();
if (list==null)
return;
for (String path : list)
{
if (".".equals(path) || "..".equals(path))
continue;
Resource item = lib.addPath(path);
if (item.isDirectory())
expandJars(item);
else
{
if (path.toLowerCase().endsWith(".jar") ||
path.toLowerCase().endsWith(".zip"))
{
URL url = item.getURL();
_classpath.add(url);
}
}
}
}
protected void initClassLoader()
{
if (_classLoader==null && _classpath!=null && _classpath.size()>0)
{
ClassLoader context=Thread.currentThread().getContextClassLoader();
if (context==null)
_classLoader=new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]));
else
_classLoader=new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]),context);
Thread.currentThread().setContextClassLoader(_classLoader);
}
}
protected boolean isXADataSource (String classname)
throws Exception
{
Class clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
boolean isXA=false;
while (!isXA && clazz!=null)
{
Class[] interfaces = clazz.getInterfaces();
for (int i=0;interfaces!=null &&!isXA && i<interfaces.length; i++)
{
if (interfaces[i].getCanonicalName().equals("javax.sql.XADataSource"))
isXA=true;
}
clazz=clazz.getSuperclass();
}
LOG.debug(isXA?"XA":"!XA");
return isXA;
}
private void processTransactionManagement() throws Exception
{
//set up a transaction manager
if (!_isTxServiceAvailable)
{
LOG.warn("No tx manager found");
}
else
{
//this invocation of jetty needs a unique random number to identify the tx manager
_utId = Integer.toHexString(_random.nextInt());
if (_txMgrPropertiesFile == null)
{
//Use system properties to config atomikos
System.setProperty("com.atomikos.icatch.no_file", "true");
//create a directory for the tx mgr log and console files to go into that will be unique
File tmpDir = new File(System.getProperty("java.io.tmpdir"));
tmpDir = new File(tmpDir, _utId);
tmpDir.mkdir();
LOG.debug("Made " + tmpDir.getAbsolutePath());
System.setProperty("com.atomikos.icatch.log_base_dir ", tmpDir.getCanonicalPath());
System.setProperty("com.atomikos.icatch.console_file_name", "tm-debug.log");
System.setProperty("com.atomikos.icatch.output_dir", tmpDir.getCanonicalPath());
System.setProperty("com.atomikos.icatch.tm_unique_name", _utId);
}
else
{
System.setProperty("com.atomikos.icatch.file", _txMgrPropertiesFile);
}
//create UserTransaction
Class utsClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.icatch.jta.UserTransactionImp");
//register in JNDI
Transaction txMgrResource = new Transaction((UserTransaction)utsClass.newInstance());
}
}
public static void main(String[] args)
{
Runner runner = new Runner();
try
{
if (args.length>0&&args[0].equalsIgnoreCase("--help"))
{
runner.usage(null);
}
else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
{
System.err.println("org.mortbay.jetty.Runner: "+Server.getVersion());
System.exit(1);
}
runner.configure(args);
runner.run();
}
catch (Exception e)
{
e.printStackTrace();
runner.usage(null);
}
}
}