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