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