This is an automated email from the ASF dual-hosted git repository.
adelbene pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git
The following commit(s) were added to refs/heads/master by this push:
new 154373e5b2 WICKET-7170: AnnotProxyFieldValueFactory now considers
defaultCandidate (#1311)
154373e5b2 is described below
commit 154373e5b2f5d5524596300d6dc031757e07b7a4
Author: Hans Schäfer <[email protected]>
AuthorDate: Thu Nov 27 21:59:34 2025 +0100
WICKET-7170: AnnotProxyFieldValueFactory now considers defaultCandidate
(#1311)
* WICKET-7170: AnnotProxyFieldValueFactory now considers
defaultCandidate-Flag
* Reset tests to focus only on implementation / bugfix
* Added test for DefaultCandidate-Scenario
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/main/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactory.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactoryDefaultCandidateTest.java
Co-authored-by: Martin Grigorov <[email protected]>
* Update
wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactoryDefaultCandidateTest.java
Co-authored-by: Martin Grigorov <[email protected]>
---------
Co-authored-by: Schäfer, H.H. (Hans Hosea) <[email protected]>
Co-authored-by: Martin Grigorov <[email protected]>
---
.../wicket/spring/ISpringContextLocator.java | 1 +
.../annot/AnnotProxyFieldValueFactory.java | 90 ++++++++++-------
.../wicket/spring/test/ApplicationContextMock.java | 4 +-
...ProxyFieldValueFactoryDefaultCandidateTest.java | 107 +++++++++++++++++++++
4 files changed, 166 insertions(+), 36 deletions(-)
diff --git
a/wicket-spring/src/main/java/org/apache/wicket/spring/ISpringContextLocator.java
b/wicket-spring/src/main/java/org/apache/wicket/spring/ISpringContextLocator.java
index 41de72ad69..86e43714d1 100644
---
a/wicket-spring/src/main/java/org/apache/wicket/spring/ISpringContextLocator.java
+++
b/wicket-spring/src/main/java/org/apache/wicket/spring/ISpringContextLocator.java
@@ -47,6 +47,7 @@ import org.springframework.context.ApplicationContext;
* @author Igor Vaynberg (ivaynberg)
*
*/
+@FunctionalInterface
public interface ISpringContextLocator extends IClusterable
{
/**
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 692857f86c..5816d198ea 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
@@ -22,7 +22,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -132,7 +134,7 @@ public class AnnotProxyFieldValueFactory implements
IFieldValueFactory
}
ResolvableType resolvableType =
ResolvableType.forField(field);
- String beanName = getBeanName(field, name, required,
resolvableType);
+ String beanName = getBeanName(field, name,
resolvableType);
SpringBeanLocator locator = new
SpringBeanLocator(beanName, field.getType(), field, contextLocator);
@@ -181,11 +183,11 @@ public class AnnotProxyFieldValueFactory implements
IFieldValueFactory
}
/**
- *
+ *
* @param field
* @return bean name
*/
- private String getBeanName(final Field field, String name, boolean
required, ResolvableType resolvableType)
+ private String getBeanName(final Field field, String name, final
ResolvableType resolvableType)
{
if (Strings.isEmpty(name))
{
@@ -230,14 +232,14 @@ public class AnnotProxyFieldValueFactory implements
IFieldValueFactory
Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(ctx,
resolvableType)));
// filter out beans that are not candidates for autowiring
- if (ctx instanceof AbstractApplicationContext)
+ if (ctx instanceof AbstractApplicationContext
abstractApplicationContext)
{
- Iterator<String> it = names.iterator();
+ final Iterator<String> it = names.iterator();
while (it.hasNext())
{
final String possibility = it.next();
- BeanDefinition beanDef = getBeanDefinition(
-
((AbstractApplicationContext)ctx).getBeanFactory(), possibility);
+ final ConfigurableListableBeanFactory
beanFactory = abstractApplicationContext.getBeanFactory();
+ final BeanDefinition beanDef =
getBeanDefinition(beanFactory, possibility);
if
(BeanFactoryUtils.isFactoryDereference(possibility) ||
possibility.startsWith("scopedTarget.")
||
(beanDef != null &&
!beanDef.isAutowireCandidate()))
@@ -249,36 +251,22 @@ public class AnnotProxyFieldValueFactory implements
IFieldValueFactory
if (names.size() > 1)
{
- if (ctx instanceof AbstractApplicationContext)
- {
- List<String> primaries = new ArrayList<>();
- for (String name : names)
- {
- BeanDefinition beanDef =
getBeanDefinition(
-
((AbstractApplicationContext)ctx).getBeanFactory(), name);
- if (beanDef instanceof
AbstractBeanDefinition)
- {
- if (beanDef.isPrimary())
- {
- primaries.add(name);
- }
- }
- }
- if (primaries.size() == 1)
- {
- return primaries.get(0);
- }
- }
-
- //use field name to find a match
- int nameIndex = names.indexOf(fieldName);
-
- if (nameIndex > -1)
+ // Check, if we can reduce the set of beannames to
exactly one beanname probing the following criterias:
+ // 1. Is there exactly one bean marked as primary?
+ // 2. Is there a bean with the same name as the field?
+ // 3. Is there exactly one bean marked as default
candidate?
+ final String exactMatchBeanName =
Optional.ofNullable(detectPrimaryBeanName(ctx, names))
+ .or(() ->
Optional.ofNullable(detectBeanNameByFieldName(fieldName, names)))
+ .orElseGet(() -> detectDefaultCandidateBeanName(ctx,
names));
+
+ // If so: take that beanname
+ if (exactMatchBeanName != null)
{
- return names.get(nameIndex);
+ return exactMatchBeanName;
}
- StringBuilder msg = new StringBuilder();
+ // Hmm, dont know which one to take....
+ final StringBuilder msg = new StringBuilder();
msg.append("More than one bean of type [");
msg.append(clazz.getName());
msg.append("] found, you have to specify the name of
the bean ");
@@ -295,6 +283,40 @@ public class AnnotProxyFieldValueFactory implements
IFieldValueFactory
return null;
}
+ private String detectPrimaryBeanName(final ApplicationContext ctx,
final List<String> beanNames)
+ {
+ return detectBeanName(ctx, beanNames,
AbstractBeanDefinition::isPrimary);
+ }
+
+ private String detectDefaultCandidateBeanName(final ApplicationContext
ctx, final List<String> beanNames)
+ {
+ return detectBeanName(ctx, beanNames,
AbstractBeanDefinition::isDefaultCandidate);
+ }
+
+ private String detectBeanName(final ApplicationContext ctx, final
List<String> beanNames, final Predicate<AbstractBeanDefinition> predicate)
+ {
+ final List<String> found = new ArrayList<>();
+ if (ctx instanceof AbstractApplicationContext
abstractApplicationContext)
+ {
+ final ConfigurableListableBeanFactory beanFactory =
abstractApplicationContext.getBeanFactory();
+ for (final String beanName : beanNames)
+ {
+ final BeanDefinition beanDefinition =
getBeanDefinition(beanFactory, beanName);
+ if (beanDefinition instanceof
AbstractBeanDefinition abstractBeanDefinition &&
predicate.test(abstractBeanDefinition))
+ {
+ found.add(beanName);
+ }
+ }
+ }
+ return found.size() == 1 ? found.get(0) : null;
+ }
+
+ private String detectBeanNameByFieldName(final String fieldName, final
List<String> beanNames)
+ {
+ return fieldName != null && beanNames.contains(fieldName) ?
fieldName : null;
+ }
+
+
public BeanDefinition getBeanDefinition(final
ConfigurableListableBeanFactory beanFactory,
final String name)
{
diff --git
a/wicket-spring/src/main/java/org/apache/wicket/spring/test/ApplicationContextMock.java
b/wicket-spring/src/main/java/org/apache/wicket/spring/test/ApplicationContextMock.java
index d5a83f8e64..5be624c52b 100644
---
a/wicket-spring/src/main/java/org/apache/wicket/spring/test/ApplicationContextMock.java
+++
b/wicket-spring/src/main/java/org/apache/wicket/spring/test/ApplicationContextMock.java
@@ -64,9 +64,9 @@ public class ApplicationContextMock extends
AbstractApplicationContext implement
* @param name
* @param bean
*/
- public <T extends Object> void putBean(final String name, final T bean)
+ public <T> void putBean(final String name, final T bean)
{
- beanFactory.registerBeanDefinition(name, new
RootBeanDefinition((Class<T>)bean.getClass(), () -> bean));
+ beanFactory.registerBeanDefinition(name, new
RootBeanDefinition((Class<T>) bean.getClass(), () -> bean));
}
/**
diff --git
a/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactoryDefaultCandidateTest.java
b/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactoryDefaultCandidateTest.java
new file mode 100644
index 0000000000..264095089c
--- /dev/null
+++
b/wicket-spring/src/test/java/org/apache/wicket/spring/injection/annot/AnnotProxyFieldValueFactoryDefaultCandidateTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.lang.reflect.Field;
+import java.util.stream.Stream;
+
+import org.apache.wicket.proxy.ILazyInitProxy;
+import org.apache.wicket.proxy.IProxyTargetLocator;
+import org.apache.wicket.spring.test.ApplicationContextMock;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+
+import jakarta.inject.Inject;
+
+/**
+ * Tests for AnnotProxyFieldValueFactory
+ * https://issues.apache.org/jira/browse/WICKET-7170
+ *
+ * @author hosea
+ */
+public class AnnotProxyFieldValueFactoryDefaultCandidateTest
+{
+
+ @ParameterizedTest
+ @MethodSource("beans")
+ public void
shouldCreateProxyForUniqueDefaultCandidate_beanNameAmbiguous(final Object obj)
throws Exception {
+ final Bean defaultCandidate = new Bean();
+ final ApplicationContextMock applicationContext = new
ApplicationContextMock();
+ // add two beans to make ambiguous
+ applicationContext.putBean("anyBean", new Bean());
+ applicationContext.putBean("primaryBean", defaultCandidate);
+ final AbstractBeanDefinition abstractBeanDefinition =
(AbstractBeanDefinition)
applicationContext.getBeanFactory().getBeanDefinition("anyBean");
+ abstractBeanDefinition.setDefaultCandidate(false);
+ final AnnotProxyFieldValueFactory factory = new
AnnotProxyFieldValueFactory(() -> applicationContext);
+
+ final Field beanByClassField =
obj.getClass().getDeclaredField("beanByClass");
+ final Object beanByClassProxy =
factory.getFieldValue(beanByClassField, obj);
+ final ILazyInitProxy lazyInitProxy =
assertInstanceOf(ILazyInitProxy.class, beanByClassProxy);
+ final IProxyTargetLocator beanByClassLocator =
lazyInitProxy.getObjectLocator();
+ assertSame(defaultCandidate,
beanByClassLocator.locateProxyTarget());
+ }
+
+
+ private static Stream<Object> beans() {
+ return Stream.of(new SpringBeanInjectable(), new
JakartaInjectInjectable());
+ }
+
+ /**
+ * Test creation fails with null springcontextlocator
+ */
+ @Test
+ public void testNullContextLocator()
+ {
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
new AnnotProxyFieldValueFactory(null));
+ }
+
+
+ public static class JakartaInjectInjectable
+ {
+ @Inject
+ private Bean beanByClass;
+
+ @Override
+ public String toString() {
+ return "JakartaInjectInjectable";
+ }
+ }
+
+ public static class SpringBeanInjectable
+ {
+ @SpringBean
+ private Bean beanByClass;
+
+ @Override
+ public String toString() {
+ return "SpringBeanInjectable";
+ }
+ }
+
+ /**
+ * Mock spring bean
+ */
+ public static class Bean
+ {
+ }
+}