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