1 package org.apache.maven.j2ee;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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><servlet><code> tag are loadable
34 * from the war file and <strong>not</strong> the classpath</li>
35 * <li>JSPs defined by a <code><servlet><code> tag exist in the war
36 * </li>
37 * <li>Taglibs defined by a <code><taglib></code> have a <code>
38 * <taglib-location></code> that exists in the war</li>
39 * <li>Error pages specified by a <code><location></code> nested
40 * within an <code><error-page></code> element must exist in the
41 * war file</li>
42 * <li>Login and error pages specified in the <code><form-login-config
43 * ></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
64 /**
65 * Creates a new instance of WarValidator
66 */
67 public WarValidator()
68 {
69 addValidationListener(getStatus());
70 }
71
72
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><servlet></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
222 validateClass(className, classLoader);
223 }
224 }
225 }
226
227 /**
228 * Validate the jsps defined in the war file (as defined by a
229 * <code><servlet></code> tag with a nested <code><jsp-file>
230 * </code> in web.xml), making sure that the resource specifed by
231 * <code><jsp-file></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
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><taglib></code> tag in web.xml), making sure that the
292 * resource specifed by <code><taglib-location></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><error-page></code> tag in web.xml), making sure that the
322 * location specifed by the nested <code><location></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><form-login-config></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 }