KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > struts > SpringBindingActionForm


1 /*
2  * Copyright 2002-2006 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 package org.springframework.web.struts;
18
19 import java.lang.reflect.InvocationTargetException JavaDoc;
20 import java.util.Iterator JavaDoc;
21 import java.util.Locale JavaDoc;
22
23 import javax.servlet.http.HttpServletRequest JavaDoc;
24
25 import org.apache.commons.beanutils.BeanUtilsBean;
26 import org.apache.commons.beanutils.ConvertUtilsBean;
27 import org.apache.commons.beanutils.PropertyUtilsBean;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.struts.Globals;
31 import org.apache.struts.action.ActionForm;
32 import org.apache.struts.action.ActionMessage;
33 import org.apache.struts.action.ActionMessages;
34 import org.apache.struts.util.MessageResources;
35
36 import org.springframework.context.MessageSourceResolvable;
37 import org.springframework.validation.Errors;
38 import org.springframework.validation.FieldError;
39 import org.springframework.validation.ObjectError;
40
41 /**
42  * A thin Struts ActionForm adapter that delegates to Spring's more complete
43  * and advanced data binder and Errors object underneath the covers to bind
44  * to POJOs and manage rejected values.
45  *
46  * <p>Exposes Spring-managed errors to the standard Struts view tags, through
47  * exposing a corresponding Struts ActionMessages object as request attribute.
48  * Also exposes current field values in a Struts-compliant fashion, including
49  * rejected values (which Spring's binding keeps even for non-String fields).
50  *
51  * <p>Consequently, Struts views can be written in a completely traditional
52  * fashion (with standard <code>html:form</code>, <code>html:errors</code>, etc),
53  * seamlessly accessing a Spring-bound POJO form object underneath.
54  *
55  * <p>Note this ActionForm is designed explicitly for use in <i>request scope</i>.
56  * It expects to receive an <code>expose</code> call from the Action, passing
57  * in the Errors object to expose plus the current HttpServletRequest.
58  *
59  * <p>Example definition in <code>struts-config.xml</code>:
60  *
61  * <pre>
62  * &lt;form-beans&gt;
63  * &lt;form-bean name="actionForm" type="org.springframework.web.struts.SpringBindingActionForm"/&gt;
64  * &lt;/form-beans&gt;</pre>
65  *
66  * Example code in a custom Struts <code>Action</code>:
67  *
68  * <pre>
69  * public ActionForward execute(ActionMapping actionMapping, ActionForm actionForm, HttpServletRequest request, HttpServletResponse response) throws Exception {
70  * SpringBindingActionForm form = (SpringBindingActionForm) actionForm;
71  * MyPojoBean bean = ...;
72  * ServletRequestDataBinder binder = new ServletRequestDataBinder(bean, "myPojo");
73  * binder.bind(request);
74  * form.expose(binder.getBindingResult(), request);
75  * return actionMapping.findForward("success");
76  * }</pre>
77  *
78  * This class is compatible with both Struts 1.2.x and Struts 1.1.
79  * On Struts 1.2, default messages registered with Spring binding errors
80  * are exposed when none of the error codes could be resolved.
81  * On Struts 1.1, this is not possible due to a limitation in the Struts
82  * message facility; hence, we expose the plain default error code there.
83  *
84  * @author Keith Donald
85  * @author Juergen Hoeller
86  * @since 1.2.2
87  * @see #expose(org.springframework.validation.Errors, javax.servlet.http.HttpServletRequest)
88  */

89 public class SpringBindingActionForm extends ActionForm {
90
91     private static final Log logger = LogFactory.getLog(SpringBindingActionForm.class);
92
93     private static boolean defaultActionMessageAvailable = true;
94
95
96     static {
97         // Register special PropertyUtilsBean subclass that knows how to
98
// extract field values from a SpringBindingActionForm.
99
// As a consequence of the static nature of Commons BeanUtils,
100
// we have to resort to this initialization hack here.
101
ConvertUtilsBean convUtils = new ConvertUtilsBean();
102         PropertyUtilsBean propUtils = new SpringBindingAwarePropertyUtilsBean();
103         BeanUtilsBean beanUtils = new BeanUtilsBean(convUtils, propUtils);
104         BeanUtilsBean.setInstance(beanUtils);
105
106         // Determine whether the Struts 1.2 support for default messages
107
// is available on ActionMessage: ActionMessage(String, boolean)
108
// with "false" to be passed into the boolean flag.
109
try {
110             ActionMessage.class.getConstructor(new Class JavaDoc[] {String JavaDoc.class, boolean.class});
111         }
112         catch (NoSuchMethodException JavaDoc ex) {
113             defaultActionMessageAvailable = false;
114         }
115     }
116
117
118     private Errors errors;
119
120     private Locale JavaDoc locale;
121
122     private MessageResources messageResources;
123
124
125     /**
126      * Set the Errors object that this SpringBindingActionForm is supposed
127      * to wrap. The contained field values and errors will be exposed
128      * to the view, accessible through Struts standard tags.
129      * @param errors the Spring Errors object to wrap, usually taken from
130      * a DataBinder that has been used for populating a POJO form object
131      * @param request the HttpServletRequest to retrieve the attributes from
132      * @see org.springframework.validation.DataBinder#getBindingResult()
133      */

134     public void expose(Errors errors, HttpServletRequest JavaDoc request) {
135         this.errors = errors;
136
137         // Obtain the locale from Struts well-known location.
138
this.locale = (Locale JavaDoc) request.getSession().getAttribute(Globals.LOCALE_KEY);
139
140         // Obtain the MessageResources from Struts' well-known location.
141
this.messageResources = (MessageResources) request.getAttribute(Globals.MESSAGES_KEY);
142
143         if (errors != null && errors.hasErrors()) {
144             // Add global ActionError instances from the Spring Errors object.
145
ActionMessages actionMessages = (ActionMessages) request.getAttribute(Globals.ERROR_KEY);
146             if (actionMessages == null) {
147                 request.setAttribute(Globals.ERROR_KEY, getActionMessages());
148             }
149             else {
150                 actionMessages.add(getActionMessages());
151             }
152         }
153     }
154
155
156     /**
157      * Return an ActionMessages representation of this SpringBindingActionForm,
158      * exposing all errors contained in the underlying Spring Errors object.
159      * @see org.springframework.validation.Errors#getAllErrors()
160      */

161     private ActionMessages getActionMessages() {
162         ActionMessages actionMessages = new ActionMessages();
163         Iterator JavaDoc it = this.errors.getAllErrors().iterator();
164         while (it.hasNext()) {
165             ObjectError objectError = (ObjectError) it.next();
166             String JavaDoc effectiveMessageKey = findEffectiveMessageKey(objectError);
167             if (effectiveMessageKey == null && !defaultActionMessageAvailable) {
168                 // Need to specify default code despite it not being resolvable:
169
// Struts 1.1 ActionMessage doesn't support default messages.
170
effectiveMessageKey = objectError.getCode();
171             }
172             ActionMessage message = (effectiveMessageKey != null) ?
173                     new ActionMessage(effectiveMessageKey, resolveArguments(objectError.getArguments())) :
174                     new ActionMessage(objectError.getDefaultMessage(), false);
175             if (objectError instanceof FieldError) {
176                 FieldError fieldError = (FieldError) objectError;
177                 actionMessages.add(fieldError.getField(), message);
178             }
179             else {
180                 actionMessages.add(ActionMessages.GLOBAL_MESSAGE, message);
181             }
182         }
183         if (logger.isDebugEnabled()) {
184             logger.debug("Final ActionMessages used for binding: " + actionMessages);
185         }
186         return actionMessages;
187     }
188
189     private Object JavaDoc[] resolveArguments(Object JavaDoc[] arguments) {
190         if (arguments == null || arguments.length == 0) {
191             return arguments;
192         }
193         for (int i = 0; i < arguments.length; i++) {
194             Object JavaDoc arg = arguments[i];
195             if (arg instanceof MessageSourceResolvable) {
196                 MessageSourceResolvable resolvable = (MessageSourceResolvable)arg;
197                 String JavaDoc[] codes = resolvable.getCodes();
198                 boolean resolved = false;
199                 if (this.messageResources != null) {
200                     for (int j = 0; j < codes.length; j++) {
201                         String JavaDoc code = codes[j];
202                         if (this.messageResources.isPresent(this.locale, code)) {
203                             arguments[i] = this.messageResources.getMessage(
204                                     this.locale, code, resolveArguments(resolvable.getArguments()));
205                             resolved = true;
206                             break;
207                         }
208                     }
209                 }
210                 if (!resolved) {
211                     arguments[i] = resolvable.getDefaultMessage();
212                 }
213             }
214         }
215         return arguments;
216     }
217
218     /**
219      * Find the most specific message key for the given error.
220      * @param error the ObjectError to find a message key for
221      * @return the most specific message key found
222      */

223     private String JavaDoc findEffectiveMessageKey(ObjectError error) {
224         if (this.messageResources != null) {
225             String JavaDoc[] possibleMatches = error.getCodes();
226             for (int i = 0; i < possibleMatches.length; i++) {
227                 if (logger.isDebugEnabled()) {
228                     logger.debug("Looking for error code '" + possibleMatches[i] + "'");
229                 }
230                 if (this.messageResources.isPresent(this.locale, possibleMatches[i])) {
231                     if (logger.isDebugEnabled()) {
232                         logger.debug("Found error code '" + possibleMatches[i] + "' in resource bundle");
233                     }
234                     return possibleMatches[i];
235                 }
236             }
237         }
238         if (logger.isDebugEnabled()) {
239             logger.debug("Could not find a suitable message error code, returning default message");
240         }
241         return null;
242     }
243
244
245     /**
246      * Get the formatted value for the property at the provided path.
247      * The formatted value is a string value for display, converted
248      * via a registered property editor.
249      * @param propertyPath the property path
250      * @return the formatted property value
251      * @throws NoSuchMethodException if called during Struts binding
252      * (without Spring Errors object being exposed), to indicate no
253      * available property to Struts
254      */

255     private Object JavaDoc getFieldValue(String JavaDoc propertyPath) throws NoSuchMethodException JavaDoc {
256         if (this.errors == null) {
257             throw new NoSuchMethodException JavaDoc(
258                     "No bean properties exposed to Struts binding - performing Spring binding later on");
259         }
260         return this.errors.getFieldValue(propertyPath);
261     }
262
263
264     /**
265      * Special subclass of PropertyUtilsBean that it is aware of SpringBindingActionForm
266      * and uses it for retrieving field values. The field values will be taken from
267      * the underlying POJO form object that the Spring Errors object was created for.
268      */

269     private static class SpringBindingAwarePropertyUtilsBean extends PropertyUtilsBean {
270
271         public Object JavaDoc getNestedProperty(Object JavaDoc bean, String JavaDoc propertyPath)
272                 throws IllegalAccessException JavaDoc, InvocationTargetException JavaDoc, NoSuchMethodException JavaDoc {
273
274             // Extract Spring-managed field value in case of SpringBindingActionForm.
275
if (bean instanceof SpringBindingActionForm) {
276                 SpringBindingActionForm form = (SpringBindingActionForm) bean;
277                 return form.getFieldValue(propertyPath);
278             }
279
280             // Else fall back to default PropertyUtils behavior.
281
return super.getNestedProperty(bean, propertyPath);
282         }
283     }
284
285 }
286
Popular Tags