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