io.github.suppierk.ddd.cqrs.BoundedContext Maven / Gradle / Ivy
/*
* Copyright 2024 Roman Khlebnov
*
* 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 io.github.suppierk.ddd.cqrs;
import io.github.suppierk.ddd.async.DomainNotificationProducer;
import io.github.suppierk.ddd.jooq.DslContextProvider;
import io.github.suppierk.ddd.util.ExcludeFromJaCoCoGeneratedReport;
import io.github.suppierk.java.lang.ThrowableRunnable;
import io.github.suppierk.java.util.function.ThrowableSupplier;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Stream;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Table;
import org.jooq.UpdatableRecord;
import org.jooq.impl.DSL;
/**
* In terms of Domain-Driven Design this class encapsulates model, its respective {@link
* DomainCommandHandler}s and any {@link DomainQueryHandler}s that can be executed against the
* model.
*
* An application is formed from one or several of {@link BoundedContext}s, serving specific
* business purpose.
*
*
One of the design choices for this class were locks around different handler maps: we modify
* multiple resources at once when we add a handler, and we do not want to introduce some kind of
* mutable structure leading to immutable {@link BoundedContext} - that will be too much
* inheritance. Instead, we give consumer a hint to stick to adding handlers within constructor or
* similar internal mechanisms by declaring methods which add new handlers as both {@code protected}
* and {@code final} - however, this is not as bulletproof as we would like, hence locks will have
* to stay.
*
* @param the database record type generated by jOOQ
*/
public abstract non-sealed class BoundedContext> extends Suspicious {
private static final String COMMAND = "Command";
private static final String QUERY = "Query";
private static final String VIEW = "View";
private final Table table;
private final DslContextProvider writeDslContextProvider;
private final DslContextProvider readDslContextProvider;
private final DomainNotificationProducer domainNotificationProducer;
private final ReentrantReadWriteLock queryHandlersLock;
private final Map>, Class extends DomainQueryHandler, ?>>>
queryClassToQueryHandlerClassMap;
private final Map>, DomainQueryHandler, ?>>
queryHandlersMap;
private final ReentrantReadWriteLock commandHandlersLock;
private final Map<
Class extends DomainCommand, ?>>, Class extends DomainCommandHandler, R, ?>>>
commandClassToCommandHandlerClassMap;
private final Map>, DomainCommandHandler, R, ?>>
commandHandlersMap;
private final ReentrantReadWriteLock viewHandlersLock;
private final Map<
Class extends DomainView, ?, ?>>, Class extends DomainViewHandler, ?, ?>>>
viewClassToViewHandlerClassMap;
private final Map>, DomainViewHandler, ?, ?>>
viewHandlersMap;
/**
* Constructs an instance of BoundedContext.
*
* @param table the jOOQ table associated with this bounded context
* @param writeDslContextProvider the jOOQ DSL Context provider for writing data
* @param readDslContextProvider the jOOQ DSL Context provider for reading data
* @param domainNotificationProducer the notification publisher
*/
protected BoundedContext(
Table table,
DslContextProvider writeDslContextProvider,
DslContextProvider readDslContextProvider,
DomainNotificationProducer domainNotificationProducer) {
this.table = throwIllegalArgumentIfNull(table, "jOOQ table");
this.writeDslContextProvider =
throwIllegalArgumentIfNull(writeDslContextProvider, "jOOQ DSL Context for writing data");
this.readDslContextProvider =
throwIllegalArgumentIfNull(readDslContextProvider, "jOOQ DSL Context for reading data");
this.domainNotificationProducer =
throwIllegalArgumentIfNull(domainNotificationProducer, "Notification Publisher");
this.queryHandlersLock = new ReentrantReadWriteLock();
this.queryClassToQueryHandlerClassMap = new HashMap<>();
this.queryHandlersMap = new HashMap<>();
this.commandHandlersLock = new ReentrantReadWriteLock();
this.commandClassToCommandHandlerClassMap = new HashMap<>();
this.commandHandlersMap = new HashMap<>();
this.viewHandlersLock = new ReentrantReadWriteLock();
this.viewClassToViewHandlerClassMap = new HashMap<>();
this.viewHandlersMap = new HashMap<>();
}
/**
* To support consumer's wish to create a higher-level Strategy-like abstraction.
*
* @return an unmodifiable view of the {@link DomainCommand} supported by the current {@link
* BoundedContext}
* @see Suppressed Sonar rule to denote
* that it doesn't matter much which exact types notification uses
*/
@SuppressWarnings("squid:S1452")
public final Set>> getSupportedDomainCommandClasses() {
return withCommandHandlersReadLock(
() -> Collections.unmodifiableSet(commandClassToCommandHandlerClassMap.keySet()));
}
/**
* To support consumer's wish to create a higher-level Strategy-like abstraction.
*
* @return an unmodifiable view of the {@link DomainQuery} supported by the current {@link
* BoundedContext}
* @see Suppressed Sonar rule to denote
* that it doesn't matter much which exact types notification uses
*/
@SuppressWarnings("squid:S1452")
public final Set>> getSupportedDomainQueryClasses() {
return withQueryHandlersReadLock(
() -> Collections.unmodifiableSet(queryClassToQueryHandlerClassMap.keySet()));
}
/**
* To support consumer's wish to create a higher-level Strategy-like abstraction.
*
* @return an unmodifiable view of the {@link DomainView}s supported by the current {@link
* BoundedContext}
* @see Suppressed Sonar rule to denote
* that it doesn't matter much which exact types notification uses
*/
@SuppressWarnings("squid:S1452")
public final Set>> getSupportedDomainViewClasses() {
return withViewHandlersReadLock(
() -> Collections.unmodifiableSet(viewClassToViewHandlerClassMap.keySet()));
}
/**
* Queries if any thread holds any of the write locks.
*
* This method is designed for use in monitoring system state, not for synchronization control.
*
* @return {@code true} if any thread holds the write lock and {@code false} otherwise
*/
@ExcludeFromJaCoCoGeneratedReport
final boolean isAnyWriteLockHeld() {
return commandHandlersLock.isWriteLocked()
|| queryHandlersLock.isWriteLocked()
|| viewHandlersLock.isWriteLocked();
}
/**
* Queries if any thread holds any of the read locks.
*
*
This method is designed for use in monitoring system state, not for synchronization control.
*
* @return {@code true} if any thread holds the read lock and {@code false} otherwise
*/
@ExcludeFromJaCoCoGeneratedReport
final boolean isAnyReadLockHeld() {
return commandHandlersLock.getReadLockCount() != 0
|| queryHandlersLock.getReadLockCount() != 0
|| viewHandlersLock.getReadLockCount() != 0;
}
/**
* Adds a {@link DomainCommandHandler} to the context.
*
*
This method ensures that a {@link DomainCommandHandler} is not registered more than once for
* a specific {@link DomainCommand} class.
*
* @param commandHandler to add
* @param the type of the command handler that extends {@link DomainCommandHandler}
* @throws IllegalArgumentException if a command handler is {@code null}
* @throws IllegalStateException if a command handler for the same {@link DomainCommand} class has
* already been registered
*/
@SuppressWarnings("unchecked")
protected final > void addDomainCommandHandler(
final C commandHandler) {
final C nonNullCommandHandler = throwIllegalArgumentIfNull(commandHandler, "Command handler");
final Class commandHandlerClass = (Class) nonNullCommandHandler.getClass();
withCommandHandlersWriteLock(
() -> {
if (commandClassToCommandHandlerClassMap.containsKey(
nonNullCommandHandler.getCommandClass())) {
throw new IllegalStateException(
"Command handler for command '%s' has been already registered"
.formatted(nonNullCommandHandler.getCommandClass().getSimpleName()));
}
commandClassToCommandHandlerClassMap.put(
nonNullCommandHandler.getCommandClass(), commandHandlerClass);
commandHandlersMap.put(commandHandlerClass, nonNullCommandHandler);
});
}
/**
* Retrieves the appropriate {@link DomainCommandHandler} for the given command.
*
* @param command the command for which the handler is to be retrieved
* @param the type of the command
* @param the type of the command handler
* @return the command handler instance associated with the given command
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
@SuppressWarnings("unchecked")
private , H extends DomainCommandHandler>
H getDomainCommandHandler(final C command) {
final Class extends DomainCommandHandler, R, ?>> commandHandlerClass =
throwUnsupportedOperationIfNull(
commandClassToCommandHandlerClassMap.get(command.getClass()),
"Command '%s' handler class".formatted(command.getClass().getName()));
return (H)
throwUnsupportedOperationIfNull(
commandHandlersMap.get(commandHandlerClass),
"Command '%s' handler instance".formatted(commandHandlerClass.getName()));
}
/**
* Creates a new model based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.Create}
* @return an instance of the model created as a result of processing the command
* @throws IllegalArgumentException if the command is null
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > R createModel(final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.Create commandHandler =
getDomainCommandHandler(nonNullCommand);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
DSL.noCondition(),
domainNotificationProducer);
});
}
/**
* Creates a list of new models based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.BatchCreate}
* @return a list of new model instances created as a result of processing the command
* @throws IllegalArgumentException if the provided command is null
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > List batchCreateModels(
final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.BatchCreate commandHandler =
getDomainCommandHandler(nonNullCommand);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
DSL.noCondition(),
domainNotificationProducer);
});
}
/**
* Updates a single model based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.Update}
* @return an updated model if it was present in the database or {@link Optional#empty()}
* @throws IllegalArgumentException if the provided command is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > Optional updateModel(final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final Condition nonNullCondition =
throwIllegalArgumentIfNull(nonNullCommand.condition(), "Command's record condition");
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.Update commandHandler =
getDomainCommandHandler(nonNullCommand);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
nonNullCondition,
domainNotificationProducer);
});
}
/**
* Updates multiple models based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.BatchUpdate}
* @return a list of any found in the database and updated models or {@link
* Collections#emptyList()}
* @throws IllegalArgumentException if the provided command is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > List batchUpdateModels(
final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final Condition nonNullCondition =
throwIllegalArgumentIfNull(nonNullCommand.condition(), "Command's records condition");
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.BatchUpdate commandHandler =
getDomainCommandHandler(nonNullCommand);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
nonNullCondition,
domainNotificationProducer);
});
}
/**
* Deletes a single model based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.Delete}
* @return a deleted model if it was present in the database or {@link Optional#empty()}
* @throws IllegalArgumentException if the provided command is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > Optional deleteModel(final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final Condition nonNullCondition =
throwIllegalArgumentIfNull(nonNullCommand.condition(), "Command's record condition");
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.Delete commandHandler =
getDomainCommandHandler(nonNullCommand);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
nonNullCondition,
domainNotificationProducer);
});
}
/**
* Deletes multiple models based on the provided {@link DomainCommand}.
*
* @param command to be processed
* @param the type of the command, which must extend {@link DomainCommand.BatchDelete}
* @return a list of any found in the database and deleted models or {@link
* Collections#emptyList()}
* @throws IllegalArgumentException if the provided command is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the command handler for the {@link DomainCommand} was
* not found
*/
public final > List batchDeleteModels(
final C command) {
final C nonNullCommand = throwIllegalArgumentIfNull(command, COMMAND);
final Condition nonNullCondition =
throwIllegalArgumentIfNull(nonNullCommand.condition(), "Command's records condition");
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullCommand);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullCommand);
return withCommandHandlersReadLock(
() -> {
final DomainCommandHandler.BatchDelete commandHandler =
getDomainCommandHandler(command);
return commandHandler.runInContext(
command,
writeDslContext,
readDslContext,
table,
nonNullCondition,
domainNotificationProducer);
});
}
/**
* Adds a {@link DomainQueryHandler} to the context.
*
* This method ensures that a {@link DomainQueryHandler} is not registered more than once for a
* specific {@link DomainQuery} class.
*
* @param queryHandler to add
* @param the type of the query handler that extends {@link DomainQueryHandler}
* @throws IllegalArgumentException if a query handler is {@code null}
* @throws IllegalStateException if a query handler for the same {@link DomainQuery} class has
* already been registered
*/
@SuppressWarnings("unchecked")
protected final > void addDomainQueryHandler(
final Q queryHandler) {
final Q nonNullQueryHandler = throwIllegalArgumentIfNull(queryHandler, "Query handler");
final Class queryHandlerClass = (Class) nonNullQueryHandler.getClass();
final Class extends DomainQuery, ?>> queryHandlerQueryClass =
throwIllegalStateIfNull(nonNullQueryHandler.getQueryClass(), "Query handler's Query class");
withQueryHandlersWriteLock(
() -> {
if (queryClassToQueryHandlerClassMap.containsKey(queryHandlerQueryClass)) {
throw new IllegalStateException(
"Query handler for '%s' has been already registered"
.formatted(queryHandlerQueryClass.getSimpleName()));
}
queryClassToQueryHandlerClassMap.put(queryHandlerQueryClass, queryHandlerClass);
queryHandlersMap.put(queryHandlerClass, nonNullQueryHandler);
});
}
/**
* Reads a single model based on the provided {@link DomainQuery}.
*
* @param query to be processed
* @param the type of the query, which must extend {@link DomainQuery.One}
* @return a model if it was present in the database or {@link Optional#empty()}
* @throws IllegalArgumentException if the provided query is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the query handler for the {@link DomainQuery} was not
* found
*/
@SuppressWarnings("unchecked")
public final > Optional queryOneModel(final Q query) {
final Q nonNullQuery = throwIllegalArgumentIfNull(query, QUERY);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullQuery);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullQuery);
return withQueryHandlersReadLock(
() -> {
final Class extends DomainQueryHandler, ?>> queryHandlerClass =
throwUnsupportedOperationIfNull(
queryClassToQueryHandlerClassMap.get(nonNullQuery.getClass()),
"Query '%s' handler class".formatted(nonNullQuery.getClass().getName()));
final DomainQueryHandler.One queryHandler =
(DomainQueryHandler.One)
throwUnsupportedOperationIfNull(
queryHandlersMap.get(queryHandlerClass),
"Query '%s' handler instance".formatted(queryHandlerClass.getName()));
return queryHandler.runInContext(
nonNullQuery, writeDslContext, readDslContext, domainNotificationProducer);
});
}
/**
* Reads multiple models based on the provided {@link DomainQuery}.
*
* @param query to be processed
* @param the type of the query, which must extend {@link DomainQuery.Many}
* @return a list of any found in the database models or {@link Stream#empty()}
* @throws IllegalArgumentException if the provided query is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the query handler for the {@link DomainQuery} was not
* found
*/
@SuppressWarnings("unchecked")
public final > List queryManyModels(final Q query) {
final Q nonNullQuery = throwIllegalArgumentIfNull(query, QUERY);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullQuery);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullQuery);
return withQueryHandlersReadLock(
() -> {
final Class extends DomainQueryHandler, ?>> queryHandlerClass =
throwUnsupportedOperationIfNull(
queryClassToQueryHandlerClassMap.get(nonNullQuery.getClass()),
"Query '%s' handler class".formatted(nonNullQuery.getClass().getName()));
final DomainQueryHandler.Many queryHandler =
(DomainQueryHandler.Many)
throwUnsupportedOperationIfNull(
queryHandlersMap.get(queryHandlerClass),
"Query '%s' handler instance".formatted(queryHandlerClass.getName()));
return queryHandler.runInContext(
nonNullQuery, writeDslContext, readDslContext, domainNotificationProducer);
});
}
/**
* Adds a {@link DomainViewHandler} to the context.
*
* This method ensures that a {@link DomainViewHandler} is not registered more than once for a
* specific {@link DomainView} class.
*
* @param viewHandler to add
* @param the type of the view handler that extends {@link DomainViewHandler}
* @throws IllegalArgumentException if a view handler is {@code null}
* @throws IllegalStateException if a view handler for the same {@link DomainView} class has
* already been registered
*/
@SuppressWarnings("unchecked")
protected final > void addDomainViewHandler(
final V viewHandler) {
final V nonNullViewHandler = throwIllegalArgumentIfNull(viewHandler, "View handler");
final Class viewHandlerClass = (Class) nonNullViewHandler.getClass();
final Class extends DomainView, ?, ?>> viewHandlerViewClass =
throwIllegalStateIfNull(nonNullViewHandler.getViewClass(), "View handler's View class");
withViewHandlersWriteLock(
() -> {
if (viewClassToViewHandlerClassMap.containsKey(viewHandlerViewClass)) {
throw new IllegalStateException(
"View handler for '%s' has been already registered"
.formatted(viewHandlerViewClass.getSimpleName()));
}
viewClassToViewHandlerClassMap.put(viewHandlerViewClass, viewHandlerClass);
viewHandlersMap.put(viewHandlerClass, nonNullViewHandler);
});
}
/**
* Returns single database view based on the provided {@link DomainView}.
*
* @param view to be processed
* @param the type of the view, which must extend {@link DomainView.One}
* @param the type of the Java view representation
* @return a view if it was constructed by the database or {@link Optional#empty()}
* @throws IllegalArgumentException if the provided view is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the view handler for the {@link DomainView} was not
* found
*/
@SuppressWarnings("unchecked")
public final , O> Optional viewOneModel(final V view) {
final V nonNullView = throwIllegalArgumentIfNull(view, VIEW);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullView);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullView);
return withViewHandlersReadLock(
() -> {
final Class extends DomainViewHandler, ?, ?>> viewHandlerClass =
throwUnsupportedOperationIfNull(
viewClassToViewHandlerClassMap.get(nonNullView.getClass()),
"View '%s' handler class".formatted(nonNullView.getClass().getName()));
final DomainViewHandler.One viewHandler =
(DomainViewHandler.One)
throwUnsupportedOperationIfNull(
viewHandlersMap.get(viewHandlerClass),
"View '%s' handler instance".formatted(viewHandlerClass.getName()));
return viewHandler.runInContext(
nonNullView, writeDslContext, readDslContext, domainNotificationProducer);
});
}
/**
* Returns multiple database views based on the provided {@link DomainView}.
*
* @param view to be processed
* @param the type of the view, which must extend {@link DomainView.One}
* @param the type of the Java view representation
* @return a list of views constructed by the database or {@link Stream#empty()}
* @throws IllegalArgumentException if the provided view is null or does not contain record
* selection criteria
* @throws UnsupportedOperationException if the view handler for the {@link DomainView} was not
* found
*/
@SuppressWarnings("unchecked")
public final , O> List viewManyModels(final V view) {
final V nonNullView = throwIllegalArgumentIfNull(view, VIEW);
final DSLContext writeDslContext = writeDslContextProvider.apply(nonNullView);
final DSLContext readDslContext = readDslContextProvider.apply(nonNullView);
return withViewHandlersReadLock(
() -> {
final Class extends DomainViewHandler, ?, ?>> viewHandlerClass =
throwUnsupportedOperationIfNull(
viewClassToViewHandlerClassMap.get(nonNullView.getClass()),
"View '%s' handler class".formatted(nonNullView.getClass().getName()));
final DomainViewHandler.Many viewHandler =
(DomainViewHandler.Many)
throwUnsupportedOperationIfNull(
viewHandlersMap.get(viewHandlerClass),
"View '%s' handler instance".formatted(viewHandlerClass.getName()));
return viewHandler.runInContext(
nonNullView, writeDslContext, readDslContext, domainNotificationProducer);
});
}
/**
* Executes a given {@link ThrowableRunnable} while holding a write lock on the {@link
* #commandHandlersLock}.
*
* The lock is guaranteed to be released after the runnable has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param runnable to be executed while holding the write lock.
*/
private void withCommandHandlersWriteLock(ThrowableRunnable runnable) {
commandHandlersLock.writeLock().lock();
try {
runnable.run();
} finally {
commandHandlersLock.writeLock().unlock();
}
}
/**
* Executes the given {@link ThrowableSupplier} while holding a read lock on the {@link
* #commandHandlersLock}.
*
*
The lock is guaranteed to be released after the supplier has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param supplier the supplier function to execute while holding the read lock
* @param the type of object returned by the supplier
* @return the result of the supplier function
*/
private O withCommandHandlersReadLock(ThrowableSupplier supplier) {
commandHandlersLock.readLock().lock();
try {
return supplier.get();
} finally {
commandHandlersLock.readLock().unlock();
}
}
/**
* Executes a given {@link ThrowableRunnable} while holding a write lock on the {@link
* #queryHandlersLock}.
*
* The lock is guaranteed to be released after the runnable has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param runnable to be executed while holding the write lock.
*/
private void withQueryHandlersWriteLock(ThrowableRunnable runnable) {
queryHandlersLock.writeLock().lock();
try {
runnable.run();
} finally {
queryHandlersLock.writeLock().unlock();
}
}
/**
* Executes the given {@link ThrowableSupplier} while holding a read lock on the {@link
* #queryHandlersLock}.
*
*
The lock is guaranteed to be released after the supplier has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param supplier the supplier function to execute while holding the read lock
* @param the type of object returned by the supplier
* @return the result of the supplier function
*/
private O withQueryHandlersReadLock(ThrowableSupplier supplier) {
queryHandlersLock.readLock().lock();
try {
return supplier.get();
} finally {
queryHandlersLock.readLock().unlock();
}
}
/**
* Executes a given {@link ThrowableRunnable} while holding a write lock on the {@link
* #viewHandlersLock}.
*
* The lock is guaranteed to be released after the runnable has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param runnable to be executed while holding the write lock.
*/
private void withViewHandlersWriteLock(ThrowableRunnable runnable) {
viewHandlersLock.writeLock().lock();
try {
runnable.run();
} finally {
viewHandlersLock.writeLock().unlock();
}
}
/**
* Executes the given {@link ThrowableSupplier} while holding a read lock on the {@link
* #viewHandlersLock}.
*
*
The lock is guaranteed to be released after the supplier has been executed, regardless of
* whether it completes successfully or throws an exception.
*
* @param supplier the supplier function to execute while holding the read lock
* @param the type of object returned by the supplier
* @return the result of the supplier function
*/
private O withViewHandlersReadLock(ThrowableSupplier supplier) {
viewHandlersLock.readLock().lock();
try {
return supplier.get();
} finally {
viewHandlersLock.readLock().unlock();
}
}
}