001/*
002 * Copyright 2002-2020 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.Collections;
026import java.util.LinkedHashSet;
027import java.util.Set;
028
029import org.springframework.beans.MutablePropertyValues;
030import org.springframework.beans.PropertyValues;
031import org.springframework.beans.factory.support.RootBeanDefinition;
032import org.springframework.lang.Nullable;
033import org.springframework.util.ReflectionUtils;
034
035/**
036 * Internal class for managing injection metadata.
037 * Not intended for direct use in applications.
038 *
039 * <p>Used by {@link AutowiredAnnotationBeanPostProcessor},
040 * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor} and
041 * {@link org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor}.
042 *
043 * @author Juergen Hoeller
044 * @since 2.5
045 */
046public class InjectionMetadata {
047
048        /**
049         * An empty {@code InjectionMetadata} instance with no-op callbacks.
050         * @since 5.2
051         */
052        public static final InjectionMetadata EMPTY = new InjectionMetadata(Object.class, Collections.emptyList()) {
053                @Override
054                protected boolean needsRefresh(Class<?> clazz) {
055                        return false;
056                }
057                @Override
058                public void checkConfigMembers(RootBeanDefinition beanDefinition) {
059                }
060                @Override
061                public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) {
062                }
063                @Override
064                public void clear(@Nullable PropertyValues pvs) {
065                }
066        };
067
068
069        private final Class<?> targetClass;
070
071        private final Collection<InjectedElement> injectedElements;
072
073        @Nullable
074        private volatile Set<InjectedElement> checkedElements;
075
076
077        /**
078         * Create a new {@code InjectionMetadata instance}.
079         * <p>Preferably use {@link #forElements} for reusing the {@link #EMPTY}
080         * instance in case of no elements.
081         * @param targetClass the target class
082         * @param elements the associated elements to inject
083         * @see #forElements
084         */
085        public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
086                this.targetClass = targetClass;
087                this.injectedElements = elements;
088        }
089
090
091        /**
092         * Determine whether this metadata instance needs to be refreshed.
093         * @param clazz the current target class
094         * @return {@code true} indicating a refresh, {@code false} otherwise
095         * @since 5.2.4
096         */
097        protected boolean needsRefresh(Class<?> clazz) {
098                return this.targetClass != clazz;
099        }
100
101        public void checkConfigMembers(RootBeanDefinition beanDefinition) {
102                Set<InjectedElement> checkedElements = new LinkedHashSet<>(this.injectedElements.size());
103                for (InjectedElement element : this.injectedElements) {
104                        Member member = element.getMember();
105                        if (!beanDefinition.isExternallyManagedConfigMember(member)) {
106                                beanDefinition.registerExternallyManagedConfigMember(member);
107                                checkedElements.add(element);
108                        }
109                }
110                this.checkedElements = checkedElements;
111        }
112
113        public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
114                Collection<InjectedElement> checkedElements = this.checkedElements;
115                Collection<InjectedElement> elementsToIterate =
116                                (checkedElements != null ? checkedElements : this.injectedElements);
117                if (!elementsToIterate.isEmpty()) {
118                        for (InjectedElement element : elementsToIterate) {
119                                element.inject(target, beanName, pvs);
120                        }
121                }
122        }
123
124        /**
125         * Clear property skipping for the contained elements.
126         * @since 3.2.13
127         */
128        public void clear(@Nullable PropertyValues pvs) {
129                Collection<InjectedElement> checkedElements = this.checkedElements;
130                Collection<InjectedElement> elementsToIterate =
131                                (checkedElements != null ? checkedElements : this.injectedElements);
132                if (!elementsToIterate.isEmpty()) {
133                        for (InjectedElement element : elementsToIterate) {
134                                element.clearPropertySkipping(pvs);
135                        }
136                }
137        }
138
139
140        /**
141         * Return an {@code InjectionMetadata} instance, possibly for empty elements.
142         * @param elements the elements to inject (possibly empty)
143         * @param clazz the target class
144         * @return a new {@link #InjectionMetadata(Class, Collection)} instance
145         * @since 5.2
146         */
147        public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
148                return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) :
149                                new InjectionMetadata(clazz, elements));
150        }
151
152        /**
153         * Check whether the given injection metadata needs to be refreshed.
154         * @param metadata the existing metadata instance
155         * @param clazz the current target class
156         * @return {@code true} indicating a refresh, {@code false} otherwise
157         * @see #needsRefresh(Class)
158         */
159        public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) {
160                return (metadata == null || metadata.needsRefresh(clazz));
161        }
162
163
164        /**
165         * A single injected element.
166         */
167        public abstract static class InjectedElement {
168
169                protected final Member member;
170
171                protected final boolean isField;
172
173                @Nullable
174                protected final PropertyDescriptor pd;
175
176                @Nullable
177                protected volatile Boolean skip;
178
179                protected InjectedElement(Member member, @Nullable PropertyDescriptor pd) {
180                        this.member = member;
181                        this.isField = (member instanceof Field);
182                        this.pd = pd;
183                }
184
185                public final Member getMember() {
186                        return this.member;
187                }
188
189                protected final Class<?> getResourceType() {
190                        if (this.isField) {
191                                return ((Field) this.member).getType();
192                        }
193                        else if (this.pd != null) {
194                                return this.pd.getPropertyType();
195                        }
196                        else {
197                                return ((Method) this.member).getParameterTypes()[0];
198                        }
199                }
200
201                protected final void checkResourceType(Class<?> resourceType) {
202                        if (this.isField) {
203                                Class<?> fieldType = ((Field) this.member).getType();
204                                if (!(resourceType.isAssignableFrom(fieldType) || fieldType.isAssignableFrom(resourceType))) {
205                                        throw new IllegalStateException("Specified field type [" + fieldType +
206                                                        "] is incompatible with resource type [" + resourceType.getName() + "]");
207                                }
208                        }
209                        else {
210                                Class<?> paramType =
211                                                (this.pd != null ? this.pd.getPropertyType() : ((Method) this.member).getParameterTypes()[0]);
212                                if (!(resourceType.isAssignableFrom(paramType) || paramType.isAssignableFrom(resourceType))) {
213                                        throw new IllegalStateException("Specified parameter type [" + paramType +
214                                                        "] is incompatible with resource type [" + resourceType.getName() + "]");
215                                }
216                        }
217                }
218
219                /**
220                 * Either this or {@link #getResourceToInject} needs to be overridden.
221                 */
222                protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
223                                throws Throwable {
224
225                        if (this.isField) {
226                                Field field = (Field) this.member;
227                                ReflectionUtils.makeAccessible(field);
228                                field.set(target, getResourceToInject(target, requestingBeanName));
229                        }
230                        else {
231                                if (checkPropertySkipping(pvs)) {
232                                        return;
233                                }
234                                try {
235                                        Method method = (Method) this.member;
236                                        ReflectionUtils.makeAccessible(method);
237                                        method.invoke(target, getResourceToInject(target, requestingBeanName));
238                                }
239                                catch (InvocationTargetException ex) {
240                                        throw ex.getTargetException();
241                                }
242                        }
243                }
244
245                /**
246                 * Check whether this injector's property needs to be skipped due to
247                 * an explicit property value having been specified. Also marks the
248                 * affected property as processed for other processors to ignore it.
249                 */
250                protected boolean checkPropertySkipping(@Nullable PropertyValues pvs) {
251                        Boolean skip = this.skip;
252                        if (skip != null) {
253                                return skip;
254                        }
255                        if (pvs == null) {
256                                this.skip = false;
257                                return false;
258                        }
259                        synchronized (pvs) {
260                                skip = this.skip;
261                                if (skip != null) {
262                                        return skip;
263                                }
264                                if (this.pd != null) {
265                                        if (pvs.contains(this.pd.getName())) {
266                                                // Explicit value provided as part of the bean definition.
267                                                this.skip = true;
268                                                return true;
269                                        }
270                                        else if (pvs instanceof MutablePropertyValues) {
271                                                ((MutablePropertyValues) pvs).registerProcessedProperty(this.pd.getName());
272                                        }
273                                }
274                                this.skip = false;
275                                return false;
276                        }
277                }
278
279                /**
280                 * Clear property skipping for this element.
281                 * @since 3.2.13
282                 */
283                protected void clearPropertySkipping(@Nullable PropertyValues pvs) {
284                        if (pvs == null) {
285                                return;
286                        }
287                        synchronized (pvs) {
288                                if (Boolean.FALSE.equals(this.skip) && this.pd != null && pvs instanceof MutablePropertyValues) {
289                                        ((MutablePropertyValues) pvs).clearProcessedProperty(this.pd.getName());
290                                }
291                        }
292                }
293
294                /**
295                 * Either this or {@link #inject} needs to be overridden.
296                 */
297                @Nullable
298                protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
299                        return null;
300                }
301
302                @Override
303                public boolean equals(@Nullable Object other) {
304                        if (this == other) {
305                                return true;
306                        }
307                        if (!(other instanceof InjectedElement)) {
308                                return false;
309                        }
310                        InjectedElement otherElement = (InjectedElement) other;
311                        return this.member.equals(otherElement.member);
312                }
313
314                @Override
315                public int hashCode() {
316                        return this.member.getClass().hashCode() * 29 + this.member.getName().hashCode();
317                }
318
319                @Override
320                public String toString() {
321                        return getClass().getSimpleName() + " for " + this.member;
322                }
323        }
324
325}