KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cactus > integration > ant > container > ContainerRunner


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

20 package org.apache.cactus.integration.ant.container;
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.net.HttpURLConnection JavaDoc;
25 import java.net.URL JavaDoc;
26
27 import org.apache.cactus.integration.ant.util.AntLog;
28 import org.apache.commons.logging.Log;
29 import org.apache.tools.ant.BuildException;
30
31 /**
32  * Support class that handles the lifecycle of a container, which basically
33  * consists of startup and shutdown.
34  *
35  * @version $Id: ContainerRunner.java,v 1.9 2004/05/31 20:05:22 vmassol Exp $
36  */

37 public final class ContainerRunner
38 {
39     // Instance Variables ------------------------------------------------------
40

41     /**
42      * The container to run.
43      */

44     private Container container;
45
46     /**
47      * The URL that is continuously pinged to check if the container is running.
48      */

49     private URL JavaDoc testURL;
50
51     /**
52      * Timeout in milliseconds after which to give up connecting to the
53      * container.
54      */

55     private long timeout = 180000;
56
57     /**
58      * The time interval in milliseconds to sleep between polling the container.
59      */

60     private long checkInterval = 500;
61
62     /**
63      * The time to sleep after the container has shut down.
64      */

65     private long shutDownWait = 2000;
66
67     /**
68      * Whether the container had already been running before.
69      */

70     private boolean alreadyRunning;
71
72     /**
73      * The server name as returned in the 'Server' header of the server's
74      * HTTP response.
75      */

76     private String JavaDoc serverName;
77
78     /**
79      * The log to use.
80      */

81     private transient Log log = AntLog.NULL;
82
83     // Constructors ------------------------------------------------------------
84

85     /**
86      * Constructor.
87      *
88      * @param theContainer The container to run
89      */

90     public ContainerRunner(Container theContainer)
91     {
92         this.container = theContainer;
93     }
94
95     // Public Methods ----------------------------------------------------------
96

97     /**
98      * Returns the server name as reported in the 'Server' header of HTTP
99      * responses from the server.
100      *
101      * @return The server name
102      */

103     public String JavaDoc getServerName()
104     {
105         return this.serverName;
106     }
107
108     /**
109      * Method called by the task to perform the startup of the container. This
110      * method takes care of starting the container in another thread, and
111      * polling the test URL to check whether startup has completed. As soon as
112      * the URL is available (or the timeout is exceeded), control is returned to
113      * the caller.
114      *
115      * @throws IllegalStateException If the 'url' property is <code>null</code>
116      */

117     public void startUpContainer() throws IllegalStateException JavaDoc
118     {
119         if (this.testURL == null)
120         {
121             throw new IllegalStateException JavaDoc("Property [url] must be set");
122         }
123
124         // Try connecting in case the server is already running. If so, does
125
// nothing
126
this.alreadyRunning = isAvailable(testConnectivity(this.testURL));
127         if (this.alreadyRunning)
128         {
129             // Server is already running. Record this information so that we
130
// don't stop it afterwards.
131
this.log.debug("Server is already running");
132             return;
133         }
134
135         // Now start the server in another thread
136
Thread JavaDoc thread = new Thread JavaDoc(new Runnable JavaDoc()
137         {
138             public void run()
139             {
140                 container.startUp();
141             }
142         });
143         thread.start();
144
145         // Continuously try calling the test URL until it succeeds or
146
// until a timeout is reached (we then throw a build exception).
147
long startTime = System.currentTimeMillis();
148         int responseCode = -1;
149         do
150         {
151             if ((System.currentTimeMillis() - startTime) > this.timeout)
152             {
153                 throw new BuildException("Failed to start the container after "
154                     + "more than [" + this.timeout + "] ms. Trying to connect "
155                     + "to the [" + this.testURL + "] test URL yielded a ["
156                     + responseCode + "] error code. Please run in debug mode "
157                     + "for more details about the error.");
158             }
159             sleep(this.checkInterval);
160             this.log.debug("Checking if server is up ...");
161             responseCode = testConnectivity(this.testURL);
162         } while (!isAvailable(responseCode));
163
164         // Wait a few ms more (just to be sure !)
165
sleep(this.container.getStartUpWait());
166
167         this.serverName = retrieveServerName(this.testURL);
168         this.log.trace("Server [" + this.serverName + "] started");
169     }
170
171     /**
172      * Method called by the task to perform the stopping of the container. This
173      * method takes care of stopping the container in another thread, and
174      * polling the test URL to check whether shutdown has completed. As soon as
175      * the URL stops responding, control is returned to the caller.
176      *
177      * @throws IllegalStateException If the 'url' property is <code>null</code>
178      */

179     public void shutDownContainer() throws IllegalStateException JavaDoc
180     {
181         if (this.testURL == null)
182         {
183             throw new IllegalStateException JavaDoc("Property [url] must be set");
184         }
185
186         // Don't shut down a container that has not been started by us
187
if (this.alreadyRunning)
188         {
189             return;
190         }
191         
192         if (!isAvailable(testConnectivity(this.testURL)))
193         {
194             this.log.debug("Server isn't running!");
195             return;
196         }
197
198         // Call the target that stops the server, in another thread. The called
199
// target must be blocking.
200
Thread JavaDoc thread = new Thread JavaDoc(new Runnable JavaDoc()
201         {
202             public void run()
203             {
204                 container.shutDown();
205             }
206         });
207         thread.start();
208
209         // Continuously try calling the test URL until it fails
210
do
211         {
212             sleep(this.checkInterval);
213         } while (isAvailable(testConnectivity(this.testURL)));
214
215         // sleep a bit longer to be sure the container has terminated
216
sleep(this.shutDownWait);
217         
218         this.log.debug("Server stopped!");
219     }
220
221     /**
222      * Sets the time interval to sleep between polling the container.
223      *
224      * The default interval is 500 milliseconds.
225      *
226      * @param theCheckInterval The interval in milliseconds
227      */

228     public void setCheckInterval(long theCheckInterval)
229     {
230         this.checkInterval = theCheckInterval;
231     }
232
233     /**
234      * Sets the log to write to.
235      *
236      * @param theLog The log to set
237      */

238     public void setLog(Log theLog)
239     {
240         this.log = theLog;
241     }
242
243     /**
244      * Sets the time to wait after the container has been shut down.
245      *
246      * The default time is 2 seconds.
247      *
248      * @param theShutDownWait The time to wait in milliseconds
249      */

250     public void setShutDownWait(long theShutDownWait)
251     {
252         this.shutDownWait = theShutDownWait;
253     }
254
255     /**
256      * Sets the timeout after which to stop trying to call the container.
257      *
258      * The default timeout is 3 minutes.
259      *
260      * @param theTimeout The timeout in milliseconds
261      */

262     public void setTimeout(long theTimeout)
263     {
264         this.timeout = theTimeout;
265     }
266
267     /**
268      * Sets the HTTP URL that will be continuously pinged to check if the
269      * container is running.
270      *
271      * @param theTestURL The URL to set
272      */

273     public void setURL(URL JavaDoc theTestURL)
274     {
275         if (!theTestURL.getProtocol().equals("http"))
276         {
277             throw new IllegalArgumentException JavaDoc("Not a HTTP URL");
278         }
279         this.testURL = theTestURL;
280     }
281
282     // Private Methods ---------------------------------------------------------
283

284     /**
285      * Tests whether we are able to connect to the HTTP server identified by the
286      * specified URL.
287      *
288      * @param theUrl The URL to check
289      * @return the HTTP response code or -1 if no connection could be
290      * established
291      */

292     private int testConnectivity(URL JavaDoc theUrl)
293     {
294         int code;
295         try
296         {
297             HttpURLConnection JavaDoc connection =
298                 (HttpURLConnection JavaDoc) theUrl.openConnection();
299             connection.setRequestProperty("Connection", "close");
300             connection.connect();
301             readFully(connection);
302             connection.disconnect();
303             code = connection.getResponseCode();
304         }
305         catch (IOException JavaDoc e)
306         {
307             this.log.debug("Failed to connect to [" + theUrl + "]", e);
308             code = -1;
309         }
310         return code;
311     }
312
313
314     /**
315      * Tests whether an HTTP return code corresponds to a valid connection
316      * to the test URL or not. Success is 200 up to but excluding 300.
317      *
318      * @param theCode the HTTP response code to verify
319      * @return <code>true</code> if the test URL could be called without error,
320      * <code>false</code> otherwise
321      */

322     private boolean isAvailable(int theCode)
323     {
324         boolean result;
325         if ((theCode != -1) && (theCode < 300))
326         {
327             result = true;
328         }
329         else
330         {
331             result = false;
332         }
333         return result;
334     }
335
336     /**
337      * Retrieves the server name of the container.
338      *
339      * @param theUrl The URL to retrieve
340      * @return The server name, or <code>null</code> if the server name could
341      * not be retrieved
342      */

343     private String JavaDoc retrieveServerName(URL JavaDoc theUrl)
344     {
345         String JavaDoc retVal = null;
346         try
347         {
348             HttpURLConnection JavaDoc connection =
349                 (HttpURLConnection JavaDoc) theUrl.openConnection();
350             connection.connect();
351             retVal = connection.getHeaderField("Server");
352             connection.disconnect();
353         }
354         catch (IOException JavaDoc e)
355         {
356             this.log.debug("Could not get server name from ["
357                 + theUrl + "]", e);
358         }
359         return retVal;
360     }
361
362     /**
363      * Fully reads the input stream from the passed HTTP URL connection to
364      * prevent (harmless) server-side exception.
365      *
366      * @param theConnection the HTTP URL connection to read from
367      * @exception IOException if an error happens during the read
368      */

369     static void readFully(HttpURLConnection JavaDoc theConnection)
370                    throws IOException JavaDoc
371     {
372         // Only read if there is data to read ... The problem is that not
373
// all servers return a content-length header. If there is no header
374
// getContentLength() returns -1. It seems to work and it seems
375
// that all servers that return no content-length header also do
376
// not block on read() operations!
377
if (theConnection.getContentLength() != 0)
378         {
379             byte[] buf = new byte[256];
380             InputStream JavaDoc in = theConnection.getInputStream();
381             while (in.read(buf) != -1)
382             {
383                 // Make sure we read all the data in the stream
384
}
385         }
386     }
387
388     /**
389      * Pauses the current thread for the specified amount.
390      *
391      * @param theMs The time to sleep in milliseconds
392      * @throws BuildException If the sleeping thread is interrupted
393      */

394     private void sleep(long theMs) throws BuildException
395     {
396         try
397         {
398             Thread.sleep(theMs);
399         }
400         catch (InterruptedException JavaDoc e)
401         {
402             throw new BuildException("Interruption during sleep", e);
403         }
404     }
405 }
406
Popular Tags