001/*
002 * Copyright 2002-2018 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.annotation;
018
019import java.beans.PropertyDescriptor;
020import java.lang.reflect.Field;
021import java.lang.reflect.InvocationTargetException;
022import java.lang.reflect.Member;
023import java.lang.reflect.Method;
024import java.util.Collection;
025import java.util.LinkedHashSet;
026import java.util.Set;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.beans.MutablePropertyValues;
032import org.springframework.beans.PropertyValues;
033import org.springframework.beans.factory.support.RootBeanDefinition;
034import org.springframework.util.ReflectionUtils;
035
036/**
037 * Internal class for managing injection metadata.
038 * Not intended for direct use in applications.
039 *
040 * <p>Used by {@link AutowiredAnnotationBeanPostProcessor},
041 * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} and
042 * {@link org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor}.
043 *
044 * @author Juergen Hoeller
045 * @since 2.5
046 */
047public class InjectionMetadata {
048
049        private static final Log logger = LogFactory.getLog(InjectionMetadata.class);
050
051        private final Class<?> targetClass;
052
053        private final Collection<InjectedElement> injectedElements;
054
055        private volatile Set<InjectedElement> checkedElements;
056
057
058        public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
059                this.targetClass = targetClass;
060                this.injectedElements = elements;
061        }
062
063
064        public void checkConfigMembers(RootBeanDefinition beanDefinition) {
065                Set<InjectedElement> checkedElements = new LinkedHashSet<InjectedElement>(this.injectedElements.size());
066                for (InjectedElement element : this.injectedElements) {
067                        Member member = element.getMember();
068                        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
069                                beanDefinition.registerExternallyManagedConfigMember(member);
070                                checkedElements.add(element);
071                                if (logger.isDebugEnabled()) {
072                                        logger.debug("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
073                                }
074                        }
075                }
076                this.checkedElements = checkedElements;
077        }
078
079        public void inject(Object target, String beanName, PropertyValues pvs) throws Throwable {
080                Collection<InjectedElement> elementsToIterate =
081                                (this.checkedElements != null ? this.checkedElements : this.injectedElements);
082                if (!elementsToIterate.isEmpty()) {
083                        for (InjectedElement element : elementsToIterate) {
084                                if (logger.isDebugEnabled()) {
085                                        logger.debug("Processing injected element of bean '" + beanName + "': " + element);
086                                }
087                                element.inject(target, beanName, pvs);
088                        }
089                }
090        }
091
092        /**
093         * @since 3.2.13
094         */
095        public void clear(PropertyValues pvs) {
096                Collection<InjectedElement> elementsToIterate =
097                                (this.checkedElements != null ? this.checkedElements : this.injectedElements);
098                if (!elementsToIterate.isEmpty()) {
099                        for (InjectedElement element : elementsToIterate) {
100                                element.clearPropertySkipping(pvs);
101                        }
102                }
103        }
104
105
106        public static boolean needsRefresh(InjectionMetadata metadata, Class<?> clazz) {
107                return (metadata == null || metadata.targetClass != clazz);
108        }
109
110
111        /**
112         * A single injected element.
113         */
114        public abstract static class InjectedElement {
115
116                protected final Member member;
117
118                protected final boolean isField;
119
120                protected final PropertyDescriptor pd;
121
122                protected volatile Boolean skip;
123
124                protected InjectedElement(Member member, PropertyDescriptor pd) {
125                        this.member = member;
126                        this.isField = (member instanceof Field);
127                        this.pd = pd;
128                }
129
130                public final Member getMember() {
131                        return this.member;
132                }
133
134                protected final Class<?> getResourceType() {
135                        if (this.isField) {
136                                return ((Field) this.member).getType();
137                        }
138                        else if (this.pd != null) {
139                                return this.pd.getPropertyType();
140                        }
141                        else {
142                                return ((Method) this.member).getParameterTypes()[0];
143                        }
144                }
145
146                protected final void checkResourceType(Class<?> resourceType) {
147                        if (this.isField) {
148                                Class<?> fieldType = ((Field) this.member).getType();
149                                if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) {
150                                        throw new IllegalStateException("Specified field type [" + fieldType +
151                                                        "] is incompatible with resource type [" + resourceType.getName() + "]");
152                                }
153                        }
154                        else {
155                                Class<?> paramType =
156                                                (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]);
157                                if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) {
158                                        throw new IllegalStateException("Specified parameter type [" + paramType +
159                                                        "] is incompatible with resource type [" + resourceType.getName() + "]");
160                                }
161                        }
162                }
163
164                /**
165                 * Either this or {@link #getResourceToInject} needs to be overridden.
166                 */
167                protected void inject(Object target, String requestingBeanName, PropertyValues pvs) throws Throwable {
168                        if (this.isField) {
169                                Field field = (Field) this.member;
170                                ReflectionUtils.makeAccessible(field);
171                                field.set(target, getResourceToInject(target, requestingBeanName));
172                        }
173                        else {
174                                if (checkPropertySkipping(pvs)) {
175                                        return;
176                                }
177                                try {
178                                        Method method = (Method) this.member;
179                                        ReflectionUtils.makeAccessible(method);
180                                        method.invoke(target, getResourceToInject(target, requestingBeanName));
181                                }
182                                catch (InvocationTargetException ex) {
183                                        throw ex.getTargetException();
184                                }
185                        }
186                }
187
188                /**
189                 * Check whether this injector's property needs to be skipped due to
190                 * an explicit property value having been specified. Also marks the
191                 * affected property as processed for other processors to ignore it.
192                 */
193                protected boolean checkPropertySkipping(PropertyValues pvs) {
194                        if (this.skip != null) {
195                                return this.skip;
196                        }
197                        if (pvs == null) {
198                                this.skip = false;
199                                return false;
200                        }
201                        synchronized (pvs) {
202                                if (this.skip != null) {
203                                        return this.skip;
204                                }
205                                if (this.pd != null) {
206                                        if (pvs.contains(this.pd.getName())) {
207                                                // Explicit value provided as part of the bean definition.
208                                                this.skip = true;
209                                                return true;
210                                        }
211                                        else if (pvs instanceof MutablePropertyValues) {
212                                                ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName());
213                                        }
214                                }
215                                this.skip = false;
216                                return false;
217                        }
218                }
219
220                /**
221                 * Clear property skipping for this element.
222                 * @since 3.2.13
223                 */
224                protected void clearPropertySkipping(PropertyValues pvs) {
225                        if (pvs == null) {
226                                return;
227                        }
228                        synchronized (pvs) {
229                                if (Boolean.FALSE.equals(this.skip) && this.pd != null && pvs instanceof MutablePropertyValues) {
230                                        ((MutablePropertyValues) pvs).clearProcessedProperty(this.pd.getName());
231                                }
232                        }
233                }
234
235                /**
236                 * Either this or {@link #inject} needs to be overridden.
237                 */
238                protected Object getResourceToInject(Object target, String requestingBeanName) {
239                        return null;
240                }
241
242                @Override
243                public boolean equals(Object other) {
244                        if (this == other) {
245                                return true;
246                        }
247                        if (!(other instanceof InjectedElement)) {
248                                return false;
249                        }
250                        InjectedElement otherElement = (InjectedElement) other;
251                        return this.member.equals(otherElement.member);
252                }
253
254                @Override
255                public int hashCode() {
256                        return this.member.getClass().hashCode() * 29 + this.member.getName().hashCode();
257                }
258
259                @Override
260                public String toString() {
261                        return getClass().getSimpleName() + " for " + this.member;
262                }
263        }
264
265}