[MNG-8259] Improve Sisu / DI bridge (#1722)

This commit is contained in:
Guillaume Nodet 2024-09-18 13:05:13 +02:00 committed by GitHub
parent 30f52651a7
commit deb15be3b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 934 additions and 444 deletions

View File

@ -71,6 +71,10 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-settings</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-di</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
@ -132,11 +136,6 @@ under the License.
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-di</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-named-locks</artifactId>

View File

@ -0,0 +1,124 @@
/*
* 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.internal.impl.di;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.di.Key;
import org.apache.maven.di.Scope;
import org.apache.maven.di.impl.DIException;
/**
* MojoExecutionScope
*/
public class MojoExecutionScope implements Scope {
protected static final class ScopeState {
private final Map<Key<?>, Supplier<?>> seeded = new HashMap<>();
private final Map<Key<?>, Object> provided = new HashMap<>();
public <T> void seed(Class<T> clazz, Supplier<T> value) {
seeded.put(Key.of(clazz), value);
}
public Collection<Object> provided() {
return provided.values();
}
}
private final ThreadLocal<LinkedList<ScopeState>> values = new ThreadLocal<>();
public MojoExecutionScope() {}
public static <T> Supplier<T> seededKeySupplier(Class<? extends T> clazz) {
return () -> {
throw new IllegalStateException(
"No instance of " + clazz.getName() + " is bound to the mojo execution scope.");
};
}
public void enter() {
LinkedList<ScopeState> stack = values.get();
if (stack == null) {
stack = new LinkedList<>();
values.set(stack);
}
stack.addFirst(new ScopeState());
}
protected ScopeState getScopeState() {
LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new IllegalStateException();
}
return stack.getFirst();
}
public void exit() {
final LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new IllegalStateException();
}
stack.removeFirst();
if (stack.isEmpty()) {
values.remove();
}
}
public <T> void seed(Class<T> clazz, Supplier<T> value) {
getScopeState().seed(clazz, value);
}
public <T> void seed(Class<T> clazz, final T value) {
seed(clazz, (Supplier<T>) () -> value);
}
@SuppressWarnings("unchecked")
@Nonnull
public <T> Supplier<T> scope(@Nonnull Key<T> key, @Nonnull Supplier<T> unscoped) {
return () -> {
LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new DIException("Cannot access " + key + " outside of a scoping block");
}
ScopeState state = stack.getFirst();
Supplier<?> seeded = state.seeded.get(key);
if (seeded != null) {
return (T) seeded.get();
}
T provided = (T) state.provided.get(key);
if (provided == null && unscoped != null) {
provided = unscoped.get();
state.provided.put(key, provided);
}
return provided;
};
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.internal.impl.di;
import org.apache.maven.di.impl.DIException;
public class OutOfScopeException extends DIException {
public OutOfScopeException(String message) {
super(message);
}
public OutOfScopeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,186 @@
/*
* 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.internal.impl.di;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.function.Supplier;
import java.util.stream.Stream;
import org.apache.maven.di.Key;
import org.apache.maven.di.Scope;
import org.apache.maven.di.impl.Types;
public class SessionScope implements Scope {
/**
* ScopeState
*/
protected static final class ScopeState {
private final Map<Key<?>, CachingProvider<?>> provided = new ConcurrentHashMap<>();
public <T> void seed(Class<T> clazz, Supplier<T> value) {
provided.put(Key.of(clazz), new CachingProvider<>(value));
}
@SuppressWarnings("unchecked")
public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
Supplier<?> provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped));
return (Supplier<T>) provider;
}
public Collection<CachingProvider<?>> providers() {
return provided.values();
}
}
protected final List<ScopeState> values = new CopyOnWriteArrayList<>();
public void enter() {
values.add(0, new ScopeState());
}
protected ScopeState getScopeState() {
if (values.isEmpty()) {
throw new OutOfScopeException("Cannot access session scope outside of a scoping block");
}
return values.get(0);
}
public void exit() {
if (values.isEmpty()) {
throw new IllegalStateException();
}
values.remove(0);
}
public <T> void seed(Class<T> clazz, Supplier<T> value) {
getScopeState().seed(clazz, value);
}
public <T> void seed(Class<T> clazz, T value) {
seed(clazz, (Supplier<T>) () -> value);
}
@Override
public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
// Lazy evaluating provider
return () -> {
if (values.isEmpty()) {
return createProxy(key, unscoped);
} else {
return getScopeState().scope(key, unscoped).get();
}
};
}
@SuppressWarnings("unchecked")
protected <T> T createProxy(Key<T> key, Supplier<T> unscoped) {
InvocationHandler dispatcher = (proxy, method, args) -> dispatch(key, unscoped, method, args);
Class<T> superType = (Class<T>) Types.getRawType(key.getType());
Class<?>[] interfaces = getInterfaces(superType);
return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher);
}
protected <T> Object dispatch(Key<T> key, Supplier<T> unscoped, Method method, Object[] args) throws Throwable {
method.setAccessible(true);
try {
return method.invoke(getScopeState().scope(key, unscoped).get(), args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
protected Class<?>[] getInterfaces(Class<?> superType) {
if (superType.isInterface()) {
return new Class<?>[] {superType};
} else {
for (Annotation a : superType.getAnnotations()) {
Class<? extends Annotation> annotationType = a.annotationType();
if (isTypeAnnotation(annotationType)) {
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()).toList();
if (!nonInterfaces.isEmpty()) {
throw new IllegalArgumentException(
"The Typed annotation must contain only interfaces but the following types are not: "
+ nonInterfaces);
}
return value;
} 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");
}
}
protected boolean isTypeAnnotation(Class<? extends Annotation> annotationType) {
return "org.apache.maven.api.di.Typed".equals(annotationType.getName());
}
/**
* A provider wrapping an existing provider with a cache
* @param <T> the provided type
*/
protected static class CachingProvider<T> implements Supplier<T> {
private final Supplier<T> provider;
private volatile T value;
CachingProvider(Supplier<T> provider) {
this.provider = provider;
}
public T value() {
return value;
}
@Override
public T get() {
if (value == null) {
synchronized (this) {
if (value == null) {
value = provider.get();
}
}
}
return value;
}
}
public static <T> Supplier<T> seededKeySupplier(Class<? extends T> clazz) {
return () -> {
throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope.");
};
}
}

View File

@ -41,6 +41,7 @@ import org.apache.maven.api.Session;
import org.apache.maven.api.Type;
import org.apache.maven.api.Version;
import org.apache.maven.api.di.Provides;
import org.apache.maven.api.di.SessionScoped;
import org.apache.maven.api.model.PluginContainer;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ArtifactManager;
@ -58,6 +59,7 @@ import org.apache.maven.di.Key;
import org.apache.maven.di.impl.DIException;
import org.apache.maven.internal.impl.AbstractSession;
import org.apache.maven.internal.impl.InternalSession;
import org.apache.maven.internal.impl.di.SessionScope;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
@ -75,7 +77,12 @@ public class ApiRunner {
injector.bindInstance(Injector.class, injector);
injector.bindImplicit(ApiRunner.class);
injector.discover(ApiRunner.class.getClassLoader());
return injector.getInstance(Session.class);
Session session = injector.getInstance(Session.class);
SessionScope scope = new SessionScope();
scope.enter();
scope.seed(Session.class, session);
injector.bindScope(SessionScoped.class, scope);
return session;
}
static class DefaultSession extends AbstractSession {

View File

@ -18,20 +18,13 @@
*/
package org.apache.maven.execution.scope.internal;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.function.Supplier;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.name.Names;
import com.google.inject.util.Providers;
import com.google.inject.name.Named;
import org.apache.maven.execution.MojoExecutionEvent;
import org.apache.maven.execution.MojoExecutionListener;
import org.apache.maven.execution.scope.WeakMojoExecutionListener;
@ -40,96 +33,22 @@ import org.apache.maven.plugin.MojoExecutionException;
/**
* MojoExecutionScope
*/
public class MojoExecutionScope implements Scope, org.apache.maven.di.Scope, MojoExecutionListener {
private static final Provider<Object> SEEDED_KEY_PROVIDER = () -> {
throw new IllegalStateException();
};
private static final class ScopeState {
private final Map<Key<?>, Provider<?>> seeded = new HashMap<>();
private final Map<Key<?>, Object> provided = new HashMap<>();
}
private final ThreadLocal<LinkedList<ScopeState>> values = new ThreadLocal<>();
public MojoExecutionScope() {}
public void enter() {
LinkedList<ScopeState> stack = values.get();
if (stack == null) {
stack = new LinkedList<>();
values.set(stack);
}
stack.addFirst(new ScopeState());
}
private ScopeState getScopeState() {
LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new IllegalStateException();
}
return stack.getFirst();
}
public void exit() throws MojoExecutionException {
final LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new IllegalStateException();
}
stack.removeFirst();
if (stack.isEmpty()) {
values.remove();
}
}
public class MojoExecutionScope extends org.apache.maven.internal.impl.di.MojoExecutionScope
implements Scope, MojoExecutionListener {
public <T> void seed(Class<T> clazz, Provider<T> value) {
getScopeState().seeded.put(Key.get(clazz), value);
getScopeState().seed(clazz, value::get);
}
public <T> void seed(Class<T> clazz, final T value) {
getScopeState().seeded.put(Key.get(clazz), Providers.of(value));
public <T> Provider<T> scope(final Key<T> key, Provider<T> unscoped) {
Object qualifier = key.getAnnotation() instanceof Named n ? n.value() : key.getAnnotation();
org.apache.maven.di.Key<T> k =
org.apache.maven.di.Key.ofType(key.getTypeLiteral().getType(), qualifier);
return scope(k, unscoped::get)::get;
}
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
return () -> {
LinkedList<ScopeState> stack = values.get();
if (stack == null || stack.isEmpty()) {
throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block");
}
ScopeState state = stack.getFirst();
Provider<?> seeded = state.seeded.get(key);
if (seeded != null) {
return (T) seeded.get();
}
T provided = (T) state.provided.get(key);
if (provided == null && unscoped != null) {
provided = unscoped.get();
state.provided.put(key, provided);
}
return provided;
};
}
@Override
public <T> Supplier<T> scope(org.apache.maven.di.Key<T> key, Annotation scope, Supplier<T> unscoped) {
Object qualifier = key.getQualifier();
Key k = qualifier != null
? Key.get(key.getType(), qualifier instanceof String s ? Names.named(s) : (Annotation) qualifier)
: Key.get(key.getType());
Provider<T> up = unscoped::get;
Provider<T> p = scope(k, up);
return p::get;
}
@SuppressWarnings({"unchecked"})
public static <T> Provider<T> seededKeyProvider() {
return (Provider<T>) SEEDED_KEY_PROVIDER;
public static <T> Provider<T> seededKeyProvider(Class<? extends T> clazz) {
return MojoExecutionScope.<T>seededKeySupplier(clazz)::get;
}
public void beforeMojoExecution(MojoExecutionEvent event) throws MojoExecutionException {
@ -154,7 +73,7 @@ public class MojoExecutionScope implements Scope, org.apache.maven.di.Scope, Moj
// the same instance can be provided multiple times under different Key's
// deduplicate instances to avoid redundant beforeXXX/afterXXX callbacks
IdentityHashMap<WeakMojoExecutionListener, Object> listeners = new IdentityHashMap<>();
for (Object provided : getScopeState().provided.values()) {
for (Object provided : getScopeState().provided()) {
if (provided instanceof WeakMojoExecutionListener) {
listeners.put((WeakMojoExecutionListener) provided, null);
}

View File

@ -40,17 +40,19 @@ public class MojoExecutionScopeModule extends AbstractModule {
// bindScope(org.apache.maven.api.di.MojoExecutionScoped.class, scope);
bind(MojoExecutionScope.class).toInstance(scope);
bind(MavenProject.class)
.toProvider(MojoExecutionScope.seededKeyProvider())
.toProvider(MojoExecutionScope.seededKeyProvider(MavenProject.class))
.in(scope);
bind(MojoExecution.class)
.toProvider(MojoExecutionScope.seededKeyProvider())
.toProvider(MojoExecutionScope.seededKeyProvider(MojoExecution.class))
.in(scope);
bind(Log.class)
.toProvider(MojoExecutionScope.seededKeyProvider(Log.class))
.in(scope);
bind(Log.class).toProvider(MojoExecutionScope.seededKeyProvider()).in(scope);
bind(org.apache.maven.api.Project.class)
.toProvider(MojoExecutionScope.seededKeyProvider())
.toProvider(MojoExecutionScope.seededKeyProvider(org.apache.maven.api.Project.class))
.in(scope);
bind(org.apache.maven.api.MojoExecution.class)
.toProvider(MojoExecutionScope.seededKeyProvider())
.toProvider(MojoExecutionScope.seededKeyProvider(org.apache.maven.api.MojoExecution.class))
.in(scope);
}
}

View File

@ -21,31 +21,27 @@ package org.apache.maven.internal.impl;
import javax.inject.Named;
import javax.inject.Provider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import com.google.inject.AbstractModule;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.Binder;
import com.google.inject.name.Names;
import com.google.inject.spi.ProviderInstanceBinding;
import org.apache.maven.api.di.MojoExecutionScoped;
import org.apache.maven.api.di.SessionScoped;
import org.apache.maven.api.services.MavenException;
import org.apache.maven.di.Injector;
import org.apache.maven.di.Key;
import org.apache.maven.di.Scope;
import org.apache.maven.di.impl.Binding;
import org.apache.maven.di.impl.DIException;
import org.apache.maven.di.impl.Dependency;
@ -54,76 +50,147 @@ import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.session.scope.internal.SessionScope;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.eclipse.sisu.BeanEntry;
import org.eclipse.sisu.inject.BeanLocator;
@Named
public class SisuDiBridgeModule extends AbstractModule {
InjectorImpl injector;
final Set<String> loaded = new HashSet<>();
protected final boolean discover;
protected InjectorImpl injector;
public SisuDiBridgeModule() {
this(true);
}
public SisuDiBridgeModule(boolean discover) {
this.discover = discover;
}
@Override
protected void configure() {
Provider<PlexusContainer> containerProvider = getProvider(PlexusContainer.class);
Provider<BeanLocator> beanLocatorProvider = getProvider(BeanLocator.class);
injector = new BridgeInjectorImpl(beanLocatorProvider, binder());
bindScope(injector, containerProvider, SessionScoped.class, SessionScope.class);
bindScope(injector, containerProvider, MojoExecutionScoped.class, MojoExecutionScope.class);
injector.bindInstance(Injector.class, injector);
bind(Injector.class).toInstance(injector);
bind(SisuDiBridgeModule.class).toInstance(this);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
if (discover) {
injector.discover(classLoader);
}
}
private void bindScope(
InjectorImpl injector,
Provider<PlexusContainer> containerProvider,
Class<? extends Annotation> sa,
Class<? extends Scope> ss) {
injector.bindScope(sa, () -> {
try {
return containerProvider.get().lookup(ss);
} catch (ComponentLookupException e) {
throw new RuntimeException(e);
}
});
}
static class BridgeInjectorImpl extends InjectorImpl {
final Provider<BeanLocator> locator;
final Binder binder;
BridgeInjectorImpl(Provider<BeanLocator> locator, Binder binder) {
this.locator = locator;
this.binder = binder;
}
@Override
protected <U> Injector bind(Key<U> key, Binding<U> binding) {
super.bind(key, binding);
if (key.getQualifier() != null) {
com.google.inject.Key<U> k = toGuiceKey(key);
this.binder.bind(k).toProvider(new BridgeProvider<>(binding));
}
return this;
}
@SuppressWarnings("unchecked")
private static <U> com.google.inject.Key<U> toGuiceKey(Key<U> key) {
if (key.getQualifier() instanceof String s) {
return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), Names.named(s));
} else if (key.getQualifier() instanceof Annotation a) {
return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType(), a);
} else {
return (com.google.inject.Key<U>) com.google.inject.Key.get(key.getType());
}
}
static class BindingToBeanEntry<T> extends Binding<T> {
private BeanEntry<Annotation, T> beanEntry;
BindingToBeanEntry(Key<T> elementType) {
super(elementType, Set.of());
}
public BindingToBeanEntry<T> toBeanEntry(BeanEntry<Annotation, T> beanEntry) {
this.beanEntry = beanEntry;
return this;
}
@Override
public Supplier<T> compile(Function<Dependency<?>, Supplier<?>> compiler) {
return beanEntry.getProvider()::get;
}
}
class BridgeProvider<T> implements Provider<T> {
final Binding<T> binding;
BridgeProvider(Binding<T> binding) {
this.binding = binding;
}
@Override
public T get() {
return compile(binding).get();
}
}
injector = new InjectorImpl() {
@Override
public <Q> Supplier<Q> getCompiledBinding(Dependency<Q> dep) {
Key<Q> key = dep.key();
Set<Binding<Q>> res = getBindings(key);
if (res != null && !res.isEmpty()) {
List<Binding<Q>> bindingList = new ArrayList<>(res);
Comparator<Binding<Q>> comparing = Comparator.comparing(Binding::getPriority);
bindingList.sort(comparing.reversed());
Binding<Q> binding = bindingList.get(0);
return compile(binding);
Class<Q> rawType = key.getRawType();
if (rawType == List.class) {
return getListSupplier(key);
} else if (rawType == Map.class) {
return getMapSupplier(key);
} else {
return getBeanSupplier(dep, key);
}
if (key.getRawType() == List.class) {
Set<Binding<Object>> res2 = getBindings(key.getTypeParameter(0));
Set<Binding<Object>> res3 = res2 != null ? new HashSet<>(res2) : new HashSet<>();
try {
List<Object> l = containerProvider
.get()
.lookupList(key.getTypeParameter(0).getRawType());
l.forEach(o -> res3.add(new Binding.BindingToInstance<>(o)));
} catch (Throwable e) {
// ignore
e.printStackTrace();
}
List<Supplier<Object>> list =
res3.stream().map(this::compile).collect(Collectors.toList());
private <Q> Supplier<Q> getBeanSupplier(Dependency<Q> dep, Key<Q> key) {
List<Binding<?>> list = new ArrayList<>();
// Add DI bindings
list.addAll(getBindings().getOrDefault(key, Set.of()));
// Add Plexus bindings
for (var bean : locator.get().locate(toGuiceKey(key))) {
if (isPlexusBean(bean)) {
list.add(new BindingToBeanEntry<>(key).toBeanEntry(bean));
}
}
if (!list.isEmpty()) {
list.sort(getBindingComparator());
//noinspection unchecked
return () -> (Q) list(list);
}
if (key.getRawType() == Map.class) {
Key<?> k = key.getTypeParameter(0);
Key<Object> v = key.getTypeParameter(1);
if (k.getRawType() == String.class) {
Set<Binding<Object>> res2 = getBindings(v);
Set<Binding<Object>> res3 = res2 != null ? new HashSet<>(res2) : new HashSet<>();
Map<String, Supplier<Object>> map = res3.stream()
.filter(b -> b.getOriginalKey() == null
|| b.getOriginalKey().getQualifier() == null
|| b.getOriginalKey().getQualifier() instanceof String)
.collect(Collectors.toMap(
b -> (String)
(b.getOriginalKey() != null
? b.getOriginalKey().getQualifier()
: null),
this::compile));
//noinspection unchecked
return () -> (Q) map(map);
}
}
try {
Q t = containerProvider.get().lookup(key.getRawType());
return compile(new Binding.BindingToInstance<>(t));
} catch (Throwable e) {
// ignore
e.printStackTrace();
}
if (dep.optional()) {
return () -> (Q) getInstance(list.iterator().next());
} else if (dep.optional()) {
return () -> null;
}
} else {
throw new DIException("No binding to construct an instance for key "
+ key.getDisplayString() + ". Existing bindings:\n"
+ getBoundKeys().stream()
@ -133,80 +200,78 @@ public class SisuDiBridgeModule extends AbstractModule {
.distinct()
.collect(Collectors.joining("\n - ", " - ", "")));
}
};
injector.bindScope(SessionScoped.class, () -> {
try {
return containerProvider.get().lookup(SessionScope.class);
} catch (ComponentLookupException e) {
throw new RuntimeException(e);
}
});
injector.bindScope(MojoExecutionScoped.class, () -> {
try {
return containerProvider.get().lookup(MojoExecutionScope.class);
} catch (ComponentLookupException e) {
throw new RuntimeException(e);
}
});
injector.bindInstance(Injector.class, injector);
bind(Injector.class).toInstance(injector);
bind(SisuDiBridgeModule.class).toInstance(this);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
loadFromClassLoader(classLoader);
injector.getBindings().keySet().stream()
.filter(k -> k.getQualifier() != null)
.sorted(Comparator.comparing(k -> k.getRawType().getName()))
.distinct()
.forEach(key -> {
Class<?> clazz = key.getRawType();
Class<Object> itf = (clazz.isInterface()
? null
: (Class<Object>) (clazz.getInterfaces().length > 0 ? clazz.getInterfaces()[0] : clazz));
if (itf != null) {
AnnotatedBindingBuilder<Object> binder = bind(itf);
if (key.getQualifier() instanceof String s && !s.isEmpty()) {
binder.annotatedWith(Names.named(s));
} else if (key.getQualifier() instanceof Annotation a) {
binder.annotatedWith(a);
}
binder.toProvider(() -> injector.getInstance(clazz));
}
});
}
public void loadFromClassLoader(ClassLoader classLoader) {
try {
for (Iterator<URL> it = classLoader
.getResources("META-INF/maven/org.apache.maven.api.di.Inject")
.asIterator();
it.hasNext(); ) {
URL url = it.next();
if (loaded.add(url.toExternalForm())) {
List<String> lines;
try (InputStream is = url.openStream();
BufferedReader reader =
new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
lines = reader.lines()
.map(String::trim)
.filter(s -> !s.isEmpty() && !s.startsWith("#"))
.toList();
private <Q> Supplier<Q> getListSupplier(Key<Q> key) {
Key<Object> elementType = key.getTypeParameter(0);
return () -> {
List<Binding<?>> list = new ArrayList<>();
// Add DI bindings
list.addAll(getBindings().getOrDefault(elementType, Set.of()));
// Add Plexus bindings
for (var bean : locator.get().locate(toGuiceKey(elementType))) {
if (isPlexusBean(bean)) {
list.add(new BindingToBeanEntry<>(elementType).toBeanEntry(bean));
}
for (String className : lines) {
}
//noinspection unchecked
return (Q) list(list.stream().sorted(getBindingComparator()).toList(), this::getInstance);
};
}
private <Q> Supplier<Q> getMapSupplier(Key<Q> key) {
Key<?> keyType = key.getTypeParameter(0);
Key<Object> valueType = key.getTypeParameter(1);
if (keyType.getRawType() != String.class) {
throw new DIException("Only String keys are supported for maps: " + key);
}
return () -> {
var comparator = getBindingComparator();
Map<String, Binding<?>> map = new HashMap<>();
for (Binding<?> b : getBindings().getOrDefault(valueType, Set.of())) {
String name =
b.getOriginalKey() != null && b.getOriginalKey().getQualifier() instanceof String s
? s
: "";
map.compute(name, (n, ob) -> ob == null || comparator.compare(ob, b) < 0 ? b : ob);
}
for (var bean : locator.get().locate(toGuiceKey(valueType))) {
if (isPlexusBean(bean)) {
Binding<?> b = new BindingToBeanEntry<>(valueType)
.toBeanEntry(bean)
.prioritize(bean.getRank());
String name = bean.getKey() instanceof com.google.inject.name.Named n ? n.value() : "";
map.compute(name, (n, ob) -> ob == null || ob.getPriority() < b.getPriority() ? b : ob);
}
}
//noinspection unchecked
return (Q) map(map, this::getInstance);
};
}
private <Q> Q getInstance(Binding<Q> binding) {
return compile(binding).get();
}
private static Comparator<Binding<?>> getBindingComparator() {
Comparator<Binding<?>> comparing = Comparator.comparing(Binding::getPriority);
return comparing.reversed();
}
private <T> boolean isPlexusBean(BeanEntry<Annotation, T> entry) {
try {
Class<?> clazz = classLoader.loadClass(className);
injector.bindImplicit(clazz);
} catch (ClassNotFoundException e) {
if ("org.eclipse.sisu.inject.LazyBeanEntry"
.equals(entry.getClass().getName())) {
Field f = entry.getClass().getDeclaredField("binding");
f.setAccessible(true);
Object b = f.get(entry);
return !(b instanceof ProviderInstanceBinding<?> pib)
|| !(pib.getUserSuppliedProvider() instanceof BridgeProvider<?>);
}
} catch (Exception e) {
// ignore
e.printStackTrace();
}
}
}
}
} catch (IOException e) {
throw new MavenException(e);
return true;
}
}
}

View File

@ -19,190 +19,36 @@
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.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.inject.Key;
import com.google.inject.OutOfScopeException;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.name.Names;
import com.google.inject.name.Named;
/**
* SessionScope
*/
public class SessionScope implements Scope, org.apache.maven.di.Scope {
private static final Provider<Object> SEEDED_KEY_PROVIDER = () -> {
throw new IllegalStateException();
};
/**
* ScopeState
*/
protected static final class ScopeState {
private final Map<Key<?>, CachingProvider<?>> provided = new ConcurrentHashMap<>();
public class SessionScope extends org.apache.maven.internal.impl.di.SessionScope implements Scope {
public <T> void seed(Class<T> clazz, Provider<T> value) {
provided.put(Key.get(clazz), new CachingProvider<>(value));
}
@SuppressWarnings("unchecked")
public <T> Provider<T> scope(Key<T> key, Provider<T> unscoped) {
Provider<?> provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped));
return (Provider<T>) provider;
}
public Collection<CachingProvider<?>> providers() {
return provided.values();
}
}
private final List<ScopeState> values = new CopyOnWriteArrayList<>();
public void enter() {
values.add(0, new ScopeState());
}
protected ScopeState getScopeState() {
if (values.isEmpty()) {
throw new OutOfScopeException("Cannot access session scope outside of a scoping block");
}
return values.get(0);
}
public void exit() {
if (values.isEmpty()) {
throw new IllegalStateException();
}
values.remove(0);
}
public <T> void seed(Class<T> clazz, Provider<T> value) {
getScopeState().seed(clazz, value);
}
public <T> void seed(Class<T> clazz, final T value) {
seed(clazz, (Provider<T>) () -> value);
getScopeState().seed(clazz, value::get);
}
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
// Lazy evaluating provider
return () -> {
if (values.isEmpty()) {
return createProxy(key, unscoped);
} else {
return getScopeState().scope(key, unscoped).get();
}
};
}
@SuppressWarnings("unchecked")
@Override
public <T> Supplier<T> scope(org.apache.maven.di.Key<T> key, Annotation scope, Supplier<T> unscoped) {
Object qualifier = key.getQualifier();
Key<?> k = qualifier != null
? Key.get(key.getType(), qualifier instanceof String s ? Names.named(s) : (Annotation) qualifier)
: Key.get(key.getType());
Provider<T> up = unscoped::get;
Provider<T> p = scope((Key<T>) k, up);
return p::get;
}
@SuppressWarnings("unchecked")
private <T> T createProxy(Key<T> key, Provider<T> unscoped) {
InvocationHandler dispatcher = (proxy, method, args) -> {
method.setAccessible(true);
try {
return method.invoke(getScopeState().scope(key, unscoped).get(), args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
};
Class<T> superType = (Class<T>) key.getTypeLiteral().getRawType();
Class<?>[] interfaces = getInterfaces(superType);
return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher);
}
private Class<?>[] getInterfaces(Class<?> superType) {
if (superType.isInterface()) {
return new Class<?>[] {superType};
} else {
for (Annotation a : superType.getAnnotations()) {
Class<? extends Annotation> annotationType = a.annotationType();
if ("org.apache.maven.api.di.Typed".equals(annotationType.getName())
|| "org.eclipse.sisu.Typed".equals(annotationType.getName())
|| "javax.enterprise.inject.Typed".equals(annotationType.getName())
|| "jakarta.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 value;
} 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");
}
}
/**
* A provider wrapping an existing provider with a cache
* @param <T> the provided type
*/
protected static class CachingProvider<T> implements Provider<T> {
private final Provider<T> provider;
private volatile T value;
CachingProvider(Provider<T> provider) {
this.provider = provider;
}
public T value() {
return value;
}
@Override
public T get() {
if (value == null) {
synchronized (this) {
if (value == null) {
value = provider.get();
}
}
}
return value;
}
}
@SuppressWarnings({"unchecked"})
public static <T> Provider<T> seededKeyProvider() {
return (Provider<T>) SEEDED_KEY_PROVIDER;
Object qualifier = key.getAnnotation() instanceof Named n ? n.value() : key.getAnnotation();
org.apache.maven.di.Key<T> k =
org.apache.maven.di.Key.ofType(key.getTypeLiteral().getType(), qualifier);
return scope(k, unscoped::get)::get;
}
public static <T> Provider<T> seededKeyProvider(Class<? extends T> clazz) {
return () -> {
throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope.");
};
return SessionScope.<T>seededKeySupplier(clazz)::get;
}
protected boolean isTypeAnnotation(Class<? extends Annotation> annotationType) {
return "org.apache.maven.api.di.Typed".equals(annotationType.getName())
|| "org.eclipse.sisu.Typed".equals(annotationType.getName())
|| "javax.enterprise.inject.Typed".equals(annotationType.getName())
|| "jakarta.enterprise.inject.Typed".equals(annotationType.getName());
}
}

View File

@ -0,0 +1,271 @@
/*
* 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.di;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.services.Source;
import org.apache.maven.api.spi.ModelParser;
import org.apache.maven.api.spi.ModelParserException;
import org.apache.maven.internal.impl.SisuDiBridgeModule;
import org.codehaus.plexus.DefaultContainerConfiguration;
import org.codehaus.plexus.DefaultPlexusContainer;
import org.codehaus.plexus.PlexusContainer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class DiTest {
// return true to run the test
static boolean testShouldNotHaveDuplicates() {
return true;
}
@Nested
class DiTest1 {
PlexusContainer container;
@BeforeEach
void setup() throws Exception {
container = new DefaultPlexusContainer(
new DefaultContainerConfiguration(),
new AbstractModule() {
@Override
protected void configure() {
bind(ModelParser.class).to(TestModelParser.class);
}
},
new SisuDiBridgeModule(false));
}
@Test
void testPlexus() throws Exception {
List<ModelParser> parsers = container.lookupList(ModelParser.class);
assertNotNull(parsers);
assertEquals(1, parsers.size());
Map<String, ModelParser> parsersMap = container.lookupMap(ModelParser.class);
assertNotNull(parsersMap);
assertEquals(1, parsersMap.size());
}
@Test
void testGuice() throws Exception {
List<Binding<ModelParser>> parsers =
container.lookup(Injector.class).findBindingsByType(TypeLiteral.get(ModelParser.class));
assertNotNull(parsers);
assertEquals(1, parsers.size());
}
@Test
void testDI() throws Exception {
DiInjected diInjected = new DiInjected();
container.lookup(org.apache.maven.di.Injector.class).injectInstance(diInjected);
assertNotNull(diInjected.parser);
assertNotNull(diInjected.parsers);
assertEquals(1, diInjected.parsers.size());
assertNotNull(diInjected.parsersMap);
assertEquals(1, diInjected.parsersMap.size());
}
static class DiInjected {
@org.apache.maven.api.di.Inject
ModelParser parser;
@org.apache.maven.api.di.Inject
List<ModelParser> parsers;
@org.apache.maven.api.di.Inject
Map<String, ModelParser> parsersMap;
}
@Named
@Singleton
static class TestModelParser implements ModelParser {
@Override
public Optional<Source> locate(Path dir) {
return Optional.empty();
}
@Override
public Model parse(Source source, Map<String, ?> options) throws ModelParserException {
return null;
}
}
}
@Nested
class DiTest2 {
PlexusContainer container;
@BeforeEach
void setup() throws Exception {
container = new DefaultPlexusContainer(new DefaultContainerConfiguration(), new SisuDiBridgeModule(false) {
@Override
protected void configure() {
super.configure();
injector.bindImplicit(TestModelParser.class);
}
});
}
@Test
void testPlexus() throws Exception {
List<ModelParser> parsers = container.lookupList(ModelParser.class);
assertNotNull(parsers);
assertEquals(1, parsers.size());
Map<String, ModelParser> parsersMap = container.lookupMap(ModelParser.class);
assertNotNull(parsersMap);
assertEquals(1, parsersMap.size());
}
@Test
void testGuice() throws Exception {
List<Binding<ModelParser>> parsers2 =
container.lookup(Injector.class).findBindingsByType(TypeLiteral.get(ModelParser.class));
assertNotNull(parsers2);
assertEquals(1, parsers2.size());
}
@Test
@EnabledIf("org.apache.maven.di.DiTest#testShouldNotHaveDuplicates")
void testDI() throws Exception {
DiInjected diInjected = new DiInjected();
container.lookup(org.apache.maven.di.Injector.class).injectInstance(diInjected);
assertNotNull(diInjected.parser);
assertNotNull(diInjected.parsers);
assertEquals(1, diInjected.parsers.size());
assertNotNull(diInjected.parsersMap);
assertEquals(1, diInjected.parsersMap.size());
}
static class DiInjected {
@org.apache.maven.api.di.Inject
ModelParser parser;
@org.apache.maven.api.di.Inject
List<ModelParser> parsers;
@org.apache.maven.api.di.Inject
Map<String, ModelParser> parsersMap;
}
@org.apache.maven.api.di.Named
@org.apache.maven.api.di.Singleton
static class TestModelParser implements ModelParser {
@Override
public Optional<Source> locate(Path dir) {
return Optional.empty();
}
@Override
public Model parse(Source source, Map<String, ?> options) throws ModelParserException {
return null;
}
}
}
@Nested
class DiTest3 {
PlexusContainer container;
@BeforeEach
void setup() throws Exception {
container = new DefaultPlexusContainer(new DefaultContainerConfiguration(), new SisuDiBridgeModule(false) {
@Override
protected void configure() {
super.configure();
injector.bindImplicit(TestModelParser.class);
}
});
}
@Test
void testPlexus() throws Exception {
List<ModelParser> parsers = container.lookupList(ModelParser.class);
assertNotNull(parsers);
assertEquals(1, parsers.size());
Map<String, ModelParser> parsersMap = container.lookupMap(ModelParser.class);
assertNotNull(parsersMap);
assertEquals(1, parsersMap.size());
}
@Test
void testGuice() throws Exception {
List<Binding<ModelParser>> parsers =
container.lookup(Injector.class).findBindingsByType(TypeLiteral.get(ModelParser.class));
assertNotNull(parsers);
assertEquals(1, parsers.size());
}
@Test
@EnabledIf("org.apache.maven.di.DiTest#testShouldNotHaveDuplicates")
void testDI() throws Exception {
DiInjected diInjected = new DiInjected();
container.lookup(org.apache.maven.di.Injector.class).injectInstance(diInjected);
assertNotNull(diInjected.parser);
assertNotNull(diInjected.parsers);
assertEquals(1, diInjected.parsers.size());
assertNotNull(diInjected.parsersMap);
assertEquals(1, diInjected.parsersMap.size());
}
static class DiInjected {
@org.apache.maven.api.di.Inject
ModelParser parser;
@org.apache.maven.api.di.Inject
List<ModelParser> parsers;
@org.apache.maven.api.di.Inject
Map<String, ModelParser> parsersMap;
}
@org.apache.maven.api.di.Named
static class TestModelParser implements ModelParser {
@Override
public Optional<Source> locate(Path dir) {
return Optional.empty();
}
@Override
public Model parse(Source source, Map<String, ?> options) throws ModelParserException {
return null;
}
}
}
}

View File

@ -39,15 +39,15 @@ class MojoExecutionScopeTest {
Object o1 = new Object();
scope.seed(Object.class, o1);
assertSame(o1, scope.scope(Key.get(Object.class), null).get());
assertSame(o1, scope.scope(Key.get(Object.class), () -> null).get());
scope.enter();
Object o2 = new Object();
scope.seed(Object.class, o2);
assertSame(o2, scope.scope(Key.get(Object.class), null).get());
assertSame(o2, scope.scope(Key.get(Object.class), () -> null).get());
scope.exit();
assertSame(o1, scope.scope(Key.get(Object.class), null).get());
assertSame(o1, scope.scope(Key.get(Object.class), () -> null).get());
scope.exit();

View File

@ -22,9 +22,9 @@ 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.internal.impl.di.OutOfScopeException;
import org.apache.maven.session.scope.internal.SessionScope;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;

View File

@ -36,27 +36,29 @@ public interface Injector {
}
@Nonnull
Injector discover(ClassLoader classLoader);
Injector discover(@Nonnull ClassLoader classLoader);
@Nonnull
Injector bindScope(Class<? extends Annotation> scopeAnnotation, Scope scope);
Injector bindScope(@Nonnull Class<? extends Annotation> scopeAnnotation, @Nonnull Scope scope);
@Nonnull
Injector bindScope(Class<? extends Annotation> scopeAnnotation, Supplier<Scope> scope);
Injector bindScope(@Nonnull Class<? extends Annotation> scopeAnnotation, @Nonnull Supplier<Scope> scope);
@Nonnull
Injector bindImplicit(Class<?> cls);
Injector bindImplicit(@Nonnull Class<?> cls);
@Nonnull
<T> Injector bindInstance(Class<T> cls, T instance);
<T> Injector bindInstance(@Nonnull Class<T> cls, @Nonnull T instance);
//
// Bean access
//
<T> void injectInstance(T instance);
<T> void injectInstance(@Nonnull T instance);
<T> T getInstance(Class<T> key);
@Nonnull
<T> T getInstance(@Nonnull Class<T> key);
<T> T getInstance(Key<T> key);
@Nonnull
<T> T getInstance(@Nonnull Key<T> key);
}

View File

@ -31,7 +31,7 @@ import org.apache.maven.di.impl.Utils;
* The key defines an identity of a binding. In any DI, a key is usually a type of the object along
* with some optional tag to distinguish between bindings which make objects of the same type.
* <p>
* In ActiveJ Inject, a key is also a type token - special abstract class that can store type information
* In Maven Inject, a key is also a type token - special abstract class that can store type information
* with the shortest syntax possible in Java.
* <p>
* For example, to create a key of type Map&lt;String, List&lt;Integer&gt;&gt;, you can just use

View File

@ -18,10 +18,34 @@
*/
package org.apache.maven.di;
import java.lang.annotation.Annotation;
import java.util.function.Supplier;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
/**
* A {@code Scope} defines how visible instances are when managed by a {@link org.apache.maven.di.Injector}.
* Typically, instances are created with <i>no scope</i>, meaning they dont retain any state from the
* frameworks perspective: the {@code Injector} generates the instance, injects it into the necessary class,
* and then immediately forgets it. By linking a scope to a specific binding, the created instance can be
* remembered and reused for future injections.
* <p>
* Instances are associated to a given scope by means of a {@link org.apache.maven.api.di.Scope @Scope}
* annotation, usually put on another annotation. For example, the {@code @Singleton} annotation is used
* to indicate that a given binding should be scoped as a singleton.
* <p>
* The following scopes are currently supported:
* <ul>
* <li>{@link org.apache.maven.api.di.Singleton @Singleton}</li>
* <li>{@link org.apache.maven.api.di.SessionScoped @SessionScoped}</li>
* <li>{@link org.apache.maven.api.di.MojoExecutionScoped @MojoExecutionScoped}</li>
* </ul>
*
* @since 4.0.0
*/
@Experimental
public interface Scope {
<T> Supplier<T> scope(Key<T> key, Annotation scope, Supplier<T> unscoped);
@Nonnull
<T> Supplier<T> scope(@Nonnull Key<T> key, @Nonnull Supplier<T> unscoped);
}

View File

@ -25,7 +25,7 @@ import org.apache.maven.di.Injector;
* (missing or cyclic dependencies, incorrect annotations etc.) or in runtime when
* you ask an {@link Injector} for an instance it does not have a {@link Binding binding} for.
*/
public final class DIException extends RuntimeException {
public class DIException extends RuntimeException {
public DIException(String message) {
super(message);
}

View File

@ -59,6 +59,7 @@ public class InjectorImpl implements Injector {
private final Map<Key<?>, Set<Binding<?>>> bindings = new HashMap<>();
private final Map<Class<? extends Annotation>, Supplier<Scope>> scopes = new HashMap<>();
private final Set<String> loadedUrls = new HashSet<>();
public InjectorImpl() {
bindScope(Singleton.class, new SingletonScope());
@ -87,8 +88,11 @@ public class InjectorImpl implements Injector {
try {
Enumeration<URL> enumeration = classLoader.getResources("META-INF/maven/org.apache.maven.api.di.Inject");
while (enumeration.hasMoreElements()) {
try (InputStream is = enumeration.nextElement().openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
URL url = enumeration.nextElement();
if (loadedUrls.add(url.toExternalForm())) {
try (InputStream is = url.openStream();
BufferedReader reader =
new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) {
for (String line :
reader.lines().filter(l -> !l.startsWith("#")).toList()) {
Class<?> clazz = classLoader.loadClass(line);
@ -96,6 +100,7 @@ public class InjectorImpl implements Injector {
}
}
}
}
} catch (Exception e) {
throw new DIException("Error while discovering DI classes from classLoader", e);
}
@ -195,7 +200,7 @@ public class InjectorImpl implements Injector {
if (res2 != null) {
List<Supplier<Object>> list = res2.stream().map(this::compile).collect(Collectors.toList());
//noinspection unchecked
return () -> (Q) list(list);
return () -> (Q) list(list, Supplier::get);
}
}
if (key.getRawType() == Map.class) {
@ -214,7 +219,7 @@ public class InjectorImpl implements Injector {
: null),
this::compile));
//noinspection unchecked
return () -> (Q) map(map);
return () -> (Q) map(map, Supplier::get);
}
}
if (dep.optional()) {
@ -241,7 +246,7 @@ public class InjectorImpl implements Injector {
.orElseThrow(() -> new DIException("Scope not bound for annotation "
+ binding.getScope().annotationType()))
.get();
compiled = scope.scope((Key<Q>) binding.getOriginalKey(), binding.getScope(), compiled);
compiled = scope.scope((Key<Q>) binding.getOriginalKey(), compiled);
}
return compiled;
}
@ -307,8 +312,8 @@ public class InjectorImpl implements Injector {
}
}
protected <K, V> Map<K, V> map(Map<K, Supplier<V>> map) {
return new WrappingMap<>(map, Supplier::get);
protected <K, V, T> Map<K, V> map(Map<K, T> map, Function<T, V> mapper) {
return new WrappingMap<>(map, mapper);
}
private static class WrappingMap<K, V, T> extends AbstractMap<K, V> {
@ -349,8 +354,8 @@ public class InjectorImpl implements Injector {
}
}
protected <T> List<T> list(List<Supplier<T>> bindingList) {
return new WrappingList<>(bindingList, Supplier::get);
protected <Q, T> List<Q> list(List<T> bindingList, Function<T, Q> mapper) {
return new WrappingList<>(bindingList, mapper);
}
private static class WrappingList<Q, T> extends AbstractList<Q> {
@ -379,8 +384,7 @@ public class InjectorImpl implements Injector {
@SuppressWarnings("unchecked")
@Override
public <T> java.util.function.Supplier<T> scope(
Key<T> key, Annotation scope, java.util.function.Supplier<T> unscoped) {
public <T> java.util.function.Supplier<T> scope(Key<T> key, java.util.function.Supplier<T> unscoped) {
return (java.util.function.Supplier<T>)
cache.computeIfAbsent(key, k -> new java.util.function.Supplier<T>() {
volatile T instance;

View File

@ -79,6 +79,7 @@ import org.apache.maven.cli.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.cli.transfer.SimplexTransferListener;
import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
import org.apache.maven.di.Injector;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.exception.DefaultExceptionHandler;
import org.apache.maven.exception.ExceptionHandler;
@ -95,7 +96,6 @@ import org.apache.maven.execution.scope.internal.MojoExecutionScope;
import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
import org.apache.maven.extension.internal.CoreExports;
import org.apache.maven.extension.internal.CoreExtensionEntry;
import org.apache.maven.internal.impl.SisuDiBridgeModule;
import org.apache.maven.jline.JLineMessageBuilderFactory;
import org.apache.maven.jline.MessageUtils;
import org.apache.maven.lifecycle.LifecycleExecutionException;
@ -732,10 +732,20 @@ public class MavenCli {
for (CoreExtensionEntry extension : extensions) {
container.discoverComponents(
extension.getClassRealm(),
new AbstractModule() {
@Override
protected void configure() {
try {
container.lookup(Injector.class).discover(extension.getClassRealm());
} catch (Throwable e) {
// ignore
e.printStackTrace();
}
}
},
new SessionScopeModule(container.lookup(SessionScope.class)),
new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)),
new ExtensionConfigurationModule(extension, extensionSource));
container.lookup(SisuDiBridgeModule.class).loadFromClassLoader(extension.getClassRealm());
}
customizeContainer(container);