001/* 002 * Copyright 2002-2012 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.beans.factory.config; 018 019import org.apache.commons.logging.Log; 020import org.apache.commons.logging.LogFactory; 021 022import org.springframework.beans.BeanWrapper; 023import org.springframework.beans.BeansException; 024import org.springframework.beans.PropertyAccessorFactory; 025import org.springframework.beans.factory.BeanFactory; 026import org.springframework.beans.factory.BeanFactoryAware; 027import org.springframework.beans.factory.BeanFactoryUtils; 028import org.springframework.beans.factory.BeanNameAware; 029import org.springframework.beans.factory.FactoryBean; 030import org.springframework.util.StringUtils; 031 032/** 033 * {@link FactoryBean} that evaluates a property path on a given target object. 034 * 035 * <p>The target object can be specified directly or via a bean name. 036 * 037 * <p>Usage examples: 038 * 039 * <pre class="code"><!-- target bean to be referenced by name --> 040 * <bean id="tb" class="org.springframework.beans.TestBean" singleton="false"> 041 * <property name="age" value="10"/> 042 * <property name="spouse"> 043 * <bean class="org.springframework.beans.TestBean"> 044 * <property name="age" value="11"/> 045 * </bean> 046 * </property> 047 * </bean> 048 * 049 * <!-- will result in 12, which is the value of property 'age' of the inner bean --> 050 * <bean id="propertyPath1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> 051 * <property name="targetObject"> 052 * <bean class="org.springframework.beans.TestBean"> 053 * <property name="age" value="12"/> 054 * </bean> 055 * </property> 056 * <property name="propertyPath" value="age"/> 057 * </bean> 058 * 059 * <!-- will result in 11, which is the value of property 'spouse.age' of bean 'tb' --> 060 * <bean id="propertyPath2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"> 061 * <property name="targetBeanName" value="tb"/> 062 * <property name="propertyPath" value="spouse.age"/> 063 * </bean> 064 * 065 * <!-- will result in 10, which is the value of property 'age' of bean 'tb' --> 066 * <bean id="tb.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/></pre> 067 * 068 * <p>If you are using Spring 2.0 and XML Schema support in your configuration file(s), 069 * you can also use the following style of configuration for property path access. 070 * (See also the appendix entitled 'XML Schema-based configuration' in the Spring 071 * reference manual for more examples.) 072 * 073 * <pre class="code"> <!-- will result in 10, which is the value of property 'age' of bean 'tb' --> 074 * <util:property-path id="name" path="testBean.age"/></pre> 075 * 076 * Thanks to Matthias Ernst for the suggestion and initial prototype! 077 * 078 * @author Juergen Hoeller 079 * @since 1.1.2 080 * @see #setTargetObject 081 * @see #setTargetBeanName 082 * @see #setPropertyPath 083 */ 084public class PropertyPathFactoryBean implements FactoryBean<Object>, BeanNameAware, BeanFactoryAware { 085 086 private static final Log logger = LogFactory.getLog(PropertyPathFactoryBean.class); 087 088 private BeanWrapper targetBeanWrapper; 089 090 private String targetBeanName; 091 092 private String propertyPath; 093 094 private Class<?> resultType; 095 096 private String beanName; 097 098 private BeanFactory beanFactory; 099 100 101 /** 102 * Specify a target object to apply the property path to. 103 * Alternatively, specify a target bean name. 104 * @param targetObject a target object, for example a bean reference 105 * or an inner bean 106 * @see #setTargetBeanName 107 */ 108 public void setTargetObject(Object targetObject) { 109 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject); 110 } 111 112 /** 113 * Specify the name of a target bean to apply the property path to. 114 * Alternatively, specify a target object directly. 115 * @param targetBeanName the bean name to be looked up in the 116 * containing bean factory (e.g. "testBean") 117 * @see #setTargetObject 118 */ 119 public void setTargetBeanName(String targetBeanName) { 120 this.targetBeanName = StringUtils.trimAllWhitespace(targetBeanName); 121 } 122 123 /** 124 * Specify the property path to apply to the target. 125 * @param propertyPath the property path, potentially nested 126 * (e.g. "age" or "spouse.age") 127 */ 128 public void setPropertyPath(String propertyPath) { 129 this.propertyPath = StringUtils.trimAllWhitespace(propertyPath); 130 } 131 132 /** 133 * Specify the type of the result from evaluating the property path. 134 * <p>Note: This is not necessary for directly specified target objects 135 * or singleton target beans, where the type can be determined through 136 * introspection. Just specify this in case of a prototype target, 137 * provided that you need matching by type (for example, for autowiring). 138 * @param resultType the result type, for example "java.lang.Integer" 139 */ 140 public void setResultType(Class<?> resultType) { 141 this.resultType = resultType; 142 } 143 144 /** 145 * The bean name of this PropertyPathFactoryBean will be interpreted 146 * as "beanName.property" pattern, if neither "targetObject" nor 147 * "targetBeanName" nor "propertyPath" have been specified. 148 * This allows for concise bean definitions with just an id/name. 149 */ 150 @Override 151 public void setBeanName(String beanName) { 152 this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName)); 153 } 154 155 156 @Override 157 public void setBeanFactory(BeanFactory beanFactory) { 158 this.beanFactory = beanFactory; 159 160 if (this.targetBeanWrapper != null && this.targetBeanName != null) { 161 throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both"); 162 } 163 164 if (this.targetBeanWrapper == null && this.targetBeanName == null) { 165 if (this.propertyPath != null) { 166 throw new IllegalArgumentException( 167 "Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'"); 168 } 169 170 // No other properties specified: check bean name. 171 int dotIndex = this.beanName.indexOf('.'); 172 if (dotIndex == -1) { 173 throw new IllegalArgumentException( 174 "Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean " + 175 "bean name '" + this.beanName + "' does not follow 'beanName.property' syntax"); 176 } 177 this.targetBeanName = this.beanName.substring(0, dotIndex); 178 this.propertyPath = this.beanName.substring(dotIndex + 1); 179 } 180 181 else if (this.propertyPath == null) { 182 // either targetObject or targetBeanName specified 183 throw new IllegalArgumentException("'propertyPath' is required"); 184 } 185 186 if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) { 187 // Eagerly fetch singleton target bean, and determine result type. 188 Object bean = this.beanFactory.getBean(this.targetBeanName); 189 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean); 190 this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath); 191 } 192 } 193 194 195 @Override 196 public Object getObject() throws BeansException { 197 BeanWrapper target = this.targetBeanWrapper; 198 if (target != null) { 199 if (logger.isWarnEnabled() && this.targetBeanName != null && 200 this.beanFactory instanceof ConfigurableBeanFactory && 201 ((ConfigurableBeanFactory) this.beanFactory).isCurrentlyInCreation(this.targetBeanName)) { 202 logger.warn("Target bean '" + this.targetBeanName + "' is still in creation due to a circular " + 203 "reference - obtained value for property '" + this.propertyPath + "' may be outdated!"); 204 } 205 } 206 else { 207 // Fetch prototype target bean... 208 Object bean = this.beanFactory.getBean(this.targetBeanName); 209 target = PropertyAccessorFactory.forBeanPropertyAccess(bean); 210 } 211 return target.getPropertyValue(this.propertyPath); 212 } 213 214 @Override 215 public Class<?> getObjectType() { 216 return this.resultType; 217 } 218 219 /** 220 * While this FactoryBean will often be used for singleton targets, 221 * the invoked getters for the property path might return a new object 222 * for each call, so we have to assume that we're not returning the 223 * same object for each {@link #getObject()} call. 224 */ 225 @Override 226 public boolean isSingleton() { 227 return false; 228 } 229 230}