View Javadoc
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.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       * ScopeState
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          // Lazy evaluating provider
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      * A provider wrapping an existing provider with a cache
154      * @param <T> the provided type
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 }