KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > shell > tomcat > EmbeddedTomcatServer


1 /*
2  * Copyright 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.shell.tomcat;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.dev.util.FileOracle;
20 import com.google.gwt.dev.util.FileOracleFactory;
21 import com.google.gwt.util.tools.Utility;
22
23 import org.apache.catalina.Connector;
24 import org.apache.catalina.ContainerEvent;
25 import org.apache.catalina.ContainerListener;
26 import org.apache.catalina.Engine;
27 import org.apache.catalina.LifecycleException;
28 import org.apache.catalina.Logger;
29 import org.apache.catalina.core.StandardContext;
30 import org.apache.catalina.core.StandardHost;
31 import org.apache.catalina.startup.Embedded;
32 import org.apache.catalina.startup.HostConfig;
33 import org.apache.coyote.tomcat5.CoyoteConnector;
34
35 import java.io.File JavaDoc;
36 import java.io.FileOutputStream JavaDoc;
37 import java.io.IOException JavaDoc;
38 import java.io.InputStream JavaDoc;
39 import java.lang.reflect.Field JavaDoc;
40 import java.net.InetAddress JavaDoc;
41 import java.net.ServerSocket JavaDoc;
42 import java.net.URL JavaDoc;
43
44 /**
45  * Wraps an instance of the Tomcat web server used in hosted mode.
46  */

47 public class EmbeddedTomcatServer {
48
49   static EmbeddedTomcatServer sTomcat;
50
51   public static int getPort() {
52     return sTomcat.port;
53   }
54
55   public static synchronized String JavaDoc start(TreeLogger topLogger, int port,
56       File JavaDoc outDir) {
57     if (sTomcat != null) {
58       throw new IllegalStateException JavaDoc("Embedded Tomcat is already running");
59     }
60
61     try {
62       new EmbeddedTomcatServer(topLogger, port, outDir);
63       return null;
64     } catch (LifecycleException e) {
65       String JavaDoc msg = e.getMessage();
66       if (msg != null && msg.indexOf("already in use") != -1) {
67         msg = "Port "
68             + port
69             + " is already is use; you probably still have another session active";
70       } else {
71         msg = "Unable to start the embedded Tomcat server; double-check that your configuration is valid";
72       }
73       return msg;
74     }
75   }
76
77   // Stop the embedded Tomcat server.
78
//
79
public static synchronized void stop() {
80     if (sTomcat != null) {
81       try {
82         sTomcat.catEmbedded.stop();
83       } catch (LifecycleException e) {
84         // There's nothing we can really do about this and the logger is
85
// gone in many scenarios, so we just ignore it.
86
//
87
} finally {
88         sTomcat = null;
89       }
90     }
91   }
92
93   /**
94    * Returns what local port the Tomcat connector is running on.
95    *
96    * When starting Tomcat with port 0 (i.e. choose an open port), there is just
97    * no way to figure out what port it actually chose. So we're using pure
98    * hackery to steal the port via reflection. The only works because we bundle
99    * Tomcat with GWT and know exactly what version it is.
100    */

101   private static int computeLocalPort(Connector connector) {
102     Throwable JavaDoc caught = null;
103     try {
104       Field JavaDoc phField = CoyoteConnector.class.getDeclaredField("protocolHandler");
105       phField.setAccessible(true);
106       Object JavaDoc protocolHandler = phField.get(connector);
107
108       Field JavaDoc epField = protocolHandler.getClass().getDeclaredField("ep");
109       epField.setAccessible(true);
110       Object JavaDoc endPoint = epField.get(protocolHandler);
111
112       Field JavaDoc ssField = endPoint.getClass().getDeclaredField("serverSocket");
113       ssField.setAccessible(true);
114       ServerSocket JavaDoc serverSocket = (ServerSocket JavaDoc) ssField.get(endPoint);
115
116       return serverSocket.getLocalPort();
117     } catch (SecurityException JavaDoc e) {
118       caught = e;
119     } catch (NoSuchFieldException JavaDoc e) {
120       caught = e;
121     } catch (IllegalArgumentException JavaDoc e) {
122       caught = e;
123     } catch (IllegalAccessException JavaDoc e) {
124       caught = e;
125     }
126     throw new RuntimeException JavaDoc(
127         "Failed to retrieve the startup port from Embedded Tomcat", caught);
128   }
129
130   private Embedded catEmbedded;
131
132   private Engine catEngine;
133
134   private StandardHost catHost = null;
135
136   private int port;
137
138   private final TreeLogger startupBranchLogger;
139
140   private EmbeddedTomcatServer(final TreeLogger topLogger, int listeningPort,
141       final File JavaDoc outDir) throws LifecycleException {
142     if (topLogger == null) {
143       throw new NullPointerException JavaDoc("No logger specified");
144     }
145
146     final TreeLogger logger = topLogger.branch(TreeLogger.INFO,
147         "Starting HTTP on port " + listeningPort, null);
148
149     startupBranchLogger = logger;
150
151     // Make myself the one static instance.
152
// NOTE: there is only one small implementation reason that this has
153
// to be a singleton, which is that the commons logger LogFactory insists
154
// on creating your logger class which must have a constructor with
155
// exactly one String argument, and since we want LoggerAdapter to delegate
156
// to the logger instance available through instance host, there is no
157
// way I can think of to delegate without accessing a static field.
158
// An inner class is almost right, except there's no outer instance.
159
//
160
sTomcat = this;
161
162     // Assume the working directory is simply the user's current directory.
163
//
164
File JavaDoc topWorkDir = new File JavaDoc(System.getProperty("user.dir"));
165
166     // Tell Tomcat its base directory so that it won't complain.
167
//
168
String JavaDoc catBase = System.getProperty("catalina.base");
169     if (catBase == null) {
170       catBase = generateDefaultCatalinaBase(logger, topWorkDir);
171       System.setProperty("catalina.base", catBase);
172     }
173
174     // Some debug messages for ourselves.
175
//
176
logger.log(TreeLogger.DEBUG, "catalina.base = " + catBase, null);
177
178     // Set up the logger that will be returned by the Commons logging factory.
179
//
180
String JavaDoc adapterClassName = CommonsLoggerAdapter.class.getName();
181     System.setProperty("org.apache.commons.logging.Log", adapterClassName);
182
183     // And set up an adapter that will work with the Catalina logger family.
184
//
185
Logger catalinaLogger = new CatalinaLoggerAdapter(topLogger);
186
187     // Create an embedded server.
188
//
189
catEmbedded = new Embedded();
190     catEmbedded.setDebug(0);
191     catEmbedded.setLogger(catalinaLogger);
192
193     // The embedded engine is called "gwt".
194
//
195
catEngine = catEmbedded.createEngine();
196     catEngine.setName("gwt");
197     catEngine.setDefaultHost("localhost");
198
199     // It answers localhost requests.
200
//
201
// String appBase = fCatalinaBaseDir.getAbsolutePath();
202
String JavaDoc appBase = catBase + "/webapps";
203     catHost = (StandardHost) catEmbedded.createHost("localhost", appBase);
204
205     // Hook up a host config to search for and pull in webapps.
206
//
207
HostConfig hostConfig = new HostConfig();
208     catHost.addLifecycleListener(hostConfig);
209
210     // Hook pre-install events so that we can add attributes to allow loaded
211
// instances to find their development instance host.
212
//
213
catHost.addContainerListener(new ContainerListener() {
214       public void containerEvent(ContainerEvent event) {
215         if (StandardHost.PRE_INSTALL_EVENT.equals(event.getType())) {
216           StandardContext webapp = (StandardContext) event.getData();
217           publishShellLoggerAttribute(logger, topLogger, webapp);
218           publishShellOutDirAttribute(logger, outDir, webapp);
219         }
220       }
221     });
222
223     // Tell the engine about the host.
224
//
225
catEngine.addChild(catHost);
226     catEngine.setDefaultHost(catHost.getName());
227
228     // Tell the embedded manager about the engine.
229
//
230
catEmbedded.addEngine(catEngine);
231     InetAddress JavaDoc nullAddr = null;
232     Connector connector = catEmbedded.createConnector(nullAddr, listeningPort,
233         false);
234     catEmbedded.addConnector(connector);
235
236     // start up!
237
catEmbedded.start();
238     port = computeLocalPort(connector);
239
240     if (port != listeningPort) {
241       logger.log(TreeLogger.INFO, "HTTP listening on port " + port, null);
242     }
243   }
244
245   public TreeLogger getLogger() {
246     return startupBranchLogger;
247   }
248
249   /*
250    * Assumes that the leaf is a file (not a directory).
251    */

252   private void copyFileNoOverwrite(TreeLogger logger, FileOracle fileOracle,
253       String JavaDoc srcResName, File JavaDoc catBase) {
254
255     File JavaDoc dest = new File JavaDoc(catBase, srcResName);
256     InputStream JavaDoc is = null;
257     FileOutputStream JavaDoc os = null;
258     try {
259       URL JavaDoc srcRes = fileOracle.find(srcResName);
260       if (srcRes == null) {
261         logger.log(TreeLogger.TRACE, "Cannot find: " + srcResName, null);
262         return;
263       }
264
265       // Only copy if src is newer than desc.
266
//
267
long srcLastModified = srcRes.openConnection().getLastModified();
268       long dstLastModified = dest.lastModified();
269
270       if (srcLastModified < dstLastModified) {
271         // Don't copy over it.
272
//
273
logger.log(TreeLogger.TRACE, "Source is older than existing: "
274             + dest.getAbsolutePath(), null);
275         return;
276       } else if (srcLastModified == dstLastModified) {
277         // Exact same time; quietly don't overwrite.
278
//
279
return;
280       }
281
282       // Make dest directories as required.
283
//
284
File JavaDoc destParent = dest.getParentFile();
285       if (destParent != null) {
286         // No need to check mkdirs result because IOException later anyway.
287
destParent.mkdirs();
288       }
289
290       // Open in and out streams.
291
//
292
is = srcRes.openStream();
293       os = new FileOutputStream JavaDoc(dest);
294
295       // Copy the bytes over.
296
//
297
Utility.streamOut(is, os, 1024);
298
299       // Try to close and change the last modified time.
300
//
301
Utility.close(os);
302       dest.setLastModified(srcLastModified);
303
304       logger.log(TreeLogger.TRACE, "Wrote: " + dest.getAbsolutePath(), null);
305     } catch (IOException JavaDoc e) {
306       logger.log(TreeLogger.WARN, "Failed to write: " + dest.getAbsolutePath(),
307           e);
308     } finally {
309       Utility.close(is);
310       Utility.close(os);
311     }
312   }
313
314   /**
315    * Extracts a valid catalina base instance from the classpath. Does not
316    * overwrite any existing files.
317    */

318   private String JavaDoc generateDefaultCatalinaBase(TreeLogger logger, File JavaDoc workDir) {
319     logger = logger.branch(
320         TreeLogger.TRACE,
321         "Property 'catalina.base' not specified; checking for a standard catalina base image instead",
322         null);
323
324     // Recursively copies out files and directories under
325
// com.google.gwt.dev.etc.tomcat.
326
//
327
FileOracleFactory fof = new FileOracleFactory();
328     final String JavaDoc tomcatEtcDir = "com/google/gwt/dev/etc/tomcat/";
329     fof.addRootPackage(tomcatEtcDir, null);
330     FileOracle fo = fof.create(logger);
331     if (fo.isEmpty()) {
332       logger.log(TreeLogger.WARN, "Could not find " + tomcatEtcDir, null);
333       return null;
334     }
335
336     File JavaDoc catBase = new File JavaDoc(workDir, "tomcat");
337     String JavaDoc[] allChildren = fo.getAllFiles();
338     for (int i = 0; i < allChildren.length; i++) {
339       String JavaDoc src = allChildren[i];
340       copyFileNoOverwrite(logger, fo, src, catBase);
341     }
342
343     return catBase.getAbsolutePath();
344   }
345
346   private void publishAttributeToWebApp(TreeLogger logger,
347       StandardContext webapp, String JavaDoc attrName, Object JavaDoc attrValue) {
348     logger.log(TreeLogger.TRACE, "Adding attribute '" + attrName
349         + "' to web app '" + webapp.getName() + "'", null);
350     webapp.getServletContext().setAttribute(attrName, attrValue);
351   }
352
353   /**
354    * Publish the shell's tree logger as an attribute. This attribute is used to
355    * find the logger out of the thin air within the shell servlet.
356    */

357   private void publishShellLoggerAttribute(TreeLogger logger,
358       TreeLogger loggerToPublish, StandardContext webapp) {
359     final String JavaDoc attr = "com.google.gwt.dev.shell.logger";
360     publishAttributeToWebApp(logger, webapp, attr, loggerToPublish);
361   }
362
363   /**
364    * Publish the shell's output dir as an attribute. This attribute is used to
365    * find it out of the thin air within the shell servlet.
366    */

367   private void publishShellOutDirAttribute(TreeLogger logger,
368       File JavaDoc outDirToPublish, StandardContext webapp) {
369     final String JavaDoc attr = "com.google.gwt.dev.shell.outdir";
370     publishAttributeToWebApp(logger, webapp, attr, outDirToPublish);
371   }
372 }
373
Popular Tags