This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new 4bd12915c9 [MNG-7662] Use proxies for session scoped beans (#950)
4bd12915c9 is described below

commit 4bd12915c9399e02d4826e5150c1a9c735d12765
Author: Guillaume Nodet <gno...@gmail.com>
AuthorDate: Thu Nov 9 14:34:11 2023 +0100

    [MNG-7662] Use proxies for session scoped beans (#950)
---
 maven-core/pom.xml                                 |   6 +
 .../maven/session/scope/internal/SessionScope.java |  47 +++++++-
 .../maven/session/scope/SessionScopeProxyTest.java | 131 +++++++++++++++++++++
 3 files changed, 183 insertions(+), 1 deletion(-)

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.
       <artifactId>plexus-testing</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-junit-jupiter</artifactId>
+      <version>${mockitoVersion}</version>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
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 <T> Provider<T> scope(final Key<T> key, final Provider<T> 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> T createProxy(Key<T> key, Provider<T> unscoped) {
+        InvocationHandler dispatcher = (proxy, method, args) -> {
+            method.setAccessible(true);
+            return method.invoke(getScopeState().scope(key, unscoped).get(), 
args);
+        };
+        Class<T> superType = (Class<T>) 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<Class<?>> 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;
+        }
+    }
+}

Reply via email to