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