diff --git a/src/main/java/com/github/sttk/sabi/AsyncGroup.java b/src/main/java/com/github/sttk/sabi/AsyncGroup.java index 1c699a2..490a0cb 100644 --- a/src/main/java/com/github/sttk/sabi/AsyncGroup.java +++ b/src/main/java/com/github/sttk/sabi/AsyncGroup.java @@ -4,10 +4,35 @@ */ package com.github.sttk.sabi; +/** + * An interface for asynchronously executing multiple {@link Runner} instances and waiting for their + * completion. + * + *

Implementations of this interface allow adding multiple {@link Runner} objects, which are then + * executed concurrently. The group waits until all added runners have finished their execution. Any + * errors occurring during the execution of a {@link Runner} are stored and can be retrieved by + * their names in a map. + */ public interface AsyncGroup { + + /** + * Represents the reason for a new {@link com.github.sttk.errs.Exc} exception object when an + * exception occurred during the execution of a {@link Runner} and the exception class was not the + * {@link com.github.sttk.errs.Exc}. + */ record RunnerFailed() {} + /** + * Represents the reason for an {@link com.github.sttk.errs.Exc} exception object when the + * creation of a thread for asynchronous execution of a {@link Runner} fails. + */ record RunnerInterrupted() {} + /** + * Adds a {@link Runner} to this group for asynchronous execution. The added runner will be + * executed in a separate thread. + * + * @param runner The {@link Runner} to be added and executed asynchronously. + */ void add(final Runner runner); } diff --git a/src/main/java/com/github/sttk/sabi/DataAcc.java b/src/main/java/com/github/sttk/sabi/DataAcc.java index a2817ea..64294e3 100644 --- a/src/main/java/com/github/sttk/sabi/DataAcc.java +++ b/src/main/java/com/github/sttk/sabi/DataAcc.java @@ -6,6 +6,26 @@ import com.github.sttk.errs.Exc; +/** + * An interface designed for implementing data access operations through default methods in its + * sub-interfaces. + * + *

Sub-interfaces of {@code DataAcc} are expected to define and implement data access methods as + * default methods. Within these default methods, the connection to the underlying data store should + * be obtained using the {@link #getDataConn(String, Class)} method provided by this interface. This + * design promotes a clear separation of concerns, allowing data access logic to be encapsulated + * within the interface itself. + */ public interface DataAcc { + /** + * Retrieves a connection to a data store. This method is intended to be used by default methods + * in sub-interfaces to obtain the necessary connection for performing data access operations. + * + * @param The type of the data connection, which must extend {@link DataConn}. + * @param name The name identifying the specific data connection to retrieve. + * @param cls The {@link Class} object representing the type of the desired data connection. + * @return A data connection object of the specified type. + * @throws Exc if an error occurs while obtaining the data connection. + */ C getDataConn(String name, Class cls) throws Exc; } diff --git a/src/main/java/com/github/sttk/sabi/DataConn.java b/src/main/java/com/github/sttk/sabi/DataConn.java index 756b6b4..55fdbd0 100644 --- a/src/main/java/com/github/sttk/sabi/DataConn.java +++ b/src/main/java/com/github/sttk/sabi/DataConn.java @@ -6,20 +6,77 @@ import com.github.sttk.errs.Exc; +/** + * The interface that abstracts a connection per session to an external data service, such as a + * database, file system, or messaging service. + * + *

Its primary purpose is to enable cohesive transaction operations across multiple external data + * services within a single transaction context. Implementations of this interface provide the + * concrete input/output operations for their respective data services. + * + *

Methods declared within this interface are designed to handle transactional logic. The + * AsyncGroup parameter in various methods allows for asynchronous processing when commit or + * rollback operations are time-consuming. + */ public interface DataConn { + /** + * Commits the changes made within the current session to the external data service. This method + * is responsible for finalizing all operations performed since the last commit or rollback. + * + * @param ag An {@link AsyncGroup} that can be used to perform asynchronous operations if the + * commit process is time-consuming. + * @throws Exc if an error occurs during the commit operation. + */ void commit(AsyncGroup ag) throws Exc; + /** + * Performs any necessary pre-commit operations. This method is called before the {@link + * #commit(AsyncGroup)} method. + * + * @param ag An {@link AsyncGroup} that can be used for asynchronous pre-commit tasks. + * @throws Exc if an error occurs during the pre-commit operation. + */ default void preCommit(AsyncGroup ag) throws Exc {} + /** + * Performs any necessary post-commit operations. This method is called after the {@link + * #commit(AsyncGroup)} method has successfully completed. + * + * @param ag An {@link AsyncGroup} that can be used for asynchronous post-commit tasks. + */ default void postCommit(AsyncGroup ag) {} + /** + * Indicates whether a force-back operation should be performed. A force-back is a mechanism to + * revert the committed changes when this connection had been already committed but the other + * connection had failed. + * + * @return {@code true} if a force-back is required, {@code false} otherwise. + */ default boolean shouldForceBack() { return false; } + /** + * Rolls back the changes made within the current session, discarding all operations performed + * since the last commit or rollback. + * + * @param ag An {@link AsyncGroup} that can be used to perform asynchronous operations if the + * rollback process is time-consuming. + */ void rollback(AsyncGroup ag); + /** + * Performs a force-back operation to revert the committed changes when this connection had been + * already committed but the other connection had failed. + * + * @param ag An {@link AsyncGroup} that can be used for asynchronous force-back tasks. + */ default void forceBack(AsyncGroup ag) {} + /** + * Closes the connection to the external data service, releasing any associated resources. This + * method should be called to ensure proper resource management. + */ void close(); } diff --git a/src/main/java/com/github/sttk/sabi/DataHub.java b/src/main/java/com/github/sttk/sabi/DataHub.java index fc42637..24684ce 100644 --- a/src/main/java/com/github/sttk/sabi/DataHub.java +++ b/src/main/java/com/github/sttk/sabi/DataHub.java @@ -8,39 +8,137 @@ import com.github.sttk.sabi.internal.DataHubInner; import java.util.Map; +/** + * {@code DataHub} is a central component in the Sabi framework that manages {@link DataSrc} and + * {@link DataConn} instances, facilitating data access and transaction management. It implements + * both {@link DataAcc} for data access operations and {@link AutoCloseable} for resource + * management. + * + *

This class allows for the registration and unregistration of local {@link DataSrc} objects, + * and provides methods to execute application logic with or without transactional boundaries. + */ public class DataHub implements DataAcc, AutoCloseable { + /** + * Represents an error reason that occurred when failing to set up global {@link DataSrc} + * instances. + * + * @param errors A map containing the names of the data sources and the corresponding exceptions + * that occurred during their setup. + */ public record FailToSetupGlobalDataSrcs(Map errors) {} + /** + * Represents an error reason that occurred when failing to set up local {@link DataSrc} + * instances. + * + * @param errors A map containing the names of the data sources and the corresponding exceptions + * that occurred during their setup. + */ public record FailToSetupLocalDataSrcs(Map errors) {} + /** + * Represents an error reason that occurred when failing to commit one or more {@link DataConn} + * instances. + * + * @param errors A map containing the names of the data connections and the corresponding + * exceptions that occurred during their commit. + */ public record FailToCommitDataConn(Map errors) {} + /** + * Represents an error reason that occurred when failing to pre-commit one or more {@link + * DataConn} instances. + * + * @param errors A map containing the names of the data connections and the corresponding + * exceptions that occurred during their pre-commit. + */ public record FailToPreCommitDataConn(Map errors) {} + /** + * Represents an error reason where no {@link DataSrc} was found to create a {@link DataConn} with + * the specified name and type. + * + * @param name The name of the data source requested. + * @param dataConnType The type of the data connection requested. + */ public record NoDataSrcToCreateDataConn(String name, String dataConnType) {} + /** + * Represents an error reason that occurred when failing to create a {@link DataConn} instance. + * + * @param name The name of the data source from which the connection was attempted. + * @param dataConnType The type of the data connection that failed to be created. + */ public record FailToCreateDataConn(String name, String dataConnType) {} + /** + * Represents an error reason where the created {@link DataConn} instance was null. + * + * @param name The name of the data source. + * @param dataConnType The type of the data connection expected. + */ public record CreatedDataConnIsNull(String name, String dataConnType) {} + /** + * Represents an error reason that occurred when failing to cast a {@link DataConn} to the + * requested type. + * + * @param name The name of the data connection. + * @param castToType The type to which the data connection was attempted to be cast. + */ public record FailToCastDataConn(String name, String castToType) {} + /** + * Represents an error reason that occurred when failing to cast the {@code DataHub} instance + * itself to the expected data access interface type for a {@link Logic}. + * + * @param castFromType The actual type of the {@code DataHub} instance that failed to cast. + */ public record FailToCastDataHub(String castFromType) {} - public record RuntimeExceptionOccured() {} + /** + * Represents an unexpected {@link RuntimeException} that occurred during pre-commit or commit + * operations. + */ + public record RuntimeExceptionOccurred() {} private final DataHubInner inner = new DataHubInner(); + /** Constructs a new {@code DataHub} instance. */ public DataHub() {} + /** + * Registers a local {@link DataSrc} with the specified name for use within this {@code DataHub} + * instance. This allows specific data sources to be managed independently from globally + * registered ones. + * + * @param name The unique name for the {@link DataSrc}. + * @param ds The {@link DataSrc} instance to register. + */ public void uses(String name, DataSrc ds) { inner.uses(name, ds); } + /** + * Unregisters a local {@link DataSrc} with the given name from this {@code DataHub} instance. + * + * @param name The name of the {@link DataSrc} to unregister. + */ public void disuses(String name) { inner.disuses(name); } + /** + * Executes the provided application {@link Logic} without transactional boundaries. The {@code + * DataHub} instance itself is passed as the data access object {@code D} to the {@link Logic}'s + * {@code run} method. + * + * @param The type of the data access object, which typically is {@code DataHub} or an + * interface implemented by {@code DataHub} that {@link Logic} expects. + * @param logic The application logic to execute. + * @throws Exc if an {@link Exc} or {@link RuntimeException} occurs during logic execution or if + * the {@code DataHub} cannot be cast to the expected data access type. + */ public void run(Logic logic) throws Exc { D data; try { @@ -60,6 +158,19 @@ public void run(Logic logic) throws Exc { } } + /** + * Executes the provided application {@link Logic} within a transactional context. The {@code + * DataHub} instance is passed as the data access object {@code D} to the {@link Logic}'s {@code + * run} method. If the logic completes successfully, a commit operation is attempted. If any + * {@link Exc}, {@link RuntimeException}, or {@link Error} occurs, a rollback operation is + * performed. + * + * @param The type of the data access object, which typically is {@code DataHub} or an + * interface implemented by {@code DataHub} that {@link Logic} expects. + * @param logic The application logic to execute transactionally. + * @throws Exc if an {@link Exc}, {@link RuntimeException}, or {@link Error} occurs during logic + * execution, pre-commit, or commit. The original exception is re-thrown after rollback. + */ public void txn(Logic logic) throws Exc { D data; try { @@ -82,11 +193,27 @@ public void txn(Logic logic) throws Exc { } } + /** + * Retrieves a {@link DataConn} instance from the managed data sources. This method is part of the + * {@link DataAcc} interface implementation. + * + * @param The type of the {@link DataConn} to retrieve, which must extend {@link DataConn}. + * @param name The name of the data source from which to get the connection. + * @param cls The {@link Class} object representing the desired type of the data connection. + * @return A {@link DataConn} instance of the specified type. + * @throws Exc if no data source is found, if the connection cannot be created, if the created + * connection is null, or if the connection cannot be cast to the specified class. + */ @Override public C getDataConn(String name, Class cls) throws Exc { return inner.getDataConn(name, cls); } + /** + * Closes all {@link DataConn} instances managed by this {@code DataHub}, releasing their + * resources. This method is part of the {@link AutoCloseable} interface and should be called to + * ensure proper resource cleanup, ideally in a try-with-resources statement. + */ @Override public void close() { inner.close(); diff --git a/src/main/java/com/github/sttk/sabi/DataSrc.java b/src/main/java/com/github/sttk/sabi/DataSrc.java index 56a6835..760a9c9 100644 --- a/src/main/java/com/github/sttk/sabi/DataSrc.java +++ b/src/main/java/com/github/sttk/sabi/DataSrc.java @@ -6,10 +6,39 @@ import com.github.sttk.errs.Exc; +/** + * The interface that abstracts a data source responsible for managing connections to external data + * services, such as databases, file systems, or messaging services. + * + *

It receives configuration for connecting to an external data service and then creates and + * supplies a {@link DataConn} instance, representing a single session connection. + */ public interface DataSrc { + /** + * Sets up the data source, performing any necessary initialization or configuration to establish + * connectivity to the external data service. This method is typically called once at the + * application startup. + * + * @param ag An {@link AsyncGroup} that can be used for asynchronous setup operations, especially + * if initialization is time-consuming. + * @throws Exc if an error occurs during the setup process. + */ void setup(AsyncGroup ag) throws Exc; + /** + * Closes the data source, releasing all resources and shutting down connections managed by this + * source. This method should be called when the application is shutting down to ensure proper + * resource cleanup. + */ void close(); + /** + * Creates and returns a new {@link DataConn} instance, representing a single session connection + * to the external data service. Each call to this method should yield a new, independent + * connection. + * + * @return A new {@link DataConn} instance for a session. + * @throws Exc if an error occurs while creating the data connection. + */ DataConn createDataConn() throws Exc; } diff --git a/src/main/java/com/github/sttk/sabi/Logic.java b/src/main/java/com/github/sttk/sabi/Logic.java index 46b0908..faf94d9 100644 --- a/src/main/java/com/github/sttk/sabi/Logic.java +++ b/src/main/java/com/github/sttk/sabi/Logic.java @@ -6,7 +6,28 @@ import com.github.sttk.errs.Exc; +/** + * Represents the application's business logic, designed to separate data access concerns from the + * core logic. + * + *

Implementations of this functional interface should focus solely on the business logic, + * utilizing the provided {@code data} object of type {@code D} for all data access operations. The + * {@link #run(Object)} method should not contain any direct data access code; instead, it should + * delegate such operations to methods of the {@code D} object. + * + *

If an exceptional condition occurs during the execution of the logic, an {@link Exc} object + * should be thrown. + * + * @param The type of the data access object through which data operations are performed. + */ @FunctionalInterface public interface Logic { + /** + * Executes the application's business logic. This method should implement the core logic, relying + * on the {@code data} object for all data access needs. + * + * @param data The data access object, providing methods for interacting with data. + * @throws Exc if an error or exceptional condition occurs during the logic execution. + */ void run(D data) throws Exc; } diff --git a/src/main/java/com/github/sttk/sabi/Sabi.java b/src/main/java/com/github/sttk/sabi/Sabi.java index 47e367c..da31f04 100644 --- a/src/main/java/com/github/sttk/sabi/Sabi.java +++ b/src/main/java/com/github/sttk/sabi/Sabi.java @@ -7,13 +7,59 @@ import com.github.sttk.errs.Exc; import com.github.sttk.sabi.internal.DataHubInner; +/** + * {@code Sabi} is the class that provides the static methods related to the global functionalities + * of sabi framework. + * + *

This class declares {@link #uses uses} method to register a {@link DataSrc} object used + * globally with its name. And this class also declares {@link #setup setup} methods, which is the + * static method to setup all global registered {@link DataSrc} objects. + * + *

The usages of these static methods is as follows: + * + *

   public class Application {
+ *       static {
+ *           Sabi.uses("foo", new FooDataSrc());
+ *           Sabi.uses("bar", new BarDataSrc());
+ *       }
+ *       public static void main(String ...args) {
+ *           int exitCode = 0;
+ *           try (var ac = Sabi.setup()) {
+ *               ...
+ *           } catch (Exception e) {
+ *               exitCode = 1;
+ *           }
+ *           System.exit(exitCode);
+ *       }
+ *   }
+ */ public final class Sabi { private Sabi() {} + /** + * Registers a {@link DataSrc} object with a unique name for global use within the Sabi framework. + * This method should typically be called in a static initializer block of your application's main + * class. + * + * @param name The unique name to associate with the {@link DataSrc}. + * @param ds The {@link DataSrc} instance to be registered. + */ public static void uses(String name, DataSrc ds) { DataHubInner.usesGlobal(name, ds); } + /** + * Sets up all globally registered {@link DataSrc} objects. This involves calling the {@link + * DataSrc#setup(AsyncGroup) setup} method on each registered data source. This method should be + * called once at the application startup. + * + *

The returned {@link AutoCloseable} object can be used in a try-with-resources statement to + * automatically invoke the close operations upon exiting the try block. + * + * @return An {@link AutoCloseable} object that, when closed, will trigger the global close + * operation. + * @throws Exc if an error occurs during the setup of any {@link DataSrc}. + */ public static AutoCloseable setup() throws Exc { return DataHubInner.setupGlobals(); } diff --git a/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java index 2ae4be6..720eacb 100644 --- a/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java +++ b/src/main/java/com/github/sttk/sabi/internal/DataHubInner.java @@ -107,7 +107,7 @@ public void commit() throws Exc { excMap.put(ptr.name, e); break; } catch (RuntimeException e) { - excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccured(), e)); + excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccurred(), e)); break; } ptr = ptr.next; @@ -128,7 +128,7 @@ public void commit() throws Exc { excMap.put(ptr.name, e); break; } catch (RuntimeException e) { - excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccured(), e)); + excMap.put(ptr.name, new Exc(new DataHub.RuntimeExceptionOccurred(), e)); break; } ptr = ptr.next;