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">&lt;!-- target bean to be referenced by name --&gt;
040 * &lt;bean id="tb" class="org.springframework.beans.TestBean" singleton="false"&gt;
041 *   &lt;property name="age" value="10"/&gt;
042 *   &lt;property name="spouse"&gt;
043 *     &lt;bean class="org.springframework.beans.TestBean"&gt;
044 *       &lt;property name="age" value="11"/&gt;
045 *     &lt;/bean&gt;
046 *   &lt;/property&gt;
047 * &lt;/bean&gt;
048 *
049 * &lt;!-- will result in 12, which is the value of property 'age' of the inner bean --&gt;
050 * &lt;bean id="propertyPath1" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"&gt;
051 *   &lt;property name="targetObject"&gt;
052 *     &lt;bean class="org.springframework.beans.TestBean"&gt;
053 *       &lt;property name="age" value="12"/&gt;
054 *     &lt;/bean&gt;
055 *   &lt;/property&gt;
056 *   &lt;property name="propertyPath" value="age"/&gt;
057 * &lt;/bean&gt;
058 *
059 * &lt;!-- will result in 11, which is the value of property 'spouse.age' of bean 'tb' --&gt;
060 * &lt;bean id="propertyPath2" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"&gt;
061 *   &lt;property name="targetBeanName" value="tb"/&gt;
062 *   &lt;property name="propertyPath" value="spouse.age"/&gt;
063 * &lt;/bean&gt;
064 *
065 * &lt;!-- will result in 10, which is the value of property 'age' of bean 'tb' --&gt;
066 * &lt;bean id="tb.age" class="org.springframework.beans.factory.config.PropertyPathFactoryBean"/&gt;</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"> &lt;!-- will result in 10, which is the value of property 'age' of bean 'tb' --&gt;
074 * &lt;util:property-path id="name" path="testBean.age"/&gt;</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}