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