001/* 002 * Copyright 2002-2018 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; 020 021import org.springframework.beans.BeanUtils; 022import org.springframework.beans.ConfigurablePropertyAccessor; 023import org.springframework.beans.PropertyAccessorUtils; 024import org.springframework.beans.PropertyEditorRegistry; 025import org.springframework.core.convert.ConversionService; 026import org.springframework.core.convert.TypeDescriptor; 027import org.springframework.core.convert.support.ConvertingPropertyEditorAdapter; 028import org.springframework.lang.Nullable; 029import org.springframework.util.Assert; 030 031/** 032 * Abstract base class for {@link BindingResult} implementations that work with 033 * Spring's {@link org.springframework.beans.PropertyAccessor} mechanism. 034 * Pre-implements field access through delegation to the corresponding 035 * PropertyAccessor methods. 036 * 037 * @author Juergen Hoeller 038 * @since 2.0 039 * @see #getPropertyAccessor() 040 * @see org.springframework.beans.PropertyAccessor 041 * @see org.springframework.beans.ConfigurablePropertyAccessor 042 */ 043@SuppressWarnings("serial") 044public abstract class AbstractPropertyBindingResult extends AbstractBindingResult { 045 046 @Nullable 047 private transient ConversionService conversionService; 048 049 050 /** 051 * Create a new AbstractPropertyBindingResult instance. 052 * @param objectName the name of the target object 053 * @see DefaultMessageCodesResolver 054 */ 055 protected AbstractPropertyBindingResult(String objectName) { 056 super(objectName); 057 } 058 059 060 public void initConversion(ConversionService conversionService) { 061 Assert.notNull(conversionService, "ConversionService must not be null"); 062 this.conversionService = conversionService; 063 if (getTarget() != null) { 064 getPropertyAccessor().setConversionService(conversionService); 065 } 066 } 067 068 /** 069 * Returns the underlying PropertyAccessor. 070 * @see #getPropertyAccessor() 071 */ 072 @Override 073 public PropertyEditorRegistry getPropertyEditorRegistry() { 074 return (getTarget() != null ? getPropertyAccessor() : null); 075 } 076 077 /** 078 * Returns the canonical property name. 079 * @see org.springframework.beans.PropertyAccessorUtils#canonicalPropertyName 080 */ 081 @Override 082 protected String canonicalFieldName(String field) { 083 return PropertyAccessorUtils.canonicalPropertyName(field); 084 } 085 086 /** 087 * Determines the field type from the property type. 088 * @see #getPropertyAccessor() 089 */ 090 @Override 091 @Nullable 092 public Class<?> getFieldType(@Nullable String field) { 093 return (getTarget() != null ? getPropertyAccessor().getPropertyType(fixedField(field)) : 094 super.getFieldType(field)); 095 } 096 097 /** 098 * Fetches the field value from the PropertyAccessor. 099 * @see #getPropertyAccessor() 100 */ 101 @Override 102 @Nullable 103 protected Object getActualFieldValue(String field) { 104 return getPropertyAccessor().getPropertyValue(field); 105 } 106 107 /** 108 * Formats the field value based on registered PropertyEditors. 109 * @see #getCustomEditor 110 */ 111 @Override 112 protected Object formatFieldValue(String field, @Nullable Object value) { 113 String fixedField = fixedField(field); 114 // Try custom editor... 115 PropertyEditor customEditor = getCustomEditor(fixedField); 116 if (customEditor != null) { 117 customEditor.setValue(value); 118 String textValue = customEditor.getAsText(); 119 // If the PropertyEditor returned null, there is no appropriate 120 // text representation for this value: only use it if non-null. 121 if (textValue != null) { 122 return textValue; 123 } 124 } 125 if (this.conversionService != null) { 126 // Try custom converter... 127 TypeDescriptor fieldDesc = getPropertyAccessor().getPropertyTypeDescriptor(fixedField); 128 TypeDescriptor strDesc = TypeDescriptor.valueOf(String.class); 129 if (fieldDesc != null && this.conversionService.canConvert(fieldDesc, strDesc)) { 130 return this.conversionService.convert(value, fieldDesc, strDesc); 131 } 132 } 133 return value; 134 } 135 136 /** 137 * Retrieve the custom PropertyEditor for the given field, if any. 138 * @param fixedField the fully qualified field name 139 * @return the custom PropertyEditor, or {@code null} 140 */ 141 @Nullable 142 protected PropertyEditor getCustomEditor(String fixedField) { 143 Class<?> targetType = getPropertyAccessor().getPropertyType(fixedField); 144 PropertyEditor editor = getPropertyAccessor().findCustomEditor(targetType, fixedField); 145 if (editor == null) { 146 editor = BeanUtils.findEditorByConvention(targetType); 147 } 148 return editor; 149 } 150 151 /** 152 * This implementation exposes a PropertyEditor adapter for a Formatter, 153 * if applicable. 154 */ 155 @Override 156 @Nullable 157 public PropertyEditor findEditor(@Nullable String field, @Nullable Class<?> valueType) { 158 Class<?> valueTypeForLookup = valueType; 159 if (valueTypeForLookup == null) { 160 valueTypeForLookup = getFieldType(field); 161 } 162 PropertyEditor editor = super.findEditor(field, valueTypeForLookup); 163 if (editor == null && this.conversionService != null) { 164 TypeDescriptor td = null; 165 if (field != null && getTarget() != null) { 166 TypeDescriptor ptd = getPropertyAccessor().getPropertyTypeDescriptor(fixedField(field)); 167 if (ptd != null && (valueType == null || valueType.isAssignableFrom(ptd.getType()))) { 168 td = ptd; 169 } 170 } 171 if (td == null) { 172 td = TypeDescriptor.valueOf(valueTypeForLookup); 173 } 174 if (this.conversionService.canConvert(TypeDescriptor.valueOf(String.class), td)) { 175 editor = new ConvertingPropertyEditorAdapter(this.conversionService, td); 176 } 177 } 178 return editor; 179 } 180 181 182 /** 183 * Provide the PropertyAccessor to work with, according to the 184 * concrete strategy of access. 185 * <p>Note that a PropertyAccessor used by a BindingResult should 186 * always have its "extractOldValueForEditor" flag set to "true" 187 * by default, since this is typically possible without side effects 188 * for model objects that serve as data binding target. 189 * @see ConfigurablePropertyAccessor#setExtractOldValueForEditor 190 */ 191 public abstract ConfigurablePropertyAccessor getPropertyAccessor(); 192 193}