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