KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > cactus > extension > jetty > JettyTestSetup


1 /*
2  * ========================================================================
3  *
4  * Copyright 2001-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.extension.jetty;
21
22 import java.io.File JavaDoc;
23 import java.io.IOException JavaDoc;
24 import java.io.InputStream JavaDoc;
25 import java.net.HttpURLConnection JavaDoc;
26 import java.net.URL JavaDoc;
27
28 import junit.extensions.TestSetup;
29 import junit.framework.Protectable;
30 import junit.framework.Test;
31 import junit.framework.TestResult;
32
33 import org.apache.cactus.internal.configuration.BaseConfiguration;
34 import org.apache.cactus.internal.configuration.Configuration;
35 import org.apache.cactus.internal.configuration.DefaultFilterConfiguration;
36 import org.apache.cactus.internal.configuration.DefaultServletConfiguration;
37 import org.apache.cactus.internal.configuration.FilterConfiguration;
38 import org.apache.cactus.internal.configuration.ServletConfiguration;
39 import org.apache.cactus.internal.util.ClassLoaderUtils;
40 import org.apache.cactus.server.FilterTestRedirector;
41 import org.apache.cactus.server.ServletTestRedirector;
42
43 /**
44  * Custom JUnit test setup to use to automatically start Jetty. Example:<br/>
45  * <code><pre>
46  * public static Test suite()
47  * {
48  * TestSuite suite = new TestSuite(Myclass.class);
49  * return new JettyTestSetup(suite);
50  * }
51  * </pre></code>
52  *
53  * @version $Id: JettyTestSetup.java,v 1.2 2004/08/17 10:35:57 vmassol Exp $
54  */

55 public class JettyTestSetup extends TestSetup
56 {
57     /**
58      * Name of optional system property that points to a Jetty XML
59      * configuration file.
60      */

61     private static final String JavaDoc CACTUS_JETTY_CONFIG_PROPERTY =
62         "cactus.jetty.config";
63
64     /**
65      * Name of optional system property that gives the directory
66      * where JSPs and other resources are located.
67      */

68     private static final String JavaDoc CACTUS_JETTY_RESOURCE_DIR_PROPERTY =
69         "cactus.jetty.resourceDir";
70
71     /**
72      * The configuration file to be used for initializing Jetty.
73      */

74     private File JavaDoc configFile;
75
76     /**
77      * The directory containing the resources of the web-application.
78      */

79     private File JavaDoc resourceDir;
80
81     /**
82      * The Jetty server object representing the running instance. It is
83      * used to stop Jetty in {@link #tearDown()}.
84      */

85     private Object JavaDoc server;
86
87     /**
88      * Whether the container had already been running before.
89      */

90     private boolean alreadyRunning;
91
92     /**
93      * Whether the container is running or not.
94      */

95     private boolean isRunning = false;
96
97     /**
98      * Whether the container should be stopped on tearDown even though
99      * it was not started by us.
100      */

101     private boolean forceShutdown = false;
102     
103     /**
104      * The Servlet configuration object used to configure Jetty.
105      */

106     private ServletConfiguration servletConfiguration;
107
108     /**
109      * The Filter configuration object used to configure Jetty.
110      */

111     private FilterConfiguration filterConfiguration;
112
113     /**
114      * The base configuration object used to configure Jetty.
115      */

116     private Configuration baseConfiguration;
117     
118     /**
119      * @param theTest the test we are decorating (usually a test suite)
120      */

121     public JettyTestSetup(Test theTest)
122     {
123         super(theTest);
124         this.baseConfiguration = new BaseConfiguration();
125         this.servletConfiguration = new DefaultServletConfiguration();
126         this.filterConfiguration = new DefaultFilterConfiguration();
127     }
128
129     /**
130      * @param theTest the test we are decorating (usually a test suite)
131      * @param theBaseConfiguration the base configuration object used to
132      * configure Jetty
133      * @param theServletConfiguration the servlet configuration object used
134      * to configure Jetty
135      * @param theFilterConfiguration the filter configuration object used
136      * to configure Jetty
137      */

138     public JettyTestSetup(Test theTest,
139         Configuration theBaseConfiguration,
140         ServletConfiguration theServletConfiguration,
141         FilterConfiguration theFilterConfiguration)
142     {
143         this(theTest);
144         this.baseConfiguration = theBaseConfiguration;
145         this.servletConfiguration = theServletConfiguration;
146         this.filterConfiguration = theFilterConfiguration;
147     }
148     
149     /**
150      * Make sure that {@link #tearDown} is called if {@link #setUp} fails
151      * to start the container properly. The default
152      * {@link TestSetup#run(TestResult)} method does not provide this feature
153      * unfortunately.
154      *
155      * @see TestSetup#run(TestResult)
156      */

157     public void run(final TestResult theResult)
158     {
159         Protectable p = new Protectable()
160         {
161             public void protect() throws Exception JavaDoc
162             {
163                 try
164                 {
165                     setUp();
166                     basicRun(theResult);
167                 }
168                 finally
169                 {
170                     tearDown();
171                 }
172             }
173         };
174         theResult.runProtected(this, p);
175     }
176     
177     /**
178      * Start an embedded Jetty server. It is allowed to pass a Jetty XML as
179      * a system property (<code>cactus.jetty.config</code>) to further
180      * configure Jetty. Example:
181      * <code>-Dcactus.jetty.config=./jetty.xml</code>.
182      *
183      * @exception Exception if an error happens during initialization
184      */

185     protected void setUp() throws Exception JavaDoc
186     {
187         // Try connecting in case the server is already running. If so, does
188
// nothing
189
URL JavaDoc contextURL = new URL JavaDoc(this.baseConfiguration.getContextURL()
190             + "/" + this.servletConfiguration.getDefaultRedirectorName()
191             + "?Cactus_Service=RUN_TEST");
192         this.alreadyRunning = isAvailable(testConnectivity(contextURL));
193         if (this.alreadyRunning)
194         {
195             // Server is already running. Record this information so that we
196
// don't stop it afterwards.
197
this.isRunning = true;
198             return;
199         }
200
201         // Note: We are currently using reflection in order not to need Jetty
202
// to compile Cactus. If the code becomes more complex or we need to
203
// add other initializer, it will be worth considering moving them
204
// to a separate "extension" subproject which will need additional jars
205
// in its classpath (using the same mechanism as the Ant project is
206
// using to conditionally compile tasks).
207

208         // Create a Jetty Server object and configure a listener
209
this.server = createServer(this.baseConfiguration);
210
211         // Create a Jetty context.
212
Object JavaDoc context = createContext(this.server, this.baseConfiguration);
213         
214         // Add the Cactus Servlet redirector
215
addServletRedirector(context, this.servletConfiguration);
216
217         // Add the Cactus Jsp redirector
218
addJspRedirector(context);
219
220         // Add the Cactus Filter redirector
221
addFilterRedirector(context, this.filterConfiguration);
222
223         // Configure Jetty with an XML file if one has been specified on the
224
// command line.
225
if (getConfigFile() != null)
226         {
227             this.server.getClass().getMethod("configure",
228                 new Class JavaDoc[] {String JavaDoc.class}).invoke(
229                     this.server, new Object JavaDoc[] {getConfigFile().toString()});
230         }
231
232         // Start the Jetty server
233
this.server.getClass().getMethod("start", null).invoke(
234             this.server, null);
235
236         this.isRunning = true;
237     }
238
239     /**
240      * Stop the running Jetty server.
241      *
242      * @exception Exception if an error happens during the shutdown
243      */

244     protected void tearDown() throws Exception JavaDoc
245     {
246         // Don't shut down a container that has not been started by us
247
if (!this.forceShutdown && this.alreadyRunning)
248         {
249             return;
250         }
251
252         if (this.server != null)
253         {
254             // First, verify if the server is running
255
boolean started = ((Boolean JavaDoc) this.server.getClass().getMethod(
256                 "isStarted", null).invoke(this.server, null)).booleanValue();
257
258             // Stop and destroy the Jetty server, if started
259
if (started)
260             {
261                 // Stop all listener and contexts
262
this.server.getClass().getMethod("stop", null).invoke(
263                     this.server, null);
264
265                 // Destroy a stopped server. Remove all components and send
266
// notifications to all event listeners.
267
this.server.getClass().getMethod("destroy", null).invoke(
268                     this.server, null);
269             }
270         }
271
272         this.isRunning = false;
273     }
274
275     /**
276      * Sets the configuration file to use for initializing Jetty.
277      *
278      * @param theConfigFile The configuration file to set
279      */

280     public final void setConfigFile(File JavaDoc theConfigFile)
281     {
282         this.configFile = theConfigFile;
283     }
284
285     /**
286      * Sets the directory in which Jetty will look for the web-application
287      * resources.
288      *
289      * @param theResourceDir The resource directory to set
290      */

291     public final void setResourceDir(File JavaDoc theResourceDir)
292     {
293         this.resourceDir = theResourceDir;
294     }
295
296     /**
297      * @param isForcedShutdown if true the container will be stopped even
298      * if it has not been started by us
299      */

300     public final void setForceShutdown(boolean isForcedShutdown)
301     {
302         this.forceShutdown = isForcedShutdown;
303     }
304     
305     /**
306      * @return The resource directory, or <code>null</code> if it has not been
307      * set
308      */

309     protected final File JavaDoc getConfigFile()
310     {
311         if (this.configFile == null)
312         {
313             String JavaDoc configFileProperty = System.getProperty(
314                 CACTUS_JETTY_CONFIG_PROPERTY);
315             if (configFileProperty != null)
316             {
317                 this.configFile = new File JavaDoc(configFileProperty);
318             }
319         }
320         return this.configFile;
321     }
322
323     /**
324      * @return The resource directory, or <code>null</code> if it has not been
325      * set
326      */

327     protected final File JavaDoc getResourceDir()
328     {
329         if (this.resourceDir == null)
330         {
331             String JavaDoc resourceDirProperty = System.getProperty(
332                 CACTUS_JETTY_RESOURCE_DIR_PROPERTY);
333             if (resourceDirProperty != null)
334             {
335                 this.resourceDir = new File JavaDoc(resourceDirProperty);
336             }
337         }
338         return this.resourceDir;
339     }
340
341     /**
342      * Create a Jetty server object and configures a listener on the
343      * port defined in the Cactus context URL property.
344      *
345      * @param theConfiguration the base Cactus configuration
346      * @return the Jetty <code>Server</code> object
347      *
348      * @exception Exception if an error happens during initialization
349      */

350     private Object JavaDoc createServer(Configuration theConfiguration)
351         throws Exception JavaDoc
352     {
353         // Create Jetty Server object
354
Class JavaDoc serverClass = ClassLoaderUtils.loadClass(
355             "org.mortbay.jetty.Server", this.getClass());
356         Object JavaDoc server = serverClass.newInstance();
357
358         URL JavaDoc contextURL = new URL JavaDoc(theConfiguration.getContextURL());
359
360         // Add a listener on the port defined in the Cactus configuration
361
server.getClass().getMethod("addListener",
362             new Class JavaDoc[] {String JavaDoc.class})
363             .invoke(server, new Object JavaDoc[] {"" + contextURL.getPort()});
364
365         return server;
366     }
367
368     /**
369      * Create a Jetty Context. We use a <code>WebApplicationContext</code>
370      * because we need to use Servlet Filters.
371      *
372      * @param theServer the Jetty Server object
373      * @param theConfiguration the base Cactus configuration
374      * @return Object the <code>WebApplicationContext</code> object
375      *
376      * @exception Exception if an error happens during initialization
377      */

378     private Object JavaDoc createContext(Object JavaDoc theServer,
379         Configuration theConfiguration) throws Exception JavaDoc
380     {
381         // Add a web application. This creates a WebApplicationContext.
382
// Note: We do not put any WEB-INF/, lib/ nor classes/ directory
383
// in the webapp.
384
URL JavaDoc contextURL = new URL JavaDoc(theConfiguration.getContextURL());
385
386         if (getResourceDir() != null)
387         {
388             theServer.getClass().getMethod("addWebApplication",
389                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class})
390                 .invoke(theServer, new Object JavaDoc[] {contextURL.getPath(),
391                     getResourceDir().toString()});
392         }
393         
394         // Retrieves the WebApplication context created by the
395
// "addWebApplication". We need it to be able to manually configure
396
// other items in the context.
397
Object JavaDoc context = theServer.getClass().getMethod(
398             "getContext", new Class JavaDoc[] {String JavaDoc.class})
399             .invoke(theServer, new Object JavaDoc[] {contextURL.getPath()});
400
401         return context;
402     }
403     
404     /**
405      * Adds the Cactus Servlet redirector configuration
406      *
407      * @param theContext the Jetty context under which to add the configuration
408      * @param theConfiguration the Cactus Servlet configuration
409      *
410      * @exception Exception if an error happens during initialization
411      */

412     private void addServletRedirector(Object JavaDoc theContext,
413         ServletConfiguration theConfiguration) throws Exception JavaDoc
414     {
415         theContext.getClass().getMethod("addServlet",
416             new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class, String JavaDoc.class})
417             .invoke(theContext,
418             new Object JavaDoc[] {theConfiguration.getDefaultRedirectorName(),
419             "/" + theConfiguration.getDefaultRedirectorName(),
420             ServletTestRedirector.class.getName()});
421     }
422     
423     /**
424      * Adds the Cactus Jsp redirector configuration. We only add it if the
425      * CACTUS_JETTY_RESOURCE_DIR_PROPERTY has been provided by the user. This
426      * is because JSPs need to be attached to a WebApplicationHandler in Jetty.
427      *
428      * @param theContext the Jetty context under which to add the configuration
429      *
430      * @exception Exception if an error happens during initialization
431      */

432     private void addJspRedirector(Object JavaDoc theContext) throws Exception JavaDoc
433     {
434         if (getResourceDir() != null)
435         {
436             theContext.getClass().getMethod("addServlet",
437                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class})
438                 .invoke(theContext,
439                 new Object JavaDoc[] {"*.jsp",
440                 "org.apache.jasper.servlet.JspServlet"});
441
442             // Get the WebApplicationHandler object in order to be able to
443
// call the addServlet() method that accpets a forced path.
444
Object JavaDoc handler = theContext.getClass().getMethod(
445                 "getWebApplicationHandler",
446                 new Class JavaDoc[] {}).invoke(theContext, new Object JavaDoc[] {});
447
448             handler.getClass().getMethod("addServlet",
449                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class, String JavaDoc.class,
450                     String JavaDoc.class})
451                 .invoke(handler,
452                 new Object JavaDoc[] {
453                     "JspRedirector",
454                     "/JspRedirector",
455                     "org.apache.jasper.servlet.JspServlet",
456                     "/jspRedirector.jsp"});
457         }
458     }
459
460     /**
461      * Adds the Cactus Filter redirector configuration. We only add it if the
462      * CACTUS_JETTY_RESOURCE_DIR_PROPERTY has been provided by the user. This
463      * is because Filters need to be attached to a WebApplicationHandler in
464      * Jetty.
465      *
466      * @param theContext the Jetty context under which to add the configuration
467      * @param theConfiguration the Cactus Filter configuration
468      *
469      * @exception Exception if an error happens during initialization
470      */

471     private void addFilterRedirector(Object JavaDoc theContext,
472         FilterConfiguration theConfiguration) throws Exception JavaDoc
473     {
474         if (getResourceDir() != null)
475         {
476             // Get the WebApplicationHandler object in order to be able to add
477
// the Cactus Filter redirector
478
Object JavaDoc handler = theContext.getClass().getMethod(
479                 "getWebApplicationHandler",
480                 new Class JavaDoc[] {}).invoke(theContext, new Object JavaDoc[] {});
481     
482             Object JavaDoc filterHolder = handler.getClass().getMethod("defineFilter",
483                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class})
484                 .invoke(handler,
485                 new Object JavaDoc[] {theConfiguration.getDefaultRedirectorName(),
486                 FilterTestRedirector.class.getName()});
487     
488             filterHolder.getClass().getMethod("addAppliesTo",
489                 new Class JavaDoc[] {String JavaDoc.class})
490                 .invoke(filterHolder, new Object JavaDoc[] {"REQUEST"});
491     
492             // Map the Cactus Filter redirector to a path
493
handler.getClass().getMethod("mapPathToFilter",
494                 new Class JavaDoc[] {String JavaDoc.class, String JavaDoc.class})
495                 .invoke(handler,
496                 new Object JavaDoc[] {"/"
497                 + theConfiguration.getDefaultRedirectorName(),
498                 theConfiguration.getDefaultRedirectorName()});
499         }
500     }
501
502     /**
503      * Tests whether we are able to connect to the HTTP server identified by the
504      * specified URL.
505      *
506      * @param theUrl The URL to check
507      * @return the HTTP response code or -1 if no connection could be
508      * established
509      */

510     protected int testConnectivity(URL JavaDoc theUrl)
511     {
512         int code;
513         try
514         {
515             HttpURLConnection JavaDoc connection =
516                 (HttpURLConnection JavaDoc) theUrl.openConnection();
517             connection.setRequestProperty("Connection", "close");
518             connection.connect();
519             readFully(connection);
520             connection.disconnect();
521             code = connection.getResponseCode();
522         }
523         catch (IOException JavaDoc e)
524         {
525             code = -1;
526         }
527         return code;
528     }
529
530     /**
531      * Tests whether an HTTP return code corresponds to a valid connection
532      * to the test URL or not. Success is 200 up to but excluding 300.
533      *
534      * @param theCode the HTTP response code to verify
535      * @return <code>true</code> if the test URL could be called without error,
536      * <code>false</code> otherwise
537      */

538     protected boolean isAvailable(int theCode)
539     {
540         boolean result;
541         if ((theCode != -1) && (theCode < 300))
542         {
543             result = true;
544         }
545         else
546         {
547             result = false;
548         }
549         return result;
550     }
551
552     /**
553      * Fully reads the input stream from the passed HTTP URL connection to
554      * prevent (harmless) server-side exception.
555      *
556      * @param theConnection the HTTP URL connection to read from
557      * @exception IOException if an error happens during the read
558      */

559     protected void readFully(HttpURLConnection JavaDoc theConnection)
560         throws IOException JavaDoc
561     {
562         // Only read if there is data to read ... The problem is that not
563
// all servers return a content-length header. If there is no header
564
// getContentLength() returns -1. It seems to work and it seems
565
// that all servers that return no content-length header also do
566
// not block on read() operations!
567
if (theConnection.getContentLength() != 0)
568         {
569             byte[] buf = new byte[256];
570             InputStream JavaDoc in = theConnection.getInputStream();
571             while (in.read(buf) != -1)
572             {
573                 // Make sure we read all the data in the stream
574
}
575         }
576     }
577
578     /**
579      * @return true if the server is running or false otherwise
580      */

581     protected boolean isRunning()
582     {
583         return this.isRunning;
584     }
585 }
586
Popular Tags