001/*
002 * Copyright 2002-2012 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 *      https://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
017package org.springframework.validation;
018
019import java.beans.PropertyEditor;
020import java.io.Serializable;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.LinkedHashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.springframework.beans.PropertyEditorRegistry;
030import org.springframework.util.Assert;
031import org.springframework.util.ObjectUtils;
032import 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@SuppressWarnings("serial")
045public abstract class AbstractBindingResult extends AbstractErrors implements BindingResult, Serializable {
046
047        private final String objectName;
048
049        private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
050
051        private final List<ObjectError> errors = new LinkedList<ObjectError>();
052
053        private final Set<String> suppressedFields = new HashSet<String>();
054
055
056        /**
057         * Create a new AbstractBindingResult instance.
058         * @param objectName the name of the target object
059         * @see DefaultMessageCodesResolver
060         */
061        protected AbstractBindingResult(String objectName) {
062                this.objectName = objectName;
063        }
064
065        /**
066         * Set the strategy to use for resolving errors into message codes.
067         * Default is DefaultMessageCodesResolver.
068         * @see DefaultMessageCodesResolver
069         */
070        public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
071                Assert.notNull(messageCodesResolver, "MessageCodesResolver must not be null");
072                this.messageCodesResolver = messageCodesResolver;
073        }
074
075        /**
076         * Return the strategy to use for resolving errors into message codes.
077         */
078        public MessageCodesResolver getMessageCodesResolver() {
079                return this.messageCodesResolver;
080        }
081
082
083        //---------------------------------------------------------------------
084        // Implementation of the Errors interface
085        //---------------------------------------------------------------------
086
087        @Override
088        public String getObjectName() {
089                return this.objectName;
090        }
091
092
093        @Override
094        public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
095                addError(new ObjectError(getObjectName(), resolveMessageCodes(errorCode), errorArgs, defaultMessage));
096        }
097
098        @Override
099        public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
100                if ("".equals(getNestedPath()) && !StringUtils.hasLength(field)) {
101                        // We're at the top of the nested object hierarchy,
102                        // so the present level is not a field but rather the top object.
103                        // The best we can do is register a global error here...
104                        reject(errorCode, errorArgs, defaultMessage);
105                        return;
106                }
107                String fixedField = fixedField(field);
108                Object newVal = getActualFieldValue(fixedField);
109                FieldError fe = new FieldError(
110                                getObjectName(), fixedField, newVal, false,
111                                resolveMessageCodes(errorCode, field), errorArgs, defaultMessage);
112                addError(fe);
113        }
114
115        @Override
116        public void addError(ObjectError error) {
117                this.errors.add(error);
118        }
119
120        @Override
121        public void addAllErrors(Errors errors) {
122                if (!errors.getObjectName().equals(getObjectName())) {
123                        throw new IllegalArgumentException("Errors object needs to have same object name");
124                }
125                this.errors.addAll(errors.getAllErrors());
126        }
127
128        @Override
129        public String[] resolveMessageCodes(String errorCode) {
130                return getMessageCodesResolver().resolveMessageCodes(errorCode, getObjectName());
131        }
132
133        @Override
134        public String[] resolveMessageCodes(String errorCode, String field) {
135                Class<?> fieldType = getFieldType(field);
136                return getMessageCodesResolver().resolveMessageCodes(
137                                errorCode, getObjectName(), fixedField(field), fieldType);
138        }
139
140
141        @Override
142        public boolean hasErrors() {
143                return !this.errors.isEmpty();
144        }
145
146        @Override
147        public int getErrorCount() {
148                return this.errors.size();
149        }
150
151        @Override
152        public List<ObjectError> getAllErrors() {
153                return Collections.unmodifiableList(this.errors);
154        }
155
156        @Override
157        public List<ObjectError> getGlobalErrors() {
158                List<ObjectError> result = new LinkedList<ObjectError>();
159                for (ObjectError objectError : this.errors) {
160                        if (!(objectError instanceof FieldError)) {
161                                result.add(objectError);
162                        }
163                }
164                return Collections.unmodifiableList(result);
165        }
166
167        @Override
168        public ObjectError getGlobalError() {
169                for (ObjectError objectError : this.errors) {
170                        if (!(objectError instanceof FieldError)) {
171                                return objectError;
172                        }
173                }
174                return null;
175        }
176
177        @Override
178        public List<FieldError> getFieldErrors() {
179                List<FieldError> result = new LinkedList<FieldError>();
180                for (ObjectError objectError : this.errors) {
181                        if (objectError instanceof FieldError) {
182                                result.add((FieldError) objectError);
183                        }
184                }
185                return Collections.unmodifiableList(result);
186        }
187
188        @Override
189        public FieldError getFieldError() {
190                for (ObjectError objectError : this.errors) {
191                        if (objectError instanceof FieldError) {
192                                return (FieldError) objectError;
193                        }
194                }
195                return null;
196        }
197
198        @Override
199        public List<FieldError> getFieldErrors(String field) {
200                List<FieldError> result = new LinkedList<FieldError>();
201                String fixedField = fixedField(field);
202                for (ObjectError objectError : this.errors) {
203                        if (objectError instanceof FieldError && isMatchingFieldError(fixedField, (FieldError) objectError)) {
204                                result.add((FieldError) objectError);
205                        }
206                }
207                return Collections.unmodifiableList(result);
208        }
209
210        @Override
211        public FieldError getFieldError(String field) {
212                String fixedField = fixedField(field);
213                for (ObjectError objectError : this.errors) {
214                        if (objectError instanceof FieldError) {
215                                FieldError fieldError = (FieldError) objectError;
216                                if (isMatchingFieldError(fixedField, fieldError)) {
217                                        return fieldError;
218                                }
219                        }
220                }
221                return null;
222        }
223
224        @Override
225        public Object getFieldValue(String field) {
226                FieldError fieldError = getFieldError(field);
227                // Use rejected value in case of error, current bean property value else.
228                Object value = (fieldError != null ? fieldError.getRejectedValue() :
229                                getActualFieldValue(fixedField(field)));
230                // Apply formatting, but not on binding failures like type mismatches.
231                if (fieldError == null || !fieldError.isBindingFailure()) {
232                        value = formatFieldValue(field, value);
233                }
234                return value;
235        }
236
237        /**
238         * This default implementation determines the type based on the actual
239         * field value, if any. Subclasses should override this to determine
240         * the type from a descriptor, even for {@code null} values.
241         * @see #getActualFieldValue
242         */
243        @Override
244        public Class<?> getFieldType(String field) {
245                Object value = getActualFieldValue(fixedField(field));
246                if (value != null) {
247                        return value.getClass();
248                }
249                return null;
250        }
251
252
253        //---------------------------------------------------------------------
254        // Implementation of BindingResult interface
255        //---------------------------------------------------------------------
256
257        /**
258         * Return a model Map for the obtained state, exposing an Errors
259         * instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
260         * and the object itself.
261         * <p>Note that the Map is constructed every time you're calling this method.
262         * Adding things to the map and then re-calling this method will not work.
263         * <p>The attributes in the model Map returned by this method are usually
264         * included in the ModelAndView for a form view that uses Spring's bind tag,
265         * which needs access to the Errors instance.
266         * @see #getObjectName
267         * @see #MODEL_KEY_PREFIX
268         * @see org.springframework.web.servlet.ModelAndView
269         * @see org.springframework.web.servlet.tags.BindTag
270         */
271        @Override
272        public Map<String, Object> getModel() {
273                Map<String, Object> model = new LinkedHashMap<String, Object>(2);
274                // Mapping from name to target object.
275                model.put(getObjectName(), getTarget());
276                // Errors instance, even if no errors.
277                model.put(MODEL_KEY_PREFIX + getObjectName(), this);
278                return model;
279        }
280
281        @Override
282        public Object getRawFieldValue(String field) {
283                return getActualFieldValue(fixedField(field));
284        }
285
286        /**
287         * This implementation delegates to the
288         * {@link #getPropertyEditorRegistry() PropertyEditorRegistry}'s
289         * editor lookup facility, if available.
290         */
291        @Override
292        public PropertyEditor findEditor(String field, Class<?> valueType) {
293                PropertyEditorRegistry editorRegistry = getPropertyEditorRegistry();
294                if (editorRegistry != null) {
295                        Class<?> valueTypeToUse = valueType;
296                        if (valueTypeToUse == null) {
297                                valueTypeToUse = getFieldType(field);
298                        }
299                        return editorRegistry.findCustomEditor(valueTypeToUse, fixedField(field));
300                }
301                else {
302                        return null;
303                }
304        }
305
306        /**
307         * This implementation returns {@code null}.
308         */
309        @Override
310        public PropertyEditorRegistry getPropertyEditorRegistry() {
311                return null;
312        }
313
314        /**
315         * Mark the specified disallowed field as suppressed.
316         * <p>The data binder invokes this for each field value that was
317         * detected to target a disallowed field.
318         * @see DataBinder#setAllowedFields
319         */
320        @Override
321        public void recordSuppressedField(String field) {
322                this.suppressedFields.add(field);
323        }
324
325        /**
326         * Return the list of fields that were suppressed during the bind process.
327         * <p>Can be used to determine whether any field values were targetting
328         * disallowed fields.
329         * @see DataBinder#setAllowedFields
330         */
331        @Override
332        public String[] getSuppressedFields() {
333                return StringUtils.toStringArray(this.suppressedFields);
334        }
335
336
337        @Override
338        public boolean equals(Object other) {
339                if (this == other) {
340                        return true;
341                }
342                if (!(other instanceof BindingResult)) {
343                        return false;
344                }
345                BindingResult otherResult = (BindingResult) other;
346                return (getObjectName().equals(otherResult.getObjectName()) &&
347                                ObjectUtils.nullSafeEquals(getTarget(), otherResult.getTarget()) &&
348                                getAllErrors().equals(otherResult.getAllErrors()));
349        }
350
351        @Override
352        public int hashCode() {
353                return getObjectName().hashCode();
354        }
355
356
357        //---------------------------------------------------------------------
358        // Template methods to be implemented/overridden by subclasses
359        //---------------------------------------------------------------------
360
361        /**
362         * Return the wrapped target object.
363         */
364        @Override
365        public abstract Object getTarget();
366
367        /**
368         * Extract the actual field value for the given field.
369         * @param field the field to check
370         * @return the current value of the field
371         */
372        protected abstract Object getActualFieldValue(String field);
373
374        /**
375         * Format the given value for the specified field.
376         * <p>The default implementation simply returns the field value as-is.
377         * @param field the field to check
378         * @param value the value of the field (either a rejected value
379         * other than from a binding error, or an actual field value)
380         * @return the formatted value
381         */
382        protected Object formatFieldValue(String field, Object value) {
383                return value;
384        }
385
386}