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}