001: /*
002: * <copyright>
003: *
004: * Copyright 2000-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.lib.web.tomcat;
027:
028: import java.io.File;
029: import java.net.BindException;
030: import java.net.ServerSocket;
031: import java.net.SocketException;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.Map;
035:
036: import javax.servlet.Servlet;
037: import javax.servlet.ServletException;
038: import javax.servlet.jsp.JspFactory;
039:
040: import org.apache.catalina.LifecycleException;
041: import org.cougaar.lib.web.engine.AbstractServletEngine;
042:
043: /**
044: * This component is a Tomcat 4.0.3 -based implementation of the
045: * {@link org.cougaar.lib.web.engine.ServletEngineService}.
046: * <p>
047: * A "tomcat.path" parameter is required, which defaults to:<pre>
048: * $CIP/webtomcat/data
049: * </pre>
050: * This directory must contain these files:
051: * <ul>
052: * <li>conf/server.xml</li>
053: * <li>conf/modules.xml</li>
054: * <li>webapps/ROOT/WEB-INF/web.xml</li>
055: * <li>work/ <i>(read/write temporary directory)</i></li>
056: * </ul>
057: * Where the above "/" directory separator matches the OS-specific
058: * character defined in File.
059: * <p>
060: * Example files are in "webtomcat/data" and should not be modified.
061: */
062: public class TomcatServletEngine extends AbstractServletEngine {
063:
064: private static final String SEP = File.separator;
065:
066: private static final String[] CONFIG_FILES = {
067: "conf" + SEP + "server.xml",
068: "conf" + SEP + "modules.xml",
069: "webapps" + SEP + "ROOT" + SEP + "WEB-INF" + SEP
070: + "web.xml", };
071:
072: private static final String JSP_FACTORY = "org.apache.jasper.runtime.JspFactoryImpl";
073:
074: private Map serverOptions;
075: private int httpPort;
076: private int httpsPort;
077: private Map httpOptions;
078: private Map httpsOptions;
079:
080: private EmbeddedTomcat et;
081:
082: /**
083: * Verify that the config files exist.
084: * <p>
085: * See the class-level notes for details.
086: *
087: * @throws RuntimeException if necessary files are missing
088: */
089: private void verifyConfigFiles(String installPath) {
090: // check args
091: if (installPath == null) {
092: throw new RuntimeException("null \"tomcat.path\"");
093: }
094:
095: // check the standard config files
096: for (int i = 0; i < CONFIG_FILES.length; i++) {
097: String s = installPath + SEP + CONFIG_FILES[i];
098: File f = new File(s);
099: if (!(f.exists())) {
100: throw new RuntimeException(
101: "Missing Tomcat config file: " + s);
102: } else if (!(f.canRead())) {
103: throw new RuntimeException(
104: "Unable to read Tomcat config file: " + s);
105: }
106: }
107:
108: // check the "work" dir for read/write
109: //
110: // better to force this directory to exist than to let
111: // Tomcat attempt to create it and quietly fail
112: String workS = installPath + SEP + "work";
113: File workDir = new File(workS);
114: if (!(workDir.exists())) {
115: throw new RuntimeException(
116: "Missing Tomcat work directory: " + workS);
117: } else if (!(workDir.isDirectory())) {
118: throw new RuntimeException(
119: "Tomcat work \"directory\" is not a directory: "
120: + workS);
121: } else if ((!(workDir.canRead())) || (!(workDir.canWrite()))) {
122: throw new RuntimeException(
123: "Unable to open Tomcat work directory for read/write access: "
124: + workS);
125: }
126:
127: // they all exist -- let Tomcat validate the contents
128: }
129:
130: protected void configure(int httpPort, int httpsPort, Map options) {
131: // save ports
132: this .httpPort = httpPort;
133: this .httpsPort = httpsPort;
134:
135: // extract the server options
136: serverOptions = new HashMap(7);
137: httpOptions = new HashMap(7);
138: httpsOptions = new HashMap(7);
139: for (Iterator iter = options.entrySet().iterator(); iter
140: .hasNext();) {
141: Map.Entry me = (Map.Entry) iter.next();
142: String key = (String) me.getKey();
143: String value = (String) me.getValue();
144: if (key.indexOf(".") < 0) {
145: serverOptions.put(key, value);
146: } else if (key.startsWith("http.")) {
147: httpOptions.put(key.substring(5), value);
148: } else if (key.startsWith("https.")) {
149: httpsOptions.put(key.substring(6), value);
150: } else {
151: // ignore?
152: }
153: }
154:
155: // set tomcat-specific defaults
156: String installPath = (String) serverOptions.get("tomcat.path");
157: if (installPath == null) {
158: // backwards-compatible argument name
159: installPath = (String) serverOptions.get("server.arg");
160: }
161: if (installPath == null) {
162: // look for tomcat config files in RUNTIME, SOCIETY, then INSTALL
163: for (int i = 0; i < 3; i++) {
164: String s = (i == 0 ? "runtime" : i == 1 ? "society"
165: : "install");
166: String base = System.getProperty("org.cougaar." + s
167: + ".path");
168: if (base != null) {
169: String path = base + SEP + "webtomcat" + SEP
170: + "data";
171: if ((new File(path)).isDirectory()) {
172: serverOptions.put("tomcat.path", path);
173: break;
174: }
175: }
176: }
177: }
178: }
179:
180: protected void startServer() throws BindException {
181: // check ports
182: if ((httpPort <= 0) && (httpsPort <= 0)) {
183: throw new IllegalStateException(
184: "Nothing to start -- both HTTP and HTTPS ports are negative");
185: }
186:
187: // verify the necessary config files
188: String installPath = (String) serverOptions.get("tomcat.path");
189: verifyConfigFiles(installPath);
190:
191: // launch tomcat with specified ports
192: // tomcat automatically loads the HookServlet due to XML scripts
193: try {
194:
195: // quick-check to see if the ports are free
196: try {
197: if (httpPort > 0) {
198: (new ServerSocket(httpPort)).close();
199: }
200: if (httpsPort > 0) {
201: (new ServerSocket(httpsPort)).close();
202: }
203: } catch (SocketException se) {
204: throw new LifecycleException(se);
205: }
206:
207: // launch the server
208: et = new EmbeddedTomcat();
209:
210: et.configure(serverOptions);
211:
212: et.setInstall(installPath);
213:
214: ClassLoader cl = getClass().getClassLoader();
215: if (cl == null) {
216: cl = ClassLoader.getSystemClassLoader();
217: }
218:
219: et.setParentClassLoader(cl);
220: try {
221: et.readConfigFile(installPath + SEP + "conf" + SEP
222: + "server.xml");
223: } catch (Exception e) {
224: throw new RuntimeException(
225: "Tomcat-internal exception: " + e.getMessage());
226: }
227:
228: if (httpPort > 0) {
229: et.addEndpoint(httpPort, httpOptions);
230: }
231:
232: if (httpsPort > 0) {
233: et.addSecureEndpoint(httpsPort, httpsOptions);
234: }
235:
236: et.embeddedStart();
237: } catch (LifecycleException te) {
238: Throwable teCause = te.getThrowable();
239: if (teCause instanceof SocketException) {
240: SocketException se = (SocketException) teCause;
241: String msg = se.getMessage();
242: if ((msg != null)
243: && ((msg.indexOf("Address already in use") >= 0) || (msg
244: .indexOf("Address in use") >= 0))) {
245: // port is already in use
246: if (se instanceof BindException) {
247: throw (BindException) se;
248: } else {
249: throw new BindException(msg);
250: }
251: }
252: }
253: throw new RuntimeException("Tomcat-internal exception: ",
254: te);
255: } catch (Exception e) {
256: throw new RuntimeException("Unknown Tomcat exception: ", e);
257: }
258:
259: if (JspFactory.getDefaultFactory() == null) {
260: try {
261: Class jspFC = Class.forName(JSP_FACTORY);
262: JspFactory jspF = (JspFactory) jspFC.newInstance();
263: JspFactory.setDefaultFactory(jspF);
264: } catch (Exception e) {
265: // no "jasper-runtime", so no JSP support!
266: //
267: // we don't need to log this; if a JSP is constructed,
268: // it'll throw an appropriate exception:
269: // java.lang.NoClassDefFoundError:
270: // org/apache/jasper/runtime/HttpJspBase
271: }
272: }
273: }
274:
275: protected void setGateway(Servlet servlet) throws ServletException {
276: HookServlet.setServlet(servlet);
277: }
278:
279: protected void stopServer() {
280: try {
281: setGateway(null);
282:
283: et.embeddedStop();
284: // Allow et to be garbage-collected.
285: et = null;
286: } catch (Exception e) {
287: System.err.println("Error while shutting down Tomcat");
288: e.printStackTrace();
289: }
290:
291: // discard configuration
292: }
293: }
|