/* tjws - Main.java
* Copyright (C) 1999-2007 Dmitriy Rogatkin. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* Visit http://tjws.sourceforge.net to get the latest information
* about Rogatkin's products.
* $Id: Main.java,v 1.19 2011/09/24 02:44:20 dmitriy Exp $
* Created on Feb 22, 2007
* @author Dmitriy
*/
package Acme.Serve;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;
import Acme.Utils;
public class Main extends Serve {
public static final String CLI_FILENAME = "cmdparams";
private static final String progName = "Serve";
protected static Serve serve;
private static Thread sdHook;
/**
* @param args
*/
public static void main(String[] args) {
String workPath = System.getProperty("user.dir", ".");
StringBuffer messages = null;
int argc = args.length;
int argn;
if (argc == 0) { // a try to read from file for java -jar server.jar
args = readArguments(workPath, CLI_FILENAME);
if (args == null) {
messages = appendMessage(messages, "Can't read from CLI file ("+CLI_FILENAME+") at "+workPath+"\n");
} else
argc = args.length;
}
Map arguments = new HashMap(20);
arguments.put(ARG_WORK_DIRECTORY, workPath);
// Parse args.
// TODO: redesign process of parameters based on a map
for (argn = 0; argn < argc && args[argn].length() > 0 && args[argn].charAt(0) == '-';) {
if (args[argn].equals("-p") && argn + 1 < argc) {
++argn;
arguments.put(ARG_PORT, new Integer(args[argn]));
} else if (args[argn].equals("-t") && argn + 1 < argc) {
++argn;
arguments.put(ARG_THROTTLES, args[argn]);
} else if (args[argn].equals("-s") && argn + 1 < argc) {
++argn;
arguments.put(ARG_SERVLETS, args[argn]);
} else if (args[argn].equals("-r") && argn + 1 < argc) {
++argn;
arguments.put(ARG_REALMS, args[argn]);
} else if (args[argn].equals("-a") && argn + 1 < argc) {
++argn;
arguments.put(ARG_ALIASES, args[argn]);
} else if (args[argn].equals("-b") && argn + 1 < argc) {
++argn;
arguments.put(ARG_BINDADDRESS, args[argn]);
} else if (args[argn].equals("-k") && argn + 1 < argc) {
++argn;
arguments.put(ARG_BACKLOG, args[argn]/*new Integer(args[argn])*/);
} else if (args[argn].equals("-j") && argn + 1 < argc) {
++argn;
arguments.put(ARG_JSP, args[argn]);
} else if (args[argn].equals("-w") && argn + 1 < argc) {
++argn;
arguments.put(ARG_WAR, args[argn]);
} else if (args[argn].equals("-c") && argn + 1 < argc) {
++argn;
arguments.put(ARG_CGI_PATH, args[argn]);
} else if (args[argn].equals("-mka") && argn + 1 < argc) {
++argn;
arguments.put(ARG_MAX_CONN_USE, args[argn]);
arguments.put(ARG_KEEPALIVE, Boolean.TRUE);
} else if (args[argn].equals("-nka")) {
arguments.put(ARG_KEEPALIVE, Boolean.FALSE);
} else if (args[argn].equals("-sp")) {
arguments.put(ARG_SESSION_PERSIST, Boolean.TRUE);
} else if (args[argn].equals("-kat") && argn + 1 < argc) {
++argn;
arguments.put(ARG_KEEPALIVE_TIMEOUT, args[argn]);
arguments.put(ARG_KEEPALIVE, Boolean.TRUE);
} else if (args[argn].equals("-e") && argn + 1 < argc) {
++argn;
try {
arguments.put(ARG_SESSION_TIMEOUT, new Integer(args[argn]));
} catch (NumberFormatException nfe) {
}
} else if (args[argn].equals("-z") && argn + 1 < argc) {
++argn;
arguments.put(ARG_THREAD_POOL_SIZE, args[argn]);
// backlog will be anyway upper limitation
} else if (args[argn].equals("-d") && argn + 1 < argc) {
++argn;
arguments.put(ARG_LOG_DIR, args[argn]);
} else if (args[argn].startsWith("-l")) {
arguments
.put(ARG_ACCESS_LOG_FMT,
"{0}:{9,number,#} {1} {2} [{3,date,dd/MMM/yyyy:HH:mm:ss Z}] \"{4} {5} {6}\" {7,number,#} {8,number} {10} {11}");
if (args[argn].length() > 2) {
arguments.put(ARG_LOG_OPTIONS, args[argn].substring(2).toUpperCase());
if (args[argn].indexOf('f') >= 0) {
++argn;
arguments.put(ARG_ACCESS_LOG_FMT, args[argn]);
}
} else
arguments.put(ARG_LOG_OPTIONS, "");
} else if (args[argn].startsWith("-nohup")) {
arguments.put(ARG_NOHUP, ARG_NOHUP);
} else if (args[argn].equals("-m") && argn + 1 < argc) {
++argn;
try {
arguments.put(ARG_MAX_ACTIVE_SESSIONS, new Integer(args[argn]));
if (((Integer) arguments.get(ARG_MAX_ACTIVE_SESSIONS)).intValue() < DEF_MIN_ACT_SESS)
arguments.put(ARG_MAX_ACTIVE_SESSIONS, new Integer(DEF_MIN_ACT_SESS));
} catch (NumberFormatException nfe) {
// ignored
}
} else if (args[argn].equals("-err")) {
if (argn + 1 < argc && args[argn + 1].startsWith("-") == false) {
++argn;
try {
arguments.put(ARG_ERR, (PrintStream) Class.forName(args[argn]).newInstance());
} catch (Error er) {
messages = appendMessage(messages,
"Problem of processing class parameter of error redirection stream: ").append(er)
.append('\n');
} catch (Exception ex) {
messages = appendMessage(messages,
"Exception in processing class parameter of error redirection stream: ").append(ex)
.append('\n');
}
} else
arguments.put(ARG_ERR, System.err);
} else if (args[argn].equals("-out")) {
if (argn + 1 < argc && args[argn + 1].startsWith("-") == false) {
++argn;
try {
arguments.put(ARG_OUT, (PrintStream) Class.forName(args[argn]).newInstance());
} catch (Error er) {
messages = appendMessage(messages,
"Problem of processing class parameter of out redirection stream: ").append(er).append(
'\n');
} catch (Exception ex) {
messages = appendMessage(messages,
"Exception in processing class parameter of out redirection stream: ").append(ex)
.append('\n');
}
}
} else if (args[argn].equals("-sh")) {
arguments.put(ARG_HTTPONLY_SC, ARG_HTTPONLY_SC);
} else if (args[argn].startsWith("-")) { // free args, note it generate problem since free arguments can match internal arguments
if (args[argn].length() > 1)
arguments.put(args[argn].substring(1),// .toUpperCase(),
argn < argc - 1 ? args[++argn] : "");
} else
usage();
++argn;
}
if (argn != argc)
usage();
if (System.getProperty(DEF_PROXY_CONFIG) != null)
arguments.put(ARG_PROXY_CONFIG, System.getProperty(DEF_PROXY_CONFIG));
// log and error stream manipulation
// TODO add log rotation feature, it can be done as plug-in
PrintStream printstream = System.err;
if (arguments.get(ARG_OUT) != null)
printstream = (PrintStream) arguments.get(ARG_OUT);
else {
String logEncoding = System.getProperty(DEF_LOGENCODING);
try {
File logDir = new File(workPath);
if (arguments.get(ARG_LOG_DIR) != null) {
File dir = new File((String) arguments.get(ARG_LOG_DIR));
if (dir.isAbsolute() == true) {
logDir = dir;
} else {
logDir = new File(workPath, dir.getPath());
}
}
File logFile = new File(logDir.getPath(), "AWS-" + System.currentTimeMillis() + ".log");
if (logEncoding != null)
printstream = new PrintStream(new FileOutputStream(logFile), true, logEncoding); /* 1.4 */
else
printstream = new PrintStream(new FileOutputStream(logFile), true);
} catch (IOException e) {
System.err.println("I/O problem at setting a log stream " + e);
}
}
if (arguments.get(ARG_ERR) != null) {
System.setErr((PrintStream) arguments.get(ARG_ERR));
} else {
System.setErr(printstream);
}
if (messages != null)
System.err.println(messages);
/**
* format path mapping from=givenpath;dir=realpath
*/
PathTreeDictionary mappingtable = new PathTreeDictionary();
if (arguments.get(ARG_ALIASES) != null) {
File file = new File((String) arguments.get(ARG_ALIASES));
if (file.isAbsolute() == false)
file = new File(workPath, file.getPath());
if (file.exists() && file.canRead()) {
try {
// DataInputStream in = new DataInputStream(
// new FileInputStream(file));
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
do {
String mappingstr = in.readLine(); // no arguments in non ASCII encoding allowed
if (mappingstr == null)
break;
if (mappingstr.startsWith("#"))
continue;
StringTokenizer maptokenzr = new StringTokenizer(mappingstr, "=;");
if (maptokenzr.hasMoreTokens()) {
if (maptokenzr.nextToken("=").equalsIgnoreCase("from")) {
if (maptokenzr.hasMoreTokens()) {
String srcpath = maptokenzr.nextToken("=;");
if (maptokenzr.hasMoreTokens()
&& maptokenzr.nextToken(";=").equalsIgnoreCase("dir"))
try {
if (maptokenzr.hasMoreTokens()) {
File mapFile = new File(maptokenzr.nextToken());
if (mapFile.isAbsolute() == false)
mapFile = new File(workPath, mapFile.getPath());
if (srcpath.endsWith("/*") == false)
if (srcpath.endsWith("/"))
srcpath += "*";
else
srcpath += "/*";
mappingtable.put(srcpath, mapFile);
}
} catch (NullPointerException e) {
}
}
}
}
} while (true);
} catch (IOException e) {
System.err.println("Problem reading aliases file: " + arguments.get(ARG_ALIASES) + "/" + e);
}
} else
System.err.println("File " + file + " (" + arguments.get(ARG_ALIASES)
+ ") doesn't exist or not readable.");
}
// format realmname=path,user:password,,,,
// TODO consider to add a role, like realmname=path,user:password[:role]
PathTreeDictionary realms = new PathTreeDictionary();
if (arguments.get(ARG_REALMS) != null) {
try {
File file = new File((String) arguments.get(ARG_REALMS));
if (file.isAbsolute() == false)
file = new File(workPath, file.getPath());
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
do {
String realmstr = in.readLine();
if (realmstr == null)
break;
if (realmstr.startsWith("#"))
continue;
StringTokenizer rt = new StringTokenizer(realmstr, "=,:");
if (rt.hasMoreTokens()) {
String realmname = null;
realmname = rt.nextToken();
if (rt.hasMoreTokens()) {
String realmPath = null;
realmPath = rt.nextToken();
if (rt.hasMoreTokens()) {
String user = rt.nextToken();
if (rt.hasMoreTokens()) {
String password = rt.nextToken();
BasicAuthRealm realm = null;
Object o[] = realms.get(realmPath);
if (o != null && o[0] != null)
realm = (BasicAuthRealm) o[0];
else {
realm = new BasicAuthRealm(realmname);
if (realmPath.endsWith("/*") == false)
realmPath+="/*";
else if (realmPath.endsWith("/"))
realmPath+="*";
realms.put(realmPath, realm);
}
realm.put(user, password);
}
}
}
}
} while (true);
} catch (IOException ioe) {
System.err.println("I/O problem in reading realms file " + arguments.get(ARG_REALMS) + ": " + ioe);
}
}
// Create the server.
serve = new Serve(arguments, printstream);
// can use log(.. after this point
serve.setMappingTable(mappingtable);
serve.setRealms(realms);
File tempFile = arguments.get(ARG_SERVLETS) == null ? null : new File((String) arguments.get(ARG_SERVLETS));
if (tempFile != null && tempFile.isAbsolute() == false)
tempFile = new File(workPath, tempFile.getPath());
final File servFile = tempFile;
if (servFile != null)
new Thread(new Runnable() {
public void run() {
readServlets(servFile);
}
}).start();
// And add the standard Servlets.
String throttles = (String) arguments.get(ARG_THROTTLES);
if (throttles == null)
serve.addDefaultServlets((String) arguments.get(ARG_CGI_PATH));
else
try {
serve.addDefaultServlets((String) arguments.get(ARG_CGI_PATH), throttles);
} catch (IOException e) {
serve.log("Problem reading throttles file: " + e, e);
System.exit(1);
}
serve.addWarDeployer((String) arguments.get(ARG_WAR), throttles);
if (arguments.get(ARG_NOHUP) == null)
new Thread(new Runnable() {
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String line;
while (true) {
try {
System.out.print("Press \"q\" <ENTER>, for gracefully stopping the server ");
line = in.readLine();
if (line != null && line.length() > 0 && line.charAt(0) == 'q') {
serve.notifyStop();
break;
}
} catch (IOException e) {
serve.log("Exception in reading from console ", e);
break;
}
}
}
}, "Stop Monitor").start();
else {
Runtime.getRuntime().addShutdownHook(sdHook = new Thread(new Runnable() {
synchronized public void run() {
serve.destroyAllServlets();
}
}, "ShutDownHook"));
}
// And run.
int code = serve.serve();
if (code != 0 && arguments.get(ARG_NOHUP) == null)
try {
System.out.println();
System.in.close(); // to break termination thread
} catch (IOException e) {
}
try {
if (sdHook != null)
Runtime.getRuntime().removeShutdownHook(sdHook);
serve.destroyAllServlets();
} catch (IllegalStateException ise) {
} catch (Throwable t) {
if (t instanceof ThreadDeath)
throw (ThreadDeath)t;
serve.log("At destroying ", t);
}
killAliveThreads();
Runtime.getRuntime().halt(code);
}
private static StringBuffer appendMessage(StringBuffer messages, String message) {
if (messages == null)
messages = new StringBuffer(100);
return messages.append(message);
}
public static String[] readArguments(String workPath, String file) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(new File(workPath, file)));
return Utils.splitStr(br.readLine(), "\"");
} catch (Exception e) { // many can happen
//e.printStackTrace();
return null;
} finally {
if (br != null)
try {
br.close();
} catch (IOException ioe) {
}
}
}
public static void stop() throws IOException {
serve.notifyStop();
}
private static void usage() {
System.out.println(Identification.serverName + " " + Identification.serverVersion + "\n" + "Usage: "
+ progName + " [-p port] [-s servletpropertiesfile] [-a aliasmappingfile]\n"
+ " [-b bind address] [-k backlog] [-l[a][r][f access_log_fmt]]\n"
+ " [-c cgi-bin-dir] [-m max_active_session] [-d log_directory]\n"
+ " [-sp] [-j jsp_servlet_class] [-w war_deployment_module_class]\n"
+ " [-nka] [-kat timeout_in_secs] [-mka max_times_connection_use]\n"
+ " [-e [-]duration_in_minutes] [-nohup] [-z max_threadpool_size]\n"
+ " [-err [class_name?PrintStream]] [-out [class_name?PrintStream]]\n"
+ " [-acceptorImpl class_name_of_Accpetor_impl [extra_acceptor_parameters] ]\n" + " Legend:\n"
+ " -sp session persistence\n" + " -l access log a - with user agent, and r - referer\n"
+ " -nka no keep alive for connection");
System.exit(1);
}
private static void killAliveThreads() {
serve.serverThreads.interrupt();
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null)
tg = tg.getParent();
int ac = tg.activeCount() + tg.activeGroupCount() + 10;
Thread[] ts = new Thread[ac];
ac = tg.enumerate(ts, true);
if (ac == ts.length)
serve.log("Destroy:interruptRunningProcesses: Not all threads will be stopped.");
// kill non daemon
for (int i = 0; i < ac; i++)
if (ts[i].isDaemon() == false) {
String tn = ts[i].getName();
//System.err.println("Interrupting and kill " + tn);
if (ts[i] == Thread.currentThread() || "Stop Monitor".equals(tn) || "ShutDownHook".equals(tn)
|| "DestroyJavaVM".equals(tn) || (tn != null && tn.startsWith("AWT-")) || "main".equals(tn))
continue;
ts[i].interrupt();
Thread.yield();
if (ts[i].isAlive()) {
try {
ts[i].stop();
} catch (Throwable t) {
if (t instanceof ThreadDeath) {
serve
.log(
"Thread death exception happened and stopping thread, thread stopping loop will be terminated",
t);
throw (ThreadDeath) t;
} else
serve.log("An exception at stopping " + ts[i] + " " + t);
}
}
}// else
//serve.log("Daemon thread "+ts[i].getName()+" is untouched.");
}
private static void readServlets(File servFile) {
/**
* servlet.properties file format servlet. <servletname>.code= <servletclass>servlet. <servletname>.initArgs= <name=value>, <name=value>
*/
Hashtable servletstbl, parameterstbl;
servletstbl = new Hashtable();
parameterstbl = new Hashtable();
if (servFile != null && servFile.exists() && servFile.canRead()) {
try {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(servFile)));
/**
* format of servlet.cfg file servlet_name;servlet_class;init_parameter1=value1;init_parameter2=value2...
*/
do {
String servletdsc = in.readLine();
if (servletdsc == null)
break;
if (servletdsc.startsWith("#"))
continue;
StringTokenizer dsctokenzr = new StringTokenizer(servletdsc, ".=,", false);
if (dsctokenzr.hasMoreTokens()) {
if (!dsctokenzr.nextToken().equalsIgnoreCase("servlet")) {
serve.log("No leading 'servlet' keyword, the sentence is skipped");
break;
}
if (dsctokenzr.hasMoreTokens()) {
String servletname = dsctokenzr.nextToken();
if (dsctokenzr.hasMoreTokens()) {
String lt = dsctokenzr.nextToken();
if (lt.equalsIgnoreCase("code")) {
if (dsctokenzr.hasMoreTokens())
servletstbl.put(servletname, dsctokenzr.nextToken("="));
} else if (lt.equalsIgnoreCase("initArgs")) {
Hashtable initparams = new Hashtable();
while (dsctokenzr.hasMoreTokens()) {
String key = dsctokenzr.nextToken("=,");
if (dsctokenzr.hasMoreTokens())
initparams.put(key, dsctokenzr.nextToken(",="));
}
parameterstbl.put(servletname, initparams);
} else
serve
.log("Unrecognized token " + lt + " in " + servletdsc
+ ", the line's skipped");
}
}
}
} while (true);
} catch (IOException e) {
serve.log("IO problem in processing servlets definition file (" + servFile + "): " + e);
}
Enumeration se = servletstbl.keys();
String servletname;
while (se.hasMoreElements()) {
servletname = (String) se.nextElement();
serve.addServlet(servletname, (String) servletstbl.get(servletname), (Hashtable) parameterstbl
.get(servletname));
}
} else
serve.log("Servlets definition file neither provided, found, nor readable: " + servFile);
}
}