diff --git a/README.md b/README.md index 4bf4dd8..6741eec 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,10 @@ So lets try a better version - still without dedicated library support... ```java setFooState(fooValue1); -try{ - -run_logic_that_works_with_state_of_foo(); -}finally{ - -clearFooState(); +try { + run_logic_that_works_with_state_of_foo(); +} finally { + clearFooState(); } ``` @@ -74,23 +72,18 @@ addition to our foo state. And we make it so that the fooState is applied condit ```java var oldFooState = getFooState(); var oldBarState = getBarState(); -if(conditionIsSatisfied){ - -setFooState(fooValue1); +if (conditionIsSatisfied) { + setFooState(fooValue1); } - setBarState(barValue1); -try{ - -run_logic_that_works_with_state_of_foo_and_bar(); -}finally{ - -setBarState(oldBarState); - if(conditionIsSatisfied){ - -setFooState(oldFooState); +try { + run_logic_that_works_with_state_of_foo_and_bar(); +} finally { + setBarState(oldBarState); + if (conditionIsSatisfied) { + setFooState(oldFooState); } - } +} ``` **You might already expect it: This approach - despite all the increased clutter that we had to produce already - still @@ -105,27 +98,22 @@ our "transactional" problem. An ugly - but admitted truly robust - solution woul ```java var oldFooState = getFooState(); -if(conditionIsSatisfied){ - -setFooState(fooValue1); +if (conditionIsSatisfied) { + setFooState(fooValue1); } - try{ -var oldBarState = getBarState(); - -setBarState(barValue1); - try{ - -run_logic_that_works_with_state_of_foo_and_bar(); - }finally{ - -setBarState(oldBarState); +try { + var oldBarState = getBarState(); + setBarState(barValue1); + try { + run_logic_that_works_with_state_of_foo_and_bar(); + } finally { + setBarState(oldBarState); } - }finally{ - if(conditionIsSatisfied){ - -setFooState(oldFooState); +} finally { + if(conditionIsSatisfied) { + setFooState(oldFooState); } - } +} ``` In real case scenarios it often becomes even more complex than what we have seen here in our example above. Normally the @@ -143,13 +131,10 @@ var revert = DefaultStateRevert.chain(chain -> { } chain.append(pushBarState(barValue1)); }); -try{ - -run_logic_that_works_with_state_of_foo_and_bar(); -}finally{ - revert. - -revert(); +try { + run_logic_that_works_with_state_of_foo_and_bar(); +} finally { + revert.revert(); } ``` diff --git a/threadly-streaming/src/main/java/org/threadlys/state/StateFetcherFactoryImpl.java b/threadly-streaming/src/main/java/org/threadlys/state/StateFetcherFactoryImpl.java index f6abc66..592e9ca 100644 --- a/threadly-streaming/src/main/java/org/threadlys/state/StateFetcherFactoryImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/state/StateFetcherFactoryImpl.java @@ -72,11 +72,11 @@ public Future submit(Callable task) { protected T executeCallableAndCleanupWorkerState(Callable task) throws Exception { var cs = contextSnapshotFactory.createSnapshot(); - var rollback = cs.apply(); + var revert = cs.apply(); try { return task.call(); } finally { - rollback.rollback(); + revert.revert(); } } diff --git a/threadly-streaming/src/main/java/org/threadlys/streams/DataProcessorExtendable.java b/threadly-streaming/src/main/java/org/threadlys/streams/DataProcessorExtendable.java index 06a712d..587c3ce 100644 --- a/threadly-streaming/src/main/java/org/threadlys/streams/DataProcessorExtendable.java +++ b/threadly-streaming/src/main/java/org/threadlys/streams/DataProcessorExtendable.java @@ -2,15 +2,15 @@ import java.util.Collection; -import org.threadlys.utils.IStateRollback; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.StateRevert; +import org.threadlys.utils.DefaultStateRevert; /** * Defines extension points for registering data processors in order to make them available within the {@link AsyncDataProcessor} engine */ public interface DataProcessorExtendable { - default IStateRollback registerDataProcessor(DataProcessor dataProcessor, Class entityType, Collection dataScopes, Collection requiredDataScopes) { - return StateRollback.chain(chain -> { + default StateRevert registerDataProcessor(DataProcessor dataProcessor, Class entityType, Collection dataScopes, Collection requiredDataScopes) { + return DefaultStateRevert.chain(chain -> { if (dataScopes != null) { for (DataScope dataScope : dataScopes) { chain.append(registerDataProcessor(dataProcessor, entityType, dataScope)); @@ -32,7 +32,7 @@ default IStateRollback registerDataProcessor(DataProcessor dataProcess * @param entityType * @param dataScope */ - IStateRollback registerDataProcessor(DataProcessor dataProcessor, Class entityType, DataScope dataScope); + StateRevert registerDataProcessor(DataProcessor dataProcessor, Class entityType, DataScope dataScope); /** * Registers a data processor to require the given data scope before executing this data processor. Normally the required data scope of maintained by another data processor. This way you can @@ -42,7 +42,7 @@ default IStateRollback registerDataProcessor(DataProcessor dataProcess * @param dataProcessor * @param requiredDataScope */ - IStateRollback registerDataProcessorDependency(DataProcessor dataProcessor, DataScope requiredDataScope); + StateRevert registerDataProcessorDependency(DataProcessor dataProcessor, DataScope requiredDataScope); /** * Registers a data processor to require the given exception handler for unhandled exceptions. Executions of {@link DataProcessor#process(Object)} will be enclosed with a try/catch and exceptions @@ -52,5 +52,5 @@ default IStateRollback registerDataProcessor(DataProcessor dataProcess * @param dataProcessor * @param exceptionHandler */ - IStateRollback registerDataProcessorExceptionHandler(DataProcessor dataProcessor, DataProcessorExceptionHandler exceptionHandler); + StateRevert registerDataProcessorExceptionHandler(DataProcessor dataProcessor, DataProcessorExceptionHandler exceptionHandler); } diff --git a/threadly-streaming/src/main/java/org/threadlys/streams/impl/AsyncDataProcessorImpl.java b/threadly-streaming/src/main/java/org/threadlys/streams/impl/AsyncDataProcessorImpl.java index 6d4ff2e..4899d62 100644 --- a/threadly-streaming/src/main/java/org/threadlys/streams/impl/AsyncDataProcessorImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/streams/impl/AsyncDataProcessorImpl.java @@ -32,10 +32,10 @@ import org.threadlys.threading.ContextSnapshot; import org.threadlys.threading.ContextSnapshotFactory; import org.threadlys.threading.impl.ForkJoinPoolGuard; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ListenersMapListAdapter; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -353,12 +353,12 @@ protected void applyDataProcessorsToEntities } protected void executeDataProcessorStages(List>> stageToRunnableSuppliersList) { - var rollback = StateRollback.empty(); + var revert = DefaultStateRevert.empty(); try { var fjp = forkJoinPoolGuard.currentForkJoinPool(); if (fjp == null) { fjp = forkJoinPoolGuard.getDefaultForkJoinPool(); - rollback = forkJoinPoolGuard.pushForkJoinPool(fjp); + revert = forkJoinPoolGuard.pushForkJoinPool(fjp); } for (var runnableSuppliersList : stageToRunnableSuppliersList) { var callables = new ArrayList>>(runnableSuppliersList.size()); @@ -385,7 +385,7 @@ protected void executeDataProcessorStages(Li updateEntities(futures, indexToEntityMap, fjp); } } finally { - rollback.rollback(); + revert.revert(); } } @@ -769,23 +769,23 @@ protected Callable> async var exceptionHandlers = dataProcessorToExceptionHandlerMap.get(dataProcessor); if (exceptionHandlers == null || exceptionHandlers.isEmpty()) { return () -> { - var rollback = cs.apply(); + var revert = cs.apply(); try { return dataProcessor.process(processorContext); } finally { - rollback.rollback(); + revert.revert(); } }; } else { return () -> { - var rollback = cs.apply(); + var revert = cs.apply(); try { return dataProcessor.process(processorContext); } catch (Throwable e) { var lastExceptionHandler = (DataProcessorExceptionHandler) exceptionHandlers.get(exceptionHandlers.size() - 1); return lastExceptionHandler.handleProcessException(dataProcessor, processorContext, e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -793,7 +793,7 @@ protected Callable> async @SuppressWarnings({ "rawtypes", "unchecked" }) @Override - public IStateRollback registerDataProcessor(DataProcessor dataProcessor, Class entityType, DataScope dataScope) { + public StateRevert registerDataProcessor(DataProcessor dataProcessor, Class entityType, DataScope dataScope) { writeLock.lock(); try { ConfigurationState newState = new ConfigurationState(state); @@ -831,7 +831,7 @@ protected void unregisterDataProcessor(DataProcessor dataProcessor, Cl } @Override - public IStateRollback registerDataProcessorDependency(DataProcessor dataProcessor, DataScope requiredDataScope) { + public StateRevert registerDataProcessorDependency(DataProcessor dataProcessor, DataScope requiredDataScope) { writeLock.lock(); try { ConfigurationState newState = new ConfigurationState(state); @@ -855,7 +855,7 @@ protected void unregisterDataProcessorDependency(DataProcessor dataPro } @Override - public IStateRollback registerDataProcessorExceptionHandler(DataProcessor dataProcessor, DataProcessorExceptionHandler exceptionHandler) { + public StateRevert registerDataProcessorExceptionHandler(DataProcessor dataProcessor, DataProcessorExceptionHandler exceptionHandler) { writeLock.lock(); try { ConfigurationState newState = new ConfigurationState(state); diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshot.java b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshot.java index 34b648c..b439f5a 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshot.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshot.java @@ -14,7 +14,7 @@ import org.threadlys.streams.CheckedPredicate; import org.threadlys.streams.CheckedRunnable; import org.threadlys.streams.CheckedSupplier; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; /** * A handle containing a snapshot of values of thread-local fields at the moment of calling {@link ContextSnapshotFactory#createSnapshot()}.
@@ -22,7 +22,7 @@ * This handle can be used either *
    *
  1. for Java Streams API - then with the {@link #push()} and {@link #pop()} operations
  2. or for wrapping Runnables / Callables for an {@link Executor} or {@link ForkJoinPool} - *
  3. or just manually with a try/finally pattern with the {@link #apply(IStateRollback...)} operations
  4. + *
  5. or just manually with a try/finally pattern with the {@link #apply(StateRevert...)} operations
  6. *
*
*
@@ -58,17 +58,16 @@ * }));
* *
  • // executed any worker thread
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   // do some calculation stuff that uses valueTL.get() from MyBean
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    *
  • * * * @author Dennis Koch (EXXETA AG) - * */ public interface ContextSnapshot extends ParallelStreamFassade { /** @@ -93,28 +92,28 @@ public interface ContextSnapshot extends ParallelStreamFassade { * Allows fine-grained control of when exactly and for how long a context snapshot shall be applied to the current thread. In most circumstances on of the {@link #scoped(CheckedFunction)} * overloads is the preferred methods as it takes automatically care about a proper try/finally structure internally for robustness against thread-local leaks. * - * @return The rollback handle that allows to revert all applied changes by simply invoking {@link IStateRollback#rollback()} + * @return The revert handle that allows to revert all applied changes by simply invoking {@link StateRevert#revert()} */ - IStateRollback apply(); + StateRevert apply(); /** * Allows fine-grained control of when exactly and for how long a context snapshot shall be applied to the current thread. In most circumstances on of the {@link #scoped(CheckedFunction)} * overloads is the preferred methods as it takes automatically care about a proper try/finally structure internally for robustness against thread-local leaks. * - * @param rollbacks - * One or more rollbacks to chain in into the returned handle - * @return The rollback handle that allows to revert all applied changes by simply invoking {@link IStateRollback#rollback()} + * @param reverts + * One or more reverts to chain in into the returned handle + * @return The revert handle that allows to revert all applied changes by simply invoking {@link StateRevert#revert()} */ - IStateRollback apply(IStateRollback... rollbacks); + StateRevert apply(StateRevert... reverts); /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given runnable. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   runnable.run();
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param runnable @@ -126,11 +125,11 @@ public interface ContextSnapshot extends ParallelStreamFassade { /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given function. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   return function.apply(arg);
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param @@ -144,11 +143,11 @@ public interface ContextSnapshot extends ParallelStreamFassade { /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given supplier. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   return supplier.get();
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param @@ -161,11 +160,11 @@ public interface ContextSnapshot extends ParallelStreamFassade { /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given callable. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   return callable.call();
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param @@ -178,11 +177,11 @@ public interface ContextSnapshot extends ParallelStreamFassade { /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given consumer. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   consumer.accept(arg);
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param @@ -195,11 +194,11 @@ public interface ContextSnapshot extends ParallelStreamFassade { /** * Convenience method to apply the current snapshot for exactly the duration of the execution of the given predicate. Calling this method is equivalent to:
    *
    - * IStateRollback rollback = cs.apply();
    + * StateRevert revert = cs.apply();
    * try {
    *   predicate.accept(arg);
    * } finally {
    - *   rollback.rollback();
    + *   revert.revert();
    * }
    * * @param diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotFactory.java b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotFactory.java index 939a484..f944009 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotFactory.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotFactory.java @@ -42,7 +42,7 @@ public interface ContextSnapshotFactory extends ParallelStreamFassade { * thread. Snapshot handles are considered applied if there is one of the * following active on the stack: *
      - *
    • {@link ContextSnapshot#apply(org.threadlys.utils.IStateRollback...)}
    • + *
    • {@link ContextSnapshot#apply(org.threadlys.utils.StateRevert...)}
    • *
    • {@link ContextSnapshot#scoped(org.threadlys.streams.CheckedRunnable)} * or any other of the scoped() overloads
    • *
    • {@link ContextSnapshot#push()}
    • diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotLifecycleListenerExtendable.java b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotLifecycleListenerExtendable.java index 179fe7a..4d5c23b 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotLifecycleListenerExtendable.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/ContextSnapshotLifecycleListenerExtendable.java @@ -1,10 +1,10 @@ package org.threadlys.threading; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; //CHECKSTYLE: JavadocMethod OFF @SuppressWarnings({ "checkstyle:JavadocMethod" }) public interface ContextSnapshotLifecycleListenerExtendable { - IStateRollback registerContextSnapshotLifecycleListener(ContextSnapshotLifecycleListener listener); + StateRevert registerContextSnapshotLifecycleListener(ContextSnapshotLifecycleListener listener); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/TaskExecutorListenerExtendable.java b/threadly-streaming/src/main/java/org/threadlys/threading/TaskExecutorListenerExtendable.java index f97cdf1..8250041 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/TaskExecutorListenerExtendable.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/TaskExecutorListenerExtendable.java @@ -1,9 +1,9 @@ package org.threadlys.threading; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; //CHECKSTYLE: JavadocMethod OFF @SuppressWarnings({ "checkstyle:JavadocMethod" }) public interface TaskExecutorListenerExtendable { - IStateRollback registerTaskExecutorListener(TaskExecutorListener listener); + StateRevert registerTaskExecutorListener(TaskExecutorListener listener); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/ThreadLocalTransferrerExtendable.java b/threadly-streaming/src/main/java/org/threadlys/threading/ThreadLocalTransferrerExtendable.java index 1c76f5f..563b24a 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/ThreadLocalTransferrerExtendable.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/ThreadLocalTransferrerExtendable.java @@ -1,9 +1,9 @@ package org.threadlys.threading; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; //CHECKSTYLE: JavadocMethod OFF @SuppressWarnings({ "checkstyle:JavadocMethod" }) public interface ThreadLocalTransferrerExtendable { - IStateRollback registerThreadLocalTransferrer(ThreadLocalTransferrer transferrer, Class beanType); + StateRevert registerThreadLocalTransferrer(ThreadLocalTransferrer transferrer, Class beanType); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/TransferrableThreadLocal.java b/threadly-streaming/src/main/java/org/threadlys/threading/TransferrableThreadLocal.java index 94fc21d..aa132d0 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/TransferrableThreadLocal.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/TransferrableThreadLocal.java @@ -1,7 +1,7 @@ package org.threadlys.threading; import org.threadlys.threading.impl.ContextSnapshotController; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; /** * Encapsulates the logic to read and write from a thread-local handle. We don't use a thread-local handle directly in order to allow via fassade-pattern also more customizable logic in order to @@ -14,7 +14,7 @@ public interface TransferrableThreadLocal { /** * Called from the master thread in order to create a snapshot of all thread-locals while executing {@link ContextSnapshotFactory#createSnapshot()}. In addition this method is also called by - * forked threads while executing {@link ContextSnapshotController#pushContext(org.threadlys.threading.impl.ContextSnapshotImpl, org.threadlys.utils.IStateRollback...)} + * forked threads while executing {@link ContextSnapshotController#pushContext(org.threadlys.threading.impl.ContextSnapshotImpl, org.threadlys.utils.StateRevert...)} * in order to be able to restore the previous state of the worker. * * @return @@ -27,5 +27,5 @@ public interface TransferrableThreadLocal { * @param value * The thread-local value that shall be applied to the current thread for the given handle */ - IStateRollback setForFork(T newForkedValue, T oldForkedValue); + StateRevert setForFork(T newForkedValue, T oldForkedValue); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ConcurrentProcessingFilter.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ConcurrentProcessingFilter.java index c407ff0..2ad4b3a 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ConcurrentProcessingFilter.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ConcurrentProcessingFilter.java @@ -93,20 +93,20 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } if (Boolean.TRUE.equals(threadlyStreamingConfiguration.getPoolPerRequest())) { var fjp = acquireForkJoinPool(); - var rollback = forkJoinPoolGuard.pushForkJoinPool(fjp); + var revert = forkJoinPoolGuard.pushForkJoinPool(fjp); try { forkJoinPoolGuard.reentrantInvokeOnForkJoinPool(() -> filterChain.doFilter(request, response)); } finally { - rollback.rollback(); + revert.revert(); releaseForkJoinPool(fjp); } } else { var fjp = forkJoinPoolGuard.getDefaultForkJoinPool(); - var rollback = forkJoinPoolGuard.pushForkJoinPool(fjp); + var revert = forkJoinPoolGuard.pushForkJoinPool(fjp); try { forkJoinPoolGuard.reentrantInvokeOnForkJoinPool(() -> filterChain.doFilter(request, response)); } finally { - rollback.rollback(); + revert.revert(); } } } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotController.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotController.java index d4d5c7c..34b95b8 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotController.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotController.java @@ -1,16 +1,16 @@ package org.threadlys.threading.impl; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; /** * Internal interface for the {@link ContextSnapshotImpl} in order to - * apply/rollback its context to/from the current thread + * apply/revert its context to/from the current thread * * @author Dennis Koch (EXXETA AG) */ public interface ContextSnapshotController { - IStateRollback pushContext(ContextSnapshotIntern contextSnapshot); + StateRevert pushContext(ContextSnapshotIntern contextSnapshot); void popContext(ContextSnapshotIntern contextSnapshot); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotControllerImpl.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotControllerImpl.java index 2077166..e3ac254 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotControllerImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotControllerImpl.java @@ -24,11 +24,11 @@ import org.threadlys.threading.Transferrable; import org.threadlys.threading.TransferrableThreadLocal; import org.threadlys.threading.TransferrableThreadLocals; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ListenersListAdapter; import org.threadlys.utils.ReflectUtil; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import lombok.extern.slf4j.Slf4j; @@ -43,7 +43,7 @@ public class ContextSnapshotControllerImpl implements ContextSnapshotFactory, ContextSnapshotLifecycleListenerExtendable, ContextSnapshotController, InitializingBean, DisposableBean { public static final Object[] ALL_NULL_VALUES = new Object[0]; - public static final IStateRollback[] ALL_NULL_ROLLBACKS = new IStateRollback[0]; + public static final StateRevert[] ALL_NULL_REVERTS = new StateRevert[0]; // this constant is intentionally different from the ABOVE. do not refactor this public static final Object[] EMPTY_VALUES = new Object[0]; @@ -213,28 +213,28 @@ protected Object[] createSnapshotForThreadLocals(TransferrableThreadLocal[] t } @Override - public IStateRollback pushContext(ContextSnapshotIntern cs) { + public StateRevert pushContext(ContextSnapshotIntern cs) { var thread = Thread.currentThread(); log.debug("identity=CS{} - context snapshot APPLY START on thread '{}-{}': {}", System.identityHashCode(cs), thread.getId(), thread.getName(), cs); var threadLocals = cs.getThreadLocals(); var values = cs.getValues(); - var oldValueRollbacks = ALL_NULL_ROLLBACKS; + var oldValueReverts = ALL_NULL_REVERTS; for (int a = threadLocals.length; a-- > 0;) { @SuppressWarnings("unchecked") TransferrableThreadLocal threadLocal = (TransferrableThreadLocal) threadLocals[a]; Object oldValue = threadLocal.get(); Object newValue = values[a]; - var rollback = threadLocal.setForFork(newValue, oldValue); - if (rollback == null || rollback == StateRollback.empty()) { + var revert = threadLocal.setForFork(newValue, oldValue); + if (revert == null || revert == DefaultStateRevert.empty()) { // no old value to store continue; } - if (oldValueRollbacks == ALL_NULL_ROLLBACKS) { + if (oldValueReverts == ALL_NULL_REVERTS) { // once: now the full lazy instantiation of the oldValues storage - oldValueRollbacks = new IStateRollback[threadLocals.length]; + oldValueReverts = new StateRevert[threadLocals.length]; } - oldValueRollbacks[a] = rollback; + oldValueReverts[a] = revert; } var oldThreadScopeMap = transferrableThreadScope.getAndRemoveThreadScopeMap(); var oldBeanProcessor = transferrableThreadScope.getBeanProcessor(); @@ -246,7 +246,7 @@ public IStateRollback pushContext(ContextSnapshotIntern cs) { pushedContexts = new ArrayList<>(); pushedContextsTL.set(pushedContexts); } - pushedContexts.add(new PushedContext(cs, oldValueRollbacks, oldThreadScopeMap, oldBeanProcessor)); + pushedContexts.add(new PushedContext(cs, oldValueReverts, oldThreadScopeMap, oldBeanProcessor)); log.debug("identity=CS{} - context snapshot APPLY FINISH on thread '{}-{}': {}", System.identityHashCode(cs), thread.getId(), thread.getName(), cs); listeners.stream() @@ -294,14 +294,14 @@ private PushedContext popRecentContextIfValid(List pushedContexts return recentContext; } - protected void applyValuesToThreadLocals(IStateRollback[] oldValues, TransferrableThreadLocal[] threadLocals) { - if (oldValues == ALL_NULL_ROLLBACKS) { + protected void applyValuesToThreadLocals(StateRevert[] oldValues, TransferrableThreadLocal[] threadLocals) { + if (oldValues == ALL_NULL_REVERTS) { return; } for (int a = oldValues.length; a-- > 0;) { var oldValue = oldValues[a]; if (oldValue != null) { - oldValue.rollback(); + oldValue.revert(); } } } @@ -322,7 +322,7 @@ public ContextSnapshot emptySnapshot() { } @Override - public IStateRollback registerContextSnapshotLifecycleListener(ContextSnapshotLifecycleListener listener) { + public StateRevert registerContextSnapshotLifecycleListener(ContextSnapshotLifecycleListener listener) { ListenersListAdapter.registerListener(listener, listeners); return () -> unregisterContextSnapshotLifecycleListener(listener); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotImpl.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotImpl.java index b6002e7..85764d3 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ContextSnapshotImpl.java @@ -26,10 +26,10 @@ import org.threadlys.threading.ThreadLocalTransferrer; import org.threadlys.threading.ThreadLocalTransferrerRegistry; import org.threadlys.threading.TransferrableThreadLocal; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ReflectUtil; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -94,25 +94,25 @@ public Function pop() { } @Override - public IStateRollback apply() { + public StateRevert apply() { return contextSnapshotController.pushContext(this); } @Override - public IStateRollback apply(IStateRollback... rollbacks) { - return StateRollback.prepend(contextSnapshotController.pushContext(this), rollbacks); + public StateRevert apply(StateRevert... reverts) { + return DefaultStateRevert.prepend(contextSnapshotController.pushContext(this), reverts); } @Override public Predicate scopedPredicate(CheckedPredicate predicate) { return t -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { return predicate.test(t); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -120,13 +120,13 @@ public Predicate scopedPredicate(CheckedPredicate predicate) { @Override public Consumer scopedConsumer(CheckedConsumer consumer) { return t -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { consumer.accept(t); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -134,13 +134,13 @@ public Consumer scopedConsumer(CheckedConsumer consumer) { @Override public Callable scopedCallable(CheckedCallable callable) { return () -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { return callable.call(); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -148,13 +148,13 @@ public Callable scopedCallable(CheckedCallable callable) { @Override public Function scoped(CheckedFunction function) { return t -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { return function.apply(t); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -162,13 +162,13 @@ public Function scoped(CheckedFunction function) { @Override public Runnable scoped(CheckedRunnable runnable) { return () -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { runnable.run(); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } @@ -176,13 +176,13 @@ public Runnable scoped(CheckedRunnable runnable) { @Override public Supplier scoped(CheckedSupplier supplier) { return () -> { - IStateRollback rollback = contextSnapshotController.pushContext(this); + StateRevert revert = contextSnapshotController.pushContext(this); try { return supplier.get(); } catch (Exception e) { throw sneakyThrowUtil.sneakyThrow(e); } finally { - rollback.rollback(); + revert.revert(); } }; } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/DecoratedForkJoinPool.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/DecoratedForkJoinPool.java index 9df8966..ed9b68e 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/DecoratedForkJoinPool.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/DecoratedForkJoinPool.java @@ -19,8 +19,8 @@ import java.util.concurrent.TimeoutException; import java.util.function.Predicate; -import org.threadlys.utils.IStateRollback; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.StateRevert; +import org.threadlys.utils.DefaultStateRevert; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -39,11 +39,11 @@ public static class DecoratedRunnable implements Runnable { @Override public void run() { - var rollback = forkJoinPool.pushMonitorCurrentThread(); + var revert = forkJoinPool.pushMonitorCurrentThread(); try { runnable.run(); } finally { - rollback.rollback(); + revert.revert(); } } } @@ -58,11 +58,11 @@ public static class DecoratedCallable implements Callable { @Override public T call() throws Exception { - var rollback = forkJoinPool.pushMonitorCurrentThread(); + var revert = forkJoinPool.pushMonitorCurrentThread(); try { return callable.call(); } finally { - rollback.rollback(); + revert.revert(); } } } @@ -97,7 +97,7 @@ public DecoratedForkJoinPool(Duration workerTimeout, int parallelism, ForkJoinWo this.workerTimeout = workerTimeout; } - public IStateRollback registerListener(DecoratedForkJoinPoolListener listener) { + public StateRevert registerListener(DecoratedForkJoinPoolListener listener) { Objects.requireNonNull(listener, "listener must be valid"); listeners.add(listener); return () -> listeners.remove(listener); @@ -121,10 +121,10 @@ public Collection resolveBusyTimedOutThreads() { .toList(); } - protected IStateRollback pushMonitorCurrentThread() { + protected StateRevert pushMonitorCurrentThread() { if (workerTimeout == null && listeners.isEmpty()) { // no monitoring at all - return StateRollback.empty(); + return DefaultStateRevert.empty(); } var thread = Thread.currentThread(); threadToReentrantCounterMap.compute(thread, (key, reentrantCounter) -> { @@ -142,7 +142,7 @@ protected IStateRollback pushMonitorCurrentThread() { threadToReentrantCounterMap.compute(thread, (key, reentrantCounter) -> { if (reentrantCounter == null || reentrantCounter.getCounter() <= 1) { // could happen in error cases where the thread referent is already cleaned up and the key lookup therefore not successful anymore - // also may happen in erroneus handling of the returned rollback handle + // also may happen in erroneus handling of the returned revert handle return null; } reentrantCounter.decrementCounter(); diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolGuard.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolGuard.java index 7722c46..9bc4c83 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolGuard.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolGuard.java @@ -16,9 +16,9 @@ import org.threadlys.streams.CheckedRunnable; import org.threadlys.utils.FutureUtil; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.NamedThreadLocal; @@ -108,11 +108,11 @@ public ForkJoinWorkerThread newThread(ForkJoinPool pool) { private Optional defaultForkJoinPool = Optional.empty(); - private IStateRollback defaultListenerRollback = StateRollback.empty(); + private StateRevert defaultListenerRevert = DefaultStateRevert.empty(); @Override public void destroy() throws Exception { - defaultListenerRollback.rollback(); + defaultListenerRevert.revert(); defaultForkJoinPool.ifPresent(ForkJoinPool::shutdownNow); } @@ -131,7 +131,7 @@ public DecoratedForkJoinPool getDefaultForkJoinPool() { return fjp; } var dfjp = createForkJoinPool(); - defaultListenerRollback = StateRollback.chain(chain -> { + defaultListenerRevert = DefaultStateRevert.chain(chain -> { for (var listener : forkJoinPoolListeners) { chain.append(dfjp.registerListener(listener)); } @@ -155,11 +155,11 @@ public DecoratedForkJoinPool createForkJoinPool() { threadlyStreamingConfiguration.getPoolSize(), threadlyStreamingConfiguration.getMaximumPoolSize(), 1, fjp -> true, 10L, TimeUnit.SECONDS); } - public IStateRollback pushForkJoinPool(ForkJoinPool fjp) { + public StateRevert pushForkJoinPool(ForkJoinPool fjp) { var existingFjp = forkJoinPoolTL.get(); if (existingFjp == null && fjp == null) { // nothing to do - return StateRollback.empty(); + return DefaultStateRevert.empty(); } forkJoinPoolTL.set(fjp); return () -> { @@ -171,14 +171,14 @@ public IStateRollback pushForkJoinPool(ForkJoinPool fjp) { }; } - public IStateRollback pushForkJoinPoolIfRequired() { + public StateRevert pushForkJoinPoolIfRequired() { var existingFjp = forkJoinPoolTL.get(); if (existingFjp != null) { // nothing to do - return StateRollback.empty(); + return DefaultStateRevert.empty(); } var fjp = createForkJoinPool(); - return StateRollback.chain(chain -> { + return DefaultStateRevert.chain(chain -> { chain.append(pushForkJoinPool(fjp)); if (log.isDebugEnabled()) { log.debug("Concurrent processing enabled (" + threadlyStreamingConfiguration.getPoolSize() + " workers)"); @@ -213,7 +213,7 @@ public void reentrantInvokeOnForkJoinPool(CheckedRunnable joinPoint) { */ public R reentrantInvokeOnForkJoinPoolWithResult(CheckedSupplier joinPoint) { Throwable ex = null; - var rollback = pushForkJoinPoolIfRequired(); + var revert = pushForkJoinPoolIfRequired(); try { ContextSnapshot cs = contextSnapshotFactory.createSnapshot(); if (threadlyStreamingConfiguration.isParallelActive()) { @@ -230,7 +230,7 @@ public R reentrantInvokeOnForkJoinPoolWithResult(CheckedSupplier joinPoin } catch (Throwable e) { ex = sneakyThrowUtil.mergeStackTraceWithCause(e); } finally { - rollback.rollback(); + revert.revert(); } throw sneakyThrowUtil.sneakyThrow(ex); } @@ -256,11 +256,11 @@ public void shutdownForkJoinPool(ForkJoinPool fjp) { @SneakyThrows protected R invokeJoinPoint(ContextSnapshot cs, CheckedSupplier joinPoint) { - var rollback = cs != null ? cs.apply() : StateRollback.empty(); + var revert = cs != null ? cs.apply() : DefaultStateRevert.empty(); try { return joinPoint.get(); } finally { - rollback.rollback(); + revert.revert(); } } } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolTaskExecutor.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolTaskExecutor.java index 36deb9b..f7600f7 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolTaskExecutor.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ForkJoinPoolTaskExecutor.java @@ -4,9 +4,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ForkJoinPool; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ListenersListAdapter; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.core.task.TaskExecutor; @@ -51,12 +51,12 @@ public void execute(Runnable command) { command.run(); return; } - var rollback = StateRollback.empty(); + var revert = DefaultStateRevert.empty(); try { var fjp = forkJoinPoolGuard.currentForkJoinPool(); if (fjp == null) { fjp = forkJoinPoolGuard.getDefaultForkJoinPool(); - rollback = forkJoinPoolGuard.pushForkJoinPool(fjp); + revert = forkJoinPoolGuard.pushForkJoinPool(fjp); } var cs = contextSnapshotFactory.createSnapshot(); if (!log.isDebugEnabled() && listeners.isEmpty()) { @@ -85,12 +85,12 @@ public void execute(Runnable command) { })); listeners.forEach(listener -> listener.taskQueued(command)); } finally { - rollback.rollback(); + revert.revert(); } } @Override - public IStateRollback registerTaskExecutorListener(TaskExecutorListener listener) { + public StateRevert registerTaskExecutorListener(TaskExecutorListener listener) { ListenersListAdapter.registerListener(listener, listeners); return () -> unregisterTaskExecutorListener(listener); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/NoOpContextSnapshot.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/NoOpContextSnapshot.java index e3534ea..cb0fd7d 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/NoOpContextSnapshot.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/NoOpContextSnapshot.java @@ -16,9 +16,9 @@ import org.threadlys.streams.CheckedSupplier; import org.threadlys.threading.ContextSnapshot; import org.threadlys.threading.ParallelStreamFassade; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import lombok.RequiredArgsConstructor; @@ -117,13 +117,13 @@ public Function pop() { } @Override - public IStateRollback apply() { - return StateRollback.empty(); + public StateRevert apply() { + return DefaultStateRevert.empty(); } @Override - public IStateRollback apply(IStateRollback... rollbacks) { - return StateRollback.all(rollbacks); + public StateRevert apply(StateRevert... reverts) { + return DefaultStateRevert.all(reverts); } @Override diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/PushedContext.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/PushedContext.java index 1874a40..ff6d90c 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/PushedContext.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/PushedContext.java @@ -3,7 +3,7 @@ import java.util.Map; import org.threadlys.threading.ContextSnapshotFactory; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -20,7 +20,7 @@ public class PushedContext { private final ContextSnapshotIntern contextSnapshot; @Getter - private final IStateRollback[] oldValues; + private final StateRevert[] oldValues; @Getter private final Map oldThreadScopeMap; diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ThreadLocalTransferrerRegistryImpl.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ThreadLocalTransferrerRegistryImpl.java index 1efbad5..e50be91 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/ThreadLocalTransferrerRegistryImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/ThreadLocalTransferrerRegistryImpl.java @@ -6,7 +6,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ListenersMapAdapter; import org.springframework.stereotype.Component; @@ -28,7 +28,7 @@ public List> getThreadLocalTransferrers(Class beanT } @Override - public IStateRollback registerThreadLocalTransferrer(ThreadLocalTransferrer transferrer, Class beanType) { + public StateRevert registerThreadLocalTransferrer(ThreadLocalTransferrer transferrer, Class beanType) { ListenersMapAdapter.registerListener(transferrer, beanType, beanTypeToTransferrerMap); return () -> unregisterThreadLocalTransferrer(transferrer, beanType); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableApplicationContext.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableApplicationContext.java index 2e4565f..e9ab8fe 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableApplicationContext.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableApplicationContext.java @@ -4,7 +4,7 @@ import java.util.List; import org.threadlys.utils.ApplicationContextHolder; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; @@ -23,7 +23,7 @@ public ApplicationContext get() { } @Override - public IStateRollback setForFork(ApplicationContext newForkedValue, ApplicationContext oldForkedValue) { + public StateRevert setForFork(ApplicationContext newForkedValue, ApplicationContext oldForkedValue) { ApplicationContextHolder.setContext(newForkedValue); return () -> ApplicationContextHolder.setContext(oldForkedValue); } diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableRequestContext.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableRequestContext.java index ce9df37..ed28f61 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableRequestContext.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableRequestContext.java @@ -3,8 +3,8 @@ import java.util.Arrays; import java.util.List; -import org.threadlys.utils.IStateRollback; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.StateRevert; +import org.threadlys.utils.DefaultStateRevert; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -23,7 +23,7 @@ public RequestAttributes get() { } @Override - public IStateRollback setForFork(RequestAttributes newForkedValue, RequestAttributes oldForkedValue) { + public StateRevert setForFork(RequestAttributes newForkedValue, RequestAttributes oldForkedValue) { RequestContextHolder.setRequestAttributes(newForkedValue, false); return () -> RequestContextHolder.setRequestAttributes(oldForkedValue, false); } @@ -34,11 +34,11 @@ public String toString() { } } - public static IStateRollback pushRequestAttributes(RequestAttributes requestAttributes) { + public static StateRevert pushRequestAttributes(RequestAttributes requestAttributes) { RequestAttributes oldRequestAttributes = RequestContextHolder.getRequestAttributes(); if (oldRequestAttributes == requestAttributes) { // nothing to do - return StateRollback.empty(); + return DefaultStateRevert.empty(); } RequestContextHolder.setRequestAttributes(requestAttributes); return () -> RequestContextHolder.setRequestAttributes(oldRequestAttributes); diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableSecurityContext.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableSecurityContext.java index b918ffe..540ed3e 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableSecurityContext.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableSecurityContext.java @@ -3,9 +3,9 @@ import java.util.Arrays; import java.util.List; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ReflectUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -28,7 +28,7 @@ public Authentication get() { } @Override - public IStateRollback setForFork(Authentication newForkedValue, Authentication oldForkedValue) { + public StateRevert setForFork(Authentication newForkedValue, Authentication oldForkedValue) { SecurityContextHolder.getContext() .setAuthentication(newForkedValue); if (oldForkedValue == null) { @@ -45,12 +45,12 @@ public String toString() { } } - public static IStateRollback pushAuthentication(Authentication authentication) { + public static StateRevert pushAuthentication(Authentication authentication) { SecurityContext context = SecurityContextHolder.getContext(); Authentication oldAuthentication = context.getAuthentication(); if (oldAuthentication == authentication) { // nothing to do - return StateRollback.empty(); + return DefaultStateRevert.empty(); } SecurityContextHolder.getContext() .setAuthentication(authentication); diff --git a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableThreadLocalsImpl.java b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableThreadLocalsImpl.java index 9320f86..8d64974 100644 --- a/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableThreadLocalsImpl.java +++ b/threadly-streaming/src/main/java/org/threadlys/threading/impl/TransferrableThreadLocalsImpl.java @@ -1,7 +1,7 @@ package org.threadlys.threading.impl; import org.threadlys.streams.CheckedFunction; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.springframework.stereotype.Component; import org.threadlys.threading.TransferrableThreadLocal; @@ -30,7 +30,7 @@ public T get() { } @Override - public IStateRollback setForFork(T newForkedValue, T oldForkedValue) { + public StateRevert setForFork(T newForkedValue, T oldForkedValue) { if (newForkedValue == null) { threadLocal.remove(); } else { @@ -63,7 +63,7 @@ public T get() { @SneakyThrows @Override - public IStateRollback setForFork(T newForkedValue, T oldForkedValue) { + public StateRevert setForFork(T newForkedValue, T oldForkedValue) { var clonedValue = valueCloner.apply(newForkedValue); if (clonedValue == null) { threadLocal.remove(); diff --git a/threadly-streaming/src/test/java/org/threadlys/threading/test/AsyncDataProcessorImplTest.java b/threadly-streaming/src/test/java/org/threadlys/threading/test/AsyncDataProcessorImplTest.java index bea4cb8..763098f 100644 --- a/threadly-streaming/src/test/java/org/threadlys/threading/test/AsyncDataProcessorImplTest.java +++ b/threadly-streaming/src/test/java/org/threadlys/threading/test/AsyncDataProcessorImplTest.java @@ -11,7 +11,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.threadlys.utils.configuration.CommonsUtilsSpringConfig; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -85,7 +85,7 @@ public boolean isPartOf(DataScope scope) { @Test void test() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); AtomicInteger invocationCount2 = new AtomicInteger(); chain.append(dataProcessorExtendable.registerDataProcessor(context -> { @@ -121,7 +121,7 @@ void test() { @Test void testWithDataScopeSupplier() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); AtomicInteger invocationCount2 = new AtomicInteger(); chain.append(dataProcessorExtendable.registerDataProcessor(context -> { @@ -145,12 +145,12 @@ void testWithDataScopeSupplier() { assertThat(invocationCount1.get()).isEqualTo(2); assertThat(invocationCount2.get()).isEqualTo(2); }) - .rollback(); + .revert(); } @Test void testExceptionWithoutExceptionHandler() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); TestEntity te1 = new TestEntity().setDomainRef(1); TestEntity te2 = new TestEntity().setDomainRef(2); @@ -168,12 +168,12 @@ void testExceptionWithoutExceptionHandler() { assertThrows(SocketTimeoutException.class, () -> asyncDataProcessor.processAllEntities(TestEntity.class, Arrays.asList(te1, te2), (entity) -> Arrays.asList(TestDataScope.DS1), entity -> new TestEntityContext(entity), entityToUsedDataScopes)); }) - .rollback(); + .revert(); } @Test void testExceptionWithExceptionHandler() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); TestEntity te1 = new TestEntity().setDomainRef(1); TestEntity te2 = new TestEntity().setDomainRef(2); @@ -199,6 +199,6 @@ public CheckedConsumer handleProcessException(DataProcessor data entity -> new TestEntityContext(entity), entityToUsedDataScopes)); assertThat(exception.getCause()).isInstanceOf(SocketTimeoutException.class); }) - .rollback(); + .revert(); } } diff --git a/threadly-streaming/src/test/java/org/threadlys/threading/test/ConcurrentProcessingTest.java b/threadly-streaming/src/test/java/org/threadlys/threading/test/ConcurrentProcessingTest.java index 855717d..296a153 100644 --- a/threadly-streaming/src/test/java/org/threadlys/threading/test/ConcurrentProcessingTest.java +++ b/threadly-streaming/src/test/java/org/threadlys/threading/test/ConcurrentProcessingTest.java @@ -81,10 +81,10 @@ import org.threadlys.threading.test.context.TestService; import org.threadlys.threading.test.context.TestServiceImpl; import org.threadlys.utils.FutureUtil; -import org.threadlys.utils.IStateRollback; +import org.threadlys.utils.StateRevert; import org.threadlys.utils.ReflectUtil; import org.threadlys.utils.SneakyThrowUtil; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.threadlys.utils.configuration.CommonsUtilsSpringConfig; import com.fasterxml.jackson.core.JsonProcessingException; import lombok.SneakyThrows; @@ -267,7 +267,7 @@ void proofMemoryLeakOnCommonsFixable() throws Throwable { } /** - * Tests that two consecutive usages of distinct snapshots work properly and also rollback properly It also shows that a thread-local scoped Spring bean behaves differently than a raw thread-local + * Tests that two consecutive usages of distinct snapshots work properly and also revert properly It also shows that a thread-local scoped Spring bean behaves differently than a raw thread-local * value here * * @throws Throwable @@ -566,7 +566,7 @@ void asyncActiveCompletable() throws Exception { @Test void securityContext() throws Exception { Authentication myAuth = Mockito.mock(Authentication.class); - IStateRollback rollback = TransferrableSecurityContext.pushAuthentication(myAuth); + StateRevert revert = TransferrableSecurityContext.pushAuthentication(myAuth); try { // 1) makes sure that all calls to fjp.currentForkJoinPool() get a valid // instance @@ -582,7 +582,7 @@ void securityContext() throws Exception { })); }); } finally { - rollback.rollback(); + revert.revert(); } } @@ -594,7 +594,7 @@ void securityContext() throws Exception { @Test void requestScopedBean() throws Exception { RequestAttributes rq = Mockito.mock(RequestAttributes.class); - IStateRollback rollback = TransferrableRequestContext.pushRequestAttributes(rq); + StateRevert revert = TransferrableRequestContext.pushRequestAttributes(rq); try { // 1) makes sure that all calls to fjp.currentForkJoinPool() get a valid // instance @@ -610,7 +610,7 @@ void requestScopedBean() throws Exception { })); }); } finally { - rollback.rollback(); + revert.revert(); } } @@ -618,7 +618,7 @@ void requestScopedBean() throws Exception { void threadLocalTransferrer() { var value = "helloCustom"; beanWithThreadLocalScope.setCustomValue(value); - IStateRollback rollback = threadLocalTransferrerExtendable.registerThreadLocalTransferrer((masterBean, forkedBean) -> { + StateRevert revert = threadLocalTransferrerExtendable.registerThreadLocalTransferrer((masterBean, forkedBean) -> { forkedBean.setCustomValue(masterBean.getCustomValue()); }, BeanWithThreadLocalScope.class); try { @@ -637,7 +637,7 @@ void threadLocalTransferrer() { })); }); } finally { - rollback.rollback(); + revert.revert(); } } @@ -666,7 +666,7 @@ void threadLocalSpringBeanRestored() throws Exception { assertThat(fjpGuard.currentForkJoinPool()).isNull(); var fjp = fjpGuard.getDefaultForkJoinPool(); assertThat(fjpGuard.currentForkJoinPool()).isNull(); - var rollback = fjpGuard.pushForkJoinPool(fjp); + var revert = fjpGuard.pushForkJoinPool(fjp); try { assertThat(fjpGuard.currentForkJoinPool()).isSameAs(fjp); @@ -720,7 +720,7 @@ void threadLocalSpringBeanRestored() throws Exception { throw ex.get(); } } finally { - rollback.rollback(); + revert.revert(); } } @@ -825,7 +825,7 @@ private void asyncActive(String expectedTlValue, String expectedValue, CheckedSu Map csInvocations = new ConcurrentHashMap<>(); Map teInvocations = new ConcurrentHashMap<>(); CountDownLatch latch = new CountDownLatch(1); - var rollback = StateRollback.chain(chain -> { + var revert = DefaultStateRevert.chain(chain -> { chain.append(csListenerRegistry.registerContextSnapshotLifecycleListener(new ContextSnapshotLifecycleListener() { @Override public void contextSnapshotApplied(ContextSnapshot contextSnapshot) { @@ -860,7 +860,7 @@ public void taskQueued(Runnable command) { assertThat(beanWithAsync.getInvocationCount()).hasSize(1); assertThat(beanWithAsync.getInvocationCount().keySet().iterator().next()).isNotEqualTo(Thread.currentThread()); } finally { - rollback.rollback(); + revert.revert(); } } diff --git a/threadly-streaming/src/test/java/org/threadlys/threading/test/DecoratedForkJoinPoolTest.java b/threadly-streaming/src/test/java/org/threadlys/threading/test/DecoratedForkJoinPoolTest.java index 92e312e..a008d47 100644 --- a/threadly-streaming/src/test/java/org/threadlys/threading/test/DecoratedForkJoinPoolTest.java +++ b/threadly-streaming/src/test/java/org/threadlys/threading/test/DecoratedForkJoinPoolTest.java @@ -88,7 +88,7 @@ public void GIVEN_runnable_WHEN_executed_by_fjp_worker_THEN_busy_threads_properl Collection busyThreads; Thread monitoredThread; - var registerRollback = fjp.registerListener(new DecoratedForkJoinPoolListener() { + var registerRevert = fjp.registerListener(new DecoratedForkJoinPoolListener() { @Override public void threadPushed(DecoratedForkJoinPool forkJoinPool, Thread fjpThread) { assertThat(threadRef.get()).isNull(); @@ -125,7 +125,7 @@ public void threadPopped(DecoratedForkJoinPool forkJoinPool, Thread fjpThread) { awaitLatchDefault(threadPopLatch); } finally { - registerRollback.rollback(); + registerRevert.revert(); } // original collection unmodified @@ -150,7 +150,7 @@ public void GIVEN_runnable_WHEN_executed_by_parallel_stream_THEN_busy_threads_pr Collection busyThreads; Thread monitoredThread; - var registerRollback = fjp.registerListener(new DecoratedForkJoinPoolListener() { + var registerRevert = fjp.registerListener(new DecoratedForkJoinPoolListener() { @Override public void threadPushed(DecoratedForkJoinPool forkJoinPool, Thread fjpThread) { assertThat(threadRef.get()).isNull(); @@ -199,7 +199,7 @@ public void threadPopped(DecoratedForkJoinPool forkJoinPool, Thread fjpThread) { awaitLatchDefault(threadPopLatch); } finally { - registerRollback.rollback(); + registerRevert.revert(); } // original collection unmodified diff --git a/threadly-streaming/src/test/java/org/threadlys/threading/test/SingleThreadAsyncDataProcessorTest.java b/threadly-streaming/src/test/java/org/threadlys/threading/test/SingleThreadAsyncDataProcessorTest.java index b1aa690..4bb1cb6 100644 --- a/threadly-streaming/src/test/java/org/threadlys/threading/test/SingleThreadAsyncDataProcessorTest.java +++ b/threadly-streaming/src/test/java/org/threadlys/threading/test/SingleThreadAsyncDataProcessorTest.java @@ -15,7 +15,7 @@ import org.threadlys.streams.DataProcessor; import org.threadlys.streams.DataProcessorContext; import org.threadlys.streams.DataProcessorExtendable; -import org.threadlys.utils.StateRollback; +import org.threadlys.utils.DefaultStateRevert; import org.threadlys.utils.configuration.CommonsUtilsSpringConfig; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -85,7 +85,7 @@ public boolean isPartOf(DataScope scope) { @Test void test() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); AtomicInteger invocationCount2 = new AtomicInteger(); chain.append(dataProcessorExtendable.registerDataProcessor(context -> { @@ -121,7 +121,7 @@ void test() { @Test void testWithDataScopeSupplier() { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); AtomicInteger invocationCount2 = new AtomicInteger(); chain.append(dataProcessorExtendable.registerDataProcessor(context -> { @@ -145,13 +145,13 @@ void testWithDataScopeSupplier() { assertThat(invocationCount1.get()).isEqualTo(2); assertThat(invocationCount2.get()).isEqualTo(2); }) - .rollback(); + .revert(); } @Test void testWithDataProcessorChain() { for (int a = 10; a-- > 0;) { - StateRollback.chain(chain -> { + DefaultStateRevert.chain(chain -> { AtomicInteger invocationCount1 = new AtomicInteger(); AtomicInteger invocationCount2 = new AtomicInteger(); AtomicInteger invocationCount3 = new AtomicInteger(); @@ -232,7 +232,7 @@ void testWithDataProcessorChain() { entityToInvocationOrder.clear(); } }) - .rollback(); + .revert(); } } } diff --git a/threadly-utils/src/main/java/org/threadlys/utils/ApplicationContextHolder.java b/threadly-utils/src/main/java/org/threadlys/utils/ApplicationContextHolder.java index 86c50b0..b6cd05d 100644 --- a/threadly-utils/src/main/java/org/threadlys/utils/ApplicationContextHolder.java +++ b/threadly-utils/src/main/java/org/threadlys/utils/ApplicationContextHolder.java @@ -24,11 +24,11 @@ public static void setContext(ApplicationContext context) { } } - public static IStateRollback pushApplicationContext(ApplicationContext applicationContext) { + public static StateRevert pushApplicationContext(ApplicationContext applicationContext) { var oldApplicationContext = ApplicationContextHolder.getContext(); if (oldApplicationContext == applicationContext) { // nothing to do - return StateRollback.empty(); + return DefaultStateRevert.empty(); } ApplicationContextHolder.setContext(applicationContext); return () -> ApplicationContextHolder.setContext(oldApplicationContext); diff --git a/threadly-utils/src/main/java/org/threadlys/utils/DefaultStateRevert.java b/threadly-utils/src/main/java/org/threadlys/utils/DefaultStateRevert.java new file mode 100644 index 0000000..4e718c8 --- /dev/null +++ b/threadly-utils/src/main/java/org/threadlys/utils/DefaultStateRevert.java @@ -0,0 +1,267 @@ +package org.threadlys.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +//CHECKSTYLE: DeclarationOrderCheck OFF +@SuppressWarnings({ "PMD.AssignmentInOperand", "checkstyle:DeclarationOrderCheck" }) +public abstract class DefaultStateRevert implements StateRevert { + private static final StateRevert[] EMPTY_ARRAY = new StateRevert[0]; + + private static class StateRevertChainImpl implements StateRevertChain { + private List reverts; + + private List firstReverts; + + private boolean firstAllowed = true; + + @Override + public void first(StateRevert revert) { + if (revert == null || revert == NONE) { + return; + } + if (!firstAllowed) { + throw new IllegalStateException("No allowed to call first() at this state"); + } + if (firstReverts == null) { + firstReverts = new ArrayList<>(); + } + firstReverts.add(revert); + } + + @Override + public void append(StateRevert revert) { + firstAllowed = false; + if (revert == null || revert == NONE) { + return; + } + if (reverts == null) { + reverts = new ArrayList<>(); + } + reverts.add(revert); + } + + public List evaluateEffectiveReverts() { + if (firstReverts == null) { + return reverts; + } + if (reverts == null) { + return firstReverts; + } + reverts.addAll(0, firstReverts); + return reverts; + } + } + + private static final class GroupingStateRevert extends DefaultStateRevert { + public GroupingStateRevert(StateRevert[] reverts) { + super(reverts); + } + + @Override + protected void doRevert() { + // intended blank + } + } + + private static final StateRevert NONE = new DefaultStateRevert(EMPTY_ARRAY) { + @Override + protected void doRevert() { + // intended blank + } + + @Override + public String toString() { + return "EmptyStateRevert"; + } + }; + + public static @NonNull StateRevert empty() { + return NONE; + } + + /** + * Collects in-progress revert chains and reverts them on any exceptional case. This is very helpful to make a proper revert if in-the-middle exceptions occur while creating multiple reverts + * + * @param chainBuilder The chain + * @return + */ + public static @NonNull StateRevert chain(@Nullable Consumer chainBuilder) { + if (chainBuilder == null) { + return NONE; + } + var success = false; + var chain = new StateRevertChainImpl(); + try { + chainBuilder.accept(chain); + success = true; + return DefaultStateRevert.all(chain.evaluateEffectiveReverts()); + } finally { + if (!success) { + DefaultStateRevert.all(chain.reverts).revert(); + } + } + } + + /** + * Allows to append a new Revert to an existing array and return it as a single merged revert handle. Note that their nested execution order is reversed: So LIFO or stack pattern oriented + * + * @param revertToPrepend new revert handle to prepend. May be null + * @param reverts revert handles to prepend to. May be null or empty + * @return A non-null revert handle presenting the merged and ordered given sum of all revert handles + * + * @see #all(StateRevert[]) + */ + public static @NonNull StateRevert prepend(@Nullable StateRevert revertToPrepend, @Nullable StateRevert... reverts) { + reverts = collapseReverts(mergeReverts(revertToPrepend, reverts)); + if (reverts.length == 0) { + return NONE; + } + if (reverts.length == 1) { + return reverts[0]; + } + return new GroupingStateRevert(reverts); + } + + /** + * Allows to append a new Revert> to an existing list and return it as a single merged revert handle. Note that their nested execution order is reversed: So LIFO or stack pattern oriented - + * hence the name + * + * @param revertToPrepend new revert handle to prepend. May be null + * @param reverts revert handles to prepend to. May be null or empty + * @return A non-null revert handle presenting the merged and ordered given sum of all revert handles + * + * @see #all(StateRevert[]) + */ + public static @NonNull StateRevert prepend(@Nullable StateRevert revertToPrepend, @Nullable List reverts) { + if (reverts == null || reverts.isEmpty()) { + return revertToPrepend != null ? revertToPrepend : NONE; + } + var revertsArray = collapseReverts(mergeReverts(revertToPrepend, reverts.toArray(StateRevert[]::new))); + if (revertsArray.length == 0) { + return NONE; + } + if (revertsArray.length == 1) { + return revertsArray[0]; + } + return new GroupingStateRevert(revertsArray); + } + + /** + * Wraps multiple state reverts into a single revert. Note that due to the stack-minded push/pop pattern the revert execution is in inverse order compared to the given array.
      + *
      + * This method is useful if you have code that creates multiple state reverts independent from each other - this means you have no ability to pass the existing reverts while creating new + * reverts. In such a scenario you can "unify" those reverts with this invocation into one handle in order to continue with less revert handles to deal with manually. + * + * @param reverts The reverts to group into a single handle and to apply in reverse order when calling {@link #revert()} on the returned handle. May be null or empty + * @return A non-null revert handle presenting the merged and ordered given sum of all revert handles + */ + public static @NonNull StateRevert all(@Nullable StateRevert... reverts) { + reverts = collapseReverts(reverts); + if (reverts.length == 0) { + return NONE; + } + if (reverts.length == 1) { + return reverts[0]; + } + return new GroupingStateRevert(reverts); + } + + /** + * @see #all(StateRevert...) + * @param reverts The reverts to group into a single handle and to apply in reverse order when calling {@link #revert()} on the returned handle. May be null or empty + * @return A non-null revert handle presenting the merged and ordered given sum of all revert handles + */ + public static @NonNull StateRevert all(@Nullable List reverts) { + if (reverts == null || reverts.size() == 0) { + return NONE; + } + return all(reverts.toArray(StateRevert[]::new)); + } + + protected static StateRevert[] mergeReverts(StateRevert newRevert, StateRevert[] reverts) { + if (newRevert == null) { + return reverts; + } + if (reverts == null) { + return new StateRevert[] { newRevert }; + } + var mergedReverts = new StateRevert[reverts.length + 1]; + System.arraycopy(reverts, 0, mergedReverts, 0, reverts.length); + mergedReverts[reverts.length] = newRevert; + return mergedReverts; + } + + protected static StateRevert[] collapseReverts(StateRevert[] reverts) { + if (reverts == null || reverts.length == 0) { + return EMPTY_ARRAY; + } + int collapsedRevertCount = 0; + boolean treeFlattening = false; + for (int b = 0, size = reverts.length; b < size; b++) { + var revert = reverts[b]; + if (revert == NONE || revert == null) { + continue; + } + if (revert instanceof GroupingStateRevert) { + collapsedRevertCount += ((GroupingStateRevert) revert).reverts.length; + treeFlattening = true; + } else { + collapsedRevertCount++; + } + } + if (collapsedRevertCount == 0) { + return EMPTY_ARRAY; + } + if (!treeFlattening && collapsedRevertCount == reverts.length) { + // nothing to do + return reverts; + } + var collapsedReverts = new StateRevert[collapsedRevertCount]; + var collapsedRevertIndex = 0; + for (int b = 0, size = reverts.length; b < size; b++) { + var revert = reverts[b]; + if (revert == NONE || revert == null) { + continue; + } + if (revert instanceof GroupingStateRevert) { + for (var nestedRevert : ((GroupingStateRevert) revert).reverts) { + collapsedReverts[collapsedRevertIndex++] = nestedRevert; + } + } else { + collapsedReverts[collapsedRevertIndex++] = revert; + } + } + return collapsedReverts; + } + + final StateRevert[] reverts; + + protected DefaultStateRevert(StateRevert... reverts) { + if (reverts == null || reverts.length == 0) { + this.reverts = EMPTY_ARRAY; + } else { + this.reverts = reverts; + } + } + + /** + * Needs to be implemented by a subclass and is invoked from {@link #revert()} + */ + protected abstract void doRevert(); + + @Override + public final void revert() { + try { + doRevert(); + } finally { + for (int a = reverts.length; a-- > 0;) { + reverts[a].revert(); + } + } + } +} diff --git a/threadly-utils/src/main/java/org/threadlys/utils/IStateRollback.java b/threadly-utils/src/main/java/org/threadlys/utils/IStateRollback.java deleted file mode 100644 index 0f24da4..0000000 --- a/threadly-utils/src/main/java/org/threadlys/utils/IStateRollback.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.threadlys.utils; - -/** - * Allows to chain apply/rollback or push/pop chains of closures. Normally - * rollbacks are executed in reverse order compared to their apply counterparts - */ -public interface IStateRollback { - /** - * Encapsulates the logic to rollback a specific action. The assumption is that - * after {@link #rollback()} all contextual state related to the prior - * application of a state change is undone. In most cases it is a very good idea - * to execute the rollback() in a finally part of a try/finally clause. - */ - void rollback(); -} diff --git a/threadly-utils/src/main/java/org/threadlys/utils/StateRevert.java b/threadly-utils/src/main/java/org/threadlys/utils/StateRevert.java new file mode 100644 index 0000000..0499b2d --- /dev/null +++ b/threadly-utils/src/main/java/org/threadlys/utils/StateRevert.java @@ -0,0 +1,15 @@ +package org.threadlys.utils; + +/** + * Allows to chain apply/revert or push/pop chains of closures. Normally + * StateReverts are executed in reverse order compared to their apply counterparts + */ +public interface StateRevert { + /** + * Encapsulates the logic to revert a specific action. The assumption is that + * after {@link #revert()} all contextual state related to the prior + * application of a state change is undone. In most cases it is a very good idea + * to execute the revert() in a finally part of a try/finally clause. + */ + void revert(); +} diff --git a/threadly-utils/src/main/java/org/threadlys/utils/StateRevertChain.java b/threadly-utils/src/main/java/org/threadlys/utils/StateRevertChain.java new file mode 100644 index 0000000..236b267 --- /dev/null +++ b/threadly-utils/src/main/java/org/threadlys/utils/StateRevertChain.java @@ -0,0 +1,48 @@ +package org.threadlys.utils; + +import org.springframework.lang.Nullable; + +/** + * Allows to collect {@link StateRevert}s under construction. + * + * @see {@link DefaultStateRevert#chain(java.util.function.Consumer)} + */ +public interface StateRevertChain { + /** + * Appends a single revert handle to chain if - and only if - the whole chain was created successfully afterwards. This is to support cases where a revert handle is already created (and + * assigned on a stack or heap variable). In such a case this revert would be called twice if we do not distinguish it from the nested revert handles (once due to the in-progress failed chain + * algorithm, and a second time due to the outer scope managed "normal" revert sequence). As a result we need to add a support in the chance to avoid the first revert during the in-progress + * failing chain phase. You may call this method at most once per chain creation.
      + *
      + * Usage example:
      + *
      + * + * var revert = myFunnyCodeStateProducesRevert();
      + * var chainedRevert = DefaultStateRevert.chain(chain -> {
      + *   chain.first(revert);
      + *   chain.append(foo());
      + *   chain.append(bar());
      + * });
      + *
      + * + * @param revert + */ + void first(@Nullable StateRevert revert); + + /** + * Appends a single revert handle to chain. May be null. The chain logic ensures that also on partial creations of the chain - if an unexpected in-progress failure happens - the partial chain + * still gets rolled back properly in reverse order
      + *
      + * Usage example:
      + *
      + * + * var revert = DefaultStateRevert.chain(chain -> {
      + *   chain.append(foo());
      + *   chain.append(bar());
      + * });
      + *
      + * + * @param revert The revert handle to chain + */ + void append(@Nullable StateRevert revert); +} diff --git a/threadly-utils/src/main/java/org/threadlys/utils/StateRollback.java b/threadly-utils/src/main/java/org/threadlys/utils/StateRollback.java deleted file mode 100644 index f56efdf..0000000 --- a/threadly-utils/src/main/java/org/threadlys/utils/StateRollback.java +++ /dev/null @@ -1,267 +0,0 @@ -package org.threadlys.utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; - -//CHECKSTYLE: DeclarationOrderCheck OFF -@SuppressWarnings({ "PMD.AssignmentInOperand", "checkstyle:DeclarationOrderCheck" }) -public abstract class StateRollback implements IStateRollback { - private static final IStateRollback[] EMPTY_ARRAY = new IStateRollback[0]; - - private static class StateRollbackChainImpl implements StateRollbackChain { - private List rollbacks; - - private List firstRollbacks; - - private boolean firstAllowed = true; - - @Override - public void first(IStateRollback rollback) { - if (rollback == null || rollback == NONE) { - return; - } - if (!firstAllowed) { - throw new IllegalStateException("No allowed to call first() at this state"); - } - if (firstRollbacks == null) { - firstRollbacks = new ArrayList<>(); - } - firstRollbacks.add(rollback); - } - - @Override - public void append(IStateRollback rollback) { - firstAllowed = false; - if (rollback == null || rollback == NONE) { - return; - } - if (rollbacks == null) { - rollbacks = new ArrayList<>(); - } - rollbacks.add(rollback); - } - - public List evaluateEffectiveRollbacks() { - if (firstRollbacks == null) { - return rollbacks; - } - if (rollbacks == null) { - return firstRollbacks; - } - rollbacks.addAll(0, firstRollbacks); - return rollbacks; - } - } - - private static final class GroupingStateRollback extends StateRollback { - public GroupingStateRollback(IStateRollback[] rollbacks) { - super(rollbacks); - } - - @Override - protected void doRollback() { - // intended blank - } - } - - private static final IStateRollback NONE = new StateRollback(EMPTY_ARRAY) { - @Override - protected void doRollback() { - // intended blank - } - - @Override - public String toString() { - return "EmptyStateRollback"; - } - }; - - public static @NonNull IStateRollback empty() { - return NONE; - } - - /** - * Collects in-progress rollback chains and reverts them on any exceptional case. This is very helpful to make a proper rollback if in-the-middle exceptions occur while creating multiple rollbacks - * - * @param chainBuilder The chain - * @return - */ - public static @NonNull IStateRollback chain(@Nullable Consumer chainBuilder) { - if (chainBuilder == null) { - return NONE; - } - var success = false; - var chain = new StateRollbackChainImpl(); - try { - chainBuilder.accept(chain); - success = true; - return StateRollback.all(chain.evaluateEffectiveRollbacks()); - } finally { - if (!success) { - StateRollback.all(chain.rollbacks).rollback(); - } - } - } - - /** - * Allows to append a new rollback to an existing array and return it as a single merged rollback handle. Note that their nested execution order is reversed: So LIFO or stack pattern oriented - * - * @param rollbackToPrepend new rollback handle to prepend. May be null - * @param rollbacks rollback handles to prepend to. May be null or empty - * @return A non-null rollback handle presenting the merged and ordered given sum of all rollback handles - * - * @see #all(IStateRollback[]) - */ - public static @NonNull IStateRollback prepend(@Nullable IStateRollback rollbackToPrepend, @Nullable IStateRollback... rollbacks) { - rollbacks = collapseRollbacks(mergeRollbacks(rollbackToPrepend, rollbacks)); - if (rollbacks.length == 0) { - return NONE; - } - if (rollbacks.length == 1) { - return rollbacks[0]; - } - return new GroupingStateRollback(rollbacks); - } - - /** - * Allows to append a new rollback to an existing list and return it as a single merged rollback handle. Note that their nested execution order is reversed: So LIFO or stack pattern oriented - - * hence the name - * - * @param rollbackToPrepend new rollback handle to prepend. May be null - * @param rollbacks rollback handles to prepend to. May be null or empty - * @return A non-null rollback handle presenting the merged and ordered given sum of all rollback handles - * - * @see #all(IStateRollback[]) - */ - public static @NonNull IStateRollback prepend(@Nullable IStateRollback rollbackToPrepend, @Nullable List rollbacks) { - if (rollbacks == null || rollbacks.isEmpty()) { - return rollbackToPrepend != null ? rollbackToPrepend : NONE; - } - var rollbacksArray = collapseRollbacks(mergeRollbacks(rollbackToPrepend, rollbacks.toArray(IStateRollback[]::new))); - if (rollbacksArray.length == 0) { - return NONE; - } - if (rollbacksArray.length == 1) { - return rollbacksArray[0]; - } - return new GroupingStateRollback(rollbacksArray); - } - - /** - * Wraps multiple state rollbacks into a single rollback. Note that due to the stack-minded push/pop pattern the rollback execution is in inverse order compared to the given array.
      - *
      - * This method is useful if you have code that creates multiple state rollbacks independent from each other - this means you have no ability to pass the existing rollbacks while creating new - * rollbacks. In such a scenario you can "unify" those rollbacks with this invocation into one handle in order to continue with less rollback handles to deal with manually. - * - * @param rollbacks The rollbacks to group into a single handle and to apply in reverse order when calling {@link #rollback()} on the returned handle. May be null or empty - * @return A non-null rollback handle presenting the merged and ordered given sum of all rollback handles - */ - public static @NonNull IStateRollback all(@Nullable IStateRollback... rollbacks) { - rollbacks = collapseRollbacks(rollbacks); - if (rollbacks.length == 0) { - return NONE; - } - if (rollbacks.length == 1) { - return rollbacks[0]; - } - return new GroupingStateRollback(rollbacks); - } - - /** - * @see #all(IStateRollback...) - * @param rollbacks The rollbacks to group into a single handle and to apply in reverse order when calling {@link #rollback()} on the returned handle. May be null or empty - * @return A non-null rollback handle presenting the merged and ordered given sum of all rollback handles - */ - public static @NonNull IStateRollback all(@Nullable List rollbacks) { - if (rollbacks == null || rollbacks.size() == 0) { - return NONE; - } - return all(rollbacks.toArray(IStateRollback[]::new)); - } - - protected static IStateRollback[] mergeRollbacks(IStateRollback newRollback, IStateRollback[] rollbacks) { - if (newRollback == null) { - return rollbacks; - } - if (rollbacks == null) { - return new IStateRollback[] { newRollback }; - } - var mergedRollbacks = new IStateRollback[rollbacks.length + 1]; - System.arraycopy(rollbacks, 0, mergedRollbacks, 0, rollbacks.length); - mergedRollbacks[rollbacks.length] = newRollback; - return mergedRollbacks; - } - - protected static IStateRollback[] collapseRollbacks(IStateRollback[] rollbacks) { - if (rollbacks == null || rollbacks.length == 0) { - return EMPTY_ARRAY; - } - int collapsedRollbackCount = 0; - boolean treeFlattening = false; - for (int b = 0, size = rollbacks.length; b < size; b++) { - var rollback = rollbacks[b]; - if (rollback == NONE || rollback == null) { - continue; - } - if (rollback instanceof GroupingStateRollback) { - collapsedRollbackCount += ((GroupingStateRollback) rollback).rollbacks.length; - treeFlattening = true; - } else { - collapsedRollbackCount++; - } - } - if (collapsedRollbackCount == 0) { - return EMPTY_ARRAY; - } - if (!treeFlattening && collapsedRollbackCount == rollbacks.length) { - // nothing to do - return rollbacks; - } - var collapsedRollbacks = new IStateRollback[collapsedRollbackCount]; - var collapsedRollbackIndex = 0; - for (int b = 0, size = rollbacks.length; b < size; b++) { - var rollback = rollbacks[b]; - if (rollback == NONE || rollback == null) { - continue; - } - if (rollback instanceof GroupingStateRollback) { - for (IStateRollback nestedRollback : ((GroupingStateRollback) rollback).rollbacks) { - collapsedRollbacks[collapsedRollbackIndex++] = nestedRollback; - } - } else { - collapsedRollbacks[collapsedRollbackIndex++] = rollback; - } - } - return collapsedRollbacks; - } - - final IStateRollback[] rollbacks; - - protected StateRollback(IStateRollback... rollbacks) { - if (rollbacks == null || rollbacks.length == 0) { - this.rollbacks = EMPTY_ARRAY; - } else { - this.rollbacks = rollbacks; - } - } - - /** - * Needs to be implemented by a subclass and is invoked from {@link #rollback()} - */ - protected abstract void doRollback(); - - @Override - public final void rollback() { - try { - doRollback(); - } finally { - for (int a = rollbacks.length; a-- > 0;) { - rollbacks[a].rollback(); - } - } - } -} diff --git a/threadly-utils/src/main/java/org/threadlys/utils/StateRollbackChain.java b/threadly-utils/src/main/java/org/threadlys/utils/StateRollbackChain.java deleted file mode 100644 index ffea6aa..0000000 --- a/threadly-utils/src/main/java/org/threadlys/utils/StateRollbackChain.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.threadlys.utils; - -import org.springframework.lang.Nullable; - -/** - * Allows to collect rollbacks under construction. - * - * @see {@link StateRollback#chain(java.util.function.Consumer)} - */ -public interface StateRollbackChain { - /** - * Appends a single rollback handle to chain if - and only if - the whole chain was created successfully afterwards. This is to support cases where a rollback handle is already created (and - * assigned on a stack or heap variable). In such a case this rollback would be called twice if we do not distinguish it from the nested rollback handles (once due to the in-progress failed chain - * algorithm, and a second time due to the outer scope managed "normal" rollback sequence). As a result we need to add a support in the chance to avoid the first rollback during the in-progress - * failing chain phase. You may call this method at most once per chain creation.
      - *
      - * Usage example:
      - *
      - * - * var rollback = myFunnyCodeStateProducesRollback();
      - * rollback = StateRollback.chain(chain -> {
      - *   chain.first(rollback);
      - *   chain.append(foo());
      - *   chain.append(bar());
      - * });
      - *
      - * - * @param rollback - */ - void first(@Nullable IStateRollback rollback); - - /** - * Appends a single rollback handle to chain. May be null. The chain logic ensures that also on partial creations of the chain - if an unexpected in-progress failure happens - the partial chain - * still gets rolled back properly in reverse order
      - *
      - * Usage example:
      - *
      - * - * var rollback = StateRollback.chain(chain -> {
      - *   chain.append(foo());
      - *   chain.append(bar());
      - * });
      - *
      - * - * @param rollback The rollback handle to chain - */ - void append(@Nullable IStateRollback rollback); -} diff --git a/threadly-utils/src/test/java/org/threadlys/utils/DefaultStateRevertTest.java b/threadly-utils/src/test/java/org/threadlys/utils/DefaultStateRevertTest.java new file mode 100644 index 0000000..a201989 --- /dev/null +++ b/threadly-utils/src/test/java/org/threadlys/utils/DefaultStateRevertTest.java @@ -0,0 +1,188 @@ +package org.threadlys.utils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.Test; + +class DefaultStateRevertTest { + + StateRevert revert1 = () -> { + }; + + StateRevert revert2 = () -> { + }; + + StateRevert revert3 = () -> { + }; + + @Test + void rawNullCases() { + assertThat(DefaultStateRevert.all((StateRevert) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all(DefaultStateRevert.empty())).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all((StateRevert[]) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all(null, null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all(DefaultStateRevert.empty(), null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all((List) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all(new StateRevert[0])).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.all(List.of())).isSameAs(DefaultStateRevert.empty()); + + assertThat(DefaultStateRevert.prepend((StateRevert) null, (StateRevert[]) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), (StateRevert[]) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend((StateRevert) null, new StateRevert[] { null })).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), new StateRevert[] { null })).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend((StateRevert) null, (StateRevert[]) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), (StateRevert[]) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend((StateRevert) null, new StateRevert[] { null })).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), new StateRevert[] { null })).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend((StateRevert) null, (List) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), (List) null)).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), List.of())).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), Collections.singletonList(null))).isSameAs(DefaultStateRevert.empty()); + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), new StateRevert[0])).isSameAs(DefaultStateRevert.empty()); + } + + @Test + void simpleNullCases() { + assertThat(((DefaultStateRevert) DefaultStateRevert.all(revert1, null, revert2)).reverts).hasSize(2); + assertThat(DefaultStateRevert.all(null, null, revert1, null)).isSameAs(revert1); + + assertThat(((DefaultStateRevert) DefaultStateRevert.prepend(revert1, null, revert2)).reverts).hasSize(2); + assertThat(DefaultStateRevert.prepend(null, null, revert1, null)).isSameAs(revert1); + } + + @Test + void flattenTree() { + assertThat(((DefaultStateRevert) DefaultStateRevert.all(revert1, DefaultStateRevert.all(revert2, null, revert3), DefaultStateRevert.empty())).reverts) // + .containsExactly(revert1, revert2, revert3); + + assertThat(((DefaultStateRevert) DefaultStateRevert.prepend(revert1, DefaultStateRevert.prepend(revert2, null, revert3), DefaultStateRevert.empty())).reverts) // + .containsExactly(revert3, revert2, revert1); + + assertThat(((DefaultStateRevert) DefaultStateRevert.prepend(revert1, List.of(DefaultStateRevert.prepend(revert2, null, revert3), DefaultStateRevert.empty()))).reverts) // + .containsExactly(revert3, revert2, revert1); + + assertThat(DefaultStateRevert.prepend(DefaultStateRevert.empty(), List.of(DefaultStateRevert.prepend(revert2, null, DefaultStateRevert.empty())))) // + .isSameAs(revert2); + } + + @Test + void chainRevertOnException() { + var callOrder = new ArrayList(); + + assertThrows(IllegalStateException.class, () -> DefaultStateRevert.chain(chain -> { + chain.append(() -> callOrder.add(1)); + chain.append(() -> callOrder.add(2)); + throw new IllegalStateException(); + }).revert()); + + assertThat(callOrder).containsExactly(2, 1); + } + + @Test + void chainRevertRegularly() { + var callOrder = new ArrayList(); + + DefaultStateRevert.chain(chain -> { + chain.append(() -> callOrder.add(1)); + chain.append(() -> callOrder.add(2)); + chain.append(() -> callOrder.add(3)); + }).revert(); + + assertThat(callOrder).containsExactly(3, 2, 1); + } + + @Test + void chainNullBuilder() { + assertThat(DefaultStateRevert.chain(null)).isEqualTo(DefaultStateRevert.empty()); + } + + @Test + void chainEmptyBuilder() { + assertThat(DefaultStateRevert.chain(chain -> { + })).isEqualTo(DefaultStateRevert.empty()); + } + + @Test + void chainWithFirstOnly() { + var callOrder = new ArrayList(); + + StateRevert revert1 = () -> callOrder.add(1); + StateRevert revert2 = () -> callOrder.add(2); + DefaultStateRevert.chain(chain -> { + chain.first(revert1); + chain.first(revert2); + }).revert(); + + assertThat(callOrder).containsExactly(2, 1); + } + + @Test + void chainWithFirst() { + var callOrder = new ArrayList(); + + StateRevert revert1 = () -> callOrder.add(1); + StateRevert revert2 = () -> callOrder.add(2); + DefaultStateRevert.chain(chain -> { + chain.first(revert1); + chain.first(revert2); + chain.first(null); + chain.first(DefaultStateRevert.empty()); + chain.append(() -> callOrder.add(3)); + chain.append(null); + chain.append(DefaultStateRevert.empty()); + chain.append(() -> callOrder.add(4)); + }).revert(); + + assertThat(callOrder).containsExactly(4, 3, 2, 1); + } + + @Test + void chainWithFirstTooLate() { + var callOrder = new ArrayList(); + + StateRevert revert1 = () -> callOrder.add(1); + + assertThrows(IllegalStateException.class, () -> DefaultStateRevert.chain(chain -> { + chain.append(() -> callOrder.add(3)); + chain.append(() -> callOrder.add(4)); + chain.first(revert1); + })); + + assertThat(callOrder).containsExactly(4, 3); + } + + @Test + void empty() { + DefaultStateRevert.empty().revert(); + + assertThat(DefaultStateRevert.empty()).hasToString("EmptyStateRevert"); + } + + @Test + void customDefaultStateRevert() { + var callOrder = new ArrayList(); + + new DefaultStateRevert((StateRevert[]) null) { + protected void doRevert() { + callOrder.add(1); + } + }.revert(); + + assertThat(callOrder).containsExactly(1); + + callOrder.clear(); + + new DefaultStateRevert(DefaultStateRevert.empty()) { + protected void doRevert() { + callOrder.add(1); + } + }.revert(); + + assertThat(callOrder).containsExactly(1); + } +} diff --git a/threadly-utils/src/test/java/org/threadlys/utils/StateRollbackTest.java b/threadly-utils/src/test/java/org/threadlys/utils/StateRollbackTest.java deleted file mode 100644 index 189abef..0000000 --- a/threadly-utils/src/test/java/org/threadlys/utils/StateRollbackTest.java +++ /dev/null @@ -1,188 +0,0 @@ -package org.threadlys.utils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -class StateRollbackTest { - - IStateRollback stateRollback1 = () -> { - }; - - IStateRollback stateRollback2 = () -> { - }; - - IStateRollback stateRollback3 = () -> { - }; - - @Test - void rawNullCases() { - assertThat(StateRollback.all((IStateRollback) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all(StateRollback.empty())).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all((IStateRollback[]) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all(null, null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all(StateRollback.empty(), null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all((List) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all(new IStateRollback[0])).isSameAs(StateRollback.empty()); - assertThat(StateRollback.all(List.of())).isSameAs(StateRollback.empty()); - - assertThat(StateRollback.prepend((IStateRollback) null, (IStateRollback[]) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), (IStateRollback[]) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend((IStateRollback) null, new IStateRollback[] { null })).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), new IStateRollback[] { null })).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend((IStateRollback) null, (IStateRollback[]) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), (IStateRollback[]) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend((IStateRollback) null, new IStateRollback[] { null })).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), new IStateRollback[] { null })).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend((IStateRollback) null, (List) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), (List) null)).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), List.of())).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), Collections.singletonList(null))).isSameAs(StateRollback.empty()); - assertThat(StateRollback.prepend(StateRollback.empty(), new IStateRollback[0])).isSameAs(StateRollback.empty()); - } - - @Test - void simpleNullCases() { - assertThat(((StateRollback) StateRollback.all(stateRollback1, null, stateRollback2)).rollbacks).hasSize(2); - assertThat(StateRollback.all(null, null, stateRollback1, null)).isSameAs(stateRollback1); - - assertThat(((StateRollback) StateRollback.prepend(stateRollback1, null, stateRollback2)).rollbacks).hasSize(2); - assertThat(StateRollback.prepend(null, null, stateRollback1, null)).isSameAs(stateRollback1); - } - - @Test - void flattenTree() { - assertThat(((StateRollback) StateRollback.all(stateRollback1, StateRollback.all(stateRollback2, null, stateRollback3), StateRollback.empty())).rollbacks) // - .containsExactly(stateRollback1, stateRollback2, stateRollback3); - - assertThat(((StateRollback) StateRollback.prepend(stateRollback1, StateRollback.prepend(stateRollback2, null, stateRollback3), StateRollback.empty())).rollbacks) // - .containsExactly(stateRollback3, stateRollback2, stateRollback1); - - assertThat(((StateRollback) StateRollback.prepend(stateRollback1, List.of(StateRollback.prepend(stateRollback2, null, stateRollback3), StateRollback.empty()))).rollbacks) // - .containsExactly(stateRollback3, stateRollback2, stateRollback1); - - assertThat(StateRollback.prepend(StateRollback.empty(), List.of(StateRollback.prepend(stateRollback2, null, StateRollback.empty())))) // - .isSameAs(stateRollback2); - } - - @Test - void chainRollbackOnException() { - var callOrder = new ArrayList(); - - assertThrows(IllegalStateException.class, () -> StateRollback.chain(chain -> { - chain.append(() -> callOrder.add(1)); - chain.append(() -> callOrder.add(2)); - throw new IllegalStateException(); - }).rollback()); - - assertThat(callOrder).containsExactly(2, 1); - } - - @Test - void chainRollbackRegularly() { - var callOrder = new ArrayList(); - - StateRollback.chain(chain -> { - chain.append(() -> callOrder.add(1)); - chain.append(() -> callOrder.add(2)); - chain.append(() -> callOrder.add(3)); - }).rollback(); - - assertThat(callOrder).containsExactly(3, 2, 1); - } - - @Test - void chainNullBuilder() { - assertThat(StateRollback.chain(null)).isEqualTo(StateRollback.empty()); - } - - @Test - void chainEmptyBuilder() { - assertThat(StateRollback.chain(chain -> { - })).isEqualTo(StateRollback.empty()); - } - - @Test - void chainWithFirstOnly() { - var callOrder = new ArrayList(); - - IStateRollback rollback1 = () -> callOrder.add(1); - IStateRollback rollback2 = () -> callOrder.add(2); - StateRollback.chain(chain -> { - chain.first(rollback1); - chain.first(rollback2); - }).rollback(); - - assertThat(callOrder).containsExactly(2, 1); - } - - @Test - void chainWithFirst() { - var callOrder = new ArrayList(); - - IStateRollback rollback1 = () -> callOrder.add(1); - IStateRollback rollback2 = () -> callOrder.add(2); - StateRollback.chain(chain -> { - chain.first(rollback1); - chain.first(rollback2); - chain.first(null); - chain.first(StateRollback.empty()); - chain.append(() -> callOrder.add(3)); - chain.append(null); - chain.append(StateRollback.empty()); - chain.append(() -> callOrder.add(4)); - }).rollback(); - - assertThat(callOrder).containsExactly(4, 3, 2, 1); - } - - @Test - void chainWithFirstTooLate() { - var callOrder = new ArrayList(); - - IStateRollback rollback1 = () -> callOrder.add(1); - - assertThrows(IllegalStateException.class, () -> StateRollback.chain(chain -> { - chain.append(() -> callOrder.add(3)); - chain.append(() -> callOrder.add(4)); - chain.first(rollback1); - })); - - assertThat(callOrder).containsExactly(4, 3); - } - - @Test - void empty() { - StateRollback.empty().rollback(); - - assertThat(StateRollback.empty()).hasToString("EmptyStateRollback"); - } - - @Test - void customStateRollback() { - var callOrder = new ArrayList(); - - new StateRollback((IStateRollback[]) null) { - protected void doRollback() { - callOrder.add(1); - } - }.rollback(); - - assertThat(callOrder).containsExactly(1); - - callOrder.clear(); - - new StateRollback(StateRollback.empty()) { - protected void doRollback() { - callOrder.add(1); - } - }.rollback(); - - assertThat(callOrder).containsExactly(1); - } -}