001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.validation;
018:
019: import java.io.Serializable;
020: import java.util.Collections;
021: import java.util.EmptyStackException;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.Set;
029: import java.util.Stack;
030:
031: import org.springframework.beans.PropertyEditorRegistry;
032: import org.springframework.util.StringUtils;
033:
034: /**
035: * Abstract implementation of the {@link BindingResult} interface and
036: * its super-interface {@link Errors}. Encapsulates common management of
037: * {@link ObjectError ObjectErrors} and {@link FieldError FieldErrors}.
038: *
039: * @author Juergen Hoeller
040: * @author Rob Harrop
041: * @since 2.0
042: * @see Errors
043: */
044: public abstract class AbstractBindingResult implements BindingResult,
045: Serializable {
046:
047: private final List errors = new LinkedList();
048:
049: private final String objectName;
050:
051: private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
052:
053: private String nestedPath = "";
054:
055: private final Stack nestedPathStack = new Stack();
056:
057: private Set suppressedFields = new HashSet();
058:
059: /**
060: * Create a new AbstractBindingResult instance.
061: * @param objectName the name of the target object
062: * @see DefaultMessageCodesResolver
063: */
064: protected AbstractBindingResult(String objectName) {
065: this .objectName = objectName;
066: }
067:
068: /**
069: * Set the strategy to use for resolving errors into message codes.
070: * Default is DefaultMessageCodesResolver.
071: * @see DefaultMessageCodesResolver
072: */
073: public void setMessageCodesResolver(
074: MessageCodesResolver messageCodesResolver) {
075: this .messageCodesResolver = messageCodesResolver;
076: }
077:
078: /**
079: * Return the strategy to use for resolving errors into message codes.
080: */
081: public MessageCodesResolver getMessageCodesResolver() {
082: return this .messageCodesResolver;
083: }
084:
085: //---------------------------------------------------------------------
086: // Implementation of Errors interface
087: //---------------------------------------------------------------------
088:
089: public String getObjectName() {
090: return this .objectName;
091: }
092:
093: public void setNestedPath(String nestedPath) {
094: doSetNestedPath(nestedPath);
095: this .nestedPathStack.clear();
096: }
097:
098: public String getNestedPath() {
099: return this .nestedPath;
100: }
101:
102: public void pushNestedPath(String subPath) {
103: this .nestedPathStack.push(getNestedPath());
104: doSetNestedPath(getNestedPath() + subPath);
105: }
106:
107: public void popNestedPath() throws IllegalArgumentException {
108: try {
109: String formerNestedPath = (String) this .nestedPathStack
110: .pop();
111: doSetNestedPath(formerNestedPath);
112: } catch (EmptyStackException ex) {
113: throw new IllegalStateException(
114: "Cannot pop nested path: no nested path on stack");
115: }
116: }
117:
118: /**
119: * Actually set the nested path.
120: * Delegated to by setNestedPath and pushNestedPath.
121: */
122: protected void doSetNestedPath(String nestedPath) {
123: if (nestedPath == null) {
124: nestedPath = "";
125: }
126: nestedPath = canonicalFieldName(nestedPath);
127: if (nestedPath.length() > 0
128: && !nestedPath.endsWith(Errors.NESTED_PATH_SEPARATOR)) {
129: nestedPath += Errors.NESTED_PATH_SEPARATOR;
130: }
131: this .nestedPath = nestedPath;
132: }
133:
134: /**
135: * Transform the given field into its full path,
136: * regarding the nested path of this instance.
137: */
138: protected String fixedField(String field) {
139: if (StringUtils.hasLength(field)) {
140: return getNestedPath() + canonicalFieldName(field);
141: } else {
142: String path = getNestedPath();
143: return (path.endsWith(Errors.NESTED_PATH_SEPARATOR) ? path
144: .substring(0, path.length()
145: - NESTED_PATH_SEPARATOR.length()) : path);
146: }
147: }
148:
149: public void reject(String errorCode) {
150: reject(errorCode, null, null);
151: }
152:
153: public void reject(String errorCode, String defaultMessage) {
154: reject(errorCode, null, defaultMessage);
155: }
156:
157: public void reject(String errorCode, Object[] errorArgs,
158: String defaultMessage) {
159: addError(new ObjectError(getObjectName(),
160: resolveMessageCodes(errorCode), errorArgs,
161: defaultMessage));
162: }
163:
164: public void rejectValue(String field, String errorCode) {
165: rejectValue(field, errorCode, null, null);
166: }
167:
168: public void rejectValue(String field, String errorCode,
169: String defaultMessage) {
170: rejectValue(field, errorCode, null, defaultMessage);
171: }
172:
173: public void rejectValue(String field, String errorCode,
174: Object[] errorArgs, String defaultMessage) {
175: if ("".equals(getNestedPath()) && !StringUtils.hasLength(field)) {
176: // We're at the top of the nested object hierarchy,
177: // so the present level is not a field but rather the top object.
178: // The best we can do is register a global error here...
179: reject(errorCode, errorArgs, defaultMessage);
180: return;
181: }
182: String fixedField = fixedField(field);
183: Object newVal = getActualFieldValue(fixedField);
184: FieldError fe = new FieldError(getObjectName(), fixedField,
185: newVal, false, resolveMessageCodes(errorCode, field),
186: errorArgs, defaultMessage);
187: addError(fe);
188: }
189:
190: /**
191: * Resolve the given error code into message codes.
192: * Calls the MessageCodesResolver with appropriate parameters.
193: * @param errorCode the error code to resolve into message codes
194: * @return the resolved message codes
195: * @see #setMessageCodesResolver
196: */
197: public String[] resolveMessageCodes(String errorCode) {
198: return getMessageCodesResolver().resolveMessageCodes(errorCode,
199: getObjectName());
200: }
201:
202: public String[] resolveMessageCodes(String errorCode, String field) {
203: String fixedField = fixedField(field);
204: Class fieldType = getFieldType(fixedField);
205: return getMessageCodesResolver().resolveMessageCodes(errorCode,
206: getObjectName(), fixedField, fieldType);
207: }
208:
209: public void addError(ObjectError error) {
210: this .errors.add(error);
211: }
212:
213: public void addAllErrors(Errors errors) {
214: if (!errors.getObjectName().equals(getObjectName())) {
215: throw new IllegalArgumentException(
216: "Errors object needs to have same object name");
217: }
218: this .errors.addAll(errors.getAllErrors());
219: }
220:
221: public boolean hasErrors() {
222: return !this .errors.isEmpty();
223: }
224:
225: public int getErrorCount() {
226: return this .errors.size();
227: }
228:
229: public List getAllErrors() {
230: return Collections.unmodifiableList(this .errors);
231: }
232:
233: public boolean hasGlobalErrors() {
234: return (getGlobalErrorCount() > 0);
235: }
236:
237: public int getGlobalErrorCount() {
238: return getGlobalErrors().size();
239: }
240:
241: public List getGlobalErrors() {
242: List result = new LinkedList();
243: for (Iterator it = this .errors.iterator(); it.hasNext();) {
244: Object error = it.next();
245: if (!(error instanceof FieldError)) {
246: result.add(error);
247: }
248: }
249: return Collections.unmodifiableList(result);
250: }
251:
252: public ObjectError getGlobalError() {
253: for (Iterator it = this .errors.iterator(); it.hasNext();) {
254: ObjectError objectError = (ObjectError) it.next();
255: if (!(objectError instanceof FieldError)) {
256: return objectError;
257: }
258: }
259: return null;
260: }
261:
262: public boolean hasFieldErrors() {
263: return (getFieldErrorCount() > 0);
264: }
265:
266: public int getFieldErrorCount() {
267: return getFieldErrors().size();
268: }
269:
270: public List getFieldErrors() {
271: List result = new LinkedList();
272: for (Iterator it = this .errors.iterator(); it.hasNext();) {
273: Object error = it.next();
274: if (error instanceof FieldError) {
275: result.add(error);
276: }
277: }
278: return Collections.unmodifiableList(result);
279: }
280:
281: public FieldError getFieldError() {
282: for (Iterator it = this .errors.iterator(); it.hasNext();) {
283: Object error = it.next();
284: if (error instanceof FieldError) {
285: return (FieldError) error;
286: }
287: }
288: return null;
289: }
290:
291: public boolean hasFieldErrors(String field) {
292: return (getFieldErrorCount(field) > 0);
293: }
294:
295: public int getFieldErrorCount(String field) {
296: return getFieldErrors(field).size();
297: }
298:
299: public List getFieldErrors(String field) {
300: List result = new LinkedList();
301: String fixedField = fixedField(field);
302: for (Iterator it = this .errors.iterator(); it.hasNext();) {
303: Object error = it.next();
304: if (error instanceof FieldError
305: && isMatchingFieldError(fixedField,
306: ((FieldError) error))) {
307: result.add(error);
308: }
309: }
310: return Collections.unmodifiableList(result);
311: }
312:
313: public FieldError getFieldError(String field) {
314: String fixedField = fixedField(field);
315: for (Iterator it = this .errors.iterator(); it.hasNext();) {
316: Object error = it.next();
317: if (error instanceof FieldError) {
318: FieldError fe = (FieldError) error;
319: if (isMatchingFieldError(fixedField, fe)) {
320: return fe;
321: }
322: }
323: }
324: return null;
325: }
326:
327: /**
328: * Check whether the given FieldError matches the given field.
329: * @param field the field that we are looking up FieldErrors for
330: * @param fieldError the candidate FieldError
331: * @return whether the FieldError matches the given field
332: */
333: protected boolean isMatchingFieldError(String field,
334: FieldError fieldError) {
335: return (field.equals(fieldError.getField()) || (field
336: .endsWith("*") && fieldError.getField().startsWith(
337: field.substring(0, field.length() - 1))));
338: }
339:
340: public Object getFieldValue(String field) {
341: FieldError fe = getFieldError(field);
342: // Use rejected value in case of error, current bean property value else.
343: Object value = null;
344: if (fe != null) {
345: value = fe.getRejectedValue();
346: } else {
347: value = getActualFieldValue(fixedField(field));
348: }
349: // Apply formatting, but not on binding failures like type mismatches.
350: if (fe == null || !fe.isBindingFailure()) {
351: value = formatFieldValue(field, value);
352: }
353: return value;
354: }
355:
356: /**
357: * This default implementation determines the type based on the actual
358: * field value, if any. Subclasses should override this to determine
359: * the type from a descriptor, even for <code>null</code> values.
360: * @see #getActualFieldValue
361: */
362: public Class getFieldType(String field) {
363: Object value = getActualFieldValue(field);
364: if (value != null) {
365: return value.getClass();
366: }
367: return null;
368: }
369:
370: //---------------------------------------------------------------------
371: // Implementation of BindingResult interface
372: //---------------------------------------------------------------------
373:
374: /**
375: * Return a model Map for the obtained state, exposing an Errors
376: * instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
377: * and the object itself.
378: * <p>Note that the Map is constructed every time you're calling this method.
379: * Adding things to the map and then re-calling this method will not work.
380: * <p>The attributes in the model Map returned by this method are usually
381: * included in the ModelAndView for a form view that uses Spring's bind tag,
382: * which needs access to the Errors instance. Spring's SimpleFormController
383: * will do this for you when rendering its form or success view. When
384: * building the ModelAndView yourself, you need to include the attributes
385: * from the model Map returned by this method yourself.
386: * @see #getObjectName
387: * @see #MODEL_KEY_PREFIX
388: * @see org.springframework.web.servlet.ModelAndView
389: * @see org.springframework.web.servlet.tags.BindTag
390: * @see org.springframework.web.servlet.mvc.SimpleFormController
391: */
392: public Map getModel() {
393: Map model = new HashMap();
394: // Errors instance, even if no errors.
395: model.put(BindingResult.MODEL_KEY_PREFIX + getObjectName(),
396: this );
397: // Mapping from name to target object.
398: model.put(getObjectName(), getTarget());
399: return model;
400: }
401:
402: /**
403: * This implementation throws an UnsupportedOperationException.
404: */
405: public PropertyEditorRegistry getPropertyEditorRegistry() {
406: throw new UnsupportedOperationException("["
407: + getClass().getName()
408: + "] does not support a PropertyEditorRegistry");
409: }
410:
411: /**
412: * Mark the specified disallowed field as suppressed.
413: * <p>The data binder invokes this for each field value that was
414: * detected to target a disallowed field.
415: * @see DataBinder#setAllowedFields
416: */
417: public void recordSuppressedField(String fieldName) {
418: this .suppressedFields.add(fieldName);
419: }
420:
421: /**
422: * Return the list of fields that were suppressed during the bind process.
423: * <p>Can be used to determine whether any field values were targetting
424: * disallowed fields.
425: * @see DataBinder#setAllowedFields
426: */
427: public String[] getSuppressedFields() {
428: return StringUtils.toStringArray(this .suppressedFields);
429: }
430:
431: public String toString() {
432: StringBuffer sb = new StringBuffer(getClass().getName());
433: sb.append(": ").append(getErrorCount()).append(" errors");
434: Iterator it = getAllErrors().iterator();
435: while (it.hasNext()) {
436: sb.append('\n').append(it.next());
437: }
438: return sb.toString();
439: }
440:
441: public boolean equals(Object other) {
442: if (this == other) {
443: return true;
444: }
445: if (!(other instanceof BindingResult)) {
446: return false;
447: }
448: BindingResult otherResult = (BindingResult) other;
449: return (getObjectName().equals(otherResult.getObjectName())
450: && getTarget().equals(otherResult.getTarget()) && getAllErrors()
451: .equals(otherResult.getAllErrors()));
452: }
453:
454: public int hashCode() {
455: return getObjectName().hashCode() * 29 + getTarget().hashCode();
456: }
457:
458: //---------------------------------------------------------------------
459: // Template methods to be implemented/overridden by subclasses
460: //---------------------------------------------------------------------
461:
462: /**
463: * Return the wrapped target object.
464: */
465: public abstract Object getTarget();
466:
467: /**
468: * Determine the canonical field name for the given field.
469: * <p>The default implementation simply returns the field name as-is.
470: * @param field the original field name
471: * @return the canonical field name
472: */
473: protected String canonicalFieldName(String field) {
474: return field;
475: }
476:
477: /**
478: * Extract the actual field value for the given field.
479: * @param field the field to check
480: * @return the current value of the field
481: */
482: protected abstract Object getActualFieldValue(String field);
483:
484: /**
485: * Format the given value for the specified field.
486: * <p>The default implementation simply returns the field value as-is.
487: * @param field the field to check
488: * @param value the value of the field (either a rejected value
489: * other than from a binding error, or an actual field value)
490: * @return the formatted value
491: */
492: protected Object formatFieldValue(String field, Object value) {
493: return value;
494: }
495:
496: }
|