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