001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.spring; 018 019import java.lang.ref.WeakReference; 020import java.lang.reflect.Field; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Iterator; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import org.apache.wicket.core.util.lang.WicketObjects; 029import org.apache.wicket.proxy.IProxyTargetLocator; 030import org.apache.wicket.util.lang.Args; 031import org.apache.wicket.util.lang.Objects; 032import org.springframework.beans.factory.NoSuchBeanDefinitionException; 033import org.springframework.beans.factory.config.BeanDefinition; 034import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 035import org.springframework.beans.factory.support.RootBeanDefinition; 036import org.springframework.context.ApplicationContext; 037import org.springframework.context.support.AbstractApplicationContext; 038import org.springframework.core.ResolvableType; 039 040/** 041 * Implementation of {@link IProxyTargetLocator} that can locate beans within a spring application 042 * context. Beans are looked up by the combination of name and type, if name is omitted only type is 043 * used. 044 * 045 * @author Igor Vaynberg (ivaynberg) 046 * @author Istvan Devai 047 * @author Tobias Soloschenko 048 */ 049public class SpringBeanLocator implements IProxyTargetLocator 050{ 051 private static final long serialVersionUID = 1L; 052 053 // Weak reference so we don't hold up WebApp classloader garbage collection. 054 private transient WeakReference<Class<?>> beanTypeCache; 055 056 private final String beanTypeName; 057 058 private String beanName; 059 060 private ISpringContextLocator springContextLocator; 061 062 private Boolean singletonCache = null; 063 064 /** 065 * Resolvable type for field to inject 066 */ 067 private ResolvableType fieldResolvableType; 068 069 /** 070 * If the field to inject is a list this is the resolvable type of its elements 071 */ 072 private ResolvableType fieldElementsResolvableType; 073 074 /** 075 * Constructor 076 * 077 * @param beanType 078 * bean class 079 * @param locator 080 * spring context locator 081 */ 082 public SpringBeanLocator(final Class<?> beanType, final ISpringContextLocator locator) 083 { 084 this(null, beanType, null, locator); 085 } 086 087 public SpringBeanLocator(final String beanName, final Class<?> beanType, 088 final ISpringContextLocator locator) 089 { 090 this(beanName, beanType, null, locator); 091 } 092 093 /** 094 * Constructor 095 * 096 * @param beanType 097 * bean class 098 * @param locator 099 * spring context locator 100 */ 101 public SpringBeanLocator(final Class<?> beanType, Field beanField, 102 final ISpringContextLocator locator) 103 { 104 this(null, beanType, beanField, locator); 105 } 106 107 /** 108 * Constructor 109 * 110 * @param beanName 111 * bean name 112 * @param beanType 113 * bean class 114 * @param locator 115 * spring context locator 116 */ 117 public SpringBeanLocator(final String beanName, final Class<?> beanType, Field beanField, 118 final ISpringContextLocator locator) 119 { 120 Args.notNull(locator, "locator"); 121 Args.notNull(beanType, "beanType"); 122 123 this.beanName = beanName; 124 beanTypeCache = new WeakReference<Class<?>>(beanType); 125 beanTypeName = beanType.getName(); 126 springContextLocator = locator; 127 128 if (beanField != null) 129 { 130 fieldResolvableType = ResolvableType.forField(beanField); 131 fieldElementsResolvableType = extractElementGeneric(fieldResolvableType); 132 } 133 } 134 135 /** 136 * If the field type is a collection (Map, Set or List) extracts type 137 * information about its elements. 138 * 139 * @param fieldResolvableType 140 * the resolvable type of the field 141 * @return the resolvable type of elements of the field, if any. 142 */ 143 private ResolvableType extractElementGeneric(ResolvableType fieldResolvableType) 144 { 145 Class<?> clazz = fieldResolvableType.resolve(); 146 147 if (Set.class.isAssignableFrom(clazz) || List.class.isAssignableFrom(clazz)) 148 { 149 return fieldResolvableType.getGeneric(); 150 } 151 else if (Map.class.isAssignableFrom(clazz)) 152 { 153 return fieldResolvableType.getGeneric(1); 154 } 155 156 return null; 157 } 158 159 /** 160 * @return returns whether the bean (the locator is supposed to istantiate) is a singleton or 161 * not 162 */ 163 public boolean isSingletonBean() 164 { 165 if (singletonCache == null) 166 { 167 singletonCache = getBeanName() != null && 168 getSpringContext().isSingleton(getBeanName()); 169 } 170 return singletonCache; 171 } 172 173 /** 174 * @return bean class this locator is configured with 175 */ 176 public Class<?> getBeanType() 177 { 178 Class<?> clazz = beanTypeCache == null ? null : beanTypeCache.get(); 179 if (clazz == null) 180 { 181 beanTypeCache = new WeakReference<>( 182 clazz = WicketObjects.resolveClass(beanTypeName)); 183 if (clazz == null) 184 { 185 throw new RuntimeException("SpringBeanLocator could not find class [" + 186 beanTypeName + "] needed to locate the [" + 187 ((beanName != null) ? (beanName) : ("bean name not specified")) + "] bean"); 188 } 189 } 190 return clazz; 191 } 192 193 @Override 194 public Object locateProxyTarget() 195 { 196 final ApplicationContext context = getSpringContext(); 197 198 return lookupSpringBean(context, beanName, getBeanType()); 199 } 200 201 /** 202 * 203 * @return ApplicationContext 204 */ 205 private ApplicationContext getSpringContext() 206 { 207 final ApplicationContext context = springContextLocator.getSpringContext(); 208 209 if (context == null) 210 { 211 throw new IllegalStateException("spring application context locator returned null"); 212 } 213 return context; 214 } 215 216 /** 217 * @return bean name this locator is configured with 218 */ 219 public final String getBeanName() 220 { 221 return beanName; 222 } 223 224 /** 225 * @return context locator this locator is configured with 226 */ 227 public final ISpringContextLocator getSpringContextLocator() 228 { 229 return springContextLocator; 230 } 231 232 /** 233 * Looks up a bean by its name and class. Throws IllegalState exception if bean not found. 234 * 235 * @param ctx 236 * spring application context 237 * 238 * @param name 239 * bean name 240 * @param clazz 241 * bean class 242 * @throws java.lang.IllegalStateException 243 * @return found bean 244 */ 245 private Object lookupSpringBean(ApplicationContext ctx, String name, Class<?> clazz) 246 { 247 try 248 { 249 // If the name is set the lookup is clear 250 if (name != null) 251 { 252 return ctx.getBean(name, clazz); 253 } 254 255 // If the beanField information is null the clazz is going to be used 256 if (fieldResolvableType == null) 257 { 258 return ctx.getBean(clazz); 259 } 260 261 // If the given class is a list try to get the generic of the list 262 Class<?> lookupClass = fieldElementsResolvableType != null ? 263 fieldElementsResolvableType.resolve() : clazz; 264 265 // Else the lookup is done via Generic 266 List<String> names = loadBeanNames(ctx, lookupClass); 267 268 Object foundBeans = getBeansByName(ctx, names); 269 270 if(foundBeans != null) 271 { 272 return foundBeans; 273 } 274 275 throw new IllegalStateException( 276 "Concrete bean could not be received from the application context for class: " + 277 clazz.getName() + "."); 278 } 279 catch (NoSuchBeanDefinitionException e) 280 { 281 throw new IllegalStateException("bean with name [" + name + "] and class [" + 282 clazz.getName() + "] not found", e); 283 } 284 } 285 286 /** 287 * Returns a list of candidate names for the given class. 288 * 289 * @param ctx 290 * spring application context 291 * @param lookupClass 292 * the class to lookup 293 * @return a list of candidate names 294 */ 295 private List<String> loadBeanNames(ApplicationContext ctx, Class<?> lookupClass) 296 { 297 List<String> beanNames = new ArrayList<>(); 298 Class<?> fieldType = getBeanType(); 299 String[] beanNamesArr = ctx.getBeanNamesForType(fieldType); 300 301 //add names for field class 302 beanNames.addAll(Arrays.asList(beanNamesArr)); 303 304 //add names for lookup class 305 if (lookupClass != fieldType) 306 { 307 beanNamesArr = ctx.getBeanNamesForType(lookupClass); 308 beanNames.addAll(Arrays.asList(beanNamesArr)); 309 } 310 311 Iterator<String> nameIterator = beanNames.iterator(); 312 313 //filter those beans who don't have a definition (used internally by Spring) 314 while (nameIterator.hasNext()) 315 { 316 if (!ctx.containsBeanDefinition(nameIterator.next())) 317 { 318 nameIterator.remove(); 319 } 320 } 321 322 return beanNames; 323 } 324 325 /** 326 * Retrieves a list of beans or a single bean for the given list of names and assignable to the 327 * current field to inject. 328 * 329 * @param ctx 330 * spring application context. 331 * @param names 332 * the list of candidate names 333 * @return a list of matching beans or a single one. 334 */ 335 private Object getBeansByName(ApplicationContext ctx, List<String> names) 336 { 337 FieldBeansCollector beansCollector = new FieldBeansCollector(fieldResolvableType); 338 339 for (String beanName : names) 340 { 341 RootBeanDefinition beanDef = getBeanDefinition(ctx, beanName); 342 343 if (beanDef == null) 344 { 345 continue; 346 } 347 348 ResolvableType candidateResolvableType = null; 349 350 //check if we have the class of the bean or the factory method. 351 //Usually if use XML as config file we have the class while we 352 //have the factory method if we use Java-based configuration. 353 if (beanDef.hasBeanClass()) 354 { 355 candidateResolvableType = ResolvableType.forClass(beanDef.getBeanClass()); 356 } 357 else if (beanDef.getResolvedFactoryMethod() != null) 358 { 359 candidateResolvableType = ResolvableType.forMethodReturnType( 360 beanDef.getResolvedFactoryMethod()); 361 } 362 363 if (candidateResolvableType == null) 364 { 365 continue; 366 } 367 368 boolean exactMatch = fieldResolvableType.isAssignableFrom(candidateResolvableType); 369 boolean elementMatch = fieldElementsResolvableType != null && fieldElementsResolvableType.isAssignableFrom(candidateResolvableType); 370 371 if (exactMatch) 372 { 373 this.beanName = beanName; 374 return ctx.getBean(beanName); 375 } 376 377 if (elementMatch) 378 { 379 beansCollector.addBean(beanName, ctx.getBean(beanName)); 380 } 381 382 } 383 384 return beansCollector.getBeansToInject(); 385 } 386 387 @Override 388 public boolean equals(final Object obj) 389 { 390 if (obj instanceof SpringBeanLocator) 391 { 392 SpringBeanLocator other = (SpringBeanLocator)obj; 393 return beanTypeName.equals(other.beanTypeName) && 394 Objects.equal(beanName, other.beanName); 395 } 396 return false; 397 } 398 399 @Override 400 public int hashCode() 401 { 402 int hashcode = beanTypeName.hashCode(); 403 if (getBeanName() != null) 404 { 405 hashcode = hashcode + (127 * beanName.hashCode()); 406 } 407 return hashcode; 408 } 409 410 /** 411 * Gets the root bean definition for the given name. 412 * 413 * @param ctx 414 * spring application context. 415 * @param name 416 * bean name 417 * @return bean definition for the current name, null if such a definition is not found. 418 */ 419 public RootBeanDefinition getBeanDefinition(final ApplicationContext ctx, final String name) 420 { 421 ConfigurableListableBeanFactory beanFactory = ((AbstractApplicationContext)ctx).getBeanFactory(); 422 423 BeanDefinition beanDef = beanFactory.containsBean(name) ? 424 beanFactory.getMergedBeanDefinition(name) : null; 425 426 if (beanDef instanceof RootBeanDefinition) 427 { 428 return (RootBeanDefinition)beanDef; 429 } 430 431 return null; 432 } 433}