View Javadoc

1   /*
2    * Copyright 2007 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  
18  
19  package net.sf.valjax;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.springframework.context.NoSuchMessageException;
24  import org.springframework.context.i18n.LocaleContextHolder;
25  import org.springframework.context.support.MessageSourceAccessor;
26  import org.springframework.validation.BindException;
27  import org.springframework.validation.FieldError;
28  import org.springframework.web.servlet.ModelAndView;
29  import org.springframework.web.servlet.mvc.SimpleFormController;
30  
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  import java.util.HashMap;
34  import java.util.Locale;
35  import java.util.Map;
36  
37  /***
38   * SimpleFormController that configures the response, view and
39   * the model needed for Ajax.
40   *
41   * <p>Uses the same workflow as described in the parent implementation.
42   * Any <code>BindException</code> errors are processed and transformed
43   * using {@link #getErrors getErrors} into a String that can be properly
44   * digested in the Ajax response.
45   *
46   * <p>You will need to use the {@link #setValidatorView setValidatorView}
47   *  to provide the default configuration as shown here:</p>
48   *
49   * <pre class="code">
50   *    &lt;property name="validatorView"&gt;
51   *      &lt;value>valjax-response&lt;/value&gt;
52   *    &lt;/property&gt;
53   * </pre>
54   *
55   * @author     Zach Legein
56   *
57   */
58  public class AjaxSimpleFormController extends SimpleFormController {
59    protected static final Log log = LogFactory.getLog(AjaxSimpleFormController.class);
60    private String validatorResponse;
61    private String validatorView;
62  
63    /***
64     * Proccess the form submission with any validation errors.
65     *
66     * <p>Check to see if the incoming {@link HttpServletRequest request}
67     * is an Ajax call or not. If it is then {@link #processAjaxSubmit processAjaxSubmit},
68     * otherwise we let {@link #processFormSubmission} do its job.
69     *
70     * @see #processAjaxSubmit
71     */
72    @Override
73    public ModelAndView processFormSubmission(HttpServletRequest request, HttpServletResponse response, Object command,
74      BindException errors)
75            throws Exception {
76  
77      // todo: not sure if we need to do this or not, if multiple forms, maybe?
78      if (request.getParameter("valjax") != null) {
79        return processAjaxSubmit(errors);
80      }
81  
82      return super.processFormSubmission(request, response, command, errors);
83    }
84  
85    /***
86     * Provide the <code>ModelAndView</code> to use for
87     * the Ajax response.
88     *
89     * <p>Returns a new ModelAndView, setting the view as described
90     * in {@link #setValidatorView setValidatorView} with the errors
91     * from {@link #getErrors getErrors} loaded into the model with
92     * {@link #setValidatorResponse setValidatorResponse} as the key
93     * to the errors. This key is then used in the view to lookup the
94     * error messages for the form.</p>
95     *
96     *
97     * @param errors Rejects from the validator
98     * @return ModelAndView to return
99     *
100    * @see #getErrors
101    * @see # setValidatorResponse
102    * @see # setValidatorView
103    */
104   public ModelAndView processAjaxSubmit(BindException errors) {
105     
106      Map<String, String> map = new HashMap<String, String>();
107 
108     if (validatorResponse == null) validatorResponse = "validatorResponse";
109 
110     try {
111       map.put(validatorResponse, getErrors(errors));
112 
113       return new ModelAndView(validatorView, map);
114     }
115     catch (Exception ex) {
116       map.put(validatorResponse, "[['id','msg'],['errors','Validation Framework Error: " + ex.getMessage() + "']]");
117       log.error("Validation Framework Error: " + ex.getMessage(), ex);
118 
119       return new ModelAndView(validatorView, map);
120     }
121   }
122 
123   /***
124    * Formats the {@link BindException errors} to be used in the AJax response.
125    *
126    * <p>Each error is wrapped in '[ ]' with the name of the field and
127    * a list of all the errors for that field, using the '|' as a delimeter
128    * for each field error. The pairing in the first '[ ]' are the preset
129    * names used to identify the field and the error message(s).</p>
130    * <pre class="code">
131    * Multiple fields with errors:
132    * [['id','msg'],['name','Required Field'],['number','Please enter a value between 10 and 20']]
133    *
134    * Multiple errors on the same field:
135    * [['id','msg'],['name','Required Field | Invalid Last Name']]
136    *
137    * Multiple errors on the same field and multiple fields with errors:
138    * [['id','msg'],['name','Required Field | Invalid Last Name'],['number','Please enter a value between 10 and 20']]
139    * </pre>   
140    *
141    * @param errors Validation errors to format.
142    * @return Ajax representation of the errors
143    *
144    */
145   protected String getErrors(BindException errors) {
146     MessageSourceAccessor msa = getMessageSourceAccessor();
147     StringBuffer buffer       = new StringBuffer("[['id','msg']");
148     Locale locale             = LocaleContextHolder.getLocale();
149 
150     for (Object obj : errors.getFieldErrors()) {
151       FieldError error = (FieldError) obj;
152 
153       try {
154         buffer.append(",['").append(error.getField()).append('\'');
155         buffer.append(",'").append(msa.getMessage(error.getCode(), error.getArguments(), locale)).append("']");
156       }
157       catch (NoSuchMessageException ex) {
158         buffer.append(",['").append(error.getField()).append("','Message code does not exist: ").append(
159             error.getCode()).append("']");
160         log.error("Message code does not exist: " + error.getCode(), ex);
161       }
162     }
163 
164     buffer.append(']');
165 
166     return buffer.toString();
167   }
168 
169   /***
170    * The view the Ajax response will use to lookup
171    * errors.
172    *
173    * <p>This is required and needs to be set. The mandatory default
174    * is as follows.</p>
175    *
176    * <pre class="code">
177    *    &lt;property name="validatorView"&gt;
178    *      &lt;value>valjax-response&lt;/value&gt;
179    *    &lt;/property&gt;
180    * </pre>
181    *
182    * <p>This corresponds to the <code>valjax-response.jsp</code> file
183    * that is used as the view when returning errors.
184    *
185    * @param validatorView Name of the view when there are errors.
186    */
187   public void setValidatorView(String validatorView) {
188     this.validatorView = validatorView;
189   }
190 
191   /***
192    * Token to use in the <code>validatorView</code> file
193    * to allow Ajax to lookup the errors.
194    *
195    * <p>Default setting, if one is not set:
196    * <b>validatorResponse</b></p>
197    *
198    * @param validatorResponse Name of the token to use to lookup errors.
199    */
200   @SuppressWarnings({ "UnusedDeclaration" })
201   public void setValidatorResponse(String validatorResponse) {
202     this.validatorResponse = validatorResponse;
203   }
204 }