[MNG-7662] Use proxies for session scoped beans (#950)

This commit is contained in:
Guillaume Nodet 2023-11-09 14:34:11 +01:00 committed by GitHub
parent e6d1b4c5de
commit 4bd12915c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 183 additions and 1 deletions

View File

@ -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>

View File

@ -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 <T> void seed(Class<T> clazz, final T value) {
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");
}
/**

View File

@ -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;
}
}
}