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.context.annotation;
018
019import java.beans.Introspector;
020import java.beans.PropertyDescriptor;
021import java.io.Serializable;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.AnnotatedElement;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.Field;
026import java.lang.reflect.Member;
027import java.lang.reflect.Method;
028import java.lang.reflect.Modifier;
029import java.net.MalformedURLException;
030import java.net.URL;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashSet;
034import java.util.LinkedHashSet;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038import java.util.concurrent.ConcurrentHashMap;
039
040import javax.annotation.PostConstruct;
041import javax.annotation.PreDestroy;
042import javax.annotation.Resource;
043import javax.ejb.EJB;
044import javax.xml.namespace.QName;
045import javax.xml.ws.Service;
046import javax.xml.ws.WebServiceClient;
047import javax.xml.ws.WebServiceRef;
048
049import org.springframework.aop.TargetSource;
050import org.springframework.aop.framework.ProxyFactory;
051import org.springframework.beans.BeanUtils;
052import org.springframework.beans.PropertyValues;
053import org.springframework.beans.factory.BeanCreationException;
054import org.springframework.beans.factory.BeanFactory;
055import org.springframework.beans.factory.BeanFactoryAware;
056import org.springframework.beans.factory.NoSuchBeanDefinitionException;
057import org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor;
058import org.springframework.beans.factory.annotation.InjectionMetadata;
059import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
060import org.springframework.beans.factory.config.ConfigurableBeanFactory;
061import org.springframework.beans.factory.config.DependencyDescriptor;
062import org.springframework.beans.factory.config.EmbeddedValueResolver;
063import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
064import org.springframework.beans.factory.support.RootBeanDefinition;
065import org.springframework.core.BridgeMethodResolver;
066import org.springframework.core.MethodParameter;
067import org.springframework.core.Ordered;
068import org.springframework.core.annotation.AnnotationUtils;
069import org.springframework.jndi.support.SimpleJndiBeanFactory;
070import org.springframework.lang.Nullable;
071import org.springframework.util.Assert;
072import org.springframework.util.ClassUtils;
073import org.springframework.util.ReflectionUtils;
074import org.springframework.util.StringUtils;
075import org.springframework.util.StringValueResolver;
076
077/**
078 * {@link org.springframework.beans.factory.config.BeanPostProcessor} implementation
079 * that supports common Java annotations out of the box, in particular the JSR-250
080 * annotations in the {@code javax.annotation} package. These common Java
081 * annotations are supported in many Java EE 5 technologies (e.g. JSF 1.2),
082 * as well as in Java 6's JAX-WS.
083 *
084 * <p>This post-processor includes support for the {@link javax.annotation.PostConstruct}
085 * and {@link javax.annotation.PreDestroy} annotations - as init annotation
086 * and destroy annotation, respectively - through inheriting from
087 * {@link InitDestroyAnnotationBeanPostProcessor} with pre-configured annotation types.
088 *
089 * <p>The central element is the {@link javax.annotation.Resource} annotation
090 * for annotation-driven injection of named beans, by default from the containing
091 * Spring BeanFactory, with only {@code mappedName} references resolved in JNDI.
092 * The {@link #setAlwaysUseJndiLookup "alwaysUseJndiLookup" flag} enforces JNDI lookups
093 * equivalent to standard Java EE 5 resource injection for {@code name} references
094 * and default names as well. The target beans can be simple POJOs, with no special
095 * requirements other than the type having to match.
096 *
097 * <p>The JAX-WS {@link javax.xml.ws.WebServiceRef} annotation is supported too,
098 * analogous to {@link javax.annotation.Resource} but with the capability of creating
099 * specific JAX-WS service endpoints. This may either point to an explicitly defined
100 * resource by name or operate on a locally specified JAX-WS service class. Finally,
101 * this post-processor also supports the EJB 3 {@link javax.ejb.EJB} annotation,
102 * analogous to {@link javax.annotation.Resource} as well, with the capability to
103 * specify both a local bean name and a global JNDI name for fallback retrieval.
104 * The target beans can be plain POJOs as well as EJB 3 Session Beans in this case.
105 *
106 * <p>The common annotations supported by this post-processor are available in
107 * Java 6 (JDK 1.6) as well as in Java EE 5/6 (which provides a standalone jar for
108 * its common annotations as well, allowing for use in any Java 5 based application).
109 *
110 * <p>For default usage, resolving resource names as Spring bean names,
111 * simply define the following in your application context:
112 *
113 * <pre class="code">
114 * &lt;bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/&gt;</pre>
115 *
116 * For direct JNDI access, resolving resource names as JNDI resource references
117 * within the Java EE application's "java:comp/env/" namespace, use the following:
118 *
119 * <pre class="code">
120 * &lt;bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"&gt;
121 *   &lt;property name="alwaysUseJndiLookup" value="true"/&gt;
122 * &lt;/bean&gt;</pre>
123 *
124 * {@code mappedName} references will always be resolved in JNDI,
125 * allowing for global JNDI names (including "java:" prefix) as well. The
126 * "alwaysUseJndiLookup" flag just affects {@code name} references and
127 * default names (inferred from the field name / property name).
128 *
129 * <p><b>NOTE:</b> A default CommonAnnotationBeanPostProcessor will be registered
130 * by the "context:annotation-config" and "context:component-scan" XML tags.
131 * Remove or turn off the default annotation configuration there if you intend
132 * to specify a custom CommonAnnotationBeanPostProcessor bean definition!
133 * <p><b>NOTE:</b> Annotation injection will be performed <i>before</i> XML injection; thus
134 * the latter configuration will override the former for properties wired through
135 * both approaches.
136 *
137 * @author Juergen Hoeller
138 * @author Sam Brannen
139 * @since 2.5
140 * @see #setAlwaysUseJndiLookup
141 * @see #setResourceFactory
142 * @see org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor
143 * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
144 */
145@SuppressWarnings("serial")
146public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBeanPostProcessor
147                implements InstantiationAwareBeanPostProcessor, BeanFactoryAware, Serializable {
148
149        @Nullable
150        private static final Class<? extends Annotation> webServiceRefClass;
151
152        @Nullable
153        private static final Class<? extends Annotation> ejbClass;
154
155        private static final Set<Class<? extends Annotation>> resourceAnnotationTypes = new LinkedHashSet<>(4);
156
157        static {
158                webServiceRefClass = loadAnnotationType("javax.xml.ws.WebServiceRef");
159                ejbClass = loadAnnotationType("javax.ejb.EJB");
160
161                resourceAnnotationTypes.add(Resource.class);
162                if (webServiceRefClass != null) {
163                        resourceAnnotationTypes.add(webServiceRefClass);
164                }
165                if (ejbClass != null) {
166                        resourceAnnotationTypes.add(ejbClass);
167                }
168        }
169
170
171        private final Set<String> ignoredResourceTypes = new HashSet<>(1);
172
173        private boolean fallbackToDefaultTypeMatch = true;
174
175        private boolean alwaysUseJndiLookup = false;
176
177        private transient BeanFactory jndiFactory = new SimpleJndiBeanFactory();
178
179        @Nullable
180        private transient BeanFactory resourceFactory;
181
182        @Nullable
183        private transient BeanFactory beanFactory;
184
185        @Nullable
186        private transient StringValueResolver embeddedValueResolver;
187
188        private final transient Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);
189
190
191        /**
192         * Create a new CommonAnnotationBeanPostProcessor,
193         * with the init and destroy annotation types set to
194         * {@link javax.annotation.PostConstruct} and {@link javax.annotation.PreDestroy},
195         * respectively.
196         */
197        public CommonAnnotationBeanPostProcessor() {
198                setOrder(Ordered.LOWEST_PRECEDENCE - 3);
199                setInitAnnotationType(PostConstruct.class);
200                setDestroyAnnotationType(PreDestroy.class);
201                ignoreResourceType("javax.xml.ws.WebServiceContext");
202        }
203
204
205        /**
206         * Ignore the given resource type when resolving {@code @Resource}
207         * annotations.
208         * <p>By default, the {@code javax.xml.ws.WebServiceContext} interface
209         * will be ignored, since it will be resolved by the JAX-WS runtime.
210         * @param resourceType the resource type to ignore
211         */
212        public void ignoreResourceType(String resourceType) {
213                Assert.notNull(resourceType, "Ignored resource type must not be null");
214                this.ignoredResourceTypes.add(resourceType);
215        }
216
217        /**
218         * Set whether to allow a fallback to a type match if no explicit name has been
219         * specified. The default name (i.e. the field name or bean property name) will
220         * still be checked first; if a bean of that name exists, it will be taken.
221         * However, if no bean of that name exists, a by-type resolution of the
222         * dependency will be attempted if this flag is "true".
223         * <p>Default is "true". Switch this flag to "false" in order to enforce a
224         * by-name lookup in all cases, throwing an exception in case of no name match.
225         * @see org.springframework.beans.factory.config.AutowireCapableBeanFactory#resolveDependency
226         */
227        public void setFallbackToDefaultTypeMatch(boolean fallbackToDefaultTypeMatch) {
228                this.fallbackToDefaultTypeMatch = fallbackToDefaultTypeMatch;
229        }
230
231        /**
232         * Set whether to always use JNDI lookups equivalent to standard Java EE 5 resource
233         * injection, <b>even for {@code name} attributes and default names</b>.
234         * <p>Default is "false": Resource names are used for Spring bean lookups in the
235         * containing BeanFactory; only {@code mappedName} attributes point directly
236         * into JNDI. Switch this flag to "true" for enforcing Java EE style JNDI lookups
237         * in any case, even for {@code name} attributes and default names.
238         * @see #setJndiFactory
239         * @see #setResourceFactory
240         */
241        public void setAlwaysUseJndiLookup(boolean alwaysUseJndiLookup) {
242                this.alwaysUseJndiLookup = alwaysUseJndiLookup;
243        }
244
245        /**
246         * Specify the factory for objects to be injected into {@code @Resource} /
247         * {@code @WebServiceRef} / {@code @EJB} annotated fields and setter methods,
248         * <b>for {@code mappedName} attributes that point directly into JNDI</b>.
249         * This factory will also be used if "alwaysUseJndiLookup" is set to "true" in order
250         * to enforce JNDI lookups even for {@code name} attributes and default names.
251         * <p>The default is a {@link org.springframework.jndi.support.SimpleJndiBeanFactory}
252         * for JNDI lookup behavior equivalent to standard Java EE 5 resource injection.
253         * @see #setResourceFactory
254         * @see #setAlwaysUseJndiLookup
255         */
256        public void setJndiFactory(BeanFactory jndiFactory) {
257                Assert.notNull(jndiFactory, "BeanFactory must not be null");
258                this.jndiFactory = jndiFactory;
259        }
260
261        /**
262         * Specify the factory for objects to be injected into {@code @Resource} /
263         * {@code @WebServiceRef} / {@code @EJB} annotated fields and setter methods,
264         * <b>for {@code name} attributes and default names</b>.
265         * <p>The default is the BeanFactory that this post-processor is defined in,
266         * if any, looking up resource names as Spring bean names. Specify the resource
267         * factory explicitly for programmatic usage of this post-processor.
268         * <p>Specifying Spring's {@link org.springframework.jndi.support.SimpleJndiBeanFactory}
269         * leads to JNDI lookup behavior equivalent to standard Java EE 5 resource injection,
270         * even for {@code name} attributes and default names. This is the same behavior
271         * that the "alwaysUseJndiLookup" flag enables.
272         * @see #setAlwaysUseJndiLookup
273         */
274        public void setResourceFactory(BeanFactory resourceFactory) {
275                Assert.notNull(resourceFactory, "BeanFactory must not be null");
276                this.resourceFactory = resourceFactory;
277        }
278
279        @Override
280        public void setBeanFactory(BeanFactory beanFactory) {
281                Assert.notNull(beanFactory, "BeanFactory must not be null");
282                this.beanFactory = beanFactory;
283                if (this.resourceFactory == null) {
284                        this.resourceFactory = beanFactory;
285                }
286                if (beanFactory instanceof ConfigurableBeanFactory) {
287                        this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
288                }
289        }
290
291
292        @Override
293        public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
294                super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
295                InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
296                metadata.checkConfigMembers(beanDefinition);
297        }
298
299        @Override
300        public void resetBeanDefinition(String beanName) {
301                this.injectionMetadataCache.remove(beanName);
302        }
303
304        @Override
305        public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
306                return null;
307        }
308
309        @Override
310        public boolean postProcessAfterInstantiation(Object bean, String beanName) {
311                return true;
312        }
313
314        @Override
315        public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
316                InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
317                try {
318                        metadata.inject(bean, beanName, pvs);
319                }
320                catch (Throwable ex) {
321                        throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
322                }
323                return pvs;
324        }
325
326        @Deprecated
327        @Override
328        public PropertyValues postProcessPropertyValues(
329                        PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {
330
331                return postProcessProperties(pvs, bean, beanName);
332        }
333
334
335        private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
336                // Fall back to class name as cache key, for backwards compatibility with custom callers.
337                String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
338                // Quick check on the concurrent map first, with minimal locking.
339                InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
340                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
341                        synchronized (this.injectionMetadataCache) {
342                                metadata = this.injectionMetadataCache.get(cacheKey);
343                                if (InjectionMetadata.needsRefresh(metadata, clazz)) {
344                                        if (metadata != null) {
345                                                metadata.clear(pvs);
346                                        }
347                                        metadata = buildResourceMetadata(clazz);
348                                        this.injectionMetadataCache.put(cacheKey, metadata);
349                                }
350                        }
351                }
352                return metadata;
353        }
354
355        private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
356                if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
357                        return InjectionMetadata.EMPTY;
358                }
359
360                List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
361                Class<?> targetClass = clazz;
362
363                do {
364                        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
365
366                        ReflectionUtils.doWithLocalFields(targetClass, field -> {
367                                if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {
368                                        if (Modifier.isStatic(field.getModifiers())) {
369                                                throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields");
370                                        }
371                                        currElements.add(new WebServiceRefElement(field, field, null));
372                                }
373                                else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
374                                        if (Modifier.isStatic(field.getModifiers())) {
375                                                throw new IllegalStateException("@EJB annotation is not supported on static fields");
376                                        }
377                                        currElements.add(new EjbRefElement(field, field, null));
378                                }
379                                else if (field.isAnnotationPresent(Resource.class)) {
380                                        if (Modifier.isStatic(field.getModifiers())) {
381                                                throw new IllegalStateException("@Resource annotation is not supported on static fields");
382                                        }
383                                        if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
384                                                currElements.add(new ResourceElement(field, field, null));
385                                        }
386                                }
387                        });
388
389                        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
390                                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
391                                if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
392                                        return;
393                                }
394                                if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
395                                        if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
396                                                if (Modifier.isStatic(method.getModifiers())) {
397                                                        throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");
398                                                }
399                                                if (method.getParameterCount() != 1) {
400                                                        throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method);
401                                                }
402                                                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
403                                                currElements.add(new WebServiceRefElement(method, bridgedMethod, pd));
404                                        }
405                                        else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
406                                                if (Modifier.isStatic(method.getModifiers())) {
407                                                        throw new IllegalStateException("@EJB annotation is not supported on static methods");
408                                                }
409                                                if (method.getParameterCount() != 1) {
410                                                        throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
411                                                }
412                                                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
413                                                currElements.add(new EjbRefElement(method, bridgedMethod, pd));
414                                        }
415                                        else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
416                                                if (Modifier.isStatic(method.getModifiers())) {
417                                                        throw new IllegalStateException("@Resource annotation is not supported on static methods");
418                                                }
419                                                Class<?>[] paramTypes = method.getParameterTypes();
420                                                if (paramTypes.length != 1) {
421                                                        throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
422                                                }
423                                                if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
424                                                        PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
425                                                        currElements.add(new ResourceElement(method, bridgedMethod, pd));
426                                                }
427                                        }
428                                }
429                        });
430
431                        elements.addAll(0, currElements);
432                        targetClass = targetClass.getSuperclass();
433                }
434                while (targetClass != null && targetClass != Object.class);
435
436                return InjectionMetadata.forElements(elements, clazz);
437        }
438
439        /**
440         * Obtain a lazily resolving resource proxy for the given name and type,
441         * delegating to {@link #getResource} on demand once a method call comes in.
442         * @param element the descriptor for the annotated field/method
443         * @param requestingBeanName the name of the requesting bean
444         * @return the resource object (never {@code null})
445         * @since 4.2
446         * @see #getResource
447         * @see Lazy
448         */
449        protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {
450                TargetSource ts = new TargetSource() {
451                        @Override
452                        public Class<?> getTargetClass() {
453                                return element.lookupType;
454                        }
455                        @Override
456                        public boolean isStatic() {
457                                return false;
458                        }
459                        @Override
460                        public Object getTarget() {
461                                return getResource(element, requestingBeanName);
462                        }
463                        @Override
464                        public void releaseTarget(Object target) {
465                        }
466                };
467                ProxyFactory pf = new ProxyFactory();
468                pf.setTargetSource(ts);
469                if (element.lookupType.isInterface()) {
470                        pf.addInterface(element.lookupType);
471                }
472                ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
473                                ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);
474                return pf.getProxy(classLoader);
475        }
476
477        /**
478         * Obtain the resource object for the given name and type.
479         * @param element the descriptor for the annotated field/method
480         * @param requestingBeanName the name of the requesting bean
481         * @return the resource object (never {@code null})
482         * @throws NoSuchBeanDefinitionException if no corresponding target resource found
483         */
484        protected Object getResource(LookupElement element, @Nullable String requestingBeanName)
485                        throws NoSuchBeanDefinitionException {
486
487                if (StringUtils.hasLength(element.mappedName)) {
488                        return this.jndiFactory.getBean(element.mappedName, element.lookupType);
489                }
490                if (this.alwaysUseJndiLookup) {
491                        return this.jndiFactory.getBean(element.name, element.lookupType);
492                }
493                if (this.resourceFactory == null) {
494                        throw new NoSuchBeanDefinitionException(element.lookupType,
495                                        "No resource factory configured - specify the 'resourceFactory' property");
496                }
497                return autowireResource(this.resourceFactory, element, requestingBeanName);
498        }
499
500        /**
501         * Obtain a resource object for the given name and type through autowiring
502         * based on the given factory.
503         * @param factory the factory to autowire against
504         * @param element the descriptor for the annotated field/method
505         * @param requestingBeanName the name of the requesting bean
506         * @return the resource object (never {@code null})
507         * @throws NoSuchBeanDefinitionException if no corresponding target resource found
508         */
509        protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
510                        throws NoSuchBeanDefinitionException {
511
512                Object resource;
513                Set<String> autowiredBeanNames;
514                String name = element.name;
515
516                if (factory instanceof AutowireCapableBeanFactory) {
517                        AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
518                        DependencyDescriptor descriptor = element.getDependencyDescriptor();
519                        if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
520                                autowiredBeanNames = new LinkedHashSet<>();
521                                resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
522                                if (resource == null) {
523                                        throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
524                                }
525                        }
526                        else {
527                                resource = beanFactory.resolveBeanByName(name, descriptor);
528                                autowiredBeanNames = Collections.singleton(name);
529                        }
530                }
531                else {
532                        resource = factory.getBean(name, element.lookupType);
533                        autowiredBeanNames = Collections.singleton(name);
534                }
535
536                if (factory instanceof ConfigurableBeanFactory) {
537                        ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
538                        for (String autowiredBeanName : autowiredBeanNames) {
539                                if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
540                                        beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
541                                }
542                        }
543                }
544
545                return resource;
546        }
547
548
549        @SuppressWarnings("unchecked")
550        @Nullable
551        private static Class<? extends Annotation> loadAnnotationType(String name) {
552                try {
553                        return (Class<? extends Annotation>)
554                                        ClassUtils.forName(name, CommonAnnotationBeanPostProcessor.class.getClassLoader());
555                }
556                catch (ClassNotFoundException ex) {
557                        return null;
558                }
559        }
560
561
562        /**
563         * Class representing generic injection information about an annotated field
564         * or setter method, supporting @Resource and related annotations.
565         */
566        protected abstract static class LookupElement extends InjectionMetadata.InjectedElement {
567
568                protected String name = "";
569
570                protected boolean isDefaultName = false;
571
572                protected Class<?> lookupType = Object.class;
573
574                @Nullable
575                protected String mappedName;
576
577                public LookupElement(Member member, @Nullable PropertyDescriptor pd) {
578                        super(member, pd);
579                }
580
581                /**
582                 * Return the resource name for the lookup.
583                 */
584                public final String getName() {
585                        return this.name;
586                }
587
588                /**
589                 * Return the desired type for the lookup.
590                 */
591                public final Class<?> getLookupType() {
592                        return this.lookupType;
593                }
594
595                /**
596                 * Build a DependencyDescriptor for the underlying field/method.
597                 */
598                public final DependencyDescriptor getDependencyDescriptor() {
599                        if (this.isField) {
600                                return new LookupDependencyDescriptor((Field) this.member, this.lookupType);
601                        }
602                        else {
603                                return new LookupDependencyDescriptor((Method) this.member, this.lookupType);
604                        }
605                }
606        }
607
608
609        /**
610         * Class representing injection information about an annotated field
611         * or setter method, supporting the @Resource annotation.
612         */
613        private class ResourceElement extends LookupElement {
614
615                private final boolean lazyLookup;
616
617                public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
618                        super(member, pd);
619                        Resource resource = ae.getAnnotation(Resource.class);
620                        String resourceName = resource.name();
621                        Class<?> resourceType = resource.type();
622                        this.isDefaultName = !StringUtils.hasLength(resourceName);
623                        if (this.isDefaultName) {
624                                resourceName = this.member.getName();
625                                if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
626                                        resourceName = Introspector.decapitalize(resourceName.substring(3));
627                                }
628                        }
629                        else if (embeddedValueResolver != null) {
630                                resourceName = embeddedValueResolver.resolveStringValue(resourceName);
631                        }
632                        if (Object.class != resourceType) {
633                                checkResourceType(resourceType);
634                        }
635                        else {
636                                // No resource type specified... check field/method.
637                                resourceType = getResourceType();
638                        }
639                        this.name = (resourceName != null ? resourceName : "");
640                        this.lookupType = resourceType;
641                        String lookupValue = resource.lookup();
642                        this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
643                        Lazy lazy = ae.getAnnotation(Lazy.class);
644                        this.lazyLookup = (lazy != null && lazy.value());
645                }
646
647                @Override
648                protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
649                        return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
650                                        getResource(this, requestingBeanName));
651                }
652        }
653
654
655        /**
656         * Class representing injection information about an annotated field
657         * or setter method, supporting the @WebServiceRef annotation.
658         */
659        private class WebServiceRefElement extends LookupElement {
660
661                private final Class<?> elementType;
662
663                private final String wsdlLocation;
664
665                public WebServiceRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
666                        super(member, pd);
667                        WebServiceRef resource = ae.getAnnotation(WebServiceRef.class);
668                        String resourceName = resource.name();
669                        Class<?> resourceType = resource.type();
670                        this.isDefaultName = !StringUtils.hasLength(resourceName);
671                        if (this.isDefaultName) {
672                                resourceName = this.member.getName();
673                                if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
674                                        resourceName = Introspector.decapitalize(resourceName.substring(3));
675                                }
676                        }
677                        if (Object.class != resourceType) {
678                                checkResourceType(resourceType);
679                        }
680                        else {
681                                // No resource type specified... check field/method.
682                                resourceType = getResourceType();
683                        }
684                        this.name = resourceName;
685                        this.elementType = resourceType;
686                        if (Service.class.isAssignableFrom(resourceType)) {
687                                this.lookupType = resourceType;
688                        }
689                        else {
690                                this.lookupType = resource.value();
691                        }
692                        this.mappedName = resource.mappedName();
693                        this.wsdlLocation = resource.wsdlLocation();
694                }
695
696                @Override
697                protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
698                        Service service;
699                        try {
700                                service = (Service) getResource(this, requestingBeanName);
701                        }
702                        catch (NoSuchBeanDefinitionException notFound) {
703                                // Service to be created through generated class.
704                                if (Service.class == this.lookupType) {
705                                        throw new IllegalStateException("No resource with name '" + this.name + "' found in context, " +
706                                                        "and no specific JAX-WS Service subclass specified. The typical solution is to either specify " +
707                                                        "a LocalJaxWsServiceFactoryBean with the given name or to specify the (generated) Service " +
708                                                        "subclass as @WebServiceRef(...) value.");
709                                }
710                                if (StringUtils.hasLength(this.wsdlLocation)) {
711                                        try {
712                                                Constructor<?> ctor = this.lookupType.getConstructor(URL.class, QName.class);
713                                                WebServiceClient clientAnn = this.lookupType.getAnnotation(WebServiceClient.class);
714                                                if (clientAnn == null) {
715                                                        throw new IllegalStateException("JAX-WS Service class [" + this.lookupType.getName() +
716                                                                        "] does not carry a WebServiceClient annotation");
717                                                }
718                                                service = (Service) BeanUtils.instantiateClass(ctor,
719                                                                new URL(this.wsdlLocation), new QName(clientAnn.targetNamespace(), clientAnn.name()));
720                                        }
721                                        catch (NoSuchMethodException ex) {
722                                                throw new IllegalStateException("JAX-WS Service class [" + this.lookupType.getName() +
723                                                                "] does not have a (URL, QName) constructor. Cannot apply specified WSDL location [" +
724                                                                this.wsdlLocation + "].");
725                                        }
726                                        catch (MalformedURLException ex) {
727                                                throw new IllegalArgumentException(
728                                                                "Specified WSDL location [" + this.wsdlLocation + "] isn't a valid URL");
729                                        }
730                                }
731                                else {
732                                        service = (Service) BeanUtils.instantiateClass(this.lookupType);
733                                }
734                        }
735                        return service.getPort(this.elementType);
736                }
737        }
738
739
740        /**
741         * Class representing injection information about an annotated field
742         * or setter method, supporting the @EJB annotation.
743         */
744        private class EjbRefElement extends LookupElement {
745
746                private final String beanName;
747
748                public EjbRefElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
749                        super(member, pd);
750                        EJB resource = ae.getAnnotation(EJB.class);
751                        String resourceBeanName = resource.beanName();
752                        String resourceName = resource.name();
753                        this.isDefaultName = !StringUtils.hasLength(resourceName);
754                        if (this.isDefaultName) {
755                                resourceName = this.member.getName();
756                                if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
757                                        resourceName = Introspector.decapitalize(resourceName.substring(3));
758                                }
759                        }
760                        Class<?> resourceType = resource.beanInterface();
761                        if (Object.class != resourceType) {
762                                checkResourceType(resourceType);
763                        }
764                        else {
765                                // No resource type specified... check field/method.
766                                resourceType = getResourceType();
767                        }
768                        this.beanName = resourceBeanName;
769                        this.name = resourceName;
770                        this.lookupType = resourceType;
771                        this.mappedName = resource.mappedName();
772                }
773
774                @Override
775                protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
776                        if (StringUtils.hasLength(this.beanName)) {
777                                if (beanFactory != null && beanFactory.containsBean(this.beanName)) {
778                                        // Local match found for explicitly specified local bean name.
779                                        Object bean = beanFactory.getBean(this.beanName, this.lookupType);
780                                        if (requestingBeanName != null && beanFactory instanceof ConfigurableBeanFactory) {
781                                                ((ConfigurableBeanFactory) beanFactory).registerDependentBean(this.beanName, requestingBeanName);
782                                        }
783                                        return bean;
784                                }
785                                else if (this.isDefaultName && !StringUtils.hasLength(this.mappedName)) {
786                                        throw new NoSuchBeanDefinitionException(this.beanName,
787                                                        "Cannot resolve 'beanName' in local BeanFactory. Consider specifying a general 'name' value instead.");
788                                }
789                        }
790                        // JNDI name lookup - may still go to a local BeanFactory.
791                        return getResource(this, requestingBeanName);
792                }
793        }
794
795
796        /**
797         * Extension of the DependencyDescriptor class,
798         * overriding the dependency type with the specified resource type.
799         */
800        private static class LookupDependencyDescriptor extends DependencyDescriptor {
801
802                private final Class<?> lookupType;
803
804                public LookupDependencyDescriptor(Field field, Class<?> lookupType) {
805                        super(field, true);
806                        this.lookupType = lookupType;
807                }
808
809                public LookupDependencyDescriptor(Method method, Class<?> lookupType) {
810                        super(new MethodParameter(method, 0), true);
811                        this.lookupType = lookupType;
812                }
813
814                @Override
815                public Class<?> getDependencyType() {
816                        return this.lookupType;
817                }
818        }
819
820}