2
* Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
3
* Distributed under the terms of either:
4
* - the common development and distribution license (CDDL), v1.0; or
5
* - the GNU Lesser General Public License, v2.1 or later
10
import java.io.FileInputStream;
11
import java.io.FileOutputStream;
12
import java.io.IOException;
13
import java.io.InputStream;
14
import java.io.InterruptedIOException;
15
import java.io.ObjectInputStream;
16
import java.io.OutputStream;
17
import java.lang.reflect.Constructor;
18
import java.net.ServerSocket;
19
import java.net.Socket;
21
import java.net.URLClassLoader;
22
import java.util.ArrayList;
23
import java.util.HashMap;
24
import java.util.Iterator;
25
import java.util.List;
27
import java.util.Properties;
30
* Implements the main launcher daemon thread. This is the class that gets
31
* launched by the command line, and owns the server socket, etc.
33
* @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
34
* @version $Id: Launcher.java,v 1.29 2007/04/23 02:55:35 rickknowles Exp $
36
public class Launcher implements Runnable {
38
static final String HTTP_LISTENER_CLASS = "winstone.HttpListener";
39
static final String HTTPS_LISTENER_CLASS = "winstone.ssl.HttpsListener";
40
static final String AJP_LISTENER_CLASS = "winstone.ajp13.Ajp13Listener";
41
static final String CLUSTER_CLASS = "winstone.cluster.SimpleCluster";
42
static final String DEFAULT_JNDI_MGR_CLASS = "winstone.jndi.ContainerJNDIManager";
44
public static final byte SHUTDOWN_TYPE = (byte) '0';
45
public static final byte RELOAD_TYPE = (byte) '4';
47
private int CONTROL_TIMEOUT = 2000; // wait 2s for control connection
48
private int DEFAULT_CONTROL_PORT = -1;
50
private Thread controlThread;
51
public final static WinstoneResourceBundle RESOURCES = new WinstoneResourceBundle("winstone.LocalStrings");
52
private int controlPort;
53
private HostGroup hostGroup;
54
private ObjectPool objectPool;
55
private List listeners;
57
private Cluster cluster;
58
private JNDIManager globalJndiManager;
61
* Constructor - initialises the web app, object pools, control port and the
62
* available protocol listeners.
64
public Launcher(Map args) throws IOException {
66
boolean useJNDI = WebAppConfiguration.booleanArg(args, "useJNDI", false);
68
// Set jndi resource handler if not set (workaround for JamVM bug)
70
Class ctxFactoryClass = Class.forName("winstone.jndi.java.javaURLContextFactory");
71
if (System.getProperty("java.naming.factory.initial") == null) {
72
System.setProperty("java.naming.factory.initial", ctxFactoryClass.getName());
74
if (System.getProperty("java.naming.factory.url.pkgs") == null) {
75
System.setProperty("java.naming.factory.url.pkgs", "winstone.jndi");
77
} catch (ClassNotFoundException err) {}
79
Logger.log(Logger.MAX, RESOURCES, "Launcher.StartupArgs", args + "");
82
this.controlPort = (args.get("controlPort") == null ? DEFAULT_CONTROL_PORT
83
: Integer.parseInt((String) args.get("controlPort")));
85
// Check for java home
86
List jars = new ArrayList();
87
List commonLibCLPaths = new ArrayList();
88
String defaultJavaHome = System.getProperty("java.home");
89
String javaHome = WebAppConfiguration.stringArg(args, "javaHome", defaultJavaHome);
90
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.UsingJavaHome", javaHome);
91
String toolsJarLocation = WebAppConfiguration.stringArg(args, "toolsJar", null);
93
if (toolsJarLocation == null) {
94
toolsJar = new File(javaHome, "lib/tools.jar");
96
// first try - if it doesn't exist, try up one dir since we might have
97
// the JRE home by mistake
98
if (!toolsJar.exists()) {
99
File javaHome2 = new File(javaHome).getParentFile();
100
File toolsJar2 = new File(javaHome2, "lib/tools.jar");
101
if (toolsJar2.exists()) {
102
javaHome = javaHome2.getCanonicalPath();
103
toolsJar = toolsJar2;
107
toolsJar = new File(toolsJarLocation);
110
// Add tools jar to classloader path
111
if (toolsJar.exists()) {
112
jars.add(toolsJar.toURL());
113
commonLibCLPaths.add(toolsJar);
114
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.AddedCommonLibJar",
116
} else if (WebAppConfiguration.booleanArg(args, "useJasper", false))
117
Logger.log(Logger.WARNING, RESOURCES, "Launcher.ToolsJarNotFound");
119
// Set up common lib class loader
120
String commonLibCLFolder = WebAppConfiguration.stringArg(args,
121
"commonLibFolder", "lib");
122
File libFolder = new File(commonLibCLFolder);
123
if (libFolder.exists() && libFolder.isDirectory()) {
124
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.UsingCommonLib",
125
libFolder.getCanonicalPath());
126
File children[] = libFolder.listFiles();
127
for (int n = 0; n < children.length; n++)
128
if (children[n].getName().endsWith(".jar")
129
|| children[n].getName().endsWith(".zip")) {
130
jars.add(children[n].toURL());
131
commonLibCLPaths.add(children[n]);
132
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.AddedCommonLibJar",
133
children[n].getName());
136
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.NoCommonLib");
138
ClassLoader commonLibCL = new URLClassLoader((URL[]) jars.toArray(new URL[jars.size()]),
139
getClass().getClassLoader());
141
Logger.log(Logger.MAX, RESOURCES, "Launcher.CLClassLoader",
142
commonLibCL.toString());
143
Logger.log(Logger.MAX, RESOURCES, "Launcher.CLClassLoader",
144
commonLibCLPaths.toString());
146
this.objectPool = new ObjectPool(args);
148
// Optionally set up clustering if enabled and libraries are available
149
String useCluster = (String) args.get("useCluster");
150
boolean switchOnCluster = (useCluster != null)
151
&& (useCluster.equalsIgnoreCase("true") || useCluster
152
.equalsIgnoreCase("yes"));
153
if (switchOnCluster) {
154
if (this.controlPort < 0) {
155
Logger.log(Logger.INFO, RESOURCES,
156
"Launcher.ClusterOffNoControlPort");
158
String clusterClassName = WebAppConfiguration.stringArg(args, "clusterClassName",
159
CLUSTER_CLASS).trim();
161
Class clusterClass = Class.forName(clusterClassName);
162
Constructor clusterConstructor = clusterClass
163
.getConstructor(new Class[] { Map.class, Integer.class });
164
this.cluster = (Cluster) clusterConstructor
165
.newInstance(new Object[] { args, new Integer(this.controlPort) });
166
} catch (ClassNotFoundException err) {
167
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.ClusterNotFound");
168
} catch (Throwable err) {
169
Logger.log(Logger.WARNING, RESOURCES, "Launcher.ClusterStartupError", err);
174
// If jndi is enabled, run the container wide jndi populator
176
String jndiMgrClassName = WebAppConfiguration.stringArg(args, "containerJndiClassName",
177
DEFAULT_JNDI_MGR_CLASS).trim();
180
Class jndiMgrClass = Class.forName(jndiMgrClassName, true, commonLibCL);
181
Constructor jndiMgrConstr = jndiMgrClass.getConstructor(new Class[] {
182
Map.class, List.class, ClassLoader.class });
183
this.globalJndiManager = (JNDIManager) jndiMgrConstr.newInstance(new Object[] {
184
args, null, commonLibCL });
185
this.globalJndiManager.setup();
186
} catch (ClassNotFoundException err) {
187
Logger.log(Logger.DEBUG, RESOURCES,
188
"Launcher.JNDIDisabled");
189
} catch (Throwable err) {
190
Logger.log(Logger.ERROR, RESOURCES,
191
"Launcher.JNDIError", jndiMgrClassName, err);
196
this.hostGroup = new HostGroup(this.cluster, this.objectPool, commonLibCL,
197
(File []) commonLibCLPaths.toArray(new File[0]), args);
199
// Create connectors (http, https and ajp)
200
this.listeners = new ArrayList();
201
spawnListener(HTTP_LISTENER_CLASS);
202
spawnListener(AJP_LISTENER_CLASS);
204
Class.forName("javax.net.ServerSocketFactory");
205
spawnListener(HTTPS_LISTENER_CLASS);
206
} catch (ClassNotFoundException err) {
207
Logger.log(Logger.DEBUG, RESOURCES,
208
"Launcher.NeedsJDK14", HTTPS_LISTENER_CLASS);
211
this.controlThread = new Thread(this, RESOURCES.getString(
212
"Launcher.ThreadName", "" + this.controlPort));
213
this.controlThread.setDaemon(false);
214
this.controlThread.start();
216
Runtime.getRuntime().addShutdownHook(new ShutdownHook(this));
221
* Instantiates listeners. Note that an exception thrown in the
222
* constructor is interpreted as the listener being disabled, so
223
* don't do anything too adventurous in the constructor, or if you do,
224
* catch and log any errors locally before rethrowing.
226
protected void spawnListener(String listenerClassName) {
228
Class listenerClass = Class.forName(listenerClassName);
229
Constructor listenerConstructor = listenerClass
230
.getConstructor(new Class[] { Map.class,
231
ObjectPool.class, HostGroup.class});
232
Listener listener = (Listener) listenerConstructor
233
.newInstance(new Object[] { args, this.objectPool,
235
if (listener.start()) {
236
this.listeners.add(listener);
238
} catch (ClassNotFoundException err) {
239
Logger.log(Logger.INFO, RESOURCES,
240
"Launcher.ListenerNotFound", listenerClassName);
241
} catch (Throwable err) {
242
Logger.log(Logger.ERROR, RESOURCES,
243
"Launcher.ListenerStartupError", listenerClassName, err);
248
* The main run method. This handles the normal thread processing.
251
boolean interrupted = false;
253
ServerSocket controlSocket = null;
255
if (this.controlPort > 0) {
256
controlSocket = new ServerSocket(this.controlPort);
257
controlSocket.setSoTimeout(CONTROL_TIMEOUT);
260
Logger.log(Logger.INFO, RESOURCES, "Launcher.StartupOK",
261
new String[] {RESOURCES.getString("ServerVersion"),
262
(this.controlPort > 0 ? "" + this.controlPort
263
: RESOURCES.getString("Launcher.ControlDisabled"))});
265
// Enter the main loop
266
while (!interrupted) {
267
// this.objectPool.removeUnusedRequestHandlers();
268
// this.hostGroup.invalidateExpiredSessions();
270
// Check for control request
271
Socket accepted = null;
273
if (controlSocket != null) {
274
accepted = controlSocket.accept();
275
if (accepted != null) {
276
handleControlRequest(accepted);
279
Thread.sleep(CONTROL_TIMEOUT);
281
} catch (InterruptedIOException err) {
282
} catch (InterruptedException err) {
284
} catch (Throwable err) {
285
Logger.log(Logger.ERROR, RESOURCES,
286
"Launcher.ShutdownError", err);
288
if (accepted != null) {
289
try {accepted.close();} catch (IOException err) {}
291
if (Thread.interrupted()) {
297
// Close server socket
298
if (controlSocket != null) {
299
controlSocket.close();
301
} catch (Throwable err) {
302
Logger.log(Logger.ERROR, RESOURCES, "Launcher.ShutdownError", err);
304
Logger.log(Logger.INFO, RESOURCES, "Launcher.ControlThreadShutdownOK");
307
protected void handleControlRequest(Socket csAccepted) throws IOException {
308
InputStream inSocket = null;
309
OutputStream outSocket = null;
310
ObjectInputStream inControl = null;
312
inSocket = csAccepted.getInputStream();
313
int reqType = inSocket.read();
314
if ((byte) reqType == SHUTDOWN_TYPE) {
315
Logger.log(Logger.INFO, RESOURCES,
316
"Launcher.ShutdownRequestReceived");
318
} else if ((byte) reqType == RELOAD_TYPE) {
319
inControl = new ObjectInputStream(inSocket);
320
String host = inControl.readUTF();
321
String prefix = inControl.readUTF();
322
Logger.log(Logger.INFO, RESOURCES, "Launcher.ReloadRequestReceived", host + prefix);
323
HostConfiguration hostConfig = this.hostGroup.getHostByName(host);
324
hostConfig.reloadWebApp(prefix);
325
} else if (this.cluster != null) {
326
outSocket = csAccepted.getOutputStream();
327
this.cluster.clusterRequest((byte) reqType,
328
inSocket, outSocket, csAccepted,
332
if (inControl != null) {
333
try {inControl.close();} catch (IOException err) {}
335
if (inSocket != null) {
336
try {inSocket.close();} catch (IOException err) {}
338
if (outSocket != null) {
339
try {outSocket.close();} catch (IOException err) {}
344
public void shutdown() {
345
// Release all listeners/pools/webapps
346
for (Iterator i = this.listeners.iterator(); i.hasNext();)
347
((Listener) i.next()).destroy();
348
this.objectPool.destroy();
349
if (this.cluster != null)
350
this.cluster.destroy();
351
this.hostGroup.destroy();
352
if (this.globalJndiManager != null) {
353
this.globalJndiManager.tearDown();
356
if (this.controlThread != null) {
357
this.controlThread.interrupt();
361
Logger.log(Logger.INFO, RESOURCES, "Launcher.ShutdownOK");
364
public boolean isRunning() {
365
return (this.controlThread != null) && this.controlThread.isAlive();
369
* Main method. This basically just accepts a few args, then initialises the
370
* listener thread. For now, just shut it down with a control-C.
372
public static void main(String argv[]) throws IOException {
373
Map args = getArgsFromCommandLine(argv);
375
if (args.containsKey("usage") || args.containsKey("help")) {
380
// Check for embedded war
381
deployEmbeddedWarfile(args);
383
// Check for embedded warfile
384
if (!args.containsKey("webroot") && !args.containsKey("warfile")
385
&& !args.containsKey("webappsDir")&& !args.containsKey("hostsDir")) {
392
} catch (Throwable err) {
393
Logger.log(Logger.ERROR, RESOURCES, "Launcher.ContainerStartupError", err);
397
public static Map getArgsFromCommandLine(String argv[]) throws IOException {
398
Map args = loadArgsFromCommandLineAndConfig(argv, "nonSwitch");
400
// Small hack to allow re-use of the command line parsing inside the control tool
401
String firstNonSwitchArgument = (String) args.get("nonSwitch");
402
args.remove("nonSwitch");
404
// Check if the non-switch arg is a file or folder, and overwrite the config
405
if (firstNonSwitchArgument != null) {
406
File webapp = new File(firstNonSwitchArgument);
407
if (webapp.exists()) {
408
if (webapp.isDirectory()) {
409
args.put("webroot", firstNonSwitchArgument);
410
} else if (webapp.isFile()) {
411
args.put("warfile", firstNonSwitchArgument);
418
public static Map loadArgsFromCommandLineAndConfig(String argv[], String nonSwitchArgName)
420
Map args = new HashMap();
422
// Load embedded properties file
423
String embeddedPropertiesFilename = RESOURCES.getString(
424
"Launcher.EmbeddedPropertiesFile");
426
InputStream embeddedPropsStream = Launcher.class.getResourceAsStream(
427
embeddedPropertiesFilename);
428
if (embeddedPropsStream != null) {
429
loadPropsFromStream(embeddedPropsStream, args);
430
embeddedPropsStream.close();
433
// Get command line args
434
String configFilename = RESOURCES.getString("Launcher.DefaultPropertyFile");
435
for (int n = 0; n < argv.length; n++) {
436
String option = argv[n];
437
if (option.startsWith("--")) {
438
int equalPos = option.indexOf('=');
439
String paramName = option.substring(2,
440
equalPos == -1 ? option.length() : equalPos);
441
if (equalPos != -1) {
442
args.put(paramName, option.substring(equalPos + 1));
444
args.put(paramName, "true");
446
if (paramName.equals("config")) {
447
configFilename = (String) args.get(paramName);
450
args.put(nonSwitchArgName, option);
454
// Load default props if available
455
File configFile = new File(configFilename);
456
if (configFile.exists() && configFile.isFile()) {
457
InputStream inConfig = new FileInputStream(configFile);
458
loadPropsFromStream(inConfig, args);
461
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.UsingPropertyFile",
469
protected static void deployEmbeddedWarfile(Map args) throws IOException {
470
String embeddedWarfileName = RESOURCES.getString("Launcher.EmbeddedWarFile");
471
InputStream embeddedWarfile = Launcher.class.getResourceAsStream(
472
embeddedWarfileName);
473
if (embeddedWarfile != null) {
474
File tempWarfile = File.createTempFile("embedded", ".war").getAbsoluteFile();
475
tempWarfile.getParentFile().mkdirs();
476
tempWarfile.deleteOnExit();
478
String embeddedWebroot = RESOURCES.getString("Launcher.EmbeddedWebroot");
479
File tempWebroot = new File(tempWarfile.getParentFile(), embeddedWebroot);
480
tempWebroot.mkdirs();
482
Logger.log(Logger.DEBUG, RESOURCES, "Launcher.CopyingEmbeddedWarfile",
483
tempWarfile.getAbsolutePath());
484
OutputStream out = new FileOutputStream(tempWarfile, true);
486
byte buffer[] = new byte[2048];
487
while ((read = embeddedWarfile.read(buffer)) != -1) {
488
out.write(buffer, 0, read);
491
embeddedWarfile.close();
493
args.put("warfile", tempWarfile.getAbsolutePath());
494
args.put("webroot", tempWebroot.getAbsolutePath());
495
args.remove("webappsDir");
496
args.remove("hostsDir");
500
protected static void loadPropsFromStream(InputStream inConfig, Map args) throws IOException {
501
Properties props = new Properties();
502
props.load(inConfig);
503
for (Iterator i = props.keySet().iterator(); i.hasNext(); ) {
504
String key = (String) i.next();
505
if (!args.containsKey(key.trim())) {
506
args.put(key.trim(), props.getProperty(key).trim());
512
public static void initLogger(Map args) throws IOException {
513
// Reset the log level
514
int logLevel = WebAppConfiguration.intArg(args, "debug", Logger.INFO);
515
// boolean showThrowingLineNo = WebAppConfiguration.booleanArg(args, "logThrowingLineNo", false);
516
boolean showThrowingThread = WebAppConfiguration.booleanArg(args, "logThrowingThread", false);
517
OutputStream logStream = null;
518
if (args.get("logfile") != null) {
519
logStream = new FileOutputStream((String) args.get("logfile"));
520
} else if (WebAppConfiguration.booleanArg(args, "logToStdErr", false)) {
521
logStream = System.err;
523
logStream = System.out;
525
// Logger.init(logLevel, logStream, showThrowingLineNo, showThrowingThread);
526
Logger.init(logLevel, logStream, showThrowingThread);
529
protected static void printUsage() {
530
// if the caller overrides the usage, use that instead.
531
String usage = USAGE;
533
usage = RESOURCES.getString("Launcher.UsageInstructions",
534
RESOURCES.getString("ServerVersion"));
535
System.out.println(usage);
539
* Overridable usage screen
541
public static String USAGE;