Repository: wicket Updated Branches: refs/heads/master 685142444 -> afc96936f
WICKET-5808 SpringBean, support generic beans Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/afc96936 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/afc96936 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/afc96936 Branch: refs/heads/master Commit: afc96936fda8cd712e626a6a3c3f64dabd99e2a5 Parents: 6851424 Author: Andrea Del Bene <[email protected]> Authored: Sun Jan 18 23:15:39 2015 +0100 Committer: Andrea Del Bene <[email protected]> Committed: Sun Jan 18 23:41:04 2015 +0100 ---------------------------------------------------------------------- .../apache/wicket/spring/SpringBeanLocator.java | 206 ++++++++++++++++++- .../annot/AnnotProxyFieldValueFactory.java | 37 ++-- .../apache/wicket/spring/BeanWithGeneric.java | 22 ++ .../annot/SpringBeanWithGenericsTest.java | 138 +++++++++++++ 4 files changed, 381 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/afc96936/wicket-spring/src/main/java/org/apache/wicket/spring/SpringBeanLocator.java ---------------------------------------------------------------------- diff --git a/wicket-spring/src/main/java/org/apache/wicket/spring/SpringBeanLocator.java b/wicket-spring/src/main/java/org/apache/wicket/spring/SpringBeanLocator.java index a279126..08404f8 100644 --- a/wicket-spring/src/main/java/org/apache/wicket/spring/SpringBeanLocator.java +++ b/wicket-spring/src/main/java/org/apache/wicket/spring/SpringBeanLocator.java @@ -17,13 +17,24 @@ package org.apache.wicket.spring; import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.wicket.core.util.lang.WicketObjects; import org.apache.wicket.proxy.IProxyTargetLocator; import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Objects; -import org.apache.wicket.core.util.lang.WicketObjects; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.core.GenericCollectionTypeResolver; +import org.springframework.core.ResolvableType; /** * Implementation of {@link IProxyTargetLocator} that can locate beans within a spring application @@ -32,6 +43,7 @@ import org.springframework.context.ApplicationContext; * * @author Igor Vaynberg (ivaynberg) * @author Istvan Devai + * @author Tobias Soloschenko */ public class SpringBeanLocator implements IProxyTargetLocator { @@ -47,7 +59,17 @@ public class SpringBeanLocator implements IProxyTargetLocator private ISpringContextLocator springContextLocator; private Boolean singletonCache = null; - + + /** Resolvable type for field to inject */ + private ResolvableType fieldResolvableType; + + /** If the field to inject is a list this is the resolvable + * type of its elements + * */ + private ResolvableType fieldCollectionResolvableType; + + private String fieldName; + /** * Constructor * @@ -58,7 +80,27 @@ public class SpringBeanLocator implements IProxyTargetLocator */ public SpringBeanLocator(final Class<?> beanType, final ISpringContextLocator locator) { - this(null, beanType, locator); + this(null, beanType, null, locator); + } + + public SpringBeanLocator(final String beanName, final Class<?> beanType, + final ISpringContextLocator locator) + { + this(beanName, beanType, null, locator); + } + + /** + * Constructor + * + * @param beanType + * bean class + * @param locator + * spring context locator + */ + public SpringBeanLocator(final Class<?> beanType, Field beanField, + final ISpringContextLocator locator) + { + this(null, beanType, beanField, locator); } /** @@ -71,17 +113,26 @@ public class SpringBeanLocator implements IProxyTargetLocator * @param locator * spring context locator */ - public SpringBeanLocator(final String beanName, final Class<?> beanType, + public SpringBeanLocator(final String beanName, final Class<?> beanType, Field beanField, final ISpringContextLocator locator) { Args.notNull(locator, "locator"); Args.notNull(beanType, "beanType"); + this.beanName = beanName; beanTypeCache = new WeakReference<Class<?>>(beanType); beanTypeName = beanType.getName(); springContextLocator = locator; - this.beanName = beanName; - springContextLocator = locator; + + if (beanField != null) + { + fieldName = beanField.getName(); + fieldResolvableType = ResolvableType.forField(beanField); + Class<?> collectionFieldType = GenericCollectionTypeResolver.getCollectionFieldType(beanField); + + fieldCollectionResolvableType = collectionFieldType != null ? + ResolvableType.forClass(collectionFieldType) : null; + } } /** @@ -173,14 +224,39 @@ public class SpringBeanLocator implements IProxyTargetLocator { try { - if (name == null) + // If the name is set the lookup is clear + if (name != null) + { + return ctx.getBean(name, clazz); + } + + // If the beanField information is null the clazz is going to be used + if (fieldResolvableType == null) { return ctx.getBean(clazz); } - else + + // If the given class is a list try to get the generic of the list + Class<?> lookupClass = clazz == List.class ? + fieldResolvableType.getGeneric(0).resolve() : clazz; + + // Else the lookup is done via Generic + List<String> names = loadBeanNames(ctx, lookupClass); + + ArrayList<Object> beansAsList = getBeansByName(ctx, names); + + if(beansAsList.size() == 1) { - return ctx.getBean(name, clazz); + return beansAsList.get(0); + } + + if(!beansAsList.isEmpty()) + { + return beansAsList; } + throw new IllegalStateException( + "Concrete bean could not be received from the application context " + + clazz.getName() + "."); } catch (NoSuchBeanDefinitionException e) { @@ -190,6 +266,94 @@ public class SpringBeanLocator implements IProxyTargetLocator } /** + * Returns a list of candidate names for the given class. + * + * @param ctx + * spring application context + * @param lookupClass + * the class to lookup + * @return a list of candidate names + */ + private List<String> loadBeanNames(ApplicationContext ctx, Class<?> lookupClass) + { + List<String> beanNames = new ArrayList<>(); + String[] beanNamesArr = BeanFactoryUtils + .beanNamesForTypeIncludingAncestors(ctx, lookupClass); + + //add field name if defined + if (ctx.containsBean(fieldName)) + { + beanNames.add(fieldName); + } + + beanNames.addAll(Arrays.asList(beanNamesArr)); + + return beanNames; + } + + /** + * Retrieves a list of beans for the given list of names and assignable to the + * current field to inject. + * + * @param ctx + * spring application context. + * @param names + * the list of candidate names + * @return a list of matching beans. + */ + private ArrayList<Object> getBeansByName(ApplicationContext ctx, List<String> names) + { + ArrayList<Object> beansAsList = new ArrayList<>(); + + for (String beanName : names) + { + RootBeanDefinition beanDef = getBeanDefinition(ctx, beanName); + + if (beanDef == null) + { + continue; + } + + ResolvableType candidateRt = null; + + //check if we have the class of the bean or the factory method. + //Usually if use XML as config file we have the class while we + //have the factory method if we use Java-based configuration. + if(beanDef.hasBeanClass()) + { + candidateRt = ResolvableType.forClass( + beanDef.getBeanClass()); + } + else if (beanDef.getResolvedFactoryMethod() != null) + { + candidateRt = ResolvableType.forMethodReturnType( + beanDef.getResolvedFactoryMethod()); + } + + if (candidateRt == null) + { + continue; + } + + boolean exactMatch = fieldResolvableType.isAssignableFrom(candidateRt); + boolean elementMatch = fieldCollectionResolvableType != null ? + fieldCollectionResolvableType.isAssignableFrom(candidateRt) : false; + + if (exactMatch || elementMatch) + { + beansAsList.add(ctx.getBean(beanName)); + } + + if(exactMatch) + { + this.beanName = beanName; + return beansAsList; + } + } + return beansAsList; + } + + /** * @see java.lang.Object#equals(java.lang.Object) */ @Override @@ -217,4 +381,28 @@ public class SpringBeanLocator implements IProxyTargetLocator } return hashcode; } + + /** + * Gets the root bean definition for the given name. + * + * @param ctx + * spring application context. + * @param name + * bean name + * @return bean definition for the current name, null if such a definition is not found. + */ + public RootBeanDefinition getBeanDefinition(final ApplicationContext ctx, + final String name) + { + AbstractBeanFactory beanFactory = (AbstractBeanFactory)( + (AbstractApplicationContext)ctx).getBeanFactory(); + + BeanDefinition beanDef = beanFactory.getMergedBeanDefinition(name); + + if (beanDef instanceof RootBeanDefinition) { + return (RootBeanDefinition)beanDef; + } + + return null; + } } http://git-wip-us.apache.org/repos/asf/wicket/blob/afc96936/wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java ---------------------------------------------------------------------- diff --git a/wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java b/wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java index 54d11de..5bda825 100644 --- a/wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java +++ b/wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java @@ -17,6 +17,8 @@ package org.apache.wicket.spring.injection.annot; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -40,6 +42,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.core.ResolvableType; /** * {@link IFieldValueFactory} that uses {@link LazyInitProxyFactory} to create proxies for Spring @@ -72,6 +75,7 @@ import org.springframework.context.support.AbstractApplicationContext; * * @author Igor Vaynberg (ivaynberg) * @author Istvan Devai + * @author Tobias Soloschenko */ public class AnnotProxyFieldValueFactory implements IFieldValueFactory { @@ -127,14 +131,10 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory required = true; } - String beanName = getBeanName(field, name, required); + Class<?> generic = ResolvableType.forField(field).resolveGeneric(0); + String beanName = getBeanName(field, name, required,generic); - if (beanName == null) - { - return null; - } - - SpringBeanLocator locator = new SpringBeanLocator(beanName, field.getType(), + SpringBeanLocator locator = new SpringBeanLocator(beanName, field.getType(),field, contextLocator); // only check the cache if the bean is a singleton @@ -168,7 +168,7 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory } // only put the proxy into the cache if the bean is a singleton - if (locator.isSingletonBean()) + if (field.getType() == List.class || locator.isSingletonBean()) { Object tmpTarget = cache.putIfAbsent(locator, target); if (tmpTarget != null) @@ -186,16 +186,18 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory * @param field * @return bean name */ - private String getBeanName(final Field field, String name, boolean required) + private String getBeanName(final Field field, String name, boolean required,Class<?> generic) { if (Strings.isEmpty(name)) { Class<?> fieldType = field.getType(); + name = beanNameCache.get(fieldType); if (name == null) { - name = getBeanNameOfClass(contextLocator.getSpringContext(), fieldType, required); + name = getBeanNameOfClass(contextLocator.getSpringContext(), fieldType, generic, + required); if (name != null) { @@ -225,8 +227,12 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory * @return spring name of the bean */ private String getBeanNameOfClass(final ApplicationContext ctx, final Class<?> clazz, - final boolean required) + final Class<?> generic, final boolean required) { + // If the clazz is instance of List return null + if(clazz == List.class){ + return null; + } // get the list of all possible matching beans List<String> names = new ArrayList<String>( Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx, clazz))); @@ -279,6 +285,10 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory return primaries.get(0); } } + if (generic != null) + { + return null; + } StringBuilder msg = new StringBuilder(); msg.append("More than one bean of type ["); msg.append(clazz.getName()); @@ -294,7 +304,7 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory } } - private BeanDefinition getBeanDefinition(final ConfigurableListableBeanFactory beanFactory, + public BeanDefinition getBeanDefinition(final ConfigurableListableBeanFactory beanFactory, final String name) { if (beanFactory.containsBeanDefinition(name)) @@ -321,6 +331,7 @@ public class AnnotProxyFieldValueFactory implements IFieldValueFactory @Override public boolean supportsField(final Field field) { - return field.isAnnotationPresent(SpringBean.class) || field.isAnnotationPresent(Inject.class); + return field.isAnnotationPresent(SpringBean.class) || + field.isAnnotationPresent(Inject.class); } } http://git-wip-us.apache.org/repos/asf/wicket/blob/afc96936/wicket-spring/src/test/java/org/apache/wicket/spring/BeanWithGeneric.java ---------------------------------------------------------------------- diff --git a/wicket-spring/src/test/java/org/apache/wicket/spring/BeanWithGeneric.java b/wicket-spring/src/test/java/org/apache/wicket/spring/BeanWithGeneric.java new file mode 100644 index 0000000..5bfe9eb --- /dev/null +++ b/wicket-spring/src/test/java/org/apache/wicket/spring/BeanWithGeneric.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.spring; + +public class BeanWithGeneric<T> +{ + +} http://git-wip-us.apache.org/repos/asf/wicket/blob/afc96936/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/SpringBeanWithGenericsTest.java ---------------------------------------------------------------------- diff --git a/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/SpringBeanWithGenericsTest.java b/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/SpringBeanWithGenericsTest.java new file mode 100644 index 0000000..524a8ed --- /dev/null +++ b/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/SpringBeanWithGenericsTest.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.wicket.spring.injection.annot; + +import java.util.Arrays; +import java.util.List; + +import org.apache.wicket.spring.BeanWithGeneric; +import org.apache.wicket.util.tester.DummyHomePage; +import org.apache.wicket.util.tester.WicketTester; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +public class SpringBeanWithGenericsTest extends Assert +{ + private WicketTester tester; + private AnnotationConfigApplicationContext ctx; + + /** + * @throws Exception + */ + @Before + public void before() throws Exception + { + tester = new WicketTester(); + + ctx = new AnnotationConfigApplicationContext(); + ctx.register(ConfigContextWithGenerics.class); + ctx.refresh(); + + SpringComponentInjector springInjector = new SpringComponentInjector( + tester.getApplication(), ctx); + + tester.getApplication().getComponentInstantiationListeners().add(springInjector); + } + + @Test + public void genericAsQualifier() throws Exception + { + AnnotatedBeanGenericQualifier page = + tester.startPage(new AnnotatedBeanGenericQualifier()); + + assertNotNull(page.getBean()); + } + + @Test + public void listOfGenerics() throws Exception + { + AnnotatedListOfBeanGenericQualifier page = + tester.startPage(new AnnotatedListOfBeanGenericQualifier()); + + assertNotNull(page.getBeans()); + assertEquals(2, page.getBeans().size()); + } + + @Test + public void listField() throws Exception + { + AnnotatedListField page = + tester.startPage(new AnnotatedListField()); + + assertNotNull(page.getStrings()); + assertEquals(3, page.getStrings().size()); + } + + class AnnotatedBeanGenericQualifier extends DummyHomePage + { + @SpringBean + private BeanWithGeneric<String> bean; + + public BeanWithGeneric<String> getBean() + { + return bean; + } + } + + class AnnotatedListOfBeanGenericQualifier extends DummyHomePage + { + @SpringBean + private List<BeanWithGeneric<?>> beans; + + public List<BeanWithGeneric<?>> getBeans() + { + return beans; + } + } + + class AnnotatedListField extends DummyHomePage + { + @SpringBean + private List<String> strings; + + public List<String> getStrings() + { + return strings; + } + } + + @Configuration + public static class ConfigContextWithGenerics + { + @Bean + public BeanWithGeneric<String> stringBean() + { + return new BeanWithGeneric<String>(); + } + + @Bean + public BeanWithGeneric<Integer> nestedBean() + { + return new BeanWithGeneric<Integer>(); + } + + @Bean + public List<String> strings() + { + return Arrays.asList("foo", "bar", "baz"); + } + } +}
