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.beans.factory.config;
018
019import java.lang.reflect.Field;
020
021import org.springframework.beans.factory.BeanClassLoaderAware;
022import org.springframework.beans.factory.BeanFactoryUtils;
023import org.springframework.beans.factory.BeanNameAware;
024import org.springframework.beans.factory.FactoryBean;
025import org.springframework.beans.factory.FactoryBeanNotInitializedException;
026import org.springframework.beans.factory.InitializingBean;
027import org.springframework.util.ClassUtils;
028import org.springframework.util.ReflectionUtils;
029import org.springframework.util.StringUtils;
030
031/**
032 * {@link FactoryBean} which retrieves a static or non-static field value.
033 *
034 * <p>Typically used for retrieving public static final constants. Usage example:
035 *
036 * <pre class="code">// standard definition for exposing a static field, specifying the "staticField" property
037 * &lt;bean id="myField" class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"&gt;
038 *   &lt;property name="staticField" value="java.sql.Connection.TRANSACTION_SERIALIZABLE"/&gt;
039 * &lt;/bean&gt;
040 *
041 * // convenience version that specifies a static field pattern as bean name
042 * &lt;bean id="java.sql.Connection.TRANSACTION_SERIALIZABLE"
043 *       class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"/&gt;</pre>
044 * </pre>
045 *
046 * <p>If you are using Spring 2.0, you can also use the following style of configuration for
047 * public static fields.
048 *
049 * <pre class="code">&lt;util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/&gt;</pre>
050 *
051 * @author Juergen Hoeller
052 * @since 1.1
053 * @see #setStaticField
054 */
055public class FieldRetrievingFactoryBean
056                implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, InitializingBean {
057
058        private Class<?> targetClass;
059
060        private Object targetObject;
061
062        private String targetField;
063
064        private String staticField;
065
066        private String beanName;
067
068        private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();
069
070        // the field we will retrieve
071        private Field fieldObject;
072
073
074        /**
075         * Set the target class on which the field is defined.
076         * Only necessary when the target field is static; else,
077         * a target object needs to be specified anyway.
078         * @see #setTargetObject
079         * @see #setTargetField
080         */
081        public void setTargetClass(Class<?> targetClass) {
082                this.targetClass = targetClass;
083        }
084
085        /**
086         * Return the target class on which the field is defined.
087         */
088        public Class<?> getTargetClass() {
089                return targetClass;
090        }
091
092        /**
093         * Set the target object on which the field is defined.
094         * Only necessary when the target field is not static;
095         * else, a target class is sufficient.
096         * @see #setTargetClass
097         * @see #setTargetField
098         */
099        public void setTargetObject(Object targetObject) {
100                this.targetObject = targetObject;
101        }
102
103        /**
104         * Return the target object on which the field is defined.
105         */
106        public Object getTargetObject() {
107                return this.targetObject;
108        }
109
110        /**
111         * Set the name of the field to be retrieved.
112         * Refers to either a static field or a non-static field,
113         * depending on a target object being set.
114         * @see #setTargetClass
115         * @see #setTargetObject
116         */
117        public void setTargetField(String targetField) {
118                this.targetField = StringUtils.trimAllWhitespace(targetField);
119        }
120
121        /**
122         * Return the name of the field to be retrieved.
123         */
124        public String getTargetField() {
125                return this.targetField;
126        }
127
128        /**
129         * Set a fully qualified static field name to retrieve,
130         * e.g. "example.MyExampleClass.MY_EXAMPLE_FIELD".
131         * Convenient alternative to specifying targetClass and targetField.
132         * @see #setTargetClass
133         * @see #setTargetField
134         */
135        public void setStaticField(String staticField) {
136                this.staticField = StringUtils.trimAllWhitespace(staticField);
137        }
138
139        /**
140         * The bean name of this FieldRetrievingFactoryBean will be interpreted
141         * as "staticField" pattern, if neither "targetClass" nor "targetObject"
142         * nor "targetField" have been specified.
143         * This allows for concise bean definitions with just an id/name.
144         */
145        @Override
146        public void setBeanName(String beanName) {
147                this.beanName = StringUtils.trimAllWhitespace(BeanFactoryUtils.originalBeanName(beanName));
148        }
149
150        @Override
151        public void setBeanClassLoader(ClassLoader classLoader) {
152                this.beanClassLoader = classLoader;
153        }
154
155
156        @Override
157        public void afterPropertiesSet() throws ClassNotFoundException, NoSuchFieldException {
158                if (this.targetClass != null && this.targetObject != null) {
159                        throw new IllegalArgumentException("Specify either targetClass or targetObject, not both");
160                }
161
162                if (this.targetClass == null && this.targetObject == null) {
163                        if (this.targetField != null) {
164                                throw new IllegalArgumentException(
165                                                "Specify targetClass or targetObject in combination with targetField");
166                        }
167
168                        // If no other property specified, consider bean name as static field expression.
169                        if (this.staticField == null) {
170                                this.staticField = this.beanName;
171                        }
172
173                        // Try to parse static field into class and field.
174                        int lastDotIndex = this.staticField.lastIndexOf('.');
175                        if (lastDotIndex == -1 || lastDotIndex == this.staticField.length()) {
176                                throw new IllegalArgumentException(
177                                                "staticField must be a fully qualified class plus static field name: " +
178                                                "e.g. 'example.MyExampleClass.MY_EXAMPLE_FIELD'");
179                        }
180                        String className = this.staticField.substring(0, lastDotIndex);
181                        String fieldName = this.staticField.substring(lastDotIndex + 1);
182                        this.targetClass = ClassUtils.forName(className, this.beanClassLoader);
183                        this.targetField = fieldName;
184                }
185
186                else if (this.targetField == null) {
187                        // Either targetClass or targetObject specified.
188                        throw new IllegalArgumentException("targetField is required");
189                }
190
191                // Try to get the exact method first.
192                Class<?> targetClass = (this.targetObject != null) ? this.targetObject.getClass() : this.targetClass;
193                this.fieldObject = targetClass.getField(this.targetField);
194        }
195
196
197        @Override
198        public Object getObject() throws IllegalAccessException {
199                if (this.fieldObject == null) {
200                        throw new FactoryBeanNotInitializedException();
201                }
202                ReflectionUtils.makeAccessible(this.fieldObject);
203                if (this.targetObject != null) {
204                        // instance field
205                        return this.fieldObject.get(this.targetObject);
206                }
207                else {
208                        // class field
209                        return this.fieldObject.get(null);
210                }
211        }
212
213        @Override
214        public Class<?> getObjectType() {
215                return (this.fieldObject != null ? this.fieldObject.getType() : null);
216        }
217
218        @Override
219        public boolean isSingleton() {
220                return false;
221        }
222
223}