1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.beans.factory.config;
18
19 import org.springframework.beans.BeanWrapper;
20 import org.springframework.beans.BeansException;
21 import org.springframework.beans.PropertyAccessorFactory;
22 import org.springframework.beans.factory.BeanFactory;
23 import org.springframework.beans.factory.BeanFactoryAware;
24 import org.springframework.beans.factory.BeanFactoryUtils;
25 import org.springframework.beans.factory.BeanNameAware;
26 import org.springframework.beans.factory.FactoryBean;
27 import org.springframework.util.StringUtils;
28
29 /**
30 * {@link FactoryBean} that evaluates a property path on a given target object.
31 *
32 * <p>The target object can be specified directly or via a bean name.
33 *
34 * <p>Usage examples:
35 *
36 * <pre class="code"><!-- target bean to be referenced by name -->
37 * <bean id="tb" class="org.springframework.beans.TestBean" singleton="false">
38 * <property name="age" value="10"/>
39 * <property name="spouse">
40 * <bean class="org.springframework.beans.TestBean">
41 * <property name="age" value="11"/>
42 * </bean>
43 * </property>
44 * </bean>
45 *
46 * <!-- will result in 12, which is the value of property 'age' of the inner bean -->
47 * <bean id="propertyPath1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
48 * <property name="targetObject">
49 * <bean class="org.springframework.beans.TestBean">
50 * <property name="age" value="12"/>
51 * </bean>
52 * </property>
53 * <property name="propertyPath" value="age"/>
54 * </bean>
55 *
56 * <!-- will result in 11, which is the value of property 'spouse.age' of bean 'tb' -->
57 * <bean id="propertyPath2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean">
58 * <property name="targetBeanName" value="tb"/>
59 * <property name="propertyPath" value="spouse.age"/>
60 * </bean>
61 *
62 * <!-- will result in 10, which is the value of property 'age' of bean 'tb' -->
63 * <bean id="tb.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/></pre>
64 *
65 * <p>If you are using Spring 2.0 and XML Schema support in your configuration file(s),
66 * you can also use the following style of configuration for property path access.
67 * (See also the appendix entitled 'XML Schema-based configuration' in the Spring
68 * reference manual for more examples.)
69 *
70 * <pre class="code"> <!-- will result in 10, which is the value of property 'age' of bean 'tb' -->
71 * <util:property-path id="name" path="testBean.age"/></pre>
72 *
73 * Thanks to Matthias Ernst for the suggestion and initial prototype!
74 *
75 * @author Juergen Hoeller
76 * @since 1.1.2
77 * @see #setTargetObject
78 * @see #setTargetBeanName
79 * @see #setPropertyPath
80 */
81 public class PropertyPathFactoryBean implements FactoryBean, BeanNameAware, BeanFactoryAware {
82
83 private BeanWrapper targetBeanWrapper;
84
85 private String targetBeanName;
86
87 private String propertyPath;
88
89 private Class resultType;
90
91 private String beanName;
92
93 private BeanFactory beanFactory;
94
95
96 /**
97 * Specify a target object to apply the property path to.
98 * Alternatively, specify a target bean name.
99 * @param targetObject a target object, for example a bean reference
100 * or an inner bean
101 * @see #setTargetBeanName
102 */
103 public void setTargetObject(Object targetObject) {
104 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(targetObject);
105 }
106
107 /**
108 * Specify the name of a target bean to apply the property path to.
109 * Alternatively, specify a target object directly.
110 * @param targetBeanName the bean name to be looked up in the
111 * containing bean factory (e.g. "testBean")
112 * @see #setTargetObject
113 */
114 public void setTargetBeanName(String targetBeanName) {
115 this.targetBeanName = StringUtils.trimAllWhitespace(targetBeanName);
116 }
117
118 /**
119 * Specify the property path to apply to the target.
120 * @param propertyPath the property path, potentially nested
121 * (e.g. "age" or "spouse.age")
122 */
123 public void setPropertyPath(String propertyPath) {
124 this.propertyPath = StringUtils.trimAllWhitespace(propertyPath);
125 }
126
127 /**
128 * Specify the type of the result from evaluating the property path.
129 * <p>Note: This is not necessary for directly specified target objects
130 * or singleton target beans, where the type can be determined through
131 * introspection. Just specify this in case of a prototype target,
132 * provided that you need matching by type (for example, for autowiring).
133 * @param resultType the result type, for example "java.lang.Integer"
134 */
135 public void setResultType(Class resultType) {
136 this.resultType = resultType;
137 }
138
139 /**
140 * The bean name of this PropertyPathFactoryBean will be interpreted
141 * as "beanName.property" pattern, if neither "targetObject" nor
142 * "targetBeanName" nor "propertyPath" have been specified.
143 * This allows for concise bean definitions with just an id/name.
144 */
145 public void setBeanName(String beanName) {
146 this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName));
147 }
148
149
150 public void setBeanFactory(BeanFactory beanFactory) {
151 this.beanFactory = beanFactory;
152
153 if (this.targetBeanWrapper != null && this.targetBeanName != null) {
154 throw new IllegalArgumentException("Specify either 'targetObject' or 'targetBeanName', not both");
155 }
156
157 if (this.targetBeanWrapper == null && this.targetBeanName == null) {
158 if (this.propertyPath != null) {
159 throw new IllegalArgumentException(
160 "Specify 'targetObject' or 'targetBeanName' in combination with 'propertyPath'");
161 }
162
163 // No other properties specified: check bean name.
164 int dotIndex = this.beanName.indexOf('.');
165 if (dotIndex == -1) {
166 throw new IllegalArgumentException(
167 "Neither 'targetObject' nor 'targetBeanName' specified, and PropertyPathFactoryBean " +
168 "bean name '" + this.beanName + "' does not follow 'beanName.property' syntax");
169 }
170 this.targetBeanName = this.beanName.substring(0, dotIndex);
171 this.propertyPath = this.beanName.substring(dotIndex + 1);
172 }
173
174 else if (this.propertyPath == null) {
175 // either targetObject or targetBeanName specified
176 throw new IllegalArgumentException("'propertyPath' is required");
177 }
178
179 if (this.targetBeanWrapper == null && this.beanFactory.isSingleton(this.targetBeanName)) {
180 // Eagerly fetch singleton target bean, and determine result type.
181 Object bean = this.beanFactory.getBean(this.targetBeanName);
182 this.targetBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
183 this.resultType = this.targetBeanWrapper.getPropertyType(this.propertyPath);
184 }
185 }
186
187
188 public Object getObject() throws BeansException {
189 BeanWrapper target = this.targetBeanWrapper;
190 if (target == null) {
191 // Fetch prototype target bean...
192 Object bean = this.beanFactory.getBean(this.targetBeanName);
193 target = PropertyAccessorFactory.forBeanPropertyAccess(bean);
194 }
195 return target.getPropertyValue(this.propertyPath);
196 }
197
198 public Class getObjectType() {
199 return this.resultType;
200 }
201
202 /**
203 * While this FactoryBean will often be used for singleton targets,
204 * the invoked getters for the property path might return a new object
205 * for each call, so we have to assume that we're not returning the
206 * same object for each {@link #getObject()} call.
207 */
208 public boolean isSingleton() {
209 return false;
210 }
211
212 }