View Javadoc

1   package org.apache.maven.struts;
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.IOException;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.commons.collections.CollectionUtils;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.maven.j2ee.WarClassLoader;
28  import org.apache.maven.j2ee.WarValidator;
29  import org.apache.regexp.RE;
30  import org.apache.regexp.RESyntaxException;
31  
32  /**
33   * A class that validates a Struts 1.0 War File. Specific validations performed
34   * are:
35   * 
36   * <ol>
37   * <li>
38   * Must pass validation as a 'standard' war file
39   * </li>
40   * <li>
41   * File has a struts configuration file
42   * </li>
43   * <li>
44   * &lt;form-bean&gt;s must have a valid <code>type</code> and
45   * <code>className</code> that exist in the war
46   * </li>
47   * <li>
48   * &lt;action&gt;s must have a valid <code>type</code>,  <code>className</code>
49   * that exist in the war
50   * </li>
51   * <li>
52   * &lt;action&gt; <code>name</code>s must refer to a &lt;form-bean&gt; in the
53   * struts configuraion
54   * </li>
55   * <li>
56   * &lt;action&gt; <code>scope</code> must be either <code>request</code> or
57   * <code>session</code>
58   * </li>
59   * <li>
60   * &lt;action&gt; <code>unknown</code> and <code>validate</code> must be
61   * <code>true</code> or <code>false</code>
62   * </li>
63   * <li>
64   * &lt;global-forwards&gt; <code>type</code> must be a class in the war
65   * </li>
66   * <li>
67   * &lt;forward&gt; <code>redirect</code> must be <code>true</code> or
68   * <code>false</code>
69   * </li>
70   * <li>
71   * &lt;forward&gt; <code>path</code> must refer to either a file in the war
72   * (e.g. a jsp), or an action defined in the struts configuration
73   * </li>
74   * </ol>
75   * 
76   * 
77   * @version $Id: Struts10WarValidator.java 170200 2005-05-15 06:24:19Z brett $
78   * @author dion
79   */
80  public class Struts10WarValidator extends WarValidator
81  {
82      //~ Instance variables ?????????????????????????????????????????????????????
83  
84      /**
85       * the location within the war of the Struts configuration file. By default
86       * this takes the value of the {@link  Struts10WarFile#DEFAULT_CONFIG
87       * default config}
88       */
89      private String config = Struts10WarFile.DEFAULT_CONFIG;
90  
91      //~ Constructors ???????????????????????????????????????????????????????????
92  
93      /**
94       * Creates a new instance of Struts10WarValidator
95       */
96      public Struts10WarValidator()
97      {
98      }
99  
100     //~ Methods ????????????????????????????????????????????????????????????????
101 
102     /**
103      * Setter for config location within the war (no leading slash)
104      * 
105      * @param config New value of property config.
106      */
107     public void setConfig(String config)
108     {
109         this.config = config;
110     }
111 
112     /**
113      * Getter for Struts config location within the war (no leading slash).
114      * e.g. <code>WEB-INF/struts-config.xml</code>
115      * 
116      * @return Value of property config.
117      */
118     public String getConfig()
119     {
120         return config;
121     }
122 
123     /**
124      * Validate struts specific war features here
125      */
126     protected void validateWarContents()
127     {
128         try
129         {
130             super.validateWarContents();
131 
132             Struts10WarFile strutsWar = new Struts10WarFile(getWarFileName());
133             strutsWar.setConfig(getConfig());
134             validateStrutsConfig(strutsWar);
135             validateFormBeans(strutsWar);
136             validateActions(strutsWar);
137             validateForwards(strutsWar);
138         }
139         catch (IOException ioe)
140         {
141             ioe.printStackTrace();
142             error("Error reading struts war file");
143         }
144     }
145 
146     /**
147      * validate a string that must contain a boolean value or be null
148      * 
149      * @param value the string to be validated
150      * 
151      * @return true if the provided string is true, false or null
152      */
153     private boolean isBoolean(String value)
154     {
155         return (value == null) || value.equals("true") || value.equals("false");
156     }
157 
158     /**
159      * validate the provided action object
160      * 
161      * @param action the action to be validated
162      * @param formBeans the form beans for the war that the actions came from
163      * @param loader a {@link ClassLoader} to verify classes are from the war
164      */
165     private void validateAction(Action action, Map formBeans, 
166                                 ClassLoader loader)
167     {
168         info("validating action for path: '" + action.getPath() + "'");
169 
170         // className must be a loadable class
171         if (action.getClassName() != null)
172         {
173             validateClass(action.getClassName(), loader);
174         }
175 
176         // name must be the name of a form bean
177         if ((action.getName() != null)
178             && (formBeans.get(action.getName()) == null))
179         {
180             error("action refers to a non-existent form bean: '"
181                   + action.getName() + "'");
182         }
183 
184         // path must start with a '/'
185         if (!action.getPath().startsWith("/"))
186         {
187             error("action path (" + action.getPath() + ") doesn't start "
188                   + "with a '/'");
189         }
190 
191         // scope if specified must be request or session
192         if ((action.getScope() != null)
193             && !(action.getScope().equals("request") 
194                  || action.getScope().equals("session")))
195         {
196             error("scope (" + action.getScope() + ") is not 'request' or "
197                   + "'session'");
198         }
199 
200         // type must be a loadable class
201         if (action.getType() != null)
202         {
203             validateClass(action.getType(), loader);
204         }
205 
206         // unknown must be true, false or null
207         if (!isBoolean(action.getUnknown())) // true or false only
208         {
209             error("unknown attribute is not 'true' or 'false'");
210         }
211     
212         // validate must be true, false or null
213         if (!isBoolean(action.getValidate())) // true or false only
214         {
215             error("validate attribute is not 'true' or 'false'");
216         }
217     }
218 
219     /**
220      * validations for the actions in the config
221      * 
222      * @param war the struts web app being validated
223      * 
224      * @throws IOException when there are problems reading the war
225      */
226     private void validateActions(Struts10WarFile war) throws IOException
227     {
228         info("validating Struts Actions");
229 
230         List actions = war.getActions();
231         Action action = null;
232         ClassLoader loader = new WarClassLoader(war, 
233                                                 getClass().getClassLoader());
234         List formBeans = war.getFormBeans();
235         Map formBeansByName = new HashMap();
236 
237         // put form beans into a map to find them easier
238         FormBean formBean = null;
239 
240         for (int index = 0; index < formBeans.size(); index++)
241         {
242             formBean = (FormBean) formBeans.get(index);
243             formBeansByName.put(formBean.getName(), formBean);
244         }
245 
246         // check actions
247         for (int index = 0; index < actions.size(); index++)
248         {
249             action = (Action) actions.get(index);
250 
251             if (CollectionUtils.cardinality(action, actions) > 1)
252             {
253                 error("action is a duplicate (by path)");
254             }
255 
256             validateAction(action, formBeansByName, loader);
257         } // end for all actions
258     } // end method
259 
260     /**
261      * validations for the form beans in the config
262      * 
263      * @param strutsWar - the struts web app being validated
264      * 
265      * @throws IOException when there are problems reading the war
266      */
267     private void validateFormBeans(Struts10WarFile strutsWar) throws IOException
268     {
269         info("validating Struts Form Beans");
270 
271         List formBeans = strutsWar.getFormBeans();
272         FormBean bean = null;
273         ClassLoader loader = new WarClassLoader(strutsWar, 
274                                                 getClass().getClassLoader());
275 
276         if (strutsWar.getFormBeansType() != null)
277         {
278             validateClass(strutsWar.getFormBeansType(), loader);
279         }
280 
281         for (int index = 0; index < formBeans.size(); index++)
282         {
283             bean = (FormBean) formBeans.get(index);
284 
285             if (CollectionUtils.cardinality(bean, formBeans) > 1)
286             {
287                 error("form bean is a duplicate (by name)");
288             }
289 
290             info("validating form bean: '" + bean.getName() + "', class: '"
291                  + bean.getType() + "'");
292             validateClass(bean.getType(), loader);
293 
294             if (bean.getClassName() != null)
295             {
296                 validateClass(bean.getClassName(), loader);
297             }
298         }
299     }
300 
301     /**
302      * Validate a single global forward.
303      * 
304      * @param war the war file the forward is from
305      * @param forward the forward to be validated
306      * @param loader a class loader for validating classes are in the war
307      * 
308      * @throws IOException when there are issues reading the war
309      * @throws IllegalStateException If the regular expression used to check
310      *         that the forward matches the action servlet is malformed
311      */
312     private void validateForward(Struts10WarFile war, Forward forward, 
313                                  ClassLoader loader) throws IOException
314     {
315         // check className, if provided
316         if (forward.getClassName() != null)
317         {
318             validateClass(forward.getClassName(), loader);
319         }
320 
321         // check name
322         if (StringUtils.isEmpty(forward.getName()))
323         {
324             error("name attribute is required");
325         }
326 
327         // check path
328         String path = forward.getPath();
329         int queryStringStart = path.indexOf("?");
330 
331         if (queryStringStart != -1)
332         {
333             path = path.substring(0, queryStringStart);
334         }
335 
336         if (!war.hasFile(path))
337         {
338             try
339             {
340                 // could be an action - check to see if it matches the pattern 
341                 // for action servlet, if it does, extract the action path, and
342                 // make sure it's in the list of actions
343                 String pattern = war.getActionServletPattern();
344 
345 
346                 // escape '.', as it has special meaning to regular exprs
347                 pattern = StringUtils.replace(pattern, ".", "\\.");
348 
349 
350                 // change * to (*) to group matching characters for access
351                 pattern = StringUtils.replace(pattern, "*", "(.*)");
352 
353                 RE regexp = new RE(pattern);
354 
355                 if (regexp.match(path))
356                 {
357                     String actionPath = regexp.getParen(1);
358                     Action dummyAction = new Action();
359                     dummyAction.setPath(actionPath);
360 
361                     if (!war.getActions().contains(dummyAction))
362                     {
363                         error("action path for forward (" + actionPath + ")"
364                               + " not found");
365                     }
366                 }
367                 else
368                 {
369                     error("No action or web resource found for '" + path + "'");
370                 }
371             }
372             catch (RESyntaxException e)
373             {
374                 throw new IllegalStateException(
375                     "bad regular expression created"
376                     + " from action servlet url-pattern in web.xml "
377                     + e.getMessage());
378             }
379         }
380 
381         // check redirect
382         if (!isBoolean(forward.getRedirect()))
383         {
384             error("redirect attribute is not 'true' or 'false'");
385         }
386     }
387 
388     /**
389      * validate the global forwards in the configuration
390      * 
391      * @param war the war file to be used for validating
392      * 
393      * @throws IOException when there are problems reading the war
394      */
395     private void validateForwards(Struts10WarFile war) throws IOException
396     {
397         info("validating global forwards");
398 
399         ClassLoader loader = new WarClassLoader(war, 
400                                                 getClass().getClassLoader());
401 
402         if (war.getGlobalForwardsType() != null)
403         {
404             validateClass(war.getGlobalForwardsType(), loader);
405         }
406 
407         List forwards = war.getForwards();
408         Forward forward = null;
409 
410         for (int index = 0; index < forwards.size(); index++)
411         {
412             forward = (Forward) forwards.get(index);
413             info("validating forward '" + forward.getName() + "', path: '"
414                  + forward.getPath() + "'");
415 
416             if (CollectionUtils.cardinality(forward, forwards) > 1)
417             {
418                 error("forward is a duplicate (by name)");
419             }
420 
421             validateForward(war, forward, loader);
422         }
423     }
424 
425     /**
426      * validations for the struts configuration file
427      * 
428      * @param strutsWar - the struts web app being validated
429      */
430     private void validateStrutsConfig(Struts10WarFile strutsWar)
431     {
432         info("validating Struts Configuration");
433 
434         if (strutsWar.getStrutsConfigEntry() == null)
435         {
436             error("Struts Configuration: '" + strutsWar.getConfig()
437                   + "' not found in the war file");
438         }
439     }
440 } // end class