View Javadoc
1   /*
2    * *************************************************************************************************************************************************************
3    *
4    * TheseFoolishThings: Miscellaneous utilities
5    * http://tidalwave.it/projects/thesefoolishthings
6    *
7    * Copyright (C) 2009 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8    *
9    * *************************************************************************************************************************************************************
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17   * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
18   *
19   * *************************************************************************************************************************************************************
20   *
21   * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22   * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23   *
24   * *************************************************************************************************************************************************************
25   */
26  package it.tidalwave.util;
27  
28  import javax.annotation.Nonnull;
29  import java.util.List;
30  import java.util.Optional;
31  import java.util.ServiceLoader;
32  import java.util.function.Supplier;
33  import it.tidalwave.role.spi.ContextManagerProvider;
34  import static it.tidalwave.role.impl.ServiceLoaderLocator.lazySupplierOf;
35  
36  /***************************************************************************************************************************************************************
37   *
38   * A facility to register and unregister global and local DCI contexts.
39   *
40   * @author  Fabrizio Giudici
41   *
42   **************************************************************************************************************************************************************/
43  public interface ContextManager
44    {
45      /***********************************************************************************************************************************************************
46       * A locator for the {@link ContextManager} which uses the {@link ServiceLoader} facility to be independent of
47       * any DI framework.
48       *
49       * This locator caches the internal reference and this is ok for production use; during tests, since multiple
50       * contexts are typically created and destroyed for each test, you should call {@link #reset()} after each test
51       * has been completed.
52       **********************************************************************************************************************************************************/
53      static class Inner
54        {
55          private static final LazySupplier<ContextManager> CONTEXT_MANAGER_REF =
56                  LazySupplier.of(() -> Inner.CONTEXT_MANAGER_PROVIDER_REF.get().getContextManager());
57  
58          private static final LazySupplier<ContextManagerProvider> CONTEXT_MANAGER_PROVIDER_REF =
59                  lazySupplierOf(ContextManagerProvider.class);
60        }
61  
62      /***********************************************************************************************************************************************************
63       * Returns a singleton instance.
64       *
65       * @return  the singleton instance
66       **********************************************************************************************************************************************************/
67      @Nonnull
68      public static ContextManager getInstance()
69        {
70          return Inner.CONTEXT_MANAGER_REF.get();
71        }
72  
73      /***********************************************************************************************************************************************************
74       * <b>This method is for testing only.</b> Sets the global {@link ContextManagerProvider}. See note about
75       * {@link #reset()}.
76       *
77       * @param   provider    the provider
78       * @see     #reset()
79       **********************************************************************************************************************************************************/
80      public static void set (@Nonnull final ContextManagerProvider provider)
81        {
82          Inner.CONTEXT_MANAGER_REF.clear();
83          Inner.CONTEXT_MANAGER_PROVIDER_REF.set(provider);
84        }
85  
86      /***********************************************************************************************************************************************************
87       * <b>This method is for testing only.</b> Resets the global {@link ContextManagerProvider}; it must be called
88       * at the test completion whenever {@link #set(ContextManagerProvider)} has been called, to avoid polluting the
89       * context of further tests.
90       *
91       * @see     #set(ContextManagerProvider)
92       **********************************************************************************************************************************************************/
93      public static void reset()
94        {
95          Inner.CONTEXT_MANAGER_REF.clear();
96          Inner.CONTEXT_MANAGER_PROVIDER_REF.clear();
97        }
98  
99      @FunctionalInterface
100     public static interface RunnableWithException<E extends Throwable>
101       {
102         public void run()
103                 throws E;
104       }
105 
106     @FunctionalInterface
107     public static interface SupplierWithException<T, E extends Throwable>
108       {
109         public T get()
110                 throws E;
111       }
112 
113     /***********************************************************************************************************************************************************
114      * Returns the list of current contexts, ordered by their priority.
115      *
116      * @return  the list of current contexts
117      **********************************************************************************************************************************************************/
118     @Nonnull
119     public List<Object> getContexts();
120 
121     /***********************************************************************************************************************************************************
122      * Finds a current context instance of the given type.
123      *
124      * @param   <T>                the static context type
125      * @param   contextType        the dynamic context type
126      * @return                     the requested context
127      **********************************************************************************************************************************************************/
128     @Nonnull
129     public <T> Optional<T> findContextOfType (@Nonnull Class<T> contextType);
130 
131     /***********************************************************************************************************************************************************
132      * Adds a global context.
133      *
134      * @param  context             the new context
135      **********************************************************************************************************************************************************/
136     public void addGlobalContext (@Nonnull Object context);
137 
138     /***********************************************************************************************************************************************************
139      * Removes a global context.
140      *
141      * @param  context            the context
142      **********************************************************************************************************************************************************/
143     public void removeGlobalContext (@Nonnull Object context);
144 
145     /***********************************************************************************************************************************************************
146      * Adds a local context.
147      *
148      * @param  context            the new context
149      **********************************************************************************************************************************************************/
150     public void addLocalContext (@Nonnull Object context);
151 
152     /***********************************************************************************************************************************************************
153      * Removes a local context.
154      *
155      * @param  context            the context
156      **********************************************************************************************************************************************************/
157     public void removeLocalContext (@Nonnull Object context);
158 
159     /***********************************************************************************************************************************************************
160      * Runs a {@link Task} associated with a new local context.
161      *
162      * @param  <V>                the type of the returned value
163      * @param  <T>                the type of the exception that can be thrown
164      * @param  context            the context
165      * @param  task               the task
166      * @return                    the value produced by the task
167      * @throws T                  the exception(s) thrown by the task
168      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
169      **********************************************************************************************************************************************************/
170     @Deprecated
171     public default <V, T extends Throwable> V runWithContext (@Nonnull final Object context,
172                                                               @Nonnull final Task<V, T> task)
173       throws T
174       {
175         return runWithContexts(List.of(context), task);
176       }
177 
178     /***********************************************************************************************************************************************************
179      * Runs a {@link Task} associated with a new bunch of local contexts.
180      *
181      * @param  <V>                the type of the returned value
182      * @param  <T>                the type of the exception that can be thrown
183      * @param  contexts           the contexts
184      * @param  task               the task
185      * @return                    the value produced by the task
186      * @throws T                  the exception(s) thrown by the task
187      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
188      **********************************************************************************************************************************************************/
189     @Deprecated
190     public default <V, T extends Throwable> V runWithContexts (@Nonnull final List<Object> contexts,
191                                                                @Nonnull final Task<V, T> task)
192       throws T
193       {
194         return runEWithContexts(task::run, contexts.toArray());
195       }
196 
197     /***********************************************************************************************************************************************************
198      * Runs a task associated with a new local context. This variant fits functional interfaces.
199      *
200      * @param  <V>                the type of the returned value of the task
201      * @param  context            the context
202      * @param  task               the task
203      * @return                    the value produced by the task
204      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
205      **********************************************************************************************************************************************************/
206     @Deprecated
207     public default <V> V runWithContext (@Nonnull final Object context, @Nonnull final Supplier<V> task)
208       {
209         return runWithContexts(task, context);
210       }
211 
212     /***********************************************************************************************************************************************************
213      * Runs a task associated with a new bunch of local contexts. This variant fits functional interfaces.
214      *
215      * @param  <V>                the type of the returned value
216      * @param  contexts           the contexts
217      * @param  task               the task
218      * @return                    the value produced by the task
219      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
220      **********************************************************************************************************************************************************/
221     @Deprecated
222     public default <V> V runWithContexts (@Nonnull final List<Object> contexts, @Nonnull final Supplier<V> task)
223       {
224         return runWithContexts(task, contexts.toArray());
225       }
226 
227     /***********************************************************************************************************************************************************
228      * Calls a runnable with some local contexts. This method fits functional interfaces.
229      *
230      * @param   runnable          the runnable
231      * @param   contexts          the contexts
232      * @since   3.2-ALPHA-12
233      **********************************************************************************************************************************************************/
234     public default void runWithContexts (@Nonnull final Runnable runnable, @Nonnull final Object ... contexts)
235       {
236         final SupplierWithException<Void, RuntimeException> se = () ->{ runnable.run(); return null; };
237         runEWithContexts(se, contexts);
238       }
239 
240     /***********************************************************************************************************************************************************
241      * Calls a supplier with some local contexts. This method fits functional interfaces.
242      *
243      * @param   <T>               the type of the result
244      * @param   supplier          the supplier
245      * @param   contexts          the contexts
246      * @return                    the value returned by the supplier
247      * @since   3.2-ALPHA-12
248      **********************************************************************************************************************************************************/
249     @Nonnull
250     public default <T> T runWithContexts (@Nonnull final Supplier<T> supplier, @Nonnull final Object ... contexts)
251       {
252         final SupplierWithException<T, RuntimeException> se = supplier::get;
253         return runEWithContexts(se, contexts);
254       }
255 
256     /***********************************************************************************************************************************************************
257      * Calls a runnable with some local contexts. This method fits functional interfaces.
258      *
259      * @param   <E>               the type of the thrown exception
260      * @param   runnable          the runnable to call
261      * @param   contexts          the contexts
262      * @throws  E                 the original exception thrown by task
263      * @since   3.2-ALPHA-12
264      **********************************************************************************************************************************************************/
265     public default <E extends Throwable> void runEWithContexts (@Nonnull final RunnableWithException<E> runnable,
266                                                                 @Nonnull final Object ... contexts)
267       throws E
268       {
269         final SupplierWithException<Void, E> se = () ->{ runnable.run(); return null; };
270         runEWithContexts(se, contexts);
271       }
272 
273     /***********************************************************************************************************************************************************
274      * Calls a task with some local contexts. This method fits functional interfaces.
275      *
276      * @param   <T>               the type of the returned value
277      * @param   <E>               the type of the thrown exception
278      * @param   task              the task to call
279      * @param   contexts          the contexts
280      * @return                    the value returned by the supplier
281      * @throws  E                 the original exception thrown by task
282      * @since   3.2-ALPHA-12
283      **********************************************************************************************************************************************************/
284     @Nonnull
285     public <T, E extends Throwable> T runEWithContexts (@Nonnull SupplierWithException<T, E> task,
286                                                         @Nonnull Object ... contexts)
287       throws E;
288 
289     /***********************************************************************************************************************************************************
290      * Creates a binder that makes it possible to bind a local context by means of a try-with-resources instead of a
291      * try/finally.
292      *
293      * <pre>
294      * try (final ContextManager.Binder binder = contextManager.binder(context))
295      *   {
296      *     ...
297      *   }
298      * </pre>
299      *
300      * @param   contexts          the contexts
301      * @return                    a binder that can be used in try-with-resources
302      * @since   3.2-ALPHA-12
303      **********************************************************************************************************************************************************/
304     @Nonnull
305     public default Binder binder (@Nonnull final Object ... contexts)
306       {
307         return new Binder(this, contexts);
308       }
309 
310     /***********************************************************************************************************************************************************
311      * Used by
312      * @since   3.2-ALPHA-12
313      **********************************************************************************************************************************************************/
314     public static class Binder implements AutoCloseable
315       {
316         @Nonnull
317         private final ContextManager contextManager;
318 
319         @Nonnull
320         private final Object[] contexts;
321 
322         private Binder (@Nonnull final ContextManager contextManager, @Nonnull final Object[] contexts)
323           {
324             this.contextManager = contextManager;
325             this.contexts = contexts;
326 
327             for (final var context : contexts)
328               {
329                 this.contextManager.addLocalContext(context);
330               }
331           }
332 
333         @Override
334         public void close()
335           {
336             for (final var context : contexts)
337               {
338                 this.contextManager.removeLocalContext(context);
339               }
340           }
341       }
342   }