ContextManager.java

  1. /*
  2.  * *********************************************************************************************************************
  3.  *
  4.  * TheseFoolishThings: Miscellaneous utilities
  5.  * http://tidalwave.it/projects/thesefoolishthings
  6.  *
  7.  * Copyright (C) 2009 - 2021 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
  12.  * the License. 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
  17.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  18.  * specific language governing permissions and limitations under the License.
  19.  *
  20.  * *********************************************************************************************************************
  21.  *
  22.  * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
  23.  * git clone https://github.com/tidalwave-it/thesefoolishthings-src
  24.  *
  25.  * *********************************************************************************************************************
  26.  */
  27. package it.tidalwave.role;

  28. import javax.annotation.CheckForNull;
  29. import javax.annotation.Nonnull;
  30. import java.util.Collections;
  31. import java.util.Iterator;
  32. import java.util.List;
  33. import java.util.Objects;
  34. import java.util.ServiceLoader;
  35. import java.util.function.Supplier;
  36. import it.tidalwave.util.NotFoundException;
  37. import it.tidalwave.util.Task;
  38. import it.tidalwave.role.spi.ContextManagerProvider;
  39. import lombok.AccessLevel;
  40. import lombok.NoArgsConstructor;
  41. import lombok.extern.slf4j.Slf4j;

  42. /***********************************************************************************************************************
  43.  *
  44.  * A facility to register and unregister global and local DCI contexts.
  45.  *
  46.  * @author  Fabrizio Giudici
  47.  *
  48.  **********************************************************************************************************************/
  49. public interface ContextManager
  50.   {
  51.     /*******************************************************************************************************************
  52.      *
  53.      * A locator for the {@link ContextManager} which uses the {@link ServiceLoader} facility to be independent of
  54.      * any DI framework.
  55.      *
  56.      * This locator caches the internal reference and this is ok for production use; during tests, since multiple
  57.      * contexts are typically created and destroyed for each test, you should call {@link #reset()} after each test
  58.      * has been completed.
  59.      *
  60.      ******************************************************************************************************************/
  61.     @Slf4j @NoArgsConstructor(access = AccessLevel.PRIVATE)
  62.     public static final class Locator
  63.       {
  64.         @CheckForNull
  65.         private static ContextManager contextManager;

  66.         @CheckForNull
  67.         private static ContextManagerProvider contextManagerProvider;

  68.         @Nonnull
  69.         public static synchronized ContextManager find ()
  70.           {
  71.             if (contextManager == null)
  72.               {
  73.                 if (contextManagerProvider == null)
  74.                   {
  75.                     final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  76.                     final Iterator<ContextManagerProvider> i =
  77.                             ServiceLoader.load(ContextManagerProvider.class, classLoader).iterator();

  78.                     if (!i.hasNext())
  79.                       {
  80.                         throw new RuntimeException("No ServiceProvider for ContextManagerProvider");
  81.                       }

  82.                     contextManagerProvider = Objects.requireNonNull(i.next(), "contextManagerProvider is null");
  83.                     assert contextManagerProvider != null; // for SpotBugs
  84.                     log.info("ContextManagerProvider instantiated from META-INF: {}", contextManagerProvider);
  85.                   }

  86.                 contextManager = Objects.requireNonNull(contextManagerProvider.getContextManager(),
  87.                                                         "Cannot find ContextManager");
  88.               }

  89.             assert contextManager != null; // for SpotBugs
  90.             return contextManager;
  91.           }

  92.         /***************************************************************************************************************
  93.          *
  94.          * <b>This method is for testing only.</b> Sets the global {@link ContextManagerProvider}. See note about
  95.          * {@link #reset()}.
  96.          *
  97.          * @param   provider    the provider
  98.          * @see     #reset()
  99.          *
  100.          **************************************************************************************************************/
  101.         public static void set (@Nonnull final ContextManagerProvider provider)
  102.           {
  103.             contextManager = null;
  104.             contextManagerProvider = provider;
  105.           }

  106.         /***************************************************************************************************************
  107.          *
  108.          * <b>This method is for testing only.</b> Resets the global {@link ContextManagerProvider}; it must be called
  109.          * at the test completion whenever {@link #set(ContextManagerProvider)} has been called, to avoid polluting the
  110.          * context of further tests.
  111.          *
  112.          * @see     #set(ContextManagerProvider)
  113.          *
  114.          **************************************************************************************************************/
  115.         public static void reset()
  116.           {
  117.             contextManager = null;
  118.             contextManagerProvider = null;
  119.           }
  120.       }

  121.     @FunctionalInterface
  122.     public static interface RunnableWithException<E extends Throwable>
  123.       {
  124.         public void run()
  125.                 throws E;
  126.       }

  127.     @FunctionalInterface
  128.     public static interface SupplierWithException<T, E extends Throwable>
  129.       {
  130.         public T get()
  131.                 throws E;
  132.       }

  133.     /*******************************************************************************************************************
  134.      *
  135.      * Returns the list of current contexts, ordered by their priority.
  136.      *
  137.      * @return  the list of current contexts
  138.      *
  139.      ******************************************************************************************************************/
  140.     @Nonnull
  141.     public List<Object> getContexts();

  142.     /*******************************************************************************************************************
  143.      *
  144.      * Finds a current context instance of the given type.
  145.      *
  146.      * @param   <T>                the static context type
  147.      * @param   contextType        the dynamic context type
  148.      * @return                     the requested context
  149.      * @throws  NotFoundException  if no context of that type is found
  150.      *
  151.      ******************************************************************************************************************/
  152.     @Nonnull
  153.     public <T> T findContextOfType (@Nonnull Class<T> contextType)
  154.       throws NotFoundException;

  155.     /*******************************************************************************************************************
  156.      *
  157.      * Adds a global context.
  158.      *
  159.      * @param  context             the new context
  160.      *
  161.      ******************************************************************************************************************/
  162.     public void addGlobalContext (@Nonnull Object context);

  163.     /*******************************************************************************************************************
  164.      *
  165.      * Removes a global context.
  166.      *
  167.      * @param  context            the context
  168.      *
  169.      ******************************************************************************************************************/
  170.     public void removeGlobalContext (@Nonnull Object context);

  171.     /*******************************************************************************************************************
  172.      *
  173.      * Adds a local context.
  174.      *
  175.      * @param  context            the new context
  176.      *
  177.      ******************************************************************************************************************/
  178.     public void addLocalContext (@Nonnull Object context);

  179.     /*******************************************************************************************************************
  180.      *
  181.      * Removes a local context.
  182.      *
  183.      * @param  context            the context
  184.      *
  185.      ******************************************************************************************************************/
  186.     public void removeLocalContext (@Nonnull Object context);

  187.     /*******************************************************************************************************************
  188.      *
  189.      * Runs a {@link Task} associated with a new local context.
  190.      *
  191.      * @param  <V>                the type of the returned value
  192.      * @param  <T>                the type of the exception that can be thrown
  193.      * @param  context            the context
  194.      * @param  task               the task
  195.      * @return                    the value produced by the task
  196.      * @throws T                  the exception(s) thrown by the task
  197.      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
  198.      *
  199.      ******************************************************************************************************************/
  200.     @Deprecated
  201.     public default <V, T extends Throwable> V runWithContext (@Nonnull final Object context,
  202.                                                               @Nonnull final Task<V, T> task)
  203.       throws T
  204.       {
  205.         return runWithContexts(Collections.singletonList(context), task);
  206.       }

  207.     /*******************************************************************************************************************
  208.      *
  209.      * Runs a {@link Task} associated with a new bunch of local contexts.
  210.      *
  211.      * @param  <V>                the type of the returned value
  212.      * @param  <T>                the type of the exception that can be thrown
  213.      * @param  contexts           the contexts
  214.      * @param  task               the task
  215.      * @return                    the value produced by the task
  216.      * @throws T                  the exception(s) thrown by the task
  217.      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
  218.      *
  219.      ******************************************************************************************************************/
  220.     @Deprecated
  221.     public default <V, T extends Throwable> V runWithContexts (@Nonnull final List<Object> contexts,
  222.                                                                @Nonnull final Task<V, T> task)
  223.       throws T
  224.       {
  225.         return runEWithContexts(task::run, contexts.toArray());
  226.       }

  227.     /*******************************************************************************************************************
  228.      *
  229.      * Runs a task associated with a new local context. This variant fits functional interfaces.
  230.      *
  231.      * @param  <V>                the type of the returned value of the task
  232.      * @param  context            the context
  233.      * @param  task               the task
  234.      * @return                    the value produced by the task
  235.      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
  236.      *
  237.      ******************************************************************************************************************/
  238.     @Deprecated
  239.     public default <V> V runWithContext (@Nonnull final Object context, @Nonnull final Supplier<V> task)
  240.       {
  241.         return runWithContexts(task, context);
  242.       }

  243.     /*******************************************************************************************************************
  244.      *
  245.      * Runs a task associated with a new bunch of local contexts. This variant fits functional interfaces.
  246.      *
  247.      * @param  <V>                the type of the returned value
  248.      * @param  contexts           the contexts
  249.      * @param  task               the task
  250.      * @return                    the value produced by the task
  251.      * @deprecated Use {@link #runWithContexts(Runnable, Object...)} or {@link #runWithContexts(Supplier, Object...)}
  252.      *
  253.      ******************************************************************************************************************/
  254.     @Deprecated
  255.     public default <V> V runWithContexts (@Nonnull final List<Object> contexts, @Nonnull final Supplier<V> task)
  256.       {
  257.         return runWithContexts(task, contexts.toArray());
  258.       }

  259.     /*******************************************************************************************************************
  260.      *
  261.      * Calls a runnable with some local contexts. This method fits functional interfaces.
  262.      *
  263.      * @param   runnable          the runnable
  264.      * @param   contexts          the contexts
  265.      * @since   3.2-ALPHA-12
  266.      *
  267.      ******************************************************************************************************************/
  268.     public default void runWithContexts (@Nonnull final Runnable runnable, @Nonnull final Object ... contexts)
  269.       {
  270.         final SupplierWithException<Void, RuntimeException> se = () ->{ runnable.run(); return null; };
  271.         runEWithContexts(se, contexts);
  272.       }

  273.     /*******************************************************************************************************************
  274.      *
  275.      * Calls a supplier with some local contexts. This method fits functional interfaces.
  276.      *
  277.      * @param   <T>               the type of the result
  278.      * @param   supplier          the supplier
  279.      * @param   contexts          the contexts
  280.      * @return                    the value returned by the supplier
  281.      * @since   3.2-ALPHA-12
  282.      *
  283.      ******************************************************************************************************************/
  284.     @Nonnull
  285.     public default <T> T runWithContexts (@Nonnull final Supplier<T> supplier, @Nonnull final Object ... contexts)
  286.       {
  287.         final SupplierWithException<T, RuntimeException> se = supplier::get;
  288.         return runEWithContexts(se, contexts);
  289.       }

  290.     /*******************************************************************************************************************
  291.      *
  292.      * Calls a runnable with some local contexts. This method fits functional interfaces.
  293.      *
  294.      * @param   <E>               the type of the thrown exception
  295.      * @param   runnable          the runnable to call
  296.      * @param   contexts          the contexts
  297.      * @throws  E                 the original exception thrown by task
  298.      * @since   3.2-ALPHA-12
  299.      *
  300.      ******************************************************************************************************************/
  301.     public default <E extends Throwable> void runEWithContexts (@Nonnull final RunnableWithException<E> runnable,
  302.                                                                 @Nonnull final Object ... contexts)
  303.       throws E
  304.       {
  305.         final SupplierWithException<Void, E> se = () ->{ runnable.run(); return null; };
  306.         runEWithContexts(se, contexts);
  307.       }

  308.     /*******************************************************************************************************************
  309.      *
  310.      * Calls a task with some local contexts. This method fits functional interfaces.
  311.      *
  312.      * @param   <T>               the type of the returned value
  313.      * @param   <E>               the type of the thrown exception
  314.      * @param   task              the task to call
  315.      * @param   contexts          the contexts
  316.      * @return                    the value returned by the supplier
  317.      * @throws  E                 the original exception thrown by task
  318.      * @since   3.2-ALPHA-12
  319.      *
  320.      ******************************************************************************************************************/
  321.     @Nonnull
  322.     public <T, E extends Throwable> T runEWithContexts (@Nonnull SupplierWithException<T, E> task,
  323.                                                         @Nonnull Object ... contexts)
  324.       throws E;

  325.     /*******************************************************************************************************************
  326.      *
  327.      * Creates a binder that makes it possible to bind a local context by means of a try-with-resources instead of a
  328.      * try/finally.
  329.      *
  330.      * <pre>
  331.      * try (final ContextManager.Binder binder = contextManager.binder(context))
  332.      *   {
  333.      *     ...
  334.      *   }
  335.      * </pre>
  336.      *
  337.      * @param   contexts          the contexts
  338.      * @return                    a binder that can be used in try-with-resources
  339.      * @since   3.2-ALPHA-12
  340.      *
  341.      ******************************************************************************************************************/
  342.     @Nonnull
  343.     public default Binder binder (@Nonnull final Object ... contexts)
  344.       {
  345.         return new Binder(this, contexts);
  346.       }

  347.     /*******************************************************************************************************************
  348.      *
  349.      * Used by
  350.      * @since   3.2-ALPHA-12
  351.      *
  352.      ******************************************************************************************************************/
  353.     public static class Binder implements AutoCloseable
  354.       {
  355.         @Nonnull
  356.         private final ContextManager contextManager;

  357.         @Nonnull
  358.         private final Object[] contexts;

  359.         private Binder (@Nonnull final ContextManager contextManager, @Nonnull final Object[] contexts)
  360.           {
  361.             this.contextManager = contextManager;
  362.             this.contexts = contexts;

  363.             for (final Object context : contexts)
  364.               {
  365.                 this.contextManager.addLocalContext(context);
  366.               }
  367.           }

  368.         @Override
  369.         public void close()
  370.           {
  371.             for (final Object context : contexts)
  372.               {
  373.                 this.contextManager.removeLocalContext(context);
  374.               }
  375.           }
  376.       }
  377.   }