001/*
002 * Copyright 2002-2019 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 org.springframework.context.support.DefaultMessageSourceResolvable;
020import org.springframework.lang.Nullable;
021import org.springframework.util.Assert;
022
023/**
024 * Encapsulates an object error, that is, a global reason for rejecting
025 * an object.
026 *
027 * <p>See the {@link DefaultMessageCodesResolver} javadoc for details on
028 * how a message code list is built for an {@code ObjectError}.
029 *
030 * @author Juergen Hoeller
031 * @since 10.03.2003
032 * @see FieldError
033 * @see DefaultMessageCodesResolver
034 */
035@SuppressWarnings("serial")
036public class ObjectError extends DefaultMessageSourceResolvable {
037
038        private final String objectName;
039
040        @Nullable
041        private transient Object source;
042
043
044        /**
045         * Create a new instance of the ObjectError class.
046         * @param objectName the name of the affected object
047         * @param defaultMessage the default message to be used to resolve this message
048         */
049        public ObjectError(String objectName, String defaultMessage) {
050                this(objectName, null, null, defaultMessage);
051        }
052
053        /**
054         * Create a new instance of the ObjectError class.
055         * @param objectName the name of the affected object
056         * @param codes the codes to be used to resolve this message
057         * @param arguments     the array of arguments to be used to resolve this message
058         * @param defaultMessage the default message to be used to resolve this message
059         */
060        public ObjectError(
061                        String objectName, @Nullable String[] codes, @Nullable Object[] arguments, @Nullable String defaultMessage) {
062
063                super(codes, arguments, defaultMessage);
064                Assert.notNull(objectName, "Object name must not be null");
065                this.objectName = objectName;
066        }
067
068
069        /**
070         * Return the name of the affected object.
071         */
072        public String getObjectName() {
073                return this.objectName;
074        }
075
076        /**
077         * Preserve the source behind this error: possibly an {@link Exception}
078         * (typically {@link org.springframework.beans.PropertyAccessException})
079         * or a Bean Validation {@link javax.validation.ConstraintViolation}.
080         * <p>Note that any such source object is being stored as transient:
081         * that is, it won't be part of a serialized error representation.
082         * @param source the source object
083         * @since 5.0.4
084         */
085        public void wrap(Object source) {
086                if (this.source != null) {
087                        throw new IllegalStateException("Already wrapping " + this.source);
088                }
089                this.source = source;
090        }
091
092        /**
093         * Unwrap the source behind this error: possibly an {@link Exception}
094         * (typically {@link org.springframework.beans.PropertyAccessException})
095         * or a Bean Validation {@link javax.validation.ConstraintViolation}.
096         * <p>The cause of the outermost exception will be introspected as well,
097         * e.g. the underlying conversion exception or exception thrown from a setter
098         * (instead of having to unwrap the {@code PropertyAccessException} in turn).
099         * @return the source object of the given type
100         * @throws IllegalArgumentException if no such source object is available
101         * (i.e. none specified or not available anymore after deserialization)
102         * @since 5.0.4
103         */
104        public <T> T unwrap(Class<T> sourceType) {
105                if (sourceType.isInstance(this.source)) {
106                        return sourceType.cast(this.source);
107                }
108                else if (this.source instanceof Throwable) {
109                        Throwable cause = ((Throwable) this.source).getCause();
110                        if (sourceType.isInstance(cause)) {
111                                return sourceType.cast(cause);
112                        }
113                }
114                throw new IllegalArgumentException("No source object of the given type available: " + sourceType);
115        }
116
117        /**
118         * Check the source behind this error: possibly an {@link Exception}
119         * (typically {@link org.springframework.beans.PropertyAccessException})
120         * or a Bean Validation {@link javax.validation.ConstraintViolation}.
121         * <p>The cause of the outermost exception will be introspected as well,
122         * e.g. the underlying conversion exception or exception thrown from a setter
123         * (instead of having to unwrap the {@code PropertyAccessException} in turn).
124         * @return whether this error has been caused by a source object of the given type
125         * @since 5.0.4
126         */
127        public boolean contains(Class<?> sourceType) {
128                return (sourceType.isInstance(this.source) ||
129                                (this.source instanceof Throwable && sourceType.isInstance(((Throwable) this.source).getCause())));
130        }
131
132
133        @Override
134        public boolean equals(@Nullable Object other) {
135                if (this == other) {
136                        return true;
137                }
138                if (other == null || other.getClass() != getClass() || !super.equals(other)) {
139                        return false;
140                }
141                ObjectError otherError = (ObjectError) other;
142                return getObjectName().equals(otherError.getObjectName());
143        }
144
145        @Override
146        public int hashCode() {
147                return (29 * super.hashCode() + getObjectName().hashCode());
148        }
149
150        @Override
151        public String toString() {
152                return "Error in object '" + this.objectName + "': " + resolvableToString();
153        }
154
155}