001/*
002 * Copyright 2002-2015 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.jndi.support;
018
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import javax.naming.NameNotFoundException;
025import javax.naming.NamingException;
026
027import org.springframework.beans.BeansException;
028import org.springframework.beans.factory.BeanDefinitionStoreException;
029import org.springframework.beans.factory.BeanFactory;
030import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
031import org.springframework.beans.factory.NoSuchBeanDefinitionException;
032import org.springframework.core.ResolvableType;
033import org.springframework.jndi.JndiLocatorSupport;
034import org.springframework.jndi.TypeMismatchNamingException;
035
036/**
037 * Simple JNDI-based implementation of Spring's
038 * {@link org.springframework.beans.factory.BeanFactory} interface.
039 * Does not support enumerating bean definitions, hence doesn't implement
040 * the {@link org.springframework.beans.factory.ListableBeanFactory} interface.
041 *
042 * <p>This factory resolves given bean names as JNDI names within the
043 * J2EE application's "java:comp/env/" namespace. It caches the resolved
044 * types for all obtained objects, and optionally also caches shareable
045 * objects (if they are explicitly marked as
046 * {@link #addShareableResource shareable resource}.
047 *
048 * <p>The main intent of this factory is usage in combination with Spring's
049 * {@link org.springframework.context.annotation.CommonAnnotationBeanPostProcessor},
050 * configured as "resourceFactory" for resolving {@code @Resource}
051 * annotations as JNDI objects without intermediate bean definitions.
052 * It may be used for similar lookup scenarios as well, of course,
053 * in particular if BeanFactory-style type checking is required.
054 *
055 * @author Juergen Hoeller
056 * @since 2.5
057 * @see org.springframework.beans.factory.support.DefaultListableBeanFactory
058 * @see org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
059 */
060public class SimpleJndiBeanFactory extends JndiLocatorSupport implements BeanFactory {
061
062        /** JNDI names of resources that are known to be shareable, i.e. can be cached */
063        private final Set<String> shareableResources = new HashSet<String>();
064
065        /** Cache of shareable singleton objects: bean name --> bean instance */
066        private final Map<String, Object> singletonObjects = new HashMap<String, Object>();
067
068        /** Cache of the types of nonshareable resources: bean name --> bean type */
069        private final Map<String, Class<?>> resourceTypes = new HashMap<String, Class<?>>();
070
071
072        public SimpleJndiBeanFactory() {
073                setResourceRef(true);
074        }
075
076
077        /**
078         * Add the name of a shareable JNDI resource,
079         * which this factory is allowed to cache once obtained.
080         * @param shareableResource the JNDI name
081         * (typically within the "java:comp/env/" namespace)
082         */
083        public void addShareableResource(String shareableResource) {
084                this.shareableResources.add(shareableResource);
085        }
086
087        /**
088         * Set a list of names of shareable JNDI resources,
089         * which this factory is allowed to cache once obtained.
090         * @param shareableResources the JNDI names
091         * (typically within the "java:comp/env/" namespace)
092         */
093        public void setShareableResources(String... shareableResources) {
094                this.shareableResources.addAll(Arrays.asList(shareableResources));
095        }
096
097
098        //---------------------------------------------------------------------
099        // Implementation of BeanFactory interface
100        //---------------------------------------------------------------------
101
102
103        @Override
104        public Object getBean(String name) throws BeansException {
105                return getBean(name, Object.class);
106        }
107
108        @Override
109        public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
110                try {
111                        if (isSingleton(name)) {
112                                return doGetSingleton(name, requiredType);
113                        }
114                        else {
115                                return lookup(name, requiredType);
116                        }
117                }
118                catch (NameNotFoundException ex) {
119                        throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment");
120                }
121                catch (TypeMismatchNamingException ex) {
122                        throw new BeanNotOfRequiredTypeException(name, ex.getRequiredType(), ex.getActualType());
123                }
124                catch (NamingException ex) {
125                        throw new BeanDefinitionStoreException("JNDI environment", name, "JNDI lookup failed", ex);
126                }
127        }
128
129        @Override
130        public <T> T getBean(Class<T> requiredType) throws BeansException {
131                return getBean(requiredType.getSimpleName(), requiredType);
132        }
133
134        @Override
135        public Object getBean(String name, Object... args) throws BeansException {
136                if (args != null) {
137                        throw new UnsupportedOperationException(
138                                        "SimpleJndiBeanFactory does not support explicit bean creation arguments");
139                }
140                return getBean(name);
141        }
142
143        @Override
144        public <T> T getBean(Class<T> requiredType, Object... args) throws BeansException {
145                if (args != null) {
146                        throw new UnsupportedOperationException(
147                                        "SimpleJndiBeanFactory does not support explicit bean creation arguments");
148                }
149                return getBean(requiredType);
150        }
151
152        @Override
153        public boolean containsBean(String name) {
154                if (this.singletonObjects.containsKey(name) || this.resourceTypes.containsKey(name)) {
155                        return true;
156                }
157                try {
158                        doGetType(name);
159                        return true;
160                }
161                catch (NamingException ex) {
162                        return false;
163                }
164        }
165
166        @Override
167        public boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
168                return this.shareableResources.contains(name);
169        }
170
171        @Override
172        public boolean isPrototype(String name) throws NoSuchBeanDefinitionException {
173                return !this.shareableResources.contains(name);
174        }
175
176        @Override
177        public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
178                Class<?> type = getType(name);
179                return (type != null && typeToMatch.isAssignableFrom(type));
180        }
181
182        @Override
183        public boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException {
184                Class<?> type = getType(name);
185                return (typeToMatch == null || (type != null && typeToMatch.isAssignableFrom(type)));
186        }
187
188        @Override
189        public Class<?> getType(String name) throws NoSuchBeanDefinitionException {
190                try {
191                        return doGetType(name);
192                }
193                catch (NameNotFoundException ex) {
194                        throw new NoSuchBeanDefinitionException(name, "not found in JNDI environment");
195                }
196                catch (NamingException ex) {
197                        return null;
198                }
199        }
200
201        @Override
202        public String[] getAliases(String name) {
203                return new String[0];
204        }
205
206
207        @SuppressWarnings("unchecked")
208        private <T> T doGetSingleton(String name, Class<T> requiredType) throws NamingException {
209                synchronized (this.singletonObjects) {
210                        if (this.singletonObjects.containsKey(name)) {
211                                Object jndiObject = this.singletonObjects.get(name);
212                                if (requiredType != null && !requiredType.isInstance(jndiObject)) {
213                                        throw new TypeMismatchNamingException(
214                                                        convertJndiName(name), requiredType, (jndiObject != null ? jndiObject.getClass() : null));
215                                }
216                                return (T) jndiObject;
217                        }
218                        T jndiObject = lookup(name, requiredType);
219                        this.singletonObjects.put(name, jndiObject);
220                        return jndiObject;
221                }
222        }
223
224        private Class<?> doGetType(String name) throws NamingException {
225                if (isSingleton(name)) {
226                        Object jndiObject = doGetSingleton(name, null);
227                        return (jndiObject != null ? jndiObject.getClass() : null);
228                }
229                else {
230                        synchronized (this.resourceTypes) {
231                                if (this.resourceTypes.containsKey(name)) {
232                                        return this.resourceTypes.get(name);
233                                }
234                                else {
235                                        Object jndiObject = lookup(name, null);
236                                        Class<?> type = (jndiObject != null ? jndiObject.getClass() : null);
237                                        this.resourceTypes.put(name, type);
238                                        return type;
239                                }
240                        }
241                }
242        }
243
244}