2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

Clover coverage report - Cactus 1.8dev for J2EE API 1.3
Coverage timestamp: Sun Mar 26 2006 18:50:18 BRT
file stats: LOC: 586   Methods: 20
NCLOC: 288   Classes: 1
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
JettyTestSetup.java 50% 78.7% 70% 71.2%
coverage coverage
 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;
 23   
 import java.io.IOException;
 24   
 import java.io.InputStream;
 25   
 import java.net.HttpURLConnection;
 26   
 import java.net.URL;
 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 239036 2004-08-17 10:35:57Z vmassol $
 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 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 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 configFile;
 75   
 
 76   
     /**
 77   
      * The directory containing the resources of the web-application.
 78   
      */
 79   
     private File 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 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  1
     public JettyTestSetup(Test theTest)
 122   
     {
 123  1
         super(theTest);
 124  1
         this.baseConfiguration = new BaseConfiguration();
 125  1
         this.servletConfiguration = new DefaultServletConfiguration();
 126  1
         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  0
     public JettyTestSetup(Test theTest, 
 139   
         Configuration theBaseConfiguration,
 140   
         ServletConfiguration theServletConfiguration,
 141   
         FilterConfiguration theFilterConfiguration)
 142   
     {
 143  0
         this(theTest);
 144  0
         this.baseConfiguration = theBaseConfiguration;
 145  0
         this.servletConfiguration = theServletConfiguration;
 146  0
         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  1
     public void run(final TestResult theResult)
 158   
     {
 159  1
         Protectable p = new Protectable()
 160   
         {
 161  1
             public void protect() throws Exception
 162   
             {
 163  1
                 try
 164   
                 {
 165  1
                     setUp();
 166  1
                     basicRun(theResult);
 167   
                 }
 168   
                 finally
 169   
                 {
 170  1
                     tearDown();
 171   
                 }
 172   
             }
 173   
         };
 174  1
         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  1
     protected void setUp() throws Exception
 186   
     {
 187   
         // Try connecting in case the server is already running. If so, does
 188   
         // nothing
 189  1
         URL contextURL = new URL(this.baseConfiguration.getContextURL()
 190   
             + "/" + this.servletConfiguration.getDefaultRedirectorName()
 191   
             + "?Cactus_Service=RUN_TEST");
 192  1
         this.alreadyRunning = isAvailable(testConnectivity(contextURL));
 193  1
         if (this.alreadyRunning)
 194   
         {
 195   
             // Server is already running. Record this information so that we
 196   
             // don't stop it afterwards.
 197  0
             this.isRunning = true;
 198  0
             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  1
         this.server = createServer(this.baseConfiguration);
 210   
 
 211   
         // Create a Jetty context.
 212  1
         Object context = createContext(this.server, this.baseConfiguration);
 213   
         
 214   
         // Add the Cactus Servlet redirector
 215  1
         addServletRedirector(context, this.servletConfiguration);
 216   
 
 217   
         // Add the Cactus Jsp redirector
 218  1
         addJspRedirector(context);
 219   
 
 220   
         // Add the Cactus Filter redirector
 221  1
         addFilterRedirector(context, this.filterConfiguration);
 222   
 
 223   
         // Configure Jetty with an XML file if one has been specified on the
 224   
         // command line.
 225  1
         if (getConfigFile() != null)
 226   
         {
 227  1
             this.server.getClass().getMethod("configure", 
 228   
                 new Class[] {String.class}).invoke(
 229   
                     this.server, new Object[] {getConfigFile().toString()});
 230   
         }
 231   
 
 232   
         // Start the Jetty server
 233  1
         this.server.getClass().getMethod("start", null).invoke(
 234   
             this.server, null);
 235   
 
 236  1
         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  1
     protected void tearDown() throws Exception
 245   
     { 
 246   
         // Don't shut down a container that has not been started by us
 247  1
         if (!this.forceShutdown && this.alreadyRunning)
 248   
         {
 249  0
             return;
 250   
         }
 251   
 
 252  1
         if (this.server != null)
 253   
         { 
 254   
             // First, verify if the server is running
 255  1
             boolean started = ((Boolean) this.server.getClass().getMethod(
 256   
                 "isStarted", null).invoke(this.server, null)).booleanValue(); 
 257   
 
 258   
             // Stop and destroy the Jetty server, if started
 259  1
             if (started)
 260   
             {
 261   
                 // Stop all listener and contexts
 262  1
                 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  1
                 this.server.getClass().getMethod("destroy", null).invoke(
 268   
                     this.server, null);
 269   
             }
 270   
         } 
 271   
 
 272  1
         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  0
     public final void setConfigFile(File theConfigFile)
 281   
     {
 282  0
         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  0
     public final void setResourceDir(File theResourceDir)
 292   
     {
 293  0
         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  0
     public final void setForceShutdown(boolean isForcedShutdown)
 301   
     {
 302  0
         this.forceShutdown = isForcedShutdown;
 303   
     }
 304   
     
 305   
     /**
 306   
      * @return The resource directory, or <code>null</code> if it has not been
 307   
      *         set
 308   
      */
 309  2
     protected final File getConfigFile()
 310   
     {
 311  2
         if (this.configFile == null)
 312   
         {
 313  1
             String configFileProperty = System.getProperty(
 314   
                 CACTUS_JETTY_CONFIG_PROPERTY);
 315  1
             if (configFileProperty != null)
 316   
             {
 317  1
                 this.configFile = new File(configFileProperty);
 318   
             }
 319   
         }
 320  2
         return this.configFile;
 321   
     }
 322   
 
 323   
     /**
 324   
      * @return The resource directory, or <code>null</code> if it has not been
 325   
      *         set
 326   
      */
 327  4
     protected final File getResourceDir()
 328   
     {
 329  4
         if (this.resourceDir == null)
 330   
         {
 331  1
             String resourceDirProperty = System.getProperty(
 332   
                 CACTUS_JETTY_RESOURCE_DIR_PROPERTY);
 333  1
             if (resourceDirProperty != null)
 334   
             {
 335  1
                 this.resourceDir = new File(resourceDirProperty);
 336   
             }
 337   
         }
 338  4
         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  1
     private Object createServer(Configuration theConfiguration) 
 351   
         throws Exception
 352   
     {
 353   
         // Create Jetty Server object
 354  1
         Class serverClass = ClassLoaderUtils.loadClass(
 355   
             "org.mortbay.jetty.Server", this.getClass());
 356  1
         Object server = serverClass.newInstance();
 357   
 
 358  1
         URL contextURL = new URL(theConfiguration.getContextURL());
 359   
 
 360   
         // Add a listener on the port defined in the Cactus configuration
 361  1
         server.getClass().getMethod("addListener", 
 362   
             new Class[] {String.class})
 363   
             .invoke(server, new Object[] {"" + contextURL.getPort()});
 364   
 
 365  1
         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  1
     private Object createContext(Object theServer,
 379   
         Configuration theConfiguration) throws Exception
 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  1
         URL contextURL = new URL(theConfiguration.getContextURL());
 385   
 
 386  1
         if (getResourceDir() != null)
 387   
         {
 388  1
             theServer.getClass().getMethod("addWebApplication", 
 389   
                 new Class[] {String.class, String.class})
 390   
                 .invoke(theServer, new Object[] {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  1
         Object context = theServer.getClass().getMethod(
 398   
             "getContext", new Class[] {String.class})
 399   
             .invoke(theServer, new Object[] {contextURL.getPath()});
 400   
 
 401  1
         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  1
     private void addServletRedirector(Object theContext,
 413   
         ServletConfiguration theConfiguration) throws Exception
 414   
     {
 415  1
         theContext.getClass().getMethod("addServlet", 
 416   
             new Class[] {String.class, String.class, String.class})
 417   
             .invoke(theContext, 
 418   
             new Object[] {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  1
     private void addJspRedirector(Object theContext) throws Exception
 433   
     {
 434  1
         if (getResourceDir() != null)
 435   
         {
 436  1
             theContext.getClass().getMethod("addServlet", 
 437   
                 new Class[] {String.class, String.class})
 438   
                 .invoke(theContext, 
 439   
                 new Object[] {"*.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  1
             Object handler = theContext.getClass().getMethod(
 445   
                 "getWebApplicationHandler", 
 446   
                 new Class[] {}).invoke(theContext, new Object[] {});
 447   
 
 448  1
             handler.getClass().getMethod("addServlet", 
 449   
                 new Class[] {String.class, String.class, String.class, 
 450   
                     String.class})
 451   
                 .invoke(handler, 
 452   
                 new Object[] {
 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  1
     private void addFilterRedirector(Object theContext,
 472   
         FilterConfiguration theConfiguration) throws Exception
 473   
     {
 474  1
         if (getResourceDir() != null)
 475   
         {
 476   
             // Get the WebApplicationHandler object in order to be able to add
 477   
             // the Cactus Filter redirector
 478  1
             Object handler = theContext.getClass().getMethod(
 479   
                 "getWebApplicationHandler", 
 480   
                 new Class[] {}).invoke(theContext, new Object[] {});
 481   
     
 482  1
             Object filterHolder = handler.getClass().getMethod("defineFilter",
 483   
                 new Class[] {String.class, String.class})
 484   
                 .invoke(handler, 
 485   
                 new Object[] {theConfiguration.getDefaultRedirectorName(),
 486   
                 FilterTestRedirector.class.getName()});        
 487   
     
 488  1
             filterHolder.getClass().getMethod("addAppliesTo",
 489   
                 new Class[] {String.class})
 490   
                 .invoke(filterHolder, new Object[] {"REQUEST"});        
 491   
     
 492   
             // Map the Cactus Filter redirector to a path
 493  1
             handler.getClass().getMethod("mapPathToFilter", 
 494   
                 new Class[] {String.class, String.class})
 495   
                 .invoke(handler, 
 496   
                 new Object[] {"/" 
 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  1
     protected int testConnectivity(URL theUrl)
 511   
     {
 512  1
         int code;
 513  1
         try
 514   
         {
 515  1
             HttpURLConnection connection = 
 516   
                 (HttpURLConnection) theUrl.openConnection();
 517  1
             connection.setRequestProperty("Connection", "close");
 518  1
             connection.connect();
 519  0
             readFully(connection);
 520  0
             connection.disconnect();
 521  0
             code = connection.getResponseCode();
 522   
         }
 523   
         catch (IOException e)
 524   
         {
 525  1
             code = -1;
 526   
         }
 527  1
         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  1
     protected boolean isAvailable(int theCode)
 539   
     {
 540  1
         boolean result;
 541  1
         if ((theCode != -1) && (theCode < 300)) 
 542   
         {
 543  0
             result = true;            
 544   
         }
 545   
         else
 546   
         {
 547  1
             result = false;
 548   
         }
 549  1
         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  0
     protected void readFully(HttpURLConnection theConnection)
 560   
         throws IOException
 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  0
         if (theConnection.getContentLength() != 0)
 568   
         {
 569  0
             byte[] buf = new byte[256];
 570  0
             InputStream in = theConnection.getInputStream();
 571  0
             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  0
     protected boolean isRunning()
 582   
     {
 583  0
         return this.isRunning;
 584   
     }
 585   
 }
 586