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");
+               }
+       }
+}

Reply via email to