1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.lang.reflect.Method;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.concurrent.CopyOnWriteArrayList;
30 import java.util.function.Supplier;
31 import java.util.stream.Stream;
32
33 import org.apache.maven.di.Key;
34 import org.apache.maven.di.Scope;
35 import org.apache.maven.di.impl.Types;
36
37 public class SessionScope implements Scope {
38
39
40
41
42 protected static final class ScopeState {
43 private final Map<Key<?>, CachingProvider<?>> provided = new ConcurrentHashMap<>();
44
45 public <T> void seed(Class<T> clazz, Supplier<T> value) {
46 provided.put(Key.of(clazz), new CachingProvider<>(value));
47 }
48
49 @SuppressWarnings("unchecked")
50 public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
51 Supplier<?> provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped));
52 return (Supplier<T>) provider;
53 }
54
55 public Collection<CachingProvider<?>> providers() {
56 return provided.values();
57 }
58 }
59
60 protected final List<ScopeState> values = new CopyOnWriteArrayList<>();
61
62 public void enter() {
63 values.add(0, new ScopeState());
64 }
65
66 protected ScopeState getScopeState() {
67 if (values.isEmpty()) {
68 throw new OutOfScopeException("Cannot access session scope outside of a scoping block");
69 }
70 return values.get(0);
71 }
72
73 public void exit() {
74 if (values.isEmpty()) {
75 throw new IllegalStateException();
76 }
77 values.remove(0);
78 }
79
80 public <T> void seed(Class<T> clazz, Supplier<T> value) {
81 getScopeState().seed(clazz, value);
82 }
83
84 public <T> void seed(Class<T> clazz, T value) {
85 seed(clazz, (Supplier<T>) () -> value);
86 }
87
88 @Override
89 public <T> Supplier<T> scope(Key<T> key, Supplier<T> unscoped) {
90
91 return () -> {
92 if (values.isEmpty()) {
93 return createProxy(key, unscoped);
94 } else {
95 return getScopeState().scope(key, unscoped).get();
96 }
97 };
98 }
99
100 @SuppressWarnings("unchecked")
101 protected <T> T createProxy(Key<T> key, Supplier<T> unscoped) {
102 InvocationHandler dispatcher = (proxy, method, args) -> dispatch(key, unscoped, method, args);
103 Class<T> superType = (Class<T>) Types.getRawType(key.getType());
104 Class<?>[] interfaces = getInterfaces(superType);
105 return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher);
106 }
107
108 protected <T> Object dispatch(Key<T> key, Supplier<T> unscoped, Method method, Object[] args) throws Throwable {
109 method.setAccessible(true);
110 try {
111 return method.invoke(getScopeState().scope(key, unscoped).get(), args);
112 } catch (InvocationTargetException e) {
113 throw e.getCause();
114 }
115 }
116
117 protected Class<?>[] getInterfaces(Class<?> superType) {
118 if (superType.isInterface()) {
119 return new Class<?>[] {superType};
120 } else {
121 for (Annotation a : superType.getAnnotations()) {
122 Class<? extends Annotation> annotationType = a.annotationType();
123 if (isTypeAnnotation(annotationType)) {
124 try {
125 Class<?>[] value =
126 (Class<?>[]) annotationType.getMethod("value").invoke(a);
127 if (value.length == 0) {
128 value = superType.getInterfaces();
129 }
130 List<Class<?>> nonInterfaces =
131 Stream.of(value).filter(c -> !c.isInterface()).toList();
132 if (!nonInterfaces.isEmpty()) {
133 throw new IllegalArgumentException(
134 "The Typed annotation must contain only interfaces but the following types are not: "
135 + nonInterfaces);
136 }
137 return value;
138 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
139 throw new IllegalStateException(e);
140 }
141 }
142 }
143 throw new IllegalArgumentException("The use of session scoped proxies require "
144 + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation");
145 }
146 }
147
148 protected boolean isTypeAnnotation(Class<? extends Annotation> annotationType) {
149 return "org.apache.maven.api.di.Typed".equals(annotationType.getName());
150 }
151
152
153
154
155
156 protected static class CachingProvider<T> implements Supplier<T> {
157 private final Supplier<T> provider;
158 private volatile T value;
159
160 CachingProvider(Supplier<T> provider) {
161 this.provider = provider;
162 }
163
164 public T value() {
165 return value;
166 }
167
168 @Override
169 public T get() {
170 if (value == null) {
171 synchronized (this) {
172 if (value == null) {
173 value = provider.get();
174 }
175 }
176 }
177 return value;
178 }
179 }
180
181 public static <T> Supplier<T> seededKeySupplier(Class<? extends T> clazz) {
182 return () -> {
183 throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope.");
184 };
185 }
186 }