View Javadoc

1   package org.apache.maven.j2ee;
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  import java.io.File;
21  import java.io.IOException;
22  import java.util.Iterator;
23  import java.util.Map;
24  
25  import org.apache.maven.j2ee.war.FormLoginConfig;
26  
27  /**
28   * A task to validate a war file. The following is checked:
29   * <ol>
30   *      <li>The war file exists</li>
31   *      <li>The war file is readable</li>
32   *      <li>The war file has a web.xml (warning)</li>
33   *      <li>Servlets defined by a <code>&lt;servlet&gt;<code> tag are loadable
34   *          from the war file and <strong>not</strong> the classpath</li>
35   *      <li>JSPs defined by a <code>&lt;servlet&gt;<code> tag exist in the war
36   *      </li>
37   *      <li>Taglibs defined by a <code>&lt;taglib&gt;</code> have a <code>
38   *          &lt;taglib-location&gt;</code> that exists in the war</li>
39   *      <li>Error pages specified by a <code>&lt;location&gt;</code> nested 
40   *          within an <code>&lt;error-page&gt;</code> element must exist in the
41   *          war file</li>
42   *      <li>Login and error pages specified in the <code>&lt;form-login-config
43   *          &gt;</code> element must exist in the war file</li>
44   * </ol>
45   * @author  dIon Gillard
46   * @version $Id: WarValidator.java 170200 2005-05-15 06:24:19Z brett $
47   */
48  public class WarValidator
49  {
50  
51      /** name of the war file to be validated */
52      private String warFileName;
53      /** broadcaster to help with events */
54      private ValidationBroadcaster broadcaster = new ValidationBroadcaster();
55      /** status listener to keep track of errors etc */
56      private ValidationStatusListener status = new ValidationStatusListener();
57      /** 
58       * whether or not the build process should fail if a validation error occurs
59       */
60      private boolean failOnError = true;
61  
62  
63      //--- Constructors ---------------------------------------------------------
64      /** 
65       * Creates a new instance of WarValidator
66       */
67      public WarValidator()
68      {
69          addValidationListener(getStatus());
70      }
71  
72      //--- Methods --------------------------------------------------------------
73      /**
74       * Provides access to the status listener that is automatically attached
75       * to the validation
76       * 
77       * @return Value of property status.
78       */
79      public ValidationStatusListener getStatus()
80      {
81          return status;
82      }
83  
84      /**
85       * Perform the validation.
86       * 
87       * @throws IllegalStateException when any error occurs
88       */
89      public void execute()
90      {
91          if (getWarFileName() == null)
92          {
93              throw new NullPointerException("war file name should not be null");
94          }
95          validate();
96          if (getStatus().isError() && isFailOnError())
97          {
98              throw new IllegalStateException("Errors occurred during validation. "
99                  + "Messages should have been provided");
100         }
101     }
102 
103     /**
104      * validate the provided war file
105      */
106     public void validate()
107     {
108         try
109         {
110             startValidation();
111             validateFile();
112             if (!getStatus().isError())
113             {
114                 validateWarContents();
115             }
116         }
117         finally
118         {
119             endValidation();
120         }
121     }
122 
123     /**
124      * Start validation - issue a started event, and check the war file is
125      * ok from a filesystem perspective - exists and is readable
126      */
127     protected void startValidation()
128     {
129         getBroadcaster().fireStartedEvent(new ValidationEvent(this,
130             getWarFileName(), "war validation started"));
131     }
132     
133     /**
134      * Hook point for subclasses to add validations
135      */
136     protected void validateWarContents()
137     {
138         validateWebXml();
139     }
140     
141     /**
142      * End validation - fire an ended event
143      */
144     protected void endValidation()
145     {
146         getBroadcaster().fireEndedEvent(new ValidationEvent(this,
147             getWarFileName(), "war validation ended"));
148     }
149     
150     /**
151      * validate the war file can be read and exists
152      */
153     private void validateFile()
154     {
155         File warFile = new File(getWarFileName());
156 
157         if (!warFile.exists())
158         {
159             error("File does not exist");
160             return;
161         }
162         if (!warFile.canRead())
163         {
164             error("File can't be read");
165             return;
166         }
167     }
168 
169     /** 
170      * Validate the web.xml entry in the provided jar file.
171      */
172     private void validateWebXml()
173     {
174         WarFile war = null;
175         try
176         {
177             war = new WarFile(getWarFileName());
178             if (war.getWebXmlEntry() == null)
179             {
180                 warning("web.xml entry not found");
181                 return;
182             }
183             validateServlets(war);
184             validateJSPs(war);
185             validateTaglibs(war);
186             validateErrorPages(war);
187             validateFormLoginConfig(war);
188         }
189         catch (IOException ioe)
190         {
191             error("Error opening war file for web.xml - possibly missing "
192                 + "manifest");
193         }
194     }
195 
196     /** 
197      * Validate the servlets defined in the war file (as defined by a
198      * <code>&lt;servlet&gt;</code> tag in web.xml), making sure that their
199      * class defined can be loaded from the war and is not part of the 
200      * external classpath
201      * 
202      * @param war the war file to validate
203      * @throws IOException when there are any issues reading the war file
204      */
205     private void validateServlets(WarFile war) throws IOException
206     {
207         Map servlets = war.getServlets();
208         if (servlets.size() != 0)
209         {
210             ClassLoader classLoader = new WarClassLoader(war, getClass().
211                 getClassLoader());
212             String className = null;
213             Map.Entry entry = null;
214             for (Iterator entries = servlets.entrySet().iterator();
215                 entries.hasNext();)
216             {
217                 entry = (Map.Entry) entries.next();
218                 className = (String) entry.getValue();
219                 info("validating servlet name: " + entry.getKey() + " class: " 
220                     + className);
221                 // check each servlet by loading the class
222                 validateClass(className, classLoader);
223             }
224         }
225     }
226 
227     /**
228      * Validate the jsps defined in the war file (as defined by a
229      * <code>&lt;servlet&gt;</code> tag with a nested <code>&lt;jsp-file&gt;
230      * </code> in web.xml), making sure that the resource specifed by
231      * <code>&lt;jsp-file&gt;</code> exists in the war file
232      * 
233      * @param war the war file to validate
234      * @throws IOException when there are any issues reading the war file
235      */
236     private void validateJSPs(WarFile war) throws IOException
237     {
238         Map jsps = war.getJSPs();
239         if (jsps.size() != 0)
240         {
241             Map.Entry entry = null;
242             for (Iterator entries = jsps.entrySet().iterator();
243                 entries.hasNext();)
244             {
245                 entry = (Map.Entry) entries.next();
246                 String jspFile = (String) entry.getValue();
247                 info("validating servlet name: " + entry.getKey()
248                     + " jsp file: " + jspFile);
249 
250                 if (!war.hasFile(jspFile))
251                 {
252                     error("JSP File: '" + jspFile + "' not found");
253                 }
254             }
255         }
256     }
257     
258     /** 
259      * Validate that the given className can be loaded by the given
260      * {@link ClasssLoader}
261      * 
262      * @param className the name of a class to attempt loading
263      * @param loader a {@link ClassLoader class loader}
264      */
265     protected void validateClass(String className, ClassLoader loader)
266     {
267         try
268         {
269             Class clazz = loader.loadClass(className);
270             if (clazz.getClassLoader() != loader)
271             {
272                 // loaded from classpath - a no no.
273                 error("class (" + className + ") loaded from system classpath " 
274                     + "rather than war file");
275             }
276         }
277         catch (ClassNotFoundException e)
278         {
279             error("class (" + className + ") not found ");
280         }
281         catch (NoClassDefFoundError error)
282         {
283             error("class (" + className + ") was found, but a referenced class "
284                 + "was missing: " + error.getMessage());
285         }
286 
287     }
288 
289     /** 
290      * Validate the taglibs defined in the war file (as defined by a
291      * <code>&lt;taglib&gt;</code> tag in web.xml), making sure that the 
292      * resource specifed by <code>&lt;taglib-location&gt;</code> exists in the 
293      * war file
294      * 
295      * @param war the war file to validate
296      * @throws IOException when there are any issues reading the war file
297      */
298     private void validateTaglibs(WarFile war) throws IOException
299     {
300         Map taglibs = war.getTaglibs();
301         if (taglibs.size() != 0)
302         {
303             Map.Entry entry = null;
304             for (Iterator entries = taglibs.entrySet().iterator();
305                 entries.hasNext();)
306             {
307                 entry = (Map.Entry) entries.next();
308                 String uri = (String) entry.getKey();
309                 String location = (String) entry.getValue();
310                 info("validating taglib uri: " + uri);
311                 if (!war.hasFile(location))
312                 {
313                     error("Taglib location: '" + location + "' not found");
314                 }
315             }
316         }
317     }
318 
319     /** 
320      * Validate the error pages defined in the war file (as defined by a
321      * <code>&lt;error-page&gt;</code> tag in web.xml), making sure that the 
322      * location specifed by the nested <code>&lt;location&gt;</code> exists in 
323      * the war file
324      * 
325      * @param war the war file to validate
326      * @throws IOException when there are any issues reading the war file
327      */
328      public void validateErrorPages(WarFile war) throws IOException
329      {
330         Map pages = war.getErrorPages();
331         if (pages.size() != 0)
332         {
333             Map.Entry entry = null;
334             for (Iterator entries = pages.entrySet().iterator();
335                 entries.hasNext();)
336             {
337                 entry = (Map.Entry) entries.next();
338                 String errorQualifier = (String) entry.getKey();
339                 String location = (String) entry.getValue();
340                 info("validating error page for: " + errorQualifier);
341                 if (!war.hasFile(location))
342                 {
343                     error("Error page location: '" + location + "' not found");
344                 }
345             }
346         }
347      }
348 
349     /**
350      * Validate that the <code>&lt;form-login-config&gt</code> element, if it
351      * exists contains valid login and error pages
352      * 
353      * @param war the war file to validate
354      * @throws IOException when there are any issues reading the war file
355      */
356     public void validateFormLoginConfig(WarFile war) throws IOException
357     {
358         FormLoginConfig config = war.getFormLoginConfig();
359         if (config != null)
360         {
361             if (!war.hasFile(config.getLoginPage()))
362             {
363                 info("<form-config-login> login-page location: '"
364                     + config.getLoginPage() + "' not found");
365             }
366             if (!war.hasFile(config.getErrorPage()))
367             {
368                 error("<form-config-login> error-page location: '"
369                     + config.getErrorPage() + "' not found");
370             }
371         }
372     }
373     
374     /**
375      * add a listener to the list to be notified
376      * 
377      * @param listener a {@link ValidationListener}
378      */
379     public void addValidationListener(ValidationListener listener)
380     {
381         getBroadcaster().addValidationListener(listener);
382     }
383 
384     /**
385      * remove a listener from the list to be notified
386      * 
387      * @param listener a {@link ValidationListener}
388      */
389     public void removeValidationListener(ValidationListener listener)
390     {
391         getBroadcaster().removeValidationListener(listener);
392     }
393 
394     /**
395      * Getter for property warFileName.
396      * 
397      * @return Value of property warFileName.
398      */
399     public String getWarFileName()
400     {
401         return warFileName;
402     }
403     
404     /**
405      * Setter for property warFileName.
406      * 
407      * @param warFileName New value of property warFileName.
408      */
409     public void setWarFileName(String warFileName)
410     {
411         this.warFileName = warFileName;
412     }    
413     
414     /**
415      * Getter for property broadcaster.
416      * 
417      * @return Value of property broadcaster.
418      */
419     protected ValidationBroadcaster getBroadcaster()
420     {
421         return broadcaster;
422     }
423     
424     /** 
425      * Add a formatter to pick up events and display the output.
426      * Used by ant when a nested formatter element is present.
427      * 
428      * @param formatter a class to format validation events
429      */
430     public void addFormatter(ValidationFormatter formatter)
431     {
432         addValidationListener((ValidationListener) formatter);
433     }
434 
435     /**
436      * Provide a string representation of the validator
437      * 
438      * @return "WarValidator(file)"
439      */
440     public String toString()
441     {
442         StringBuffer buffer = new StringBuffer("WarValidator");
443         if (getWarFileName() != null)
444         {
445             buffer.append("(").append(getWarFileName()).append(")");
446         }
447         return buffer.toString();
448     }
449     
450     /**
451      * Whether the build process will fail if a validation error occurs.
452      * 
453      * @return Value of property failOnError.
454      */
455     public boolean isFailOnError()
456     {
457         return failOnError;
458     }
459     
460     /**
461      * Set whether the build process will fail if a validation error occurs.
462      * 
463      * @param failOnError New value of property failOnError.
464      */
465     public void setFailOnError(boolean failOnError)
466     {
467         this.failOnError = failOnError;
468     }
469     
470     /**
471      * Helper method to fire an information message using the broadcaster
472      * 
473      * @param message to be fired
474      */
475     protected void info(String message) 
476     {
477         getBroadcaster().fireInformationEvent(new ValidationEvent(this,
478             getWarFileName(), message));
479     }
480     
481     /**
482      * Helper method to fire an error message using the broadcaster
483      * 
484      * @param message to be fired
485      */
486     protected void error(String message) 
487     {
488         getBroadcaster().fireErrorEvent(new ValidationEvent(this,
489             getWarFileName(), message));
490     }
491 
492     /**
493      * Helper method to fire a warning message using the broadcaster
494      * 
495      * @param message to be fired
496      */
497     protected void warning(String message) 
498     {
499         getBroadcaster().fireWarningEvent(new ValidationEvent(this,
500             getWarFileName(), message));
501     }
502 
503 }