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}