001: /* tjws - Main.java
002: * Copyright (C) 1999-2007 Dmitriy Rogatkin. All rights reserved.
003: * Redistribution and use in source and binary forms, with or without
004: * modification, are permitted provided that the following conditions
005: * are met:
006: * 1. Redistributions of source code must retain the above copyright
007: * notice, this list of conditions and the following disclaimer.
008: * 2. Redistributions in binary form must reproduce the above copyright
009: * notice, this list of conditions and the following disclaimer in the
010: * documentation and/or other materials provided with the distribution.
011: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
012: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
013: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
014: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
015: * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
016: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
017: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
018: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
019: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
020: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
021: * SUCH DAMAGE.
022: *
023: * Visit http://tjws.sourceforge.net to get the latest infromation
024: * about Rogatkin's products.
025: * $Id: Main.java,v 1.11 2008/02/01 02:34:29 dmitriy Exp $
026: * Created on Feb 22, 2007
027: * @author Dmitriy
028: */
029: package Acme.Serve;
030:
031: import java.io.BufferedReader;
032: import java.io.File;
033: import java.io.FileInputStream;
034: import java.io.FileOutputStream;
035: import java.io.FileReader;
036: import java.io.IOException;
037: import java.io.InputStreamReader;
038: import java.io.PrintStream;
039: import java.util.Enumeration;
040: import java.util.HashMap;
041: import java.util.Hashtable;
042: import java.util.Map;
043: import java.util.StringTokenizer;
044:
045: import Acme.Utils;
046:
047: public class Main extends Serve {
048: public static final String CLI_FILENAME = "cmdparams";
049:
050: private static final String progName = "Serve";
051:
052: protected static Serve serve;
053:
054: private static Thread sdHook;
055:
056: /**
057: * @param args
058: */
059: public static void main(String[] args) {
060: String workPath = System.getProperty("user.dir", ".");
061: StringBuffer messages = null;
062:
063: int argc = args.length;
064: int argn;
065: if (argc == 0) { // a try to read from file for java -jar server.jar
066: args = readArguments(workPath, CLI_FILENAME);
067: if (args == null) {
068: messages = appendMessage(messages,
069: "Can't read from CLI file\n");
070: } else
071: argc = args.length;
072: }
073:
074: Map arguments = new HashMap(20);
075: arguments.put(ARG_WORK_DIRECTORY, workPath);
076: // Parse args.
077: // TODO: redesign process of parameters based on a map
078: for (argn = 0; argn < argc && args[argn].length() > 0
079: && args[argn].charAt(0) == '-';) {
080: if (args[argn].equals("-p") && argn + 1 < argc) {
081: ++argn;
082: arguments.put(ARG_PORT, new Integer(args[argn]));
083: } else if (args[argn].equals("-t") && argn + 1 < argc) {
084: ++argn;
085: arguments.put(ARG_THROTTLES, args[argn]);
086: } else if (args[argn].equals("-s") && argn + 1 < argc) {
087: ++argn;
088: arguments.put(ARG_SERVLETS, args[argn]);
089: } else if (args[argn].equals("-r") && argn + 1 < argc) {
090: ++argn;
091: arguments.put(ARG_REALMS, args[argn]);
092: } else if (args[argn].equals("-a") && argn + 1 < argc) {
093: ++argn;
094: arguments.put(ARG_ALIASES, args[argn]);
095: } else if (args[argn].equals("-b") && argn + 1 < argc) {
096: ++argn;
097: arguments.put(ARG_BINDADDRESS, args[argn]);
098: } else if (args[argn].equals("-k") && argn + 1 < argc) {
099: ++argn;
100: arguments.put(ARG_BACKLOG, args[argn]);
101: } else if (args[argn].equals("-j") && argn + 1 < argc) {
102: ++argn;
103: arguments.put(ARG_JSP, args[argn]);
104: } else if (args[argn].equals("-w") && argn + 1 < argc) {
105: ++argn;
106: arguments.put(ARG_WAR, args[argn]);
107: } else if (args[argn].equals("-c") && argn + 1 < argc) {
108: ++argn;
109: arguments.put(ARG_CGI_PATH, args[argn]);
110: } else if (args[argn].equals("-mka") && argn + 1 < argc) {
111: ++argn;
112: arguments.put(ARG_MAX_CONN_USE, args[argn]);
113: arguments.put(ARG_KEEPALIVE, Boolean.TRUE);
114: } else if (args[argn].equals("-nka")) {
115: arguments.put(ARG_KEEPALIVE, Boolean.FALSE);
116: } else if (args[argn].equals("-sp")) {
117: arguments.put(ARG_SESSION_PERSIST, Boolean.TRUE);
118: } else if (args[argn].equals("-kat") && argn + 1 < argc) {
119: ++argn;
120: arguments.put(ARG_KEEPALIVE_TIMEOUT, args[argn]);
121: arguments.put(ARG_KEEPALIVE, Boolean.TRUE);
122: } else if (args[argn].equals("-e") && argn + 1 < argc) {
123: ++argn;
124: try {
125: arguments.put(ARG_SESSION_TIMEOUT, new Integer(
126: args[argn]));
127: } catch (NumberFormatException nfe) {
128: }
129: } else if (args[argn].equals("-z") && argn + 1 < argc) {
130: ++argn;
131: arguments.put(ARG_THREAD_POOL_SIZE, args[argn]);
132: // backlog will be anyway upper limitation
133: } else if (args[argn].equals("-d") && argn + 1 < argc) {
134: ++argn;
135: arguments.put(ARG_LOG_DIR, args[argn]);
136: } else if (args[argn].startsWith("-l")) {
137: arguments
138: .put(
139: ARG_ACCESS_LOG_FMT,
140: "{0}:{9} {1} {2} [{3,date,dd/MMM/yyyy:HH:mm:ss Z}] \"{4} {5} {6}\" {7,number,#} {8,number} {10} {11}");
141: if (args[argn].length() > 2) {
142: arguments.put(ARG_LOG_OPTIONS, args[argn]
143: .substring(2).toUpperCase());
144: if (args[argn].indexOf('f') >= 0) {
145: ++argn;
146: arguments.put(ARG_ACCESS_LOG_FMT, args[argn]);
147: }
148: } else
149: arguments.put(ARG_LOG_OPTIONS, "");
150: } else if (args[argn].startsWith("-nohup")) {
151: arguments.put(ARG_NOHUP, ARG_NOHUP);
152: } else if (args[argn].equals("-m") && argn + 1 < argc) {
153: ++argn;
154: try {
155: arguments.put(ARG_MAX_ACTIVE_SESSIONS, new Integer(
156: args[argn]));
157: if (((Integer) arguments
158: .get(ARG_MAX_ACTIVE_SESSIONS)).intValue() < DEF_MIN_ACT_SESS)
159: arguments.put(ARG_MAX_ACTIVE_SESSIONS,
160: new Integer(DEF_MIN_ACT_SESS));
161: } catch (NumberFormatException nfe) {
162: // ignored
163: }
164: } else if (args[argn].equals("-err")) {
165: if (argn + 1 < argc
166: && args[argn + 1].startsWith("-") == false) {
167: ++argn;
168: try {
169: arguments.put(ARG_ERR, (PrintStream) Class
170: .forName(args[argn]).newInstance());
171: } catch (Error er) {
172: messages = appendMessage(messages,
173: "Problem of processing class parameter of error redirection stream: ")
174: .append(er).append('\n');
175: } catch (Exception ex) {
176: messages = appendMessage(messages,
177: "Exception in processing class parameter of error redirection stream: ")
178: .append(ex).append('\n');
179: }
180: } else
181: arguments.put(ARG_ERR, System.err);
182: } else if (args[argn].equals("-out")) {
183: if (argn + 1 < argc
184: && args[argn + 1].startsWith("-") == false) {
185: ++argn;
186: try {
187: arguments.put(ARG_OUT, (PrintStream) Class
188: .forName(args[argn]).newInstance());
189: } catch (Error er) {
190: messages = appendMessage(messages,
191: "Problem of processing class parameter of out redirection stream: ")
192: .append(er).append('\n');
193: } catch (Exception ex) {
194: messages = appendMessage(messages,
195: "Exception in processing class parameter of out redirection stream: ")
196: .append(ex).append('\n');
197: }
198: }
199: } else if (args[argn].startsWith("-")) { // free args, note it generate problem since free arguments can match internal arguments
200: if (args[argn].length() > 1)
201: arguments.put(args[argn].substring(1),// .toUpperCase(),
202: argn < argc - 1 ? args[++argn] : "");
203: } else
204: usage();
205:
206: ++argn;
207: }
208: if (argn != argc)
209: usage();
210: // log and error stream manipulation
211: // TODO add log rotation feature, it can be done as plug-in
212: PrintStream printstream = System.err;
213: if (arguments.get(ARG_OUT) != null)
214: printstream = (PrintStream) arguments.get(ARG_OUT);
215: else {
216: String logEncoding = System.getProperty(DEF_LOGENCODING);
217: try {
218: File logDir = new File(workPath);
219: if (arguments.get(ARG_LOG_DIR) != null) {
220: File dir = new File((String) arguments
221: .get(ARG_LOG_DIR));
222: if (dir.isAbsolute() == true) {
223: logDir = dir;
224: } else {
225: logDir = new File(workPath, dir.getPath());
226: }
227: }
228: File logFile = new File(logDir.getPath(), "AWS-"
229: + System.currentTimeMillis() + ".log");
230: if (logEncoding != null)
231: printstream = new PrintStream(new FileOutputStream(
232: logFile), true, logEncoding); /* 1.4 */
233: else
234: printstream = new PrintStream(new FileOutputStream(
235: logFile), true);
236: } catch (IOException e) {
237: System.err
238: .println("I/O problem at setting a log stream "
239: + e);
240: }
241: }
242: if (arguments.get(ARG_ERR) != null) {
243: System.setErr((PrintStream) arguments.get(ARG_ERR));
244: } else {
245: System.setErr(printstream);
246: }
247: if (messages != null)
248: System.err.println(messages);
249: /**
250: * format path mapping from=givenpath;dir=realpath
251: */
252: PathTreeDictionary mappingtable = new PathTreeDictionary();
253: if (arguments.get(ARG_ALIASES) != null) {
254: File file = new File((String) arguments.get(ARG_ALIASES));
255: if (file.isAbsolute() == false)
256: file = new File(workPath, file.getPath());
257: if (file.exists() && file.canRead()) {
258: try {
259: // DataInputStream in = new DataInputStream(
260: // new FileInputStream(file));
261: BufferedReader in = new BufferedReader(
262: new InputStreamReader(new FileInputStream(
263: file)));
264: do {
265: String mappingstr = in.readLine(); // no arguments in non ASCII encoding allowed
266: if (mappingstr == null)
267: break;
268: if (mappingstr.startsWith("#"))
269: continue;
270: StringTokenizer maptokenzr = new StringTokenizer(
271: mappingstr, "=;");
272: if (maptokenzr.hasMoreTokens()) {
273: if (maptokenzr.nextToken("=")
274: .equalsIgnoreCase("from")) {
275: if (maptokenzr.hasMoreTokens()) {
276: String srcpath = maptokenzr
277: .nextToken("=;");
278: if (maptokenzr.hasMoreTokens()
279: && maptokenzr.nextToken(
280: ";=")
281: .equalsIgnoreCase(
282: "dir"))
283: try {
284: if (maptokenzr
285: .hasMoreTokens()) {
286: File mapFile = new File(
287: maptokenzr
288: .nextToken());
289: if (mapFile
290: .isAbsolute() == false)
291: mapFile = new File(
292: workPath,
293: mapFile
294: .getPath());
295: mappingtable.put(
296: srcpath,
297: mapFile);
298: }
299: } catch (NullPointerException e) {
300: }
301: }
302: }
303: }
304: } while (true);
305: } catch (IOException e) {
306: System.err.println("Problem reading aliases file: "
307: + arguments.get(ARG_ALIASES) + "/" + e);
308: }
309: } else
310: System.err.println("File " + file + " ("
311: + arguments.get(ARG_ALIASES)
312: + ") doesn't exist or not readable.");
313: }
314: // format realmname=path,user:password,,,,
315: // TODO consider to add a role, like realmname=path,user:password[:role]
316: PathTreeDictionary realms = new PathTreeDictionary();
317: if (arguments.get(ARG_REALMS) != null) {
318: try {
319: File file = new File((String) arguments.get(ARG_REALMS));
320: if (file.isAbsolute() == false)
321: file = new File(workPath, file.getPath());
322: BufferedReader in = new BufferedReader(
323: new InputStreamReader(new FileInputStream(file)));
324:
325: do {
326: String realmstr = in.readLine();
327: if (realmstr == null)
328: break;
329: if (realmstr.startsWith("#"))
330: continue;
331: StringTokenizer rt = new StringTokenizer(realmstr,
332: "=,:");
333: if (rt.hasMoreTokens()) {
334: String realmname = null;
335: realmname = rt.nextToken();
336: if (rt.hasMoreTokens()) {
337: String realmPath = null;
338: realmPath = rt.nextToken();
339: if (rt.hasMoreTokens()) {
340: String user = rt.nextToken();
341: if (rt.hasMoreTokens()) {
342: String password = rt.nextToken();
343: BasicAuthRealm realm = null;
344: Object o[] = realms.get(realmPath);
345: if (o != null && o[0] != null)
346: realm = (BasicAuthRealm) o[0];
347: else {
348: realm = new BasicAuthRealm(
349: realmname);
350: realms.put(realmPath, realm);
351: }
352: realm.put(user, password);
353: }
354: }
355: }
356: }
357: } while (true);
358: } catch (IOException ioe) {
359: System.err
360: .println("I/O problem in reading realms file "
361: + arguments.get(ARG_REALMS) + ": "
362: + ioe);
363: }
364: }
365: // Create the server.
366: serve = new Serve(arguments, printstream);
367: // can use log(.. after this point
368: serve.setMappingTable(mappingtable);
369: serve.setRealms(realms);
370: File tempFile = arguments.get(ARG_SERVLETS) == null ? null
371: : new File((String) arguments.get(ARG_SERVLETS));
372: if (tempFile != null && tempFile.isAbsolute() == false)
373: tempFile = new File(workPath, tempFile.getPath());
374: final File servFile = tempFile;
375: if (servFile != null)
376: new Thread(new Runnable() {
377: public void run() {
378: readServlets(servFile);
379: }
380: }).start();
381: // And add the standard Servlets.
382: String throttles = (String) arguments.get(ARG_THROTTLES);
383: if (throttles == null)
384: serve.addDefaultServlets((String) arguments
385: .get(ARG_CGI_PATH));
386: else
387: try {
388: serve.addDefaultServlets((String) arguments
389: .get(ARG_CGI_PATH), throttles);
390: } catch (IOException e) {
391: serve.log("Problem reading throttles file: " + e, e);
392: System.exit(1);
393: }
394: serve
395: .addWarDeployer((String) arguments.get(ARG_WAR),
396: throttles);
397: if (arguments.get(ARG_NOHUP) == null)
398: new Thread(new Runnable() {
399: public void run() {
400: BufferedReader in = new BufferedReader(
401: new InputStreamReader(System.in));
402: String line;
403: while (true) {
404: try {
405: System.out
406: .print("Press \"q\" <ENTER>, for gracefully stopping the server ");
407: line = in.readLine();
408: if (line != null && line.length() > 0
409: && line.charAt(0) == 'q') {
410: serve.notifyStop();
411: break;
412: }
413: } catch (IOException e) {
414: serve
415: .log(
416: "Exception in reading from console ",
417: e);
418: break;
419: }
420: }
421: }
422: }, "Stop Monitor").start();
423: else {
424: Runtime.getRuntime().addShutdownHook(
425: sdHook = new Thread(new Runnable() {
426: synchronized public void run() {
427: serve.destroyAllServlets();
428: }
429: }, "ShutDownHook"));
430: }
431: // And run.
432: int code = serve.serve();
433: if (code != 0 && arguments.get(ARG_NOHUP) == null)
434: try {
435: System.out.println();
436: System.in.close(); // to break termination thread
437: } catch (IOException e) {
438: }
439: try {
440: if (sdHook != null)
441: Runtime.getRuntime().removeShutdownHook(sdHook);
442: serve.destroyAllServlets();
443: } catch (IllegalStateException ise) {
444:
445: } catch (Throwable t) {
446: serve.log("At destroying ", t);
447: }
448: killAliveThreads();
449: Runtime.getRuntime().halt(code);
450: }
451:
452: private static StringBuffer appendMessage(StringBuffer messages,
453: String message) {
454: if (messages == null)
455: messages = new StringBuffer(100);
456: return messages.append(message);
457: }
458:
459: public static String[] readArguments(String workPath, String file) {
460: BufferedReader br = null;
461: try {
462: br = new BufferedReader(new FileReader(new File(workPath,
463: file)));
464: return Utils.splitStr(br.readLine(), "\"");
465: } catch (Exception e) { // many can happen
466: return null;
467: } finally {
468: if (br != null)
469: try {
470: br.close();
471: } catch (IOException ioe) {
472: }
473: }
474: }
475:
476: public static void stop() throws IOException {
477: serve.notifyStop();
478: }
479:
480: private static void usage() {
481: System.out
482: .println(Identification.serverName
483: + " "
484: + Identification.serverVersion
485: + "\n"
486: + "Usage: "
487: + progName
488: + " [-p port] [-s servletpropertiesfile] [-a aliasmappingfile]\n"
489: + " [-b bind address] [-k backlog] [-l[a][r][f access_log_fmt]]\n"
490: + " [-c cgi-bin-dir] [-m max_active_session] [-d log_directory]\n"
491: + " [-sp] [-j jsp_servlet_class] [-w war_deployment_module_class]\n"
492: + " [-nka] [-kat timeout_in_secs] [-mka max_times_connection_use]\n"
493: + " [-e [-]duration_in_minutes] [-nohup] [-z max_threadpool_size]\n"
494: + " [-err [class_name?PrintStream]] [-out [class_name?PrintStream]]\n"
495: + " [-acceptorImpl class_name_of_Accpetor_impl [extra_acceptor_parameters] ]\n"
496: + " Legend:\n"
497: + " -sp session persistence\n"
498: + " -l access log a - with user agent, and r - referer\n"
499: + " -nka no keep alive for connection");
500: System.exit(1);
501: }
502:
503: private static void killAliveThreads() {
504: serve.serverThreads.interrupt();
505: ThreadGroup tg = Thread.currentThread().getThreadGroup();
506: while (tg.getParent() != null)
507: tg = tg.getParent();
508: int ac = tg.activeCount() + tg.activeGroupCount() + 10;
509:
510: Thread[] ts = new Thread[ac];
511: ac = tg.enumerate(ts, true);
512: if (ac == ts.length)
513: serve
514: .log("Destroy:interruptRunningProcesses: Not all threads will be stopped.");
515: // kill non daemon
516: for (int i = 0; i < ac; i++)
517: if (ts[i].isDaemon() == false) {
518: String tn = ts[i].getName();
519: //System.err.println("Interrupting and kill " + tn);
520:
521: if (ts[i] == Thread.currentThread()
522: || "Stop Monitor".equals(tn)
523: || "ShutDownHook".equals(tn)
524: || "DestroyJavaVM".equals(tn)
525: || (tn != null && tn.startsWith("AWT-"))
526: || "main".equals(tn))
527: continue;
528: ts[i].interrupt();
529: Thread.yield();
530: if (ts[i].isAlive()) {
531: try {
532: ts[i].stop();
533: } catch (Throwable t) {
534: if (t instanceof ThreadDeath) {
535: serve
536: .log(
537: "Thread death exception happened and stopping thread, thread stopping loop will be terminated",
538: t);
539: throw (ThreadDeath) t;
540: } else
541: serve.log("An exception at stopping "
542: + ts[i] + " " + t);
543: }
544: }
545: }// else
546: //serve.log("Daemon thread "+ts[i].getName()+" is untouched.");
547: }
548:
549: private static void readServlets(File servFile) {
550: /**
551: * servlet.properties file format servlet. <servletname>.code= <servletclass>servlet. <servletname>.initArgs= <name=value>, <name=value>
552: */
553: Hashtable servletstbl, parameterstbl;
554: servletstbl = new Hashtable();
555: parameterstbl = new Hashtable();
556: if (servFile != null && servFile.exists() && servFile.canRead()) {
557: try {
558: BufferedReader in = new BufferedReader(
559: new InputStreamReader(new FileInputStream(
560: servFile)));
561: /**
562: * format of servlet.cfg file servlet_name;servlet_class;init_parameter1=value1;init_parameter2=value2...
563: */
564: do {
565: String servletdsc = in.readLine();
566: if (servletdsc == null)
567: break;
568: if (servletdsc.startsWith("#"))
569: continue;
570: StringTokenizer dsctokenzr = new StringTokenizer(
571: servletdsc, ".=,", false);
572: if (dsctokenzr.hasMoreTokens()) {
573: if (!dsctokenzr.nextToken().equalsIgnoreCase(
574: "servlet")) {
575: serve
576: .log("No leading 'servlet' keyword, the sentence is skipped");
577: break;
578: }
579: if (dsctokenzr.hasMoreTokens()) {
580: String servletname = dsctokenzr.nextToken();
581:
582: if (dsctokenzr.hasMoreTokens()) {
583: String lt = dsctokenzr.nextToken();
584: if (lt.equalsIgnoreCase("code")) {
585: if (dsctokenzr.hasMoreTokens())
586: servletstbl
587: .put(
588: servletname,
589: dsctokenzr
590: .nextToken("="));
591: } else if (lt
592: .equalsIgnoreCase("initArgs")) {
593: Hashtable initparams = new Hashtable();
594: while (dsctokenzr.hasMoreTokens()) {
595: String key = dsctokenzr
596: .nextToken("=,");
597: if (dsctokenzr.hasMoreTokens())
598: initparams
599: .put(
600: key,
601: dsctokenzr
602: .nextToken(",="));
603: }
604: parameterstbl.put(servletname,
605: initparams);
606: } else
607: serve.log("Unrecognized token "
608: + lt + " in " + servletdsc
609: + ", the line's skipped");
610: }
611: }
612: }
613: } while (true);
614: } catch (IOException e) {
615: serve
616: .log("IO problem in processing servlets definition file ("
617: + servFile + "): " + e);
618: }
619: Enumeration se = servletstbl.keys();
620: String servletname;
621: while (se.hasMoreElements()) {
622: servletname = (String) se.nextElement();
623: serve.addServlet(servletname, (String) servletstbl
624: .get(servletname), (Hashtable) parameterstbl
625: .get(servletname));
626: }
627: } else
628: serve
629: .log("Servlets definition file neither provided, found, nor readable: "
630: + servFile);
631: }
632: }
|