diff --git a/maven-core/pom.xml b/maven-core/pom.xml
index eac040fd51..dda9f8be42 100644
--- a/maven-core/pom.xml
+++ b/maven-core/pom.xml
@@ -174,6 +174,12 @@ under the License.
plexus-testing
test
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockitoVersion}
+ test
+
diff --git a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java
index 320ab1a3a8..aa8e45116b 100644
--- a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java
+++ b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java
@@ -18,11 +18,16 @@
*/
package org.apache.maven.session.scope.internal;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
@@ -89,7 +94,47 @@ public class SessionScope implements Scope {
public Provider scope(final Key key, final Provider unscoped) {
// Lazy evaluating provider
- return () -> getScopeState().scope(key, unscoped).get();
+ return () -> {
+ if (values.isEmpty()) {
+ return createProxy(key, unscoped);
+ } else {
+ return getScopeState().scope(key, unscoped).get();
+ }
+ };
+ }
+
+ @SuppressWarnings("unchecked")
+ private T createProxy(Key key, Provider unscoped) {
+ InvocationHandler dispatcher = (proxy, method, args) -> {
+ method.setAccessible(true);
+ return method.invoke(getScopeState().scope(key, unscoped).get(), args);
+ };
+ Class superType = (Class) key.getTypeLiteral().getRawType();
+ for (Annotation a : superType.getAnnotations()) {
+ Class extends Annotation> annotationType = a.annotationType();
+ if ("org.eclipse.sisu.Typed".equals(annotationType.getName())
+ || "javax.enterprise.inject.Typed".equals(annotationType.getName())) {
+ try {
+ Class>[] value =
+ (Class>[]) annotationType.getMethod("value").invoke(a);
+ if (value.length == 0) {
+ value = superType.getInterfaces();
+ }
+ List> nonInterfaces =
+ Stream.of(value).filter(c -> !c.isInterface()).collect(Collectors.toList());
+ if (!nonInterfaces.isEmpty()) {
+ throw new IllegalArgumentException(
+ "The Typed annotation must contain only interfaces but the following types are not: "
+ + nonInterfaces);
+ }
+ return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), value, dispatcher);
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ throw new IllegalArgumentException("The use of session scoped proxies require "
+ + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation");
}
/**
diff --git a/maven-core/src/test/java/org/apache/maven/session/scope/SessionScopeProxyTest.java b/maven-core/src/test/java/org/apache/maven/session/scope/SessionScopeProxyTest.java
new file mode 100644
index 0000000000..5988c64c9e
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/session/scope/SessionScopeProxyTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.maven.session.scope;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import com.google.inject.OutOfScopeException;
+import org.apache.maven.SessionScoped;
+import org.apache.maven.api.Session;
+import org.apache.maven.session.scope.internal.SessionScope;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.testing.PlexusTest;
+import org.eclipse.sisu.Typed;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@PlexusTest
+@ExtendWith(MockitoExtension.class)
+public class SessionScopeProxyTest {
+
+ @Mock
+ Session session;
+
+ @Inject
+ SessionScope sessionScope;
+
+ @Inject
+ PlexusContainer container;
+
+ @Test
+ void testProxiedSessionScopedBean() throws ComponentLookupException {
+ ComponentLookupException e =
+ assertThrows(ComponentLookupException.class, () -> container.lookup(MySingletonBean2.class));
+ assertTrue(e.getMessage().matches("[\\s\\S]*: Can not set .* field .* to [\\s\\S]*"));
+
+ MySingletonBean bean = container.lookup(MySingletonBean.class);
+ assertNotNull(bean);
+ assertNotNull(bean.anotherBean);
+ assertSame(bean.anotherBean.getClass(), AnotherBean.class);
+ assertNotNull(bean.myBean);
+ assertNotSame(bean.myBean.getClass(), MySessionScopedBean.class);
+
+ assertThrows(OutOfScopeException.class, () -> bean.myBean.getSession());
+
+ sessionScope.enter();
+ sessionScope.seed(Session.class, this.session);
+ assertNotNull(bean.myBean.getSession());
+ assertNotNull(bean.myBean.getAnotherBean());
+ assertSame(bean.myBean.getAnotherBean().getClass(), AnotherBean.class);
+ }
+
+ @Named
+ static class MySingletonBean {
+ @Inject
+ @Named("scoped")
+ BeanItf myBean;
+
+ @Inject
+ @Named("another")
+ BeanItf2 anotherBean;
+ }
+
+ @Named
+ static class MySingletonBean2 {
+ @Inject
+ @Named("scoped")
+ MySessionScopedBean myBean;
+
+ @Inject
+ @Named("another")
+ BeanItf2 anotherBean;
+ }
+
+ interface BeanItf {
+ Session getSession();
+
+ BeanItf2 getAnotherBean();
+ }
+
+ interface BeanItf2 {}
+
+ @Named("another")
+ @Singleton
+ static class AnotherBean implements BeanItf2 {}
+
+ @Named("scoped")
+ @SessionScoped
+ @Typed
+ static class MySessionScopedBean implements BeanItf {
+ @Inject
+ Session session;
+
+ @Inject
+ BeanItf2 anotherBean;
+
+ public Session getSession() {
+ return session;
+ }
+
+ public BeanItf2 getAnotherBean() {
+ return anotherBean;
+ }
+ }
+}