/*
* Copyright (c) 2010-2016. Axon Framework
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.axonframework.messaging.unitofwork;
import org.axonframework.messaging.MetaData;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Default entry point to gain access to the current UnitOfWork. Components managing transactional boundaries can
* register and clear UnitOfWork instances, which components can use.
*
* @author Allard Buijze
* @since 0.6
*/
public abstract class CurrentUnitOfWork {
private static final ThreadLocal<Deque<UnitOfWork<?>>> CURRENT = new ThreadLocal<>();
/**
* Indicates whether a unit of work has already been started. This method can be used by interceptors to prevent
* nesting of UnitOfWork instances.
*
* @return whether a UnitOfWork has already been started.
*/
public static boolean isStarted() {
return CURRENT.get() != null && !CURRENT.get().isEmpty();
}
/**
* If a UnitOfWork is started, invokes the given {@code consumer} with the active Unit of Work. Otherwise,
* it does nothing
*
* @param consumer The consumer to invoke if a Unit of Work is active
* @return {@code true} if a unit of work is active, {@code false} otherwise
*/
public static boolean ifStarted(Consumer<UnitOfWork<?>> consumer) {
if (isStarted()) {
consumer.accept(get());
return true;
}
return false;
}
/**
* If a Unit of Work is started, execute the given {@code function} on it. Otherwise, returns an empty Optional.
* Use this method when you wish to retrieve information from a Unit of Work, reverting to a default when no Unit
* of Work is started.
*
* @param function The function to apply to the unit of work, if present
* @param <T> The type of return value expected
* @return an optional containing the result of the function, or an empty Optional when no Unit of Work was started
* @throws NullPointerException when a Unit of Work is present and the function returns null
*/
public static <T> Optional<T> map(Function<UnitOfWork<?>, T> function) {
return isStarted() ? Optional.of(function.apply(get())) : Optional.empty();
}
/**
* Gets the UnitOfWork bound to the current thread. If no UnitOfWork has been started, an {@link
* IllegalStateException} is thrown.
* <p/>
* To verify whether a UnitOfWork is already active, use {@link #isStarted()}.
*
* @return The UnitOfWork bound to the current thread.
* @throws IllegalStateException if no UnitOfWork is active
*/
public static UnitOfWork<?> get() {
if (isEmpty()) {
throw new IllegalStateException("No UnitOfWork is currently started for this thread.");
}
return CURRENT.get().peek();
}
private static boolean isEmpty() {
Deque<UnitOfWork<?>> unitsOfWork = CURRENT.get();
return unitsOfWork == null || unitsOfWork.isEmpty();
}
/**
* Commits the current UnitOfWork. If no UnitOfWork was started, an {@link IllegalStateException} is thrown.
*
* @throws IllegalStateException if no UnitOfWork is currently started.
* @see UnitOfWork#commit()
*/
public static void commit() {
get().commit();
}
/**
* Binds the given {@code unitOfWork} to the current thread. If other UnitOfWork instances were bound, they
* will be marked as inactive until the given UnitOfWork is cleared.
*
* @param unitOfWork The UnitOfWork to bind to the current thread.
*/
public static void set(UnitOfWork<?> unitOfWork) {
if (CURRENT.get() == null) {
CURRENT.set(new LinkedList<>());
}
CURRENT.get().push(unitOfWork);
}
/**
* Clears the UnitOfWork currently bound to the current thread, if that UnitOfWork is the given
* {@code unitOfWork}.
*
* @param unitOfWork The UnitOfWork expected to be bound to the current thread.
* @throws IllegalStateException when the given UnitOfWork was not the current active UnitOfWork. This exception
* indicates a potentially wrong nesting of Units Of Work.
*/
public static void clear(UnitOfWork<?> unitOfWork) {
if (!isStarted()) {
throw new IllegalStateException("Could not clear this UnitOfWork. There is no UnitOfWork active.");
}
if (CURRENT.get().peek() == unitOfWork) {
CURRENT.get().pop();
if (CURRENT.get().isEmpty()) {
CURRENT.remove();
}
} else {
throw new IllegalStateException("Could not clear this UnitOfWork. It is not the active one.");
}
}
/**
* Returns the Correlation Data attached to the current Unit of Work, or an empty {@link MetaData} instance
* if no Unit of Work is started.
*
* @return a MetaData instance representing the current Unit of Work's correlation data, or an empty MetaData
* instance if no Unit of Work is started.
* @see UnitOfWork#getCorrelationData()
*/
public static MetaData correlationData() {
return CurrentUnitOfWork.map(UnitOfWork::getCorrelationData).orElse(MetaData.emptyInstance());
}
private CurrentUnitOfWork() {
}
}