All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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>> queryClassToQueryHandlerClassMap; private final Map>, DomainQueryHandler> queryHandlersMap; private final ReentrantReadWriteLock commandHandlersLock; private final Map< Class>, Class>> commandClassToCommandHandlerClassMap; private final Map>, DomainCommandHandler> commandHandlersMap; private final ReentrantReadWriteLock viewHandlersLock; private final Map< Class>, Class>> 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> 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> 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> 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> 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> 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> 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> 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy