View Javadoc
1   package org.andromda.cartridges.jsf.validator;
2   
3   import java.io.InputStream;
4   import java.io.Serializable;
5   import java.lang.reflect.Method;
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.HashMap;
9   import java.util.Locale;
10  import java.util.Map;
11  import javax.faces.application.FacesMessage;
12  import javax.faces.component.UIComponent;
13  import javax.faces.context.ExternalContext;
14  import javax.faces.context.FacesContext;
15  import javax.faces.validator.Validator;
16  import javax.faces.validator.ValidatorException;
17  import org.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.apache.commons.validator.Field;
20  import org.apache.commons.validator.Form;
21  import org.apache.commons.validator.ValidatorAction;
22  import org.apache.commons.validator.ValidatorResources;
23  
24  /**
25   * A JSF validator that uses the apache commons-validator to perform either
26   * client or server side validation.
27   */
28  public class JSFValidator
29      implements Validator,
30          Serializable
31  {
32      private static final Log logger = LogFactory.getLog(JSFValidator.class);
33  
34      /**
35       * Constructs a new instance of this class with the given <code>form</code> and
36       * <code>validatorAction</code>.
37       *
38       * @param formIdIn
39       * @param validatorActionIn
40       */
41      public JSFValidator(final String formIdIn, final ValidatorAction validatorActionIn)
42      {
43          this.formId = formIdIn;
44          this.validatorAction = validatorActionIn;
45      }
46  
47      private String formId;
48  
49      /**
50       * Default constructor for faces-config.xml
51       */
52      public JSFValidator()
53      {
54          // - default constructor for faces-config.xml
55      }
56  
57      /**
58       * Validator type.
59       */
60      private String type;
61  
62      /**
63       * The setter method for the <code>type</code> property. This property is
64       * passed through to the commons-validator.
65       *
66       * @param typeIn The new value for the <code>type</code> property.
67       */
68      public void setType(final String typeIn)
69      {
70          this.type = typeIn;
71      }
72  
73      /**
74       * The getter method for the <code>type</code> property. This property is
75       * passed through to the commons-validator.
76       * @return  this.type
77       */
78      public String getType()
79      {
80          return this.type;
81      }
82  
83      /**
84       * Parameter for the error message. This parameter is passed through to the
85       * commons-validator.
86       */
87      private String[] args;
88  
89      /**
90       * The setter method for the <code>args</code> property. This property is
91       * passed through to the commons-validator..
92       *
93       * @param argsIn The new value for the <code>args</code> property.
94       */
95      public void setArgs(final String[] argsIn)
96      {
97          this.args = argsIn;
98      }
99  
100     /**
101      * The parameters for this validator.
102      */
103     private final Map parameters = new HashMap();
104 
105     /**
106      * Gets the parameters for this validator (keyed by name).
107      *
108      * @return a map containing all parameters.
109      */
110     public Map getParameters()
111     {
112         return this.parameters;
113     }
114 
115     /**
116      * Adds the parameter with the given <code>name</code> and the given
117      * <code>value</code>.
118      *
119      * @param name the name of the parameter.
120      * @param value the parameter's value
121      */
122     public void addParameter(
123         final String name,
124         final Object value)
125     {
126         this.parameters.put(
127             name,
128             value);
129     }
130 
131     /**
132      * Returns the commons-validator action that's appropriate for the validator
133      * with the given <code>name</code>. This method lazily configures
134      * validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
135      * and <code>/WEB-INF/validation.xml</code>.
136      *
137      * @param name The name of the validator
138      * @return getValidatorResources().getValidatorAction(name)
139      */
140     public static ValidatorAction getValidatorAction(final String name)
141     {
142         return getValidatorResources().getValidatorAction(name);
143     }
144 
145     /**
146      * The commons-validator action, that carries out the actual validation.
147      */
148     private ValidatorAction validatorAction;
149 
150     /**
151      * The location of the validator rules.
152      */
153     public static final String RULES_LOCATION = "/WEB-INF/validator-rules.xml";
154 
155     /**
156      * The key that stores the validator resources
157      */
158     private static final String VALIDATOR_RESOURCES_KEY = "org.andromda.jsf.validator.resources";
159 
160     /**
161      * Returns the commons-validator resources. This method lazily configures
162      * validator resources by reading <code>/WEB-INF/validator-rules.xml</code>
163      * and <code>/WEB-INF/validation.xml</code>.
164      *
165      * @return the commons-validator resources
166      */
167     public static ValidatorResources getValidatorResources()
168     {
169         final FacesContext context = FacesContext.getCurrentInstance();
170         final ExternalContext external = context.getExternalContext();
171         final Map applicationMap = external.getApplicationMap();
172         ValidatorResources validatorResources = (ValidatorResources)applicationMap.get(VALIDATOR_RESOURCES_KEY);
173         if (validatorResources == null)
174         {
175             final String rulesResource = RULES_LOCATION;
176             final String validationResource = "/WEB-INF/validation.xml";
177             final InputStream rulesInput = external.getResourceAsStream(rulesResource);
178             if (rulesInput == null)
179             {
180                 throw new JSFValidatorException("Could not find rules file '" + rulesResource + '\'');
181             }
182             final InputStream validationInput = external.getResourceAsStream(validationResource);
183             if (validationInput != null)
184             {
185                 final InputStream[] inputs = new InputStream[] {rulesInput, validationInput};
186                 try
187                 {
188                     validatorResources = new ValidatorResources(inputs);
189                     applicationMap.put(
190                         VALIDATOR_RESOURCES_KEY,
191                         validatorResources);
192                 }
193                 catch (final Throwable throwable)
194                 {
195                     throw new JSFValidatorException(throwable);
196                 }
197             }
198             else
199             {
200                 logger.info(
201                     "No validation rules could be loaded from --> '" + validationResource +
202                     ", validation not configured");
203             }
204         }
205         return validatorResources;
206     }
207 
208     /**
209      * This <code>validate</code> method is called by JSF to verify the
210      * component to which the validator is attached.
211      *
212      * @see javax.faces.validator.Validator#validate(javax.faces.context.FacesContext, javax.faces.component.UIComponent, Object)
213      */
214     public void validate(
215         final FacesContext context,
216         final UIComponent component,
217         final Object value)
218     {
219         if (this.formId != null)
220         {
221             try
222             {
223                 final Form validatorForm = getValidatorResources().getForm(
224                     Locale.getDefault(),
225                     this.formId);
226                 if (validatorForm != null)
227                 {
228                     final Field field = this.getFormField(validatorForm, component.getId());
229                     if (field != null)
230                     {
231                         final Collection errors = new ArrayList();
232                         this.getValidatorMethod().invoke(
233                             this.getValidatorClass(),
234                                 context, value, this.getParameters(), errors, this.validatorAction,
235                                 field);
236                         if (!errors.isEmpty())
237                         {
238                             throw new ValidatorException(new FacesMessage(
239                                     FacesMessage.SEVERITY_ERROR,
240                                     (String)errors.iterator().next(),
241                                     null));
242                         }
243                     }
244                     else
245                     {
246                         logger.error("No field with id '" + component.getId() + "' found on form '" + this.formId + '\'');
247                     }
248                 }
249                 else
250                 {
251                     logger.error("No validator form could be found with id '" + this.formId + '\'');
252                 }
253             }
254             catch (final ValidatorException exception)
255             {
256                 throw exception;
257             }
258             catch (final Exception exception)
259             {
260                 logger.error(
261                     exception.getMessage(),
262                     exception);
263             }
264         }
265     }
266 
267     /**
268      * Gets the validator class from the underlying <code>validatorAction</code>.
269      * @return the validator class
270      * @throws ClassNotFoundException
271      */
272     private Class getValidatorClass()
273         throws ClassNotFoundException
274     {
275         final FacesContext context = FacesContext.getCurrentInstance();
276         final ExternalContext external = context.getExternalContext();
277         final Map applicationMap = external.getApplicationMap();
278         final String validatorClassName = this.validatorAction.getClassname();
279         Class validatorClass = (Class)applicationMap.get(validatorClassName);
280         if (validatorClass == null)
281         {
282             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
283             if (classLoader == null)
284             {
285                 classLoader = getClass().getClassLoader();
286             }
287             validatorClass = classLoader.loadClass(validatorClassName);
288             applicationMap.put(
289                 validatorClassName,
290                 validatorClass);
291         }
292         return validatorClass;
293     }
294 
295     /**
296      * Gets the validator method for the underlying <code>validatorAction</code>.
297      *
298      * @return the validator method.
299      * @throws ClassNotFoundException
300      * @throws NoSuchMethodException
301      */
302     private Method getValidatorMethod()
303         throws ClassNotFoundException, NoSuchMethodException
304     {
305         Class[] parameterTypes =
306             new Class[]
307             {
308                 javax.faces.context.FacesContext.class, Object.class, java.util.Map.class,
309                 java.util.Collection.class, org.apache.commons.validator.ValidatorAction.class,
310                 org.apache.commons.validator.Field.class
311             };
312         return this.getValidatorClass().getMethod(
313             this.validatorAction.getMethod(),
314             parameterTypes);
315     }
316 
317     /**
318      * Attempts to retrieve the form field from the form with the given <code>formName</code>
319      * and the field with the given <code>fieldName</code>.  If it can't be retrieved, null
320      * is returned.
321      * @param form the form to validate.
322      * @param fieldName the name of the field.
323      * @return the found field or null if it could not be found.
324      */
325     private Field getFormField(
326         final Form form,
327         final String fieldName)
328     {
329         Field field = null;
330         if (form != null)
331         {
332             field = form.getField(fieldName);
333         }
334         return field;
335     }
336 
337     /**
338      * Retrieves an error message, using the validator's message combined with
339      * the errant value.
340      * @param context
341      * @return ValidatorMessages.getMessage
342      */
343     public String getErrorMessage(final FacesContext context)
344     {
345         return ValidatorMessages.getMessage(
346             this.validatorAction,
347             this.args,
348             context);
349     }
350 
351     /**
352      * @see Object#toString()
353      */
354     @Override
355     public String toString()
356     {
357         return super.toString() + ":formId=" + this.formId + ", validatorAction="
358             + (this.validatorAction != null ? this.validatorAction.getName() : null);
359     }
360 
361     private static final long serialVersionUID = 1;
362 }