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 try {
111 return method.invoke(getScopeState().scope(key, unscoped).get(), args);
112 } catch (InvocationTargetException e) {
113 throw e.getCause();
114 }
115 };
116 Class<T> superType = (Class<T>) key.getTypeLiteral().getRawType();
117 Class<?>[] interfaces = getInterfaces(superType);
118 return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher);
119 }
120
121 private Class<?>[] getInterfaces(Class<?> superType) {
122 if (superType.isInterface()) {
123 return new Class<?>[] {superType};
124 } else {
125 for (Annotation a : superType.getAnnotations()) {
126 Class<? extends Annotation> annotationType = a.annotationType();
127 if ("org.eclipse.sisu.Typed".equals(annotationType.getName())
128 || "javax.enterprise.inject.Typed".equals(annotationType.getName())
129 || "jakarta.enterprise.inject.Typed".equals(annotationType.getName())) {
130 try {
131 Class<?>[] value =
132 (Class<?>[]) annotationType.getMethod("value").invoke(a);
133 if (value.length == 0) {
134 value = superType.getInterfaces();
135 }
136 List<Class<?>> nonInterfaces =
137 Stream.of(value).filter(c -> !c.isInterface()).collect(Collectors.toList());
138 if (!nonInterfaces.isEmpty()) {
139 throw new IllegalArgumentException(
140 "The Typed annotation must contain only interfaces but the following types are not: "
141 + nonInterfaces);
142 }
143 return value;
144 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
145 throw new IllegalStateException(e);
146 }
147 }
148 }
149 throw new IllegalArgumentException("The use of session scoped proxies require "
150 + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation");
151 }
152 }
153
154
155
156
157
158 protected static class CachingProvider<T> implements Provider<T> {
159 private final Provider<T> provider;
160 private volatile T value;
161
162 CachingProvider(Provider<T> provider) {
163 this.provider = provider;
164 }
165
166 public T value() {
167 return value;
168 }
169
170 @Override
171 public T get() {
172 if (value == null) {
173 synchronized (this) {
174 if (value == null) {
175 value = provider.get();
176 }
177 }
178 }
179 return value;
180 }
181 }
182
183 @SuppressWarnings({"unchecked"})
184 public static <T> Provider<T> seededKeyProvider() {
185 return (Provider<T>) SEEDED_KEY_PROVIDER;
186 }
187
188 public static <T> Provider<T> seededKeyProvider(Class<? extends T> clazz) {
189 return () -> {
190 throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope.");
191 };
192 }
193 }