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.net.InetAddress;
029: import java.util.Map;
030:
031: import org.apache.catalina.Connector;
032: import org.apache.catalina.Container;
033: import org.apache.catalina.Engine;
034: import org.apache.catalina.Lifecycle;
035: import org.apache.catalina.LifecycleException;
036: import org.apache.catalina.Server;
037: import org.apache.catalina.Service;
038: import org.apache.catalina.connector.http.HttpConnector;
039: import org.apache.catalina.net.SSLServerSocketFactory;
040: import org.apache.catalina.net.ServerSocketFactory;
041: import org.apache.catalina.startup.Catalina;
042: import org.apache.catalina.util.xml.XmlMapper;
043:
044: /**
045: * This class wraps the org.apache.catalina.startup.Catalina class to
046: * follow similar behavior as Tomcat 3.3's EmbededTomcat class. The
047: * most important functionality that it handles is reading the server.xml
048: * file and then allowing the caller access to the objects. In specific,
049: * the Connectors needed to be configurable dynamically.
050: *
051: * This class inherits from Catalina only to get access to the server objects.
052: */
053: public class EmbeddedTomcat extends Catalina {
054:
055: /**
056: * Default constructor
057: */
058: public EmbeddedTomcat() {
059: }
060:
061: /**
062: * Configure the server.
063: * <p>
064: * Current valid options:
065: * <ul>
066: * <li>"debug"<br>
067: * Optional debug flag, which defaults to "false".
068: * </li><p>
069: * </ul>
070: *
071: * @param options Map of optional parameters
072: */
073: public void configure(Map options) {
074: if (options != null) {
075: // DISABLED Tomcat logger debugging:
076: // See Cougaar bug 1402.
077: // Fix postponed to after 9.4 release.
078: //
079: //this.debug = "true".equals(options.get("debug"));
080: }
081: }
082:
083: /**
084: * Returns a reference to the Server object.
085: *
086: * @throws IllegalStateException If the configuration file hasn't been read.
087: * @see #readConfigFile
088: */
089: public Server getServer() {
090: if (server == null)
091: throw new IllegalStateException(
092: "The configuration file has not been read");
093: return server;
094: }
095:
096: /**
097: * Returns a reference to the Service. There may only be one service in
098: * the configuration file.
099: *
100: * @throws IllegalStateException If the configuration file hasn't been read.
101: * @see #readConfigFile
102: */
103: public Service getService() {
104: Service[] services = getServer().findServices();
105: if (services == null || services.length != 1) {
106: throw new IllegalStateException(
107: "There must be one and only one Service with in the Server tag");
108: }
109:
110: return services[0];
111: }
112:
113: /**
114: * Returns the Engine object. There may only be one Engine.
115: *
116: * @throws IllegalStateException If the configuration file hasn't been read.
117: * @see #readConfigFile
118: */
119: public Engine getEngine() {
120: Container contEngine = getService().getContainer();
121: if (contEngine == null || !(contEngine instanceof Engine)) {
122: throw new IllegalStateException(
123: "There must be one Engine tag within the Service tag");
124: }
125: return (Engine) contEngine;
126: }
127:
128: /**
129: * Sets the catalina.home and catalina.base paths to the given String
130: */
131: public void setInstall(String installPath) {
132: System.setProperty("catalina.home", installPath);
133: System.setProperty("catalina.base", installPath);
134: }
135:
136: /**
137: * Reads the xml configuration file given. This must be called before
138: * the server is started via embeddedStart.
139: */
140: public void readConfigFile(String configFile) throws Exception {
141: this .configFile = configFile;
142: XmlMapper mapper = createStartMapper();
143: mapper.readXml(configFile(), this );
144: }
145:
146: /**
147: * Starts the web server.
148: */
149: public void embeddedStart() throws LifecycleException {
150: server.initialize();
151: if (server instanceof Lifecycle) {
152: ((Lifecycle) server).start();
153: }
154: }
155:
156: /**
157: * Stops the web server.
158: */
159: public void embeddedStop() throws LifecycleException {
160: if (server instanceof Lifecycle) {
161: ((Lifecycle) server).stop();
162: }
163: }
164:
165: /**
166: * Adds an HTTPConnector to the server.
167: * <p>
168: * Current valid options:
169: * <ul>
170: * <li>"factory"<br>
171: * Optional classname for server socket factory, which
172: * defaults to
173: * "org.apache.catalina.net.DefaultServerSocketFactory"
174: * </li><p>
175: * </ul>
176: *
177: * @param port The TCP port number to bind to
178: * @param options Map of optional parameters
179: */
180: public void addEndpoint(int port, Map options) {
181: String factoryClassname = null;
182: int acceptCount = 10;
183:
184: if (options != null) {
185: factoryClassname = (String) options.get("factory");
186: String ac = (String) options.get("acceptCount");
187: if (ac != null)
188: acceptCount = Integer.parseInt(ac);
189: // check for unknown option names?
190:
191: /*
192: // other options we may want to add:
193: minProcessors="5"
194: maxProcessors="75"
195: enableLookups="true"
196: debug="0"
197: connectionTimeout="60000"
198: */
199: }
200:
201: ServerSocketFactory fact = null;
202: if (factoryClassname != null) {
203: try {
204: Class cl = Class.forName(factoryClassname);
205: fact = (ServerSocketFactory) cl.newInstance();
206: } catch (Exception e) {
207: throw new RuntimeException(
208: "Invalid server socket factory: "
209: + factoryClassname, e);
210: }
211: }
212:
213: addEndpoint(port, null, fact, acceptCount);
214: }
215:
216: /**
217: * Adds an HTTP socket connector to the server.
218: */
219: public void addEndpoint(int port, InetAddress address,
220: ServerSocketFactory fact, int acceptCount) {
221: HttpConnector ctr = new HttpConnector();
222: if (acceptCount >= 0)
223: ctr.setAcceptCount(acceptCount);
224: if (fact != null)
225: ctr.setFactory(fact);
226: if (address != null)
227: ctr.setAddress(address.getHostAddress());
228: ctr.setPort(port);
229: ctr.setEnableLookups(false);
230: if (debug)
231: ctr.setDebug(30);
232:
233: getService().addConnector(ctr);
234:
235: setRedirectPort(ctr);
236: }
237:
238: /**
239: * Adds an HTTPS socket connector to the server.
240: * <p>
241: * Note, if the factory is not specified, then Tomcat
242: * imposes these restrictions:
243: * <ul>
244: * <li>The certificate name must be "tomcat".
245: * </li><p>
246: * <li>The keystore and the truststore must be the
247: * same file.
248: * </li><p>
249: * </ul>
250: * <p>
251: * Current valid options:
252: * <ul>
253: * <li>"factory"<br>
254: * Optional classname for server socket factory, which
255: * defaults to
256: * "org.apache.catalina.net.SSLServerSocketFactory"
257: * </li><p>
258: * <li>"keystore"<br>
259: * Optional file name for the certificate keystore.
260: * </li><p>
261: * <li>"keypass"<br>
262: * Optional password for the certificate keystore.
263: * </li><p>
264: * <li>"clientAuth"<br>
265: * If the value is "true", then client authentication
266: * is enabled.
267: * </li><p>
268: * </ul>
269: *
270: * @param port The TCP port number to bind to
271: * @param options Map of optional parameters
272: */
273: public void addSecureEndpoint(int port, Map options) {
274: String factoryClassname = null;
275: String keystore = null;
276: String keypass = null;
277: boolean clientAuth = false;
278:
279: if (options != null) {
280: factoryClassname = (String) options.get("factory");
281: keystore = (String) options.get("keystore");
282: keypass = (String) options.get("keypass");
283: clientAuth = "true".equals(options.get("clientAuth"));
284: // also see "addEndpoint(..)" options
285: }
286:
287: ServerSocketFactory fact;
288: if (factoryClassname == null) {
289: if ((keystore == null) || (keypass == null)) {
290: throw new IllegalArgumentException(
291: "Must specify a keystore and keypass when using "
292: + "HTTPS and the default server socket factory");
293: }
294: fact = new SSLServerSocketFactory();
295: } else {
296: try {
297: Class cl = Class.forName(factoryClassname);
298: fact = (ServerSocketFactory) cl.newInstance();
299: } catch (Exception e) {
300: throw new RuntimeException(
301: "Invalid server socket factory: "
302: + factoryClassname, e);
303: }
304: }
305:
306: // replace with reflective parameter setting?
307: if (fact instanceof SSLServerSocketFactory) {
308: SSLServerSocketFactory sslfact = (SSLServerSocketFactory) fact;
309: if (keystore != null) {
310: sslfact.setKeystoreFile(keystore);
311: }
312: if (keypass != null) {
313: sslfact.setKeystorePass(keypass);
314: }
315: if (clientAuth) {
316: sslfact.setClientAuth(true);
317: }
318: // protocol?
319: }
320:
321: addSecureEndpoint(port, null, fact);
322: }
323:
324: /**
325: * Adds an HTTPS socket connector to the server.
326: */
327: public void addSecureEndpoint(int port, InetAddress address,
328: ServerSocketFactory fact) {
329: HttpConnector ctr = new HttpConnector();
330: if (fact != null)
331: ctr.setFactory(fact);
332: if (address != null)
333: ctr.setAddress(address.getHostAddress());
334: ctr.setPort(port);
335: ctr.setEnableLookups(false);
336: ctr.setScheme("https");
337: ctr.setSecure(true);
338: if (debug)
339: ctr.setDebug(30);
340:
341: setRedirectPort(ctr);
342:
343: getService().addConnector(ctr);
344:
345: }
346:
347: /**
348: * Sets the redirection port all HTTP connections if this is an
349: * HTTPS connection or the redirection port of this connection
350: * if an HTTPS connection exists and this is an HTTP connection.
351: *
352: * @param conn The HTTP to set the redirection port or the HTTPS
353: * connection to set all other connections' redirection ports.
354: */
355: protected void setRedirectPort(HttpConnector conn) {
356: Connector[] connectors = getService().findConnectors();
357:
358: if (connectors == null) {
359: return; // there are no connectors
360: }
361:
362: for (int i = 0; i < connectors.length; i++) {
363: if (!(connectors[i] instanceof HttpConnector)) {
364: continue; // don't do anything to non HTTP connectors
365: }
366: HttpConnector httpConn = (HttpConnector) connectors[i];
367:
368: if (httpConn.getSecure()) {
369: if (!conn.getSecure()) {
370: // found the HTTPS connection
371: conn.setRedirectPort(httpConn.getPort());
372: return;
373: }
374: } else {
375: if (conn.getSecure()) {
376: httpConn.setRedirectPort(conn.getPort());
377: }
378: }
379: }
380: }
381: }
|