Skip to content

Commit 74fd607

Browse files
committed
Implement DI scopes
... and reuse them for Sisu
1 parent 2b13a43 commit 74fd607

File tree

13 files changed

+421
-284
lines changed

13 files changed

+421
-284
lines changed

maven-api-impl/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ under the License.
7171
<groupId>org.apache.maven</groupId>
7272
<artifactId>maven-api-settings</artifactId>
7373
</dependency>
74+
<dependency>
75+
<groupId>org.apache.maven</groupId>
76+
<artifactId>maven-di</artifactId>
77+
</dependency>
7478
<dependency>
7579
<groupId>org.apache.maven.resolver</groupId>
7680
<artifactId>maven-resolver-api</artifactId>
@@ -132,11 +136,6 @@ under the License.
132136
<groupId>org.assertj</groupId>
133137
<artifactId>assertj-core</artifactId>
134138
</dependency>
135-
<dependency>
136-
<groupId>org.apache.maven</groupId>
137-
<artifactId>maven-di</artifactId>
138-
<scope>test</scope>
139-
</dependency>
140139
<dependency>
141140
<groupId>org.apache.maven.resolver</groupId>
142141
<artifactId>maven-resolver-named-locks</artifactId>
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl.di;
20+
21+
import java.util.Collection;
22+
import java.util.HashMap;
23+
import java.util.LinkedList;
24+
import java.util.Map;
25+
import java.util.function.Supplier;
26+
27+
import org.apache.maven.api.annotations.Nonnull;
28+
import org.apache.maven.di.Key;
29+
import org.apache.maven.di.Scope;
30+
import org.apache.maven.di.impl.DIException;
31+
32+
/**
33+
* MojoExecutionScope
34+
*/
35+
public class MojoExecutionScope implements Scope {
36+
37+
protected static final class ScopeState {
38+
private final Map<Key<?>, Supplier<?>> seeded = new HashMap<>();
39+
40+
private final Map<Key<?>, Object> provided = new HashMap<>();
41+
42+
public <T> void seed(Class<T> clazz, Supplier<T> value) {
43+
seeded.put(Key.of(clazz), value);
44+
}
45+
46+
public Collection<Object> provided() {
47+
return provided.values();
48+
}
49+
}
50+
51+
private final ThreadLocal<LinkedList<ScopeState>> values = new ThreadLocal<>();
52+
53+
public MojoExecutionScope() {}
54+
55+
public static <T> Supplier<T> seededKeySupplier(Class<? extends T> clazz) {
56+
return () -> {
57+
throw new IllegalStateException(
58+
"No instance of " + clazz.getName() + " is bound to the mojo execution scope.");
59+
};
60+
}
61+
62+
public void enter() {
63+
LinkedList<ScopeState> stack = values.get();
64+
if (stack == null) {
65+
stack = new LinkedList<>();
66+
values.set(stack);
67+
}
68+
stack.addFirst(new ScopeState());
69+
}
70+
71+
protected ScopeState getScopeState() {
72+
LinkedList<ScopeState> stack = values.get();
73+
if (stack == null || stack.isEmpty()) {
74+
throw new IllegalStateException();
75+
}
76+
return stack.getFirst();
77+
}
78+
79+
public void exit() {
80+
final LinkedList<ScopeState> stack = values.get();
81+
if (stack == null || stack.isEmpty()) {
82+
throw new IllegalStateException();
83+
}
84+
stack.removeFirst();
85+
if (stack.isEmpty()) {
86+
values.remove();
87+
}
88+
}
89+
90+
public <T> void seed(Class<T> clazz, Supplier<T> value) {
91+
getScopeState().seed(clazz, value);
92+
}
93+
94+
public <T> void seed(Class<T> clazz, final T value) {
95+
seed(clazz, (Supplier<T>) () -> value);
96+
}
97+
98+
@SuppressWarnings("unchecked")
99+
@Nonnull
100+
public <T> Supplier<T> scope(@Nonnull Key<T> key, @Nonnull Supplier<T> unscoped) {
101+
return () -> {
102+
LinkedList<ScopeState> stack = values.get();
103+
if (stack == null || stack.isEmpty()) {
104+
throw new DIException("Cannot access " + key + " outside of a scoping block");
105+
}
106+
107+
ScopeState state = stack.getFirst();
108+
109+
Supplier<?> seeded = state.seeded.get(key);
110+
111+
if (seeded != null) {
112+
return (T) seeded.get();
113+
}
114+
115+
T provided = (T) state.provided.get(key);
116+
if (provided == null && unscoped != null) {
117+
provided = unscoped.get();
118+
state.provided.put(key, provided);
119+
}
120+
121+
return provided;
122+
};
123+
}
124+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl.di;
20+
21+
import org.apache.maven.di.impl.DIException;
22+
23+
public class OutOfScopeException extends DIException {
24+
public OutOfScopeException(String message) {
25+
super(message);
26+
}
27+
28+
public OutOfScopeException(String message, Throwable cause) {
29+
super(message, cause);
30+
}
31+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl.di;
20+
21+
import java.lang.annotation.Annotation;
22+
import java.lang.reflect.InvocationHandler;
23+
import java.lang.reflect.InvocationTargetException;
24+
import java.util.Collection;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.concurrent.ConcurrentHashMap;
28+
import java.util.concurrent.CopyOnWriteArrayList;
29+
import java.util.function.Supplier;
30+
import java.util.stream.Stream;
31+
32+
import org.apache.maven.di.Key;
33+
import org.apache.maven.di.Scope;
34+
import org.apache.maven.di.impl.Types;
35+
36+
public class SessionScope implements Scope {
37+
38+
/**
39+
* ScopeState
40+
*/
41+
protected static final class ScopeState {
42+
private final Map<Key<?>, CachingProvider<?>> provided = new ConcurrentHashMap<>();
43+
44+
public <T> void seed(Class<T> clazz, Supplier<T> value) {
45+
provided.put(Key.of(clazz), new CachingProvider<>(value));
46+
}
47+
48+
@SuppressWarnings("unchecked")
49+
public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
50+
Supplier<?> provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped));
51+
return (Supplier<T>) provider;
52+
}
53+
54+
public Collection<CachingProvider<?>> providers() {
55+
return provided.values();
56+
}
57+
}
58+
59+
protected final List<ScopeState> values = new CopyOnWriteArrayList<>();
60+
61+
public void enter() {
62+
values.add(0, new ScopeState());
63+
}
64+
65+
protected ScopeState getScopeState() {
66+
if (values.isEmpty()) {
67+
throw new OutOfScopeException("Cannot access session scope outside of a scoping block");
68+
}
69+
return values.get(0);
70+
}
71+
72+
public void exit() {
73+
if (values.isEmpty()) {
74+
throw new IllegalStateException();
75+
}
76+
values.remove(0);
77+
}
78+
79+
public <T> void seed(Class<T> clazz, Supplier<T> value) {
80+
getScopeState().seed(clazz, value);
81+
}
82+
83+
public <T> void seed(Class<T> clazz, T value) {
84+
seed(clazz, (Supplier<T>) () -> value);
85+
}
86+
87+
@Override
88+
public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
89+
// Lazy evaluating provider
90+
return () -> {
91+
if (values.isEmpty()) {
92+
return createProxy(key, unscoped);
93+
} else {
94+
return getScopeState().scope(key, unscoped).get();
95+
}
96+
};
97+
}
98+
99+
@SuppressWarnings("unchecked")
100+
protected <T> T createProxy(Key<T> key, Supplier<T> unscoped) {
101+
InvocationHandler dispatcher = (proxy, method, args) -> {
102+
method.setAccessible(true);
103+
try {
104+
return method.invoke(getScopeState().scope(key, unscoped).get(), args);
105+
} catch (InvocationTargetException e) {
106+
throw e.getCause();
107+
}
108+
};
109+
Class<T> superType = (Class<T>) Types.getRawType(key.getType());
110+
Class<?>[] interfaces = getInterfaces(superType);
111+
return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher);
112+
}
113+
114+
protected Class<?>[] getInterfaces(Class<?> superType) {
115+
if (superType.isInterface()) {
116+
return new Class<?>[] {superType};
117+
} else {
118+
for (Annotation a : superType.getAnnotations()) {
119+
Class<? extends Annotation> annotationType = a.annotationType();
120+
if (isTypeAnnotation(annotationType)) {
121+
try {
122+
Class<?>[] value =
123+
(Class<?>[]) annotationType.getMethod("value").invoke(a);
124+
if (value.length == 0) {
125+
value = superType.getInterfaces();
126+
}
127+
List<Class<?>> nonInterfaces =
128+
Stream.of(value).filter(c -> !c.isInterface()).toList();
129+
if (!nonInterfaces.isEmpty()) {
130+
throw new IllegalArgumentException(
131+
"The Typed annotation must contain only interfaces but the following types are not: "
132+
+ nonInterfaces);
133+
}
134+
return value;
135+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
136+
throw new IllegalStateException(e);
137+
}
138+
}
139+
}
140+
throw new IllegalArgumentException("The use of session scoped proxies require "
141+
+ "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation");
142+
}
143+
}
144+
145+
protected boolean isTypeAnnotation(Class<? extends Annotation> annotationType) {
146+
return "org.apache.maven.api.di.Typed".equals(annotationType.getName());
147+
}
148+
149+
/**
150+
* A provider wrapping an existing provider with a cache
151+
* @param <T> the provided type
152+
*/
153+
protected static class CachingProvider<T> implements Supplier<T> {
154+
private final Supplier<T> provider;
155+
private volatile T value;
156+
157+
CachingProvider(Supplier<T> provider) {
158+
this.provider = provider;
159+
}
160+
161+
public T value() {
162+
return value;
163+
}
164+
165+
@Override
166+
public T get() {
167+
if (value == null) {
168+
synchronized (this) {
169+
if (value == null) {
170+
value = provider.get();
171+
}
172+
}
173+
}
174+
return value;
175+
}
176+
}
177+
178+
public static <T> Supplier<T> seededKeySupplier(Class<? extends T> clazz) {
179+
return () -> {
180+
throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope.");
181+
};
182+
}
183+
}

0 commit comments

Comments
 (0)