001/*
002 * Copyright 2002-2017 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.util.HashMap;
020import java.util.Map;
021import java.util.Set;
022
023import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
024import org.springframework.beans.factory.config.BeanDefinition;
025import org.springframework.lang.Nullable;
026
027/**
028 * Simple {@link ScopeMetadataResolver} implementation that follows JSR-330 scoping rules:
029 * defaulting to prototype scope unless {@link javax.inject.Singleton} is present.
030 *
031 * <p>This scope resolver can be used with {@link ClassPathBeanDefinitionScanner} and
032 * {@link AnnotatedBeanDefinitionReader} for standard JSR-330 compliance. However,
033 * in practice, you will typically use Spring's rich default scoping instead - or extend
034 * this resolver with custom scoping annotations that point to extended Spring scopes.
035 *
036 * @author Juergen Hoeller
037 * @since 3.0
038 * @see #registerScope
039 * @see #resolveScopeName
040 * @see ClassPathBeanDefinitionScanner#setScopeMetadataResolver
041 * @see AnnotatedBeanDefinitionReader#setScopeMetadataResolver
042 */
043public class Jsr330ScopeMetadataResolver implements ScopeMetadataResolver {
044
045        private final Map<String, String> scopeMap = new HashMap<>();
046
047
048        public Jsr330ScopeMetadataResolver() {
049                registerScope("javax.inject.Singleton", BeanDefinition.SCOPE_SINGLETON);
050        }
051
052
053        /**
054         * Register an extended JSR-330 scope annotation, mapping it onto a
055         * specific Spring scope by name.
056         * @param annotationType the JSR-330 annotation type as a Class
057         * @param scopeName the Spring scope name
058         */
059        public final void registerScope(Class<?> annotationType, String scopeName) {
060                this.scopeMap.put(annotationType.getName(), scopeName);
061        }
062
063        /**
064         * Register an extended JSR-330 scope annotation, mapping it onto a
065         * specific Spring scope by name.
066         * @param annotationType the JSR-330 annotation type by name
067         * @param scopeName the Spring scope name
068         */
069        public final void registerScope(String annotationType, String scopeName) {
070                this.scopeMap.put(annotationType, scopeName);
071        }
072
073        /**
074         * Resolve the given annotation type into a named Spring scope.
075         * <p>The default implementation simply checks against registered scopes.
076         * Can be overridden for custom mapping rules, e.g. naming conventions.
077         * @param annotationType the JSR-330 annotation type
078         * @return the Spring scope name
079         */
080        @Nullable
081        protected String resolveScopeName(String annotationType) {
082                return this.scopeMap.get(annotationType);
083        }
084
085
086        @Override
087        public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
088                ScopeMetadata metadata = new ScopeMetadata();
089                metadata.setScopeName(BeanDefinition.SCOPE_PROTOTYPE);
090                if (definition instanceof AnnotatedBeanDefinition) {
091                        AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
092                        Set<String> annTypes = annDef.getMetadata().getAnnotationTypes();
093                        String found = null;
094                        for (String annType : annTypes) {
095                                Set<String> metaAnns = annDef.getMetadata().getMetaAnnotationTypes(annType);
096                                if (metaAnns.contains("javax.inject.Scope")) {
097                                        if (found != null) {
098                                                throw new IllegalStateException("Found ambiguous scope annotations on bean class [" +
099                                                                definition.getBeanClassName() + "]: " + found + ", " + annType);
100                                        }
101                                        found = annType;
102                                        String scopeName = resolveScopeName(annType);
103                                        if (scopeName == null) {
104                                                throw new IllegalStateException(
105                                                                "Unsupported scope annotation - not mapped onto Spring scope name: " + annType);
106                                        }
107                                        metadata.setScopeName(scopeName);
108                                }
109                        }
110                }
111                return metadata;
112        }
113
114}