actorRegister = new ConcurrentHashMap<>();
+ private final AtomicInteger idCounter = new AtomicInteger(0);
+
+ public void startActor(Actor actor) {
+ String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID
+ actor.setActorId(actorId); // assign the actor it's ID
+ actorRegister.put(actorId, actor); // Register and save the actor with it's ID
+ executor.submit(actor); // Run the actor in a thread
+ }
+
+ public Actor getActorById(String actorId) {
+ return actorRegister.get(actorId); // Find by Id
+ }
+
+ public void shutdown() {
+ executor.shutdownNow(); // Stop all threads
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
new file mode 100644
index 000000000000..79fe79e48a6f
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java
@@ -0,0 +1,64 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * The Actor Model is a design pattern used to handle concurrency in a safe, scalable, and
+ * message-driven way.
+ *
+ * In the Actor Model: - An **Actor** is an independent unit that has its own state and behavior.
+ * - Actors **communicate only through messages** — they do not share memory. - An **ActorSystem**
+ * is responsible for creating, starting, and managing the lifecycle of actors. - Messages are
+ * delivered asynchronously, and each actor processes them one at a time.
+ *
+ *
💡 Key benefits: - No shared memory = no need for complex thread-safety - Easy to scale with
+ * many actors - Suitable for highly concurrent or distributed systems
+ *
+ *
🔍 This example demonstrates the Actor Model: - `ActorSystem` starts two actors: `srijan` and
+ * `ansh`. - `ExampleActor` and `ExampleActor2` extend the `Actor` class and override the
+ * `onReceive()` method to handle messages. - Actors communicate using `send()` to pass `Message`
+ * objects that include the message content and sender's ID. - The actors process messages
+ * **asynchronously in separate threads**, and we allow a short delay (`Thread.sleep`) to let them
+ * run. - The system is shut down gracefully at the end.
+ */
+package com.iluwatar.actormodel;
+
+public class App {
+ public static void main(String[] args) throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+ Actor srijan = new ExampleActor(system);
+ Actor ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+ srijan.send(new Message("Hello srijan!", ansh.getActorId()));
+
+ Thread.sleep(1000); // Give time for messages to process
+
+ srijan.stop(); // Stop the actor gracefully
+ ansh.stop();
+ system.shutdown(); // Stop the actor system
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
new file mode 100644
index 000000000000..fd49325f44bd
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java
@@ -0,0 +1,53 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ // Logger log = Logger.getLogger(getClass().getName());
+
+ @Override
+ protected void onReceive(Message message) {
+ LOGGER.info(
+ "[{}]Received : {} from : [{}]", getActorId(), message.getContent(), message.getSenderId());
+ Actor sender = actorSystem.getActorById(message.getSenderId()); // sender actor id
+ // Reply of the message
+ if (sender != null && !message.getSenderId().equals(getActorId())) {
+ sender.send(new Message("I got your message ", getActorId()));
+ }
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
new file mode 100644
index 000000000000..037f96716558
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java
@@ -0,0 +1,46 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ExampleActor2 extends Actor {
+ private final ActorSystem actorSystem;
+ @Getter private final List receivedMessages = new ArrayList<>();
+
+ public ExampleActor2(ActorSystem actorSystem) {
+ this.actorSystem = actorSystem;
+ }
+
+ @Override
+ protected void onReceive(Message message) {
+ receivedMessages.add(message.getContent());
+ LOGGER.info("[{}]Received : {}", getActorId(), message.getContent());
+ }
+}
diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
new file mode 100644
index 000000000000..03ca6e02cac0
--- /dev/null
+++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java
@@ -0,0 +1,35 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actormodel;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class Message {
+ private final String content;
+ private final String senderId;
+}
diff --git a/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
new file mode 100644
index 000000000000..a4a0dee569ab
--- /dev/null
+++ b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java
@@ -0,0 +1,63 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.actor;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.iluwatar.actormodel.ActorSystem;
+import com.iluwatar.actormodel.App;
+import com.iluwatar.actormodel.ExampleActor;
+import com.iluwatar.actormodel.ExampleActor2;
+import com.iluwatar.actormodel.Message;
+import org.junit.jupiter.api.Test;
+
+public class ActorModelTest {
+ @Test
+ void testMainMethod() throws InterruptedException {
+ App.main(new String[] {});
+ }
+
+ @Test
+ public void testMessagePassing() throws InterruptedException {
+ ActorSystem system = new ActorSystem();
+
+ ExampleActor srijan = new ExampleActor(system);
+ ExampleActor2 ansh = new ExampleActor2(system);
+
+ system.startActor(srijan);
+ system.startActor(ansh);
+
+ // Ansh recieves a message from Srijan
+ ansh.send(new Message("Hello ansh", srijan.getActorId()));
+
+ // Wait briefly to allow async processing
+ Thread.sleep(200);
+
+ // Check that Srijan received the message
+ assertTrue(
+ ansh.getReceivedMessages().contains("Hello ansh"),
+ "ansh should receive the message from Srijan");
+ }
+}
diff --git a/backpressure/pom.xml b/backpressure/pom.xml
index fcc15892fb8a..8f6a54178799 100644
--- a/backpressure/pom.xml
+++ b/backpressure/pom.xml
@@ -55,7 +55,7 @@
io.projectreactor
reactor-test
- 3.8.0-M1
+ 3.8.0-RC1
test
diff --git a/dao-factory/README.md b/dao-factory/README.md
new file mode 100644
index 000000000000..c3ee3c5e893d
--- /dev/null
+++ b/dao-factory/README.md
@@ -0,0 +1,360 @@
+---
+title: "DAO Factory Pattern: Flexible Data Access Layer for Seamless Data Source Switching"
+shortTitle: DAO Factory
+description: "Learn the Data Access Object Pattern combine with Abstract Factory Pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge."
+category: Structural
+language: en
+tag:
+ - Abstraction
+ - Data access
+ - Layer architecture
+ - Persistence
+---
+
+## Also known as
+
+* DAO Factory
+* Factory for Data Access Object strategy using Abstract Factory
+
+
+## Intent of Data Access Object Factory Design Pattern
+
+The DAO Factory combines the Data Access Object and Abstract Factory patterns to seperate business logic from data access logic, while increasing flexibility when switching between different data sources.
+
+## Detailed Explanation of Data Access Object Factory Pattern with Real-World Examples
+
+Real-world example
+
+> A real-world analogy for the DAO Factory pattern is a multilingual customer service center. Imagine a bank that serves customers speaking different languages—English, French, and Spanish. When a customer calls, an automated system first detects the customer's preferred language, then routes the call to the appropriate support team that speaks that language. Each team follows the same company policies (standard procedures), but handles interactions in a language-specific way.
+>
+> In the same way, the DAO Factory pattern uses a factory to determine the correct set of DAO implementations based on the data source (e.g., MySQL, MongoDB). Each DAO factory returns a group of DAOs tailored to a specific data source, all conforming to the same interfaces. This allows the application to interact with any supported database in a consistent manner, without changing the business logic—just like how the customer service system handles multiple languages while following the same support protocols.
+
+In plain words
+
+> The DAO Factory pattern abstracts the creation of Data Access Objects (DAOs), allowing you to request a specific DAO from a central factory without worrying about its underlying implementation. This makes the code easier to maintain and flexible to change, especially when switching between databases or storage mechanisms.
+
+Wikipedia says
+
+> The Data Access Object (DAO) design pattern is a structural pattern that provides an abstract interface to some type of database or other persistence mechanism. By mapping application calls to the persistence layer, the DAO provides some specific data operations without exposing details of the database. The DAO Factory is an extension of this concept, responsible for generating the required DAO implementations.
+
+Class diagram
+
+
+
+## Programmatic Example of Data Access Object Factory in Java
+
+In this example, the persistence object represents a Customer.
+
+We are considering a flexible storage strategy where the application should be able to work with three different types of data sources: an H2 in-memory relational database (RDBMS), a MongoDB (object-oriented database), and a JSON flat file (flat file storage).
+
+``` java
+public enum DataSourceType {
+H2,
+Mongo,
+FlatFile
+}
+```
+
+First, we define a Customer class that will be persisted in different storage systems. The ID field is generic to maintain compatibility with both relational and object-oriented databases.
+
+``` java
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class Customer implements Serializable {
+private T id;
+private String name;
+}
+```
+
+Next, we define a CustomerDAO interface that outlines the standard CRUD operations on the Customer model. This interface will have three concrete implementations, each corresponding to a specific data source: H2 in-memory database, MongoDB, and JSON file.
+
+``` java
+public interface CustomerDAO {
+
+ void save(Customer customer);
+
+ void update(Customer customer);
+
+ void delete(T id);
+
+ List> findAll();
+
+ Optional> findById(T id);
+}
+```
+
+Here is the implementations
+
+``` java
+@Slf4j
+@RequiredArgsConstructor
+public class H2CustomerDAO implements CustomerDAO {
+private final DataSource dataSource;
+private final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)";
+private final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?";
+private final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?";
+private final String SELECT_CUSTOMER_BY_ID = "SELECT * FROM customer WHERE id= ?";
+private final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer";
+private final String CREATE_SCHEMA =
+"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
+private final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
+
+ @Override
+ public void save(Customer customer) {
+ // Implement operation save for H2
+ }
+
+ @Override
+ public void update(Customer customer) {
+ // Implement operation save for H2
+ }
+
+ @Override
+ public void delete(Long id) {
+ // Implement operation delete for H2
+ }
+
+ @Override
+ public List> findAll() {
+ // Implement operation find all for H2
+ }
+
+ @Override
+ public Optional> findById(Long id) {
+ // Implement operation find by id for H2
+ }
+}
+```
+
+``` java
+@Slf4j
+@RequiredArgsConstructor
+public class MongoCustomerDAO implements CustomerDAO {
+private final MongoCollection customerCollection;
+
+ // Implement CRUD operation with MongoDB data source
+}
+```
+
+``` java
+@Slf4j
+@RequiredArgsConstructor
+public class FlatFileCustomerDAO implements CustomerDAO {
+ private final Path filePath;
+ private final Gson gson;
+ Type customerListType = new TypeToken>>() {
+ }.getType();
+
+ // Implement CRUD operation with Flat file data source
+}
+```
+
+After that, we create an abstract class DAOFactory that defines two key methods: a static method getDataSource() and an abstract method createCustomerDAO().
+
+- The getDataSource() method is a factory selector—it returns a concrete DAOFactory instance based on the type of data source requested.
+
+- Each subclass of DAOFactory will implement the createCustomerDAO() method to provide the corresponding CustomerDAO implementation.
+
+``` java
+public abstract class DAOFactory {
+ public static DAOFactory getDataSource(DataSourceType dataSourceType) {
+ return switch (dataSourceType) {
+ case H2 -> new H2DataSourceFactory();
+ case Mongo -> new MongoDataSourceFactory();
+ case FlatFile -> new FlatFileDataSourceFactory();
+ };
+ }
+
+ public abstract CustomerDAO createCustomerDAO();
+}
+```
+
+We then implement three specific factory classes:
+
+H2DataSourceFactory for H2 in-memory RDBMS
+``` java
+public class H2DataSourceFactory extends DAOFactory {
+ private final String DB_URL = "jdbc:h2:~/test";
+ private final String USER = "sa";
+ private final String PASS = "";
+
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ return new H2CustomerDAO(createDataSource());
+ }
+
+ private DataSource createDataSource() {
+ var dataSource = new JdbcDataSource();
+ dataSource.setURL(DB_URL);
+ dataSource.setUser(USER);
+ dataSource.setPassword(PASS);
+ return dataSource;
+ }
+}
+```
+
+MongoDataSourceFactory for MongoDB
+``` java
+public class MongoDataSourceFactory extends DAOFactory {
+ private final String CONN_STR = "mongodb://localhost:27017/";
+ private final String DB_NAME = "dao_factory";
+ private final String COLLECTION_NAME = "customer";
+
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ try {
+ MongoClient mongoClient = MongoClients.create(CONN_STR);
+ MongoDatabase database = mongoClient.getDatabase(DB_NAME);
+ MongoCollection customerCollection = database.getCollection(COLLECTION_NAME);
+ return new MongoCustomerDAO(customerCollection);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Error: " + e);
+ }
+ }
+}
+```
+
+FlatFileDataSourceFactory for flat file storage using JSON
+``` java
+public class FlatFileDataSourceFactory extends DAOFactory {
+ private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json";
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ Path filePath = Paths.get(FILE_PATH);
+ Gson gson = new GsonBuilder()
+ .setPrettyPrinting()
+ .serializeNulls()
+ .create();
+ return new FlatFileCustomerDAO(filePath, gson);
+ }
+}
+```
+
+Finally, in the main function of client code, we will demonstrate CRUD operations on the Customer using three data source type.
+``` java
+ // Perform CRUD H2 Database
+ LOGGER.debug("H2 - Create customer");
+ performCreateCustomer(customerDAO,
+ List.of(customerInmemory1, customerInmemory2, customerInmemory3));
+ LOGGER.debug("H2 - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateInmemory);
+ LOGGER.debug("H2 - Delete customer");
+ performDeleteCustomer(customerDAO, 3L);
+ deleteSchema(customerDAO);
+
+ // Perform CRUD MongoDb
+ daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo);
+ customerDAO = daoFactory.createCustomerDAO();
+ LOGGER.debug("Mongo - Create customer");
+ performCreateCustomer(customerDAO, List.of(customer4, customer5));
+ LOGGER.debug("Mongo - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateMongo);
+ LOGGER.debug("Mongo - Delete customer");
+ performDeleteCustomer(customerDAO, idCustomerMongo2);
+ deleteSchema(customerDAO);
+
+ // Perform CRUD Flat file
+ daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile);
+ customerDAO = daoFactory.createCustomerDAO();
+ LOGGER.debug("Flat file - Create customer");
+ performCreateCustomer(customerDAO,
+ List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3));
+ LOGGER.debug("Flat file - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateFlatFile);
+ LOGGER.debug("Flat file - Delete customer");
+ performDeleteCustomer(customerDAO, 3L);
+ deleteSchema(customerDAO);
+```
+
+The program output
+``` java
+17:17:24.368 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Create customer
+17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Green)
+17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
+17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue)
+17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Update customer
+17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow)
+17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
+17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue)
+17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Delete customer
+17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow)
+17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red)
+17:17:24.747 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Create customer
+17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
+17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Elliot)
+17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Update customer
+17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
+17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Henry)
+17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Delete customer
+17:17:24.850 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca)
+17:17:24.876 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Create customer
+17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Duc)
+17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
+17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat)
+17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Update customer
+17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh)
+17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
+17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat)
+17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Delete customer
+17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh)
+17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang)
+```
+## When to Use the Data Access Object Factory Pattern in Java
+
+Use the DAO Factory Pattern when:
+
+* The application needs to support multiple types of storage (RDBMS, NoSQL, file system, etc.) with minimal changes to business logic.
+* You want to abstract and isolate persistence logic from the core application logic.
+* You aim to make your data access layer pluggable and easy to extend with new storage technologies.
+* You want to enable easier unit testing and dependency injection by providing mock implementations of DAOs.
+* Runtime configuration (e.g., via environment variables or application settings) determines which data source to use.
+
+## Data Access Object Factory Pattern Java Tutorials
+
+* [Core J2EE Patterns - Data Access Object (Oracle)](https://www.oracle.com/java/technologies/dataaccessobject.html)
+* [DAO Factories: Java Design Patterns (Youtube)](https://www.youtube.com/watch?v=5HGe9s9qM-o)
+* [Java Design Patterns and Architecture (CaveofProgramming)](https://caveofprogramming.teachable.com/courses/2084/lectures/39549)
+
+## Real-World Applications of Data Access Object Factory Pattern in Java
+
+* Enterprise Java Applications: Where switching between test, dev, and production databases is common (e.g., MySQL ↔ MongoDB ↔ In-Memory).
+* Spring Data JPA & Repository Abstraction: Though Spring provides its own abstraction, the concept is similar to DAO factory for modular and pluggable persistence.
+* Microservices with Varying Storage Backends: Different microservices might store data in SQL, NoSQL, or even flat files; using a DAO Factory per service ensures consistency.
+* Data Integration Tools: Tools that support importing/exporting from various formats (CSV, JSON, databases) often use DAO factories behind the scenes.
+* Framework-Level Implementations: Custom internal frameworks where persistence layers need to support multiple database types.
+
+## Benefits and Trade-offs of Data Access Object Factory Pattern
+
+Benefits:
+
+* Abstraction of Data Source Logic: Client code interacts only with DAO interfaces, completely decoupled from how and where the data is stored.
+* Flexibility in Persistence Strategy: Easily switch between databases (e.g., H2, MongoDB, flat files) by changing the factory configuration.
+* Improved Maintainability: Storage logic for each data source is encapsulated within its own DAO implementation and factory, making it easier to update or extend.
+* Code Reusability: Common data access logic (e.g., CRUD operations) can be reused across different implementations and projects.
+* Testability: DAOs and factories can be mocked or stubbed easily, which supports unit testing and dependency injection.
+
+Trade-offs:
+* Increased Complexity: Introducing abstract DAOs and multiple factory classes adds structural complexity to the codebase.
+* Boilerplate Code: Requires defining many interfaces and implementations, even for simple data access needs.
+* Less Transparent Behavior: Since clients access DAOs indirectly via factories, understanding the concrete data source behavior may require deeper inspection.
+
+## Related Java Design Patterns
+
+* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): DAO Factory is a concrete application of the Factory Pattern, used to create DAO objects in a flexible way.
+* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): When supporting multiple data sources (e.g., MySQLDAO, OracleDAO), DAO Factory can act as an Abstract Factory.
+* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/data-access-object/): The core pattern managed by DAO Factory, it separates data access logic from business logic.
+* [Singleton](https://java-design-patterns.com/patterns/singleton/): DAO Factory is often implemented as a Singleton to ensure only one instance manages DAO creation.
+* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Can be used alongside DAO Factory to retrieve DAO services efficiently.
+* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): In frameworks like Spring, DAOs are typically injected into the service layer instead of being retrieved from a factory.
+
+
+## References and Credits
+
+* [DAO Factory - J2EE Design Patterns Book](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re15.html)
+* [DAO Factory patterns with Hibernate](http://www.giuseppeurso.eu/en/dao-factory-patterns-with-hibernate/)
+* [Design Patterns - Java Means DURGA SOFT](https://www.scribd.com/document/407219980/2-DAO-Factory-Design-Pattern)
+* [Generic DAO pattern - Hibernate](https://in.relation.to/2005/09/09/generic-dao-pattern-with-jdk-50/)
+
\ No newline at end of file
diff --git a/dao-factory/etc/dao-factory.png b/dao-factory/etc/dao-factory.png
new file mode 100644
index 000000000000..d93547d79957
Binary files /dev/null and b/dao-factory/etc/dao-factory.png differ
diff --git a/dao-factory/etc/dao-factory.puml b/dao-factory/etc/dao-factory.puml
new file mode 100644
index 000000000000..5196c5a1ebd9
--- /dev/null
+++ b/dao-factory/etc/dao-factory.puml
@@ -0,0 +1,74 @@
+@startuml
+package com.iluwatar.daofactory {
+ class App {
+ {static} void main(String[] args)
+ }
+
+ class Customer {
+ T id
+ String name
+ }
+
+ interface CustomerDAO {
+ void save(Customer customer)
+ void update(Customer customer)
+ void delete(ID id)
+ List> findAll()
+ Optional> findById(ID id)
+ void deleteSchema()
+ }
+
+ abstract class DAOFactory {
+ {static} DAOFactory getDataSource(DataSourceType dataType)
+ {abstract} CustomerDAO createCustomerDAO()
+ }
+
+ enum DataSourceType {
+ H2
+ Mongo
+ FlatFile
+ }
+
+ class FlatFileCustomerDAO implements CustomerDAO {
+ void save(Customer customer)
+ void update(Customer customer)
+ void delete(Long id)
+ List> findAll()
+ Optional> findById(Long id)
+ void deleteSchema()
+ }
+
+ class H2CustomerDAO implements CustomerDAO {
+ void save(Customer customer)
+ void update(Customer customer)
+ void delete(Long id)
+ List> findAll()
+ Optional> findById(Long id)
+ void deleteSchema()
+ }
+
+ class FlatFileDataSourceFactory extends DAOFactory {
+ CustomerDAO createCustomerDAO()
+ }
+
+ class H2DataSourceFactory extends DAOFactory {
+ CustomerDAO createCustomerDAO()
+ }
+
+ class MongoCustomerDAO implements CustomerDAO {
+ void save(Customer customer)
+ void update(Customer customer)
+ void delete(ObjectId id)
+ List> findAll()
+ Optional> findById(ObjectId id)
+ void deleteSchema()
+ }
+ class MongoDataSourceFactory extends DAOFactory {
+ CustomerDAO createCustomerDAO()
+ }
+
+ DataSourceType ..+ DAOFactory
+ DAOFactory ..+ App
+ App --> Customer
+ }
+@enduml
\ No newline at end of file
diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml
new file mode 100644
index 000000000000..2e771e736688
--- /dev/null
+++ b/dao-factory/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ dao-factory
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+ com.h2database
+ h2
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.mongodb
+ mongodb-driver-legacy
+
+
+ com.google.code.gson
+ gson
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
\ No newline at end of file
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java
new file mode 100644
index 000000000000..b80d3c5ac56a
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java
@@ -0,0 +1,124 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import java.io.Serializable;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.bson.types.ObjectId;
+
+@Slf4j
+public class App {
+
+ public static void main(String[] args) {
+ var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
+ CustomerDAO customerDAO = daoFactory.createCustomerDAO();
+
+ // Perform CRUD H2 Database
+ if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) {
+ h2CustomerDAO.deleteSchema();
+ h2CustomerDAO.createSchema();
+ }
+ Customer customerInmemory1 = new Customer<>(1L, "Green");
+ Customer customerInmemory2 = new Customer<>(2L, "Red");
+ Customer customerInmemory3 = new Customer<>(3L, "Blue");
+ Customer customerUpdateInmemory = new Customer<>(1L, "Yellow");
+
+ LOGGER.debug("H2 - Create customer");
+ performCreateCustomer(
+ customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3));
+ LOGGER.debug("H2 - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateInmemory);
+ LOGGER.debug("H2 - Delete customer");
+ performDeleteCustomer(customerDAO, 3L);
+ deleteSchema(customerDAO);
+
+ // Perform CRUD MongoDb
+ daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
+ customerDAO = daoFactory.createCustomerDAO();
+ ObjectId idCustomerMongo1 = new ObjectId();
+ ObjectId idCustomerMongo2 = new ObjectId();
+ Customer customer4 = new Customer<>(idCustomerMongo1, "Masca");
+ Customer customer5 = new Customer<>(idCustomerMongo2, "Elliot");
+ Customer customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry");
+
+ LOGGER.debug("Mongo - Create customer");
+ performCreateCustomer(customerDAO, List.of(customer4, customer5));
+ LOGGER.debug("Mongo - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateMongo);
+ LOGGER.debug("Mongo - Delete customer");
+ performDeleteCustomer(customerDAO, idCustomerMongo2);
+ deleteSchema(customerDAO);
+
+ // Perform CRUD Flat file
+ daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
+ customerDAO = daoFactory.createCustomerDAO();
+ Customer customerFlatFile1 = new Customer<>(1L, "Duc");
+ Customer customerFlatFile2 = new Customer<>(2L, "Quang");
+ Customer customerFlatFile3 = new Customer<>(3L, "Nhat");
+ Customer customerUpdateFlatFile = new Customer<>(1L, "Thanh");
+ LOGGER.debug("Flat file - Create customer");
+ performCreateCustomer(
+ customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3));
+ LOGGER.debug("Flat file - Update customer");
+ performUpdateCustomer(customerDAO, customerUpdateFlatFile);
+ LOGGER.debug("Flat file - Delete customer");
+ performDeleteCustomer(customerDAO, 3L);
+ deleteSchema(customerDAO);
+ }
+
+ public static void deleteSchema(CustomerDAO customerDAO) {
+ customerDAO.deleteSchema();
+ }
+
+ public static void performCreateCustomer(
+ CustomerDAO customerDAO, List> customerList) {
+ for (Customer customer : customerList) {
+ customerDAO.save(customer);
+ }
+ List> customers = customerDAO.findAll();
+ for (Customer customer : customers) {
+ LOGGER.debug(customer.toString());
+ }
+ }
+
+ public static void performUpdateCustomer(
+ CustomerDAO customerDAO, Customer customerUpdate) {
+ customerDAO.update(customerUpdate);
+ List> customers = customerDAO.findAll();
+ for (Customer customer : customers) {
+ LOGGER.debug(customer.toString());
+ }
+ }
+
+ public static void performDeleteCustomer(
+ CustomerDAO customerDAO, T customerId) {
+ customerDAO.delete(customerId);
+ List> customers = customerDAO.findAll();
+ for (Customer customer : customers) {
+ LOGGER.debug(customer.toString());
+ }
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java
new file mode 100644
index 000000000000..9559e765c7d4
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java
@@ -0,0 +1,36 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+/** Customer exception */
+public class CustomException extends RuntimeException {
+ public CustomException(String message) {
+ super(message);
+ }
+
+ public CustomException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java
new file mode 100644
index 000000000000..95b675487d27
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java
@@ -0,0 +1,47 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import java.io.Serializable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * A customer generic POJO that represents the data that can be stored in any supported data source.
+ * This class is designed t work with various ID types (e.g., Long, String, or ObjectId) through
+ * generic, making it adaptable to different persistence system.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class Customer implements Serializable {
+ private T id;
+ private String name;
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java
new file mode 100644
index 000000000000..34316b4c49af
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java
@@ -0,0 +1,85 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * The Data Access Object (DAO) pattern provides an abstraction layer between the application and
+ * the database. It encapsulates data access logic, allowing the application to work with domain
+ * objects instead of direct database operations.
+ *
+ * Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping
+ * client code unchanged.
+ *
+ * @see H2CustomerDAO
+ * @see MongoCustomerDAO
+ * @see FlatFileCustomerDAO
+ */
+public interface CustomerDAO {
+ /**
+ * Persist the given customer
+ *
+ * @param customer the customer to persist
+ */
+ void save(Customer customer);
+
+ /**
+ * Update the given customer
+ *
+ * @param customer the customer to update
+ */
+ void update(Customer customer);
+
+ /**
+ * Delete the customer with the given id
+ *
+ * @param id the id of the customer to delete
+ */
+ void delete(T id);
+
+ /**
+ * Find all customers
+ *
+ * @return a list of customers
+ */
+ List> findAll();
+
+ /**
+ * Find the customer with the given id
+ *
+ * @param id the id of the customer to find
+ * @return the customer with the given id
+ */
+ Optional> findById(T id);
+
+ /**
+ * Delete the customer schema. After executing the statements, this function will be called to
+ * clean up the data and delete the records.
+ */
+ void deleteSchema();
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java
new file mode 100644
index 000000000000..e7d33186bec5
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java
@@ -0,0 +1,45 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+/**
+ * An abstract factory class that provides a way to create concrete DAO (Data Access Object)
+ * factories for different data sources types (e.g., H2, Mongo, FlatFile).
+ *
+ * This class follows the Abstract Factory design pattern, allowing applications to retrieve the
+ * approriate DAO implementation without being tightly coupled to a specific data source.
+ *
+ * @see H2DataSourceFactory
+ * @see MongoDataSourceFactory
+ * @see FlatFileDataSourceFactory
+ */
+public abstract class DAOFactory {
+ /**
+ * Retrieves a {@link CustomerDAO} implementation specific to the underlying data source..
+ *
+ * @return A data source-specific implementation of {@link CustomerDAO}
+ */
+ public abstract CustomerDAO createCustomerDAO();
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java
new file mode 100644
index 000000000000..08585622d00d
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java
@@ -0,0 +1,62 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+/**
+ * {@code DAOFactoryProvider} is a utility class responsible for providing concrete implementations
+ * of the {@link DAOFactory} interface based on the specified data source type.
+ *
+ *
This class acts as an entry point to obtain DAO factories for different storage mechanisms
+ * such as relational databases (e.g., H2), document stores (e.g., MongoDB), or file-based systems.
+ * It uses the {@link DataSourceType} enumeration to determine which concrete factory to
+ * instantiate.
+ *
+ *
Example usage:
+ *
+ *
{@code
+ * DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
+ * }
+ */
+public class DAOFactoryProvider {
+
+ private DAOFactoryProvider() {}
+
+ /**
+ * Returns a concrete {@link DAOFactory} intance based on the specified data source type.
+ *
+ * @param dataSourceType The type of data source for which a factory is needed. Supported values:
+ * {@code H2}, {@code Mongo}, {@code FlatFile}
+ * @return A {@link DAOFactory} implementation corresponding to the given data source type.
+ * @throws IllegalArgumentException if the given data source type is not supported.
+ */
+ public static DAOFactory getDataSource(DataSourceType dataSourceType) {
+ return switch (dataSourceType) {
+ case H2 -> new H2DataSourceFactory();
+ case MONGO -> new MongoDataSourceFactory();
+ case FLAT_FILE -> new FlatFileDataSourceFactory();
+ default -> throw new IllegalArgumentException("Unsupported data source type");
+ };
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java
new file mode 100644
index 000000000000..da01d451f09e
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java
@@ -0,0 +1,32 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+/** Enumerates the types of data sources supported by the application. */
+public enum DataSourceType {
+ H2,
+ MONGO,
+ FLAT_FILE
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java
new file mode 100644
index 000000000000..8f1f1f144f77
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java
@@ -0,0 +1,175 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * A Flat File implementation of {@link CustomerDAO}, which store the customer data in a JSON file
+ * at path {@code ~/Desktop/customer.json}.
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class FlatFileCustomerDAO implements CustomerDAO {
+ private final Path filePath;
+ private final Gson gson;
+ Type customerListType = new TypeToken>>() {}.getType();
+
+ protected Reader createReader(Path filePath) throws IOException {
+ return new FileReader(filePath.toFile());
+ }
+
+ protected Writer createWriter(Path filePath) throws IOException {
+ return new FileWriter(filePath.toFile());
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void save(Customer customer) {
+ List> customers = new LinkedList<>();
+ if (filePath.toFile().exists()) {
+ try (Reader reader = createReader(filePath)) {
+ customers = gson.fromJson(reader, customerListType);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to read customer data", ex);
+ }
+ }
+ customers.add(customer);
+ try (Writer writer = createWriter(filePath)) {
+ gson.toJson(customers, writer);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to write customer data", ex);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void update(Customer customer) {
+ if (!filePath.toFile().exists()) {
+ throw new CustomException("File not found");
+ }
+ List> customers;
+ try (Reader reader = createReader(filePath)) {
+ customers = gson.fromJson(reader, customerListType);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to read customer data", ex);
+ }
+ customers.stream()
+ .filter(c -> c.getId().equals(customer.getId()))
+ .findFirst()
+ .ifPresentOrElse(
+ c -> c.setName(customer.getName()),
+ () -> {
+ throw new CustomException("Customer not found with id: " + customer.getId());
+ });
+ try (Writer writer = createWriter(filePath)) {
+ gson.toJson(customers, writer);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to write customer data", ex);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void delete(Long id) {
+ if (!filePath.toFile().exists()) {
+ throw new CustomException("File not found");
+ }
+ List> customers;
+ try (Reader reader = createReader(filePath)) {
+ customers = gson.fromJson(reader, customerListType);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to read customer data", ex);
+ }
+ Customer customerToRemove =
+ customers.stream()
+ .filter(c -> c.getId().equals(id))
+ .findFirst()
+ .orElseThrow(() -> new CustomException("Customer not found with id: " + id));
+ customers.remove(customerToRemove);
+ try (Writer writer = createWriter(filePath)) {
+ gson.toJson(customers, writer);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to write customer data", ex);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List> findAll() {
+ if (!filePath.toFile().exists()) {
+ throw new CustomException("File not found");
+ }
+ List> customers;
+ try (Reader reader = createReader(filePath)) {
+ customers = gson.fromJson(reader, customerListType);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to read customer data", ex);
+ }
+ return customers;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Optional> findById(Long id) {
+ if (!filePath.toFile().exists()) {
+ throw new CustomException("File not found");
+ }
+ List> customers = null;
+ try (Reader reader = createReader(filePath)) {
+ customers = gson.fromJson(reader, customerListType);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to read customer data", ex);
+ }
+ return customers.stream().filter(c -> c.getId().equals(id)).findFirst();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteSchema() {
+ if (!filePath.toFile().exists()) {
+ throw new CustomException("File not found");
+ }
+ try {
+ Files.delete(filePath);
+ } catch (IOException ex) {
+ throw new CustomException("Failed to delete customer data");
+ }
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java
new file mode 100644
index 000000000000..f423376703b5
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java
@@ -0,0 +1,43 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/** FlatFileDataSourceFactory concrete factory. */
+public class FlatFileDataSourceFactory extends DAOFactory {
+ private static final String FILE_PATH =
+ System.getProperty("user.home") + "/Desktop/customer.json";
+
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ Path filePath = Paths.get(FILE_PATH);
+ Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
+ return new FlatFileCustomerDAO(filePath, gson);
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java
new file mode 100644
index 000000000000..fe027426391c
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java
@@ -0,0 +1,179 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import javax.sql.DataSource;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * An implementation of {@link CustomerDAO} that uses H2 database (http://www.h2database.com/) which
+ * is an in-memory database and data will lost after application exits.
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class H2CustomerDAO implements CustomerDAO {
+ private final DataSource dataSource;
+ private static final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)";
+ private static final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?";
+ private static final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?";
+ private static final String SELECT_CUSTOMER_BY_ID =
+ "SELECT customer.id, customer.name FROM customer WHERE id= ?";
+ private static final String SELECT_ALL_CUSTOMERS = "SELECT customer.* FROM customer";
+ private static final String CREATE_SCHEMA =
+ "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
+ private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
+
+ /** {@inheritDoc} */
+ @Override
+ public void save(Customer customer) {
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) {
+ saveStatement.setLong(1, customer.getId());
+ saveStatement.setString(2, customer.getName());
+ saveStatement.execute();
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void update(Customer customer) {
+ if (Objects.isNull(customer) || Objects.isNull(customer.getId())) {
+ throw new CustomException("Custome null or customer id null");
+ }
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
+ PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) {
+ selectStatement.setLong(1, customer.getId());
+ try (ResultSet resultSet = selectStatement.executeQuery()) {
+ if (!resultSet.next()) {
+ throw new CustomException("Customer not found with id: " + customer.getId());
+ }
+ }
+ updateStatement.setString(1, customer.getName());
+ updateStatement.setLong(2, customer.getId());
+ updateStatement.executeUpdate();
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void delete(Long id) {
+ if (Objects.isNull(id)) {
+ throw new CustomException("Customer id null");
+ }
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
+ PreparedStatement deleteStatement = connection.prepareStatement(DELETE_CUSTOMER)) {
+ selectStatement.setLong(1, id);
+ try (ResultSet resultSet = selectStatement.executeQuery()) {
+ if (!resultSet.next()) {
+ throw new CustomException("Customer not found with id: " + id);
+ }
+ }
+ deleteStatement.setLong(1, id);
+ deleteStatement.execute();
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List> findAll() {
+ List> customers = new LinkedList<>();
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) {
+ try (ResultSet resultSet = selectStatement.executeQuery()) {
+ while (resultSet.next()) {
+ Long idCustomer = resultSet.getLong("id");
+ String nameCustomer = resultSet.getString("name");
+ customers.add(new Customer<>(idCustomer, nameCustomer));
+ }
+ }
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ return customers;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Optional> findById(Long id) {
+ if (Objects.isNull(id)) {
+ throw new CustomException("Customer id null");
+ }
+ Customer customer = null;
+ try (Connection connection = dataSource.getConnection();
+ PreparedStatement selectByIdStatement =
+ connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) {
+ selectByIdStatement.setLong(1, id);
+ try (ResultSet resultSet = selectByIdStatement.executeQuery()) {
+ while (resultSet.next()) {
+ Long idCustomer = resultSet.getLong("id");
+ String nameCustomer = resultSet.getString("name");
+ customer = new Customer<>(idCustomer, nameCustomer);
+ }
+ }
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ return Optional.ofNullable(customer);
+ }
+
+ /** Create customer schema. */
+ public void createSchema() {
+ try (Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement()) {
+ statement.execute(CREATE_SCHEMA);
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ }
+
+ /** {@inheritDoc}} */
+ @Override
+ public void deleteSchema() {
+ try (Connection connection = dataSource.getConnection();
+ Statement statement = connection.createStatement(); ) {
+ statement.execute(DROP_SCHEMA);
+ } catch (SQLException e) {
+ throw new CustomException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java
new file mode 100644
index 000000000000..dbb39dd98f3b
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java
@@ -0,0 +1,48 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import javax.sql.DataSource;
+import org.h2.jdbcx.JdbcDataSource;
+
+/** H2DataSourceFactory concrete factory. */
+public class H2DataSourceFactory extends DAOFactory {
+ private static final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
+ private static final String USER = "sa";
+ private static final String PASS = "";
+
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ return new H2CustomerDAO(createDataSource());
+ }
+
+ private DataSource createDataSource() {
+ var dataSource = new JdbcDataSource();
+ dataSource.setURL(DB_URL);
+ dataSource.setUser(USER);
+ dataSource.setPassword(PASS);
+ return dataSource;
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java
new file mode 100644
index 000000000000..1870f61e85fd
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java
@@ -0,0 +1,106 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.Updates;
+import com.mongodb.client.result.DeleteResult;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.bson.types.ObjectId;
+
+/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */
+@Slf4j
+@RequiredArgsConstructor
+public class MongoCustomerDAO implements CustomerDAO {
+ private final MongoCollection customerCollection;
+
+ /** {@inheritDoc} */
+ @Override
+ public void save(Customer customer) {
+ Document customerDocument = new Document("_id", customer.getId());
+ customerDocument.append("name", customer.getName());
+ customerCollection.insertOne(customerDocument);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void update(Customer customer) {
+ Document updateQuery = new Document("_id", customer.getId());
+ Bson update = Updates.set("name", customer.getName());
+ customerCollection.updateOne(updateQuery, update);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void delete(ObjectId objectId) {
+ Bson deleteQuery = Filters.eq("_id", objectId);
+ DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery);
+ if (deleteResult.getDeletedCount() == 0) {
+ throw new CustomException("Delete failed: No document found with id: " + objectId);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public List> findAll() {
+ List> customers = new LinkedList<>();
+ FindIterable customerDocuments = customerCollection.find();
+ for (Document customerDocument : customerDocuments) {
+ Customer customer =
+ new Customer<>(
+ (ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
+ customers.add(customer);
+ }
+ return customers;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Optional> findById(ObjectId objectId) {
+ Bson filter = Filters.eq("_id", objectId);
+ Document customerDocument = customerCollection.find(filter).first();
+ Customer customerResult = null;
+ if (customerDocument != null) {
+ customerResult =
+ new Customer<>(
+ (ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
+ }
+ return Optional.ofNullable(customerResult);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void deleteSchema() {
+ customerCollection.drop();
+ }
+}
diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java
new file mode 100644
index 000000000000..5a7b1f1b1ece
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java
@@ -0,0 +1,51 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import org.bson.Document;
+import org.bson.types.ObjectId;
+
+/** MongoDataSourceFactory concrete factory. */
+public class MongoDataSourceFactory extends DAOFactory {
+ private static final String CONN_STR = "mongodb://localhost:27017/";
+ private static final String DB_NAME = "dao_factory";
+ private static final String COLLECTION_NAME = "customer";
+
+ @Override
+ public CustomerDAO createCustomerDAO() {
+ try {
+ MongoClient mongoClient = MongoClients.create(CONN_STR);
+ MongoDatabase database = mongoClient.getDatabase(DB_NAME);
+ MongoCollection customerCollection = database.getCollection(COLLECTION_NAME);
+ return new MongoCustomerDAO(customerCollection);
+ } catch (CustomException e) {
+ throw new CustomException("Error: " + e);
+ }
+ }
+}
diff --git a/dao-factory/src/main/resources/logback.xml b/dao-factory/src/main/resources/logback.xml
new file mode 100644
index 000000000000..f82341ebb2ab
--- /dev/null
+++ b/dao-factory/src/main/resources/logback.xml
@@ -0,0 +1,12 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java
new file mode 100644
index 000000000000..12efea42bdc6
--- /dev/null
+++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java
@@ -0,0 +1,94 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** {@link App} */
+class AppTest {
+ /** Test perform CRUD in main class */
+ private CustomerDAO mockLongCustomerDAO;
+
+ private CustomerDAO mockObjectIdCustomerDAO;
+
+ @BeforeEach
+ void setUp() {
+ mockLongCustomerDAO = mock(CustomerDAO.class);
+ mockObjectIdCustomerDAO = mock(CustomerDAO.class);
+ }
+
+ @Test
+ void testPerformCreateCustomerWithLongId() {
+ Customer c1 = new Customer<>(1L, "Test1");
+ Customer c2 = new Customer<>(2L, "Test2");
+
+ when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2));
+
+ App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2));
+
+ verify(mockLongCustomerDAO).save(c1);
+ verify(mockLongCustomerDAO).save(c2);
+ verify(mockLongCustomerDAO).findAll();
+ }
+
+ @Test
+ void testPerformUpdateCustomerWithObjectId() {
+ ObjectId id = new ObjectId();
+ Customer updatedCustomer = new Customer<>(id, "Updated");
+
+ when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer));
+
+ App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer);
+
+ verify(mockObjectIdCustomerDAO).update(updatedCustomer);
+ verify(mockObjectIdCustomerDAO).findAll();
+ }
+
+ @Test
+ void testPerformDeleteCustomerWithLongId() {
+ Long id = 100L;
+ Customer remainingCustomer = new Customer<>(1L, "Remaining");
+
+ when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer));
+
+ App.performDeleteCustomer(mockLongCustomerDAO, id);
+
+ verify(mockLongCustomerDAO).delete(id);
+ verify(mockLongCustomerDAO).findAll();
+ }
+
+ @Test
+ void testDeleteSchema() {
+ App.deleteSchema(mockLongCustomerDAO);
+ verify(mockLongCustomerDAO).deleteSchema();
+ }
+}
diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java
new file mode 100644
index 000000000000..f8aaf199762d
--- /dev/null
+++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java
@@ -0,0 +1,54 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+import org.junit.jupiter.api.Test;
+
+/** {@link DAOFactory} */
+class DAOFactoryTest {
+
+ @Test
+ void verifyH2CustomerDAOCreation() {
+ var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
+ var customerDAO = daoFactory.createCustomerDAO();
+ assertInstanceOf(H2CustomerDAO.class, customerDAO);
+ }
+
+ @Test
+ void verifyMongoCustomerDAOCreation() {
+ var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
+ var customerDAO = daoFactory.createCustomerDAO();
+ assertInstanceOf(MongoCustomerDAO.class, customerDAO);
+ }
+
+ @Test
+ void verifyFlatFileCustomerDAOCreation() {
+ var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
+ var customerDAO = daoFactory.createCustomerDAO();
+ assertInstanceOf(FlatFileCustomerDAO.class, customerDAO);
+ }
+}
diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java
new file mode 100644
index 000000000000..470964f4217a
--- /dev/null
+++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java
@@ -0,0 +1,500 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/** {@link FlatFileCustomerDAO} */
+class FlatFileCustomerDAOTest {
+ private Path filePath;
+ private File file;
+ private Gson gson;
+
+ private final Type customerListType = new TypeToken>>() {}.getType();
+ private final Customer existingCustomer = new Customer<>(1L, "Thanh");
+ private FlatFileCustomerDAO flatFileCustomerDAO;
+ private FileReader fileReader;
+ private FileWriter fileWriter;
+
+ @BeforeEach
+ void setUp() {
+ filePath = mock(Path.class);
+ file = mock(File.class);
+ gson = mock(Gson.class);
+ fileReader = mock(FileReader.class);
+ fileWriter = mock(FileWriter.class);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ return fileWriter;
+ }
+ };
+ when(filePath.toFile()).thenReturn(file);
+ }
+
+ /** Class test with scenario Save Customer */
+ @Nested
+ class Save {
+ @Test
+ void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() {
+ when(file.exists()).thenReturn(false);
+ flatFileCustomerDAO.save(existingCustomer);
+
+ verify(gson)
+ .toJson(
+ argThat(
+ (List> list) ->
+ list.size() == 1 && list.getFirst().equals(existingCustomer)),
+ eq(fileWriter));
+ }
+
+ @Test
+ void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() {
+ when(file.exists()).thenReturn(true);
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
+ flatFileCustomerDAO.save(existingCustomer);
+
+ verify(gson).fromJson(fileReader, customerListType);
+ verify(gson)
+ .toJson(
+ argThat(
+ (List> list) ->
+ list.size() == 1 && list.getFirst().equals(existingCustomer)),
+ eq(fileWriter));
+ }
+
+ @Test
+ void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() {
+ List> customers = new LinkedList<>();
+ customers.add(new Customer<>(2L, "Duc"));
+ customers.add(new Customer<>(3L, "Nguyen"));
+ when(file.exists()).thenReturn(true);
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(customers);
+
+ flatFileCustomerDAO.save(existingCustomer);
+
+ verify(gson).fromJson(fileReader, customerListType);
+ verify(gson).toJson(argThat((List> list) -> list.size() == 3), eq(fileWriter));
+ }
+
+ @Test
+ void whenReadFails_thenThrowException() {
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ throw new IOException("Failed to read file");
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ when(file.exists()).thenReturn(true);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer));
+ }
+
+ @Test
+ void whenWriteFails_thenThrowException() {
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ throw new IOException("Failed to write file");
+ }
+ };
+ when(file.exists()).thenReturn(true);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer));
+ }
+ }
+
+ /** Class test with scenario Update Customer */
+ @Nested
+ class Update {
+ @Test
+ void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() {
+ when(file.exists()).thenReturn(false);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
+ }
+
+ @Test
+ void whenReadFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ throw new IOException("Failed to read file");
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
+ }
+
+ @Test
+ void whenWriteFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ when(gson.fromJson(any(Reader.class), eq(customerListType)))
+ .thenReturn(
+ new LinkedList<>() {
+ {
+ add(new Customer<>(1L, "Quang"));
+ }
+ });
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ throw new IOException("Failed to write file");
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
+ }
+
+ @Test
+ void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ return fileWriter;
+ }
+ };
+ flatFileCustomerDAO.update(existingCustomer);
+ verify(gson)
+ .toJson(
+ argThat(
+ (List> customers) ->
+ customers.size() == 1
+ && customers.stream()
+ .anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh"))),
+ eq(fileWriter));
+ }
+
+ @Test
+ void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(2L, "Quang"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer));
+ }
+ }
+
+ /** Class test with scenario Delete Customer */
+ @Nested
+ class Delete {
+ @Test
+ void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() {
+ when(file.exists()).thenReturn(false);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
+ }
+
+ @Test
+ void whenReadFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ throw new IOException("Failed to read file");
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
+ }
+
+ @Test
+ void whenWriteFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) throws IOException {
+ throw new IOException("Failed to write file");
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L));
+ }
+
+ @Test
+ void givenValidId_whenDeleteCustomer_thenDeleteSucceed() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ existingListCustomer.add(new Customer<>(2L, "Thanh"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+
+ flatFileCustomerDAO.delete(1L);
+ assertEquals(1, existingListCustomer.size());
+ verify(gson)
+ .toJson(
+ argThat(
+ (List> customers) ->
+ customers.stream()
+ .noneMatch(c -> c.getId().equals(1L) && c.getName().equals("Quang"))),
+ eq(fileWriter));
+ }
+
+ @Test
+ void givenIdNotExist_whenDeleteCustomer_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ existingListCustomer.add(new Customer<>(2L, "Thanh"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) {
+ return fileReader;
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(3L));
+ }
+ }
+
+ /** Class test with scenario Find All Customer */
+ @Nested
+ class FindAll {
+ @Test
+ void givenFileNotExist_thenThrowException() {
+ when(file.exists()).thenReturn(false);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll());
+ }
+
+ @Test
+ void whenReadFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ throw new IOException("Failed to read file");
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll());
+ }
+
+ @Test
+ void givenEmptyCustomer_thenReturnEmptyList() {
+ when(file.exists()).thenReturn(true);
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
+ List> customers = flatFileCustomerDAO.findAll();
+ assertEquals(0, customers.size());
+ verify(gson).fromJson(fileReader, customerListType);
+ }
+
+ @Test
+ void givenCustomerExist_thenReturnCustomerList() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ existingListCustomer.add(new Customer<>(2L, "Thanh"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ List> customers = flatFileCustomerDAO.findAll();
+ assertEquals(2, customers.size());
+ }
+ }
+
+ /** Class test with scenario Find By Id Customer */
+ @Nested
+ class FindById {
+
+ @Test
+ void givenFilePathNotExist_whenFindById_thenThrowException() {
+ when(file.exists()).thenReturn(false);
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L));
+ }
+
+ @Test
+ void whenReadFails_thenThrowException() {
+ when(file.exists()).thenReturn(true);
+ flatFileCustomerDAO =
+ new FlatFileCustomerDAO(filePath, gson) {
+ @Override
+ protected Reader createReader(Path filePath) throws IOException {
+ throw new IOException("Failed to read file");
+ }
+
+ @Override
+ protected Writer createWriter(Path filePath) {
+ return fileWriter;
+ }
+ };
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L));
+ }
+
+ @Test
+ void givenIdCustomerExist_whenFindById_thenReturnCustomer() {
+ when(file.exists()).thenReturn(true);
+ List> existingListCustomer = new LinkedList<>();
+ existingListCustomer.add(new Customer<>(1L, "Quang"));
+ existingListCustomer.add(new Customer<>(2L, "Thanh"));
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer);
+ Optional> customer = flatFileCustomerDAO.findById(1L);
+ assertTrue(customer.isPresent());
+ assertEquals("Quang", customer.get().getName());
+ }
+
+ @Test
+ void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() {
+ when(file.exists()).thenReturn(true);
+ when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>());
+ Optional> customers = flatFileCustomerDAO.findById(1L);
+ assertTrue(customers.isEmpty());
+ }
+ }
+
+ /** Clas test with scenario Delete schema */
+ @Nested
+ class DeleteSchema {
+ @Test
+ void givenFilePathExist_thenDeleteFile() {
+ when(file.exists()).thenReturn(true);
+
+ try (MockedStatic mockedFiles = mockStatic(Files.class)) {
+ flatFileCustomerDAO.deleteSchema();
+ mockedFiles.verify(() -> Files.delete(filePath), times(1));
+ }
+ }
+
+ @Test
+ void givenFilePathNotExist_thenThrowException() {
+ when(file.exists()).thenReturn(false);
+
+ try (MockedStatic mockedFiles = mockStatic(Files.class)) {
+ assertThrows(CustomException.class, () -> flatFileCustomerDAO.deleteSchema());
+ mockedFiles.verify(() -> Files.delete(filePath), times(0));
+ }
+ }
+ }
+}
diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java
new file mode 100644
index 000000000000..ce7def36e5bc
--- /dev/null
+++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java
@@ -0,0 +1,300 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.List;
+import javax.sql.DataSource;
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/** Tests {@link H2CustomerDAO} */
+class H2CustomerDAOTest {
+ private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
+ private static final String USER = "sa";
+ private static final String PASS = "";
+ private static final String CREATE_SCHEMA =
+ "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
+ private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
+ private final Customer existingCustomer = new Customer<>(1L, "Nguyen");
+ private H2CustomerDAO h2CustomerDAO;
+
+ @BeforeEach
+ void createSchema() throws SQLException {
+ try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
+ var statement = connection.createStatement()) {
+ statement.execute(CREATE_SCHEMA);
+ }
+ }
+
+ @AfterEach
+ void deleteSchema() throws SQLException {
+ try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
+ var statement = connection.createStatement()) {
+ statement.execute(DROP_SCHEMA);
+ }
+ }
+
+ /** Class test for scenario connect with datasource succeed */
+ @Nested
+ class ConnectionSucceed {
+
+ @BeforeEach
+ void setUp() {
+ var dataSource = new JdbcDataSource();
+ dataSource.setURL(DB_URL);
+ dataSource.setUser(USER);
+ dataSource.setPassword(PASS);
+ h2CustomerDAO = new H2CustomerDAO(dataSource);
+ assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer));
+ var customer = h2CustomerDAO.findById(existingCustomer.getId());
+ assertTrue(customer.isPresent());
+ assertEquals(customer.get().getName(), existingCustomer.getName());
+ assertEquals(customer.get().getId(), existingCustomer.getId());
+ }
+
+ @Nested
+ class SaveCustomer {
+ @Test
+ void givenValidCustomer_whenSaveCustomer_thenAddSucceed() {
+ var customer = new Customer<>(2L, "Duc");
+ assertDoesNotThrow(() -> h2CustomerDAO.save(customer));
+ var customerInDb = h2CustomerDAO.findById(customer.getId());
+ assertTrue(customerInDb.isPresent());
+ assertEquals(customerInDb.get().getName(), customer.getName());
+ assertEquals(customerInDb.get().getId(), customer.getId());
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(2, customers.size());
+ }
+
+ @Test
+ void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() {
+ var customer = new Customer<>(existingCustomer.getId(), "Duc");
+ assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(1, customers.size());
+ }
+ }
+
+ @Nested
+ class UpdateCustomer {
+ @Test
+ void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
+ var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
+ assertDoesNotThrow(() -> h2CustomerDAO.update(customerUpdate));
+ var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
+ assertTrue(customerInDb.isPresent());
+ assertEquals(customerInDb.get().getName(), customerUpdate.getName());
+ }
+
+ @Test
+ void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() {
+ var customerUpdate = new Customer<>(100L, "Duc");
+ var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
+ assertTrue(customerInDb.isEmpty());
+ assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
+ }
+
+ @Test
+ void givenNull_whenUpdateCustomer_thenThrowException() {
+ assertThrows(CustomException.class, () -> h2CustomerDAO.update(null));
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(1, customers.size());
+ }
+ }
+
+ @Nested
+ class DeleteCustomer {
+ @Test
+ void givenValidId_whenDeleteCustomer_thenDeleteSucceed() {
+ assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
+ var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
+ assertTrue(customerInDb.isEmpty());
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(0, customers.size());
+ }
+
+ @Test
+ void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() {
+ var customerInDb = h2CustomerDAO.findById(100L);
+ assertTrue(customerInDb.isEmpty());
+ assertThrows(CustomException.class, () -> h2CustomerDAO.delete(100L));
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(1, customers.size());
+ assertEquals(existingCustomer.getName(), customers.get(0).getName());
+ assertEquals(existingCustomer.getId(), customers.get(0).getId());
+ }
+
+ @Test
+ void givenNull_whenDeleteCustomer_thenThrowException() {
+ assertThrows(CustomException.class, () -> h2CustomerDAO.delete(null));
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(1, customers.size());
+ assertEquals(existingCustomer.getName(), customers.get(0).getName());
+ }
+ }
+
+ @Nested
+ class FindAllCustomers {
+ @Test
+ void givenNonCustomerInDb_whenFindAllCustomer_thenReturnEmptyList() {
+ assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(0, customers.size());
+ }
+
+ @Test
+ void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() {
+ List> customers = h2CustomerDAO.findAll();
+ assertEquals(1, customers.size());
+ assertEquals(existingCustomer.getName(), customers.get(0).getName());
+ assertEquals(existingCustomer.getId(), customers.get(0).getId());
+ }
+ }
+
+ @Nested
+ class FindCustomerById {
+ @Test
+ void givenValidId_whenFindById_thenReturnCustomer() {
+ var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
+ assertTrue(customerInDb.isPresent());
+ assertEquals(existingCustomer.getName(), customerInDb.get().getName());
+ assertEquals(existingCustomer.getId(), customerInDb.get().getId());
+ }
+
+ @Test
+ void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() {
+ var customerNotExist = h2CustomerDAO.findById(100L);
+ assertTrue(customerNotExist.isEmpty());
+ }
+
+ @Test
+ void givenNull_whenFindById_thenThrowException() {
+ assertThrows(CustomException.class, () -> h2CustomerDAO.findById(null));
+ }
+ }
+
+ @Nested
+ class CreateSchema {
+ @Test
+ void whenCreateSchema_thenNotThrowException() {
+ assertDoesNotThrow(() -> h2CustomerDAO.createSchema());
+ }
+ }
+
+ @Nested
+ class DeleteSchema {
+ @Test
+ void whenDeleteSchema_thenNotThrowException() {
+ assertDoesNotThrow(() -> h2CustomerDAO.deleteSchema());
+ }
+ }
+ }
+
+ /** Class test with scenario connect with data source failed */
+ @Nested
+ class ConnectionFailed {
+ private static final String EXCEPTION_CAUSE = "Connection not available";
+
+ @BeforeEach
+ void setUp() throws SQLException {
+ h2CustomerDAO = new H2CustomerDAO(mockedDataSource());
+ }
+
+ private DataSource mockedDataSource() throws SQLException {
+ var mockedDataSource = mock(DataSource.class);
+ var mockedConnection = mock(Connection.class);
+ var exception = new SQLException(EXCEPTION_CAUSE);
+ doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
+ doThrow(exception).when(mockedConnection).createStatement();
+ doReturn(mockedConnection).when(mockedDataSource).getConnection();
+ return mockedDataSource;
+ }
+
+ @Test
+ void givenValidCustomer_whenSaveCustomer_thenThrowException() {
+ var customer = new Customer<>(2L, "Duc");
+ CustomException exception =
+ assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void givenValidCustomer_whenUpdateCustomer_thenThrowException() {
+ var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
+ CustomException exception =
+ assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void givenValidId_whenDeleteCustomer_thenThrowException() {
+ Long idCustomer = existingCustomer.getId();
+ CustomException exception =
+ assertThrows(CustomException.class, () -> h2CustomerDAO.delete(idCustomer));
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void whenFindAll_thenThrowException() {
+ CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::findAll);
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void whenFindById_thenThrowException() {
+ Long idCustomer = existingCustomer.getId();
+ CustomException exception =
+ assertThrows(CustomException.class, () -> h2CustomerDAO.findById(idCustomer));
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void whenCreateSchema_thenThrowException() {
+ CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::createSchema);
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+
+ @Test
+ void whenDeleteSchema_thenThrowException() {
+ CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::deleteSchema);
+ assertEquals(EXCEPTION_CAUSE, exception.getMessage());
+ }
+ }
+}
diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java
new file mode 100644
index 000000000000..c56e72c30389
--- /dev/null
+++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java
@@ -0,0 +1,163 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.iluwatar.daofactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.mongodb.client.FindIterable;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.result.DeleteResult;
+import com.mongodb.client.result.UpdateResult;
+import java.util.List;
+import java.util.Optional;
+import org.bson.BsonDocument;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.bson.types.ObjectId;
+import org.junit.jupiter.api.Test;
+
+/** Tests {@link MongoCustomerDAO} */
+class MongoCustomerDAOTest {
+ MongoCollection customerCollection = mock(MongoCollection.class);
+ MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection);
+
+ @Test
+ void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() {
+ Customer customer = new Customer<>(new ObjectId(), "John");
+ mongoCustomerDAO.save(customer);
+ verify(customerCollection)
+ .insertOne(
+ argThat(
+ document ->
+ document.get("_id").equals(customer.getId())
+ && document.get("name").equals(customer.getName())));
+ }
+
+ @Test
+ void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
+ ObjectId customerId = new ObjectId();
+ Customer customerUpdated = new Customer<>(customerId, "John");
+ when(customerCollection.updateOne(any(Bson.class), any(Bson.class)))
+ .thenReturn(UpdateResult.acknowledged(1L, 1L, null));
+ mongoCustomerDAO.update(customerUpdated);
+ verify(customerCollection)
+ .updateOne(
+ argThat(
+ (Bson filter) -> {
+ Document filterDoc = (Document) filter;
+ return filterDoc.getObjectId("_id").equals(customerId);
+ }),
+ argThat(
+ (Bson update) -> {
+ BsonDocument bsonDoc = update.toBsonDocument();
+ BsonDocument setDoc = bsonDoc.getDocument("$set");
+ return setDoc.getString("name").getValue().equals(customerUpdated.getName());
+ }));
+ }
+
+ @Test
+ void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() {
+ ObjectId customerId = new ObjectId();
+ when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(1));
+ mongoCustomerDAO.delete(customerId);
+ verify(customerCollection)
+ .deleteOne(
+ argThat(
+ (Bson filter) -> {
+ BsonDocument filterDoc = filter.toBsonDocument();
+ return filterDoc.getObjectId("_id").getValue().equals(customerId);
+ }));
+ }
+
+ @Test
+ void givenIdNotExist_whenDeleteCustomer_thenThrowException() {
+ ObjectId customerId = new ObjectId();
+ when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0));
+ assertThrows(CustomException.class, () -> mongoCustomerDAO.delete(customerId));
+ verify(customerCollection)
+ .deleteOne(
+ argThat(
+ (Bson filter) -> {
+ BsonDocument filterDoc = filter.toBsonDocument();
+ return filterDoc.getObjectId("_id").getValue().equals(customerId);
+ }));
+ }
+
+ @Test
+ void findAll_thenReturnAllCustomers() {
+ FindIterable findIterable = mock(FindIterable.class);
+ MongoCursor cursor = mock(MongoCursor.class);
+ Document customerDoc1 = new Document("_id", new ObjectId()).append("name", "Duc");
+ Document customerDoc2 = new Document("_id", new ObjectId()).append("name", "Thanh");
+ when(customerCollection.find()).thenReturn(findIterable);
+ when(findIterable.iterator()).thenReturn(cursor);
+ when(cursor.hasNext()).thenReturn(true, true, false);
+ when(cursor.next()).thenReturn(customerDoc1, customerDoc2);
+ List> customerList = mongoCustomerDAO.findAll();
+ assertEquals(2, customerList.size());
+ verify(customerCollection).find();
+ }
+
+ @Test
+ void givenValidId_whenFindById_thenReturnCustomer() {
+ FindIterable findIterable = mock(FindIterable.class);
+ ObjectId customerId = new ObjectId();
+ String customerName = "Duc";
+ Document customerDoc = new Document("_id", customerId).append("name", customerName);
+ when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
+ when(findIterable.first()).thenReturn(customerDoc);
+
+ Optional> customer = mongoCustomerDAO.findById(customerId);
+ assertTrue(customer.isPresent());
+ assertEquals(customerId, customer.get().getId());
+ assertEquals(customerName, customer.get().getName());
+ }
+
+ @Test
+ void givenNotExistingId_whenFindById_thenReturnEmpty() {
+ FindIterable findIterable = mock(FindIterable.class);
+ ObjectId customerId = new ObjectId();
+ when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
+ when(findIterable.first()).thenReturn(null);
+ Optional> customer = mongoCustomerDAO.findById(customerId);
+ assertTrue(customer.isEmpty());
+ verify(customerCollection).find(Filters.eq("_id", customerId));
+ }
+
+ @Test
+ void whenDeleteSchema_thenDeleteCollection() {
+ mongoCustomerDAO.deleteSchema();
+ verify(customerCollection).drop();
+ }
+}
diff --git a/data-access-object/README.md b/data-access-object/README.md
index bd020252d253..7e84299e23e1 100644
--- a/data-access-object/README.md
+++ b/data-access-object/README.md
@@ -199,10 +199,6 @@ The program output:
10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1
```
-## Detailed Explanation of Data Access Object Pattern with Real-World Examples
-
-
-
## When to Use the Data Access Object Pattern in Java
Use the Data Access Object in any of the following situations:
diff --git a/data-locality/README.md b/data-locality/README.md
index e9f556b8ad5c..a59103b3f89b 100644
--- a/data-locality/README.md
+++ b/data-locality/README.md
@@ -128,10 +128,6 @@ The console output:
In this way, the data-locality module demonstrates the Data Locality pattern. By updating all components of the same type together, it increases the likelihood that the data needed for the update is already in the cache, thereby improving performance.
-## Detailed Explanation of Data Locality Pattern with Real-World Examples
-
-
-
## When to Use the Data Locality Pattern in Java
This pattern is applicable in scenarios where large datasets are processed and performance is critical. It's particularly useful in:
diff --git a/dependency-injection/README.md b/dependency-injection/README.md
index c3fc2c15a977..c9a2848bdfde 100644
--- a/dependency-injection/README.md
+++ b/dependency-injection/README.md
@@ -118,10 +118,6 @@ The program output:
11:54:05.308 [main] INFO com.iluwatar.dependency.injection.Tobacco -- GuiceWizard smoking RivendellTobacco
```
-## Detailed Explanation of Dependency Injection Pattern with Real-World Examples
-
-
-
## When to Use the Dependency Injection Pattern in Java
* When aiming to reduce the coupling between classes and increase the modularity of the application.
diff --git a/dynamic-proxy/pom.xml b/dynamic-proxy/pom.xml
index 236723c52682..decbb24fd02d 100644
--- a/dynamic-proxy/pom.xml
+++ b/dynamic-proxy/pom.xml
@@ -46,7 +46,7 @@
com.fasterxml.jackson.core
jackson-core
- 2.18.2
+ 2.19.0
com.fasterxml.jackson.core
@@ -56,7 +56,7 @@
org.springframework
spring-web
- 7.0.0-M3
+ 7.0.0-M4
org.junit.jupiter
diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml
index 4cfd05d7adac..5660054d8195 100644
--- a/event-sourcing/pom.xml
+++ b/event-sourcing/pom.xml
@@ -50,7 +50,7 @@
com.fasterxml.jackson.core
jackson-core
- 2.18.2
+ 2.19.0
com.fasterxml.jackson.core
diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java
index 28b039cc7738..88ff5c55fa56 100644
--- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java
+++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java
@@ -1,3 +1,27 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
package com.iluwatar.leaderfollowers;
import java.security.SecureRandom;
diff --git a/localization/fa/abstract-document/README.md b/localization/fa/abstract-document/README.md
new file mode 100644
index 000000000000..7097ffc8b4ea
--- /dev/null
+++ b/localization/fa/abstract-document/README.md
@@ -0,0 +1,243 @@
+---
+title: "الگوی Abstract Document در جاوا: سادهسازی مدیریت داده با انعطافپذیری"
+shortTitle: Abstract Document
+description: "الگوی طراحی Abstract Document در جاوا را بررسی کنید. با هدف، توضیح، کاربرد، مزایا و نمونههای دنیای واقعی برای پیادهسازی ساختارهای دادهای پویا و انعطافپذیر آشنا شوید."
+category: Structural
+language: fa
+tag:
+ - Abstraction
+ - Decoupling
+ - Dynamic typing
+ - Encapsulation
+ - Extensibility
+ - Polymorphism
+---
+
+## هدف الگوی طراحی Abstract Document
+
+الگوی طراحی Abstract Document در جاوا یک الگوی طراحی ساختاری مهم است که راهی یکپارچه برای مدیریت ساختارهای دادهای سلسلهمراتبی و درختی فراهم میکند، با تعریف یک واسط مشترک برای انواع مختلف اسناد. این الگو ساختار اصلی سند را از فرمتهای خاص داده جدا میکند، که باعث بهروزرسانی پویا و نگهداری سادهتر میشود.
+
+## توضیح دقیق الگوی Abstract Document با نمونههای دنیای واقعی
+
+الگوی طراحی Abstract Document در جاوا امکان مدیریت پویا ویژگیهای پویا(غیر استاتیک) را فراهم میکند. این الگو از مفهوم traits استفاده میکند تا ایمنی نوعداده (type safety) را فراهم کرده و ویژگیهای کلاسهای مختلف را به مجموعهای از واسطها تفکیک کند.
+
+مثال دنیای واقعی
+
+> فرض کنید یک سیستم کتابخانه از الگوی Abstract Document در جاوا استفاده میکند، جایی که کتابها میتوانند فرمتها و ویژگیهای متنوعی داشته باشند: کتابهای فیزیکی، کتابهای الکترونیکی، و کتابهای صوتی. هر فرمت ویژگیهای خاص خود را دارد، مانند تعداد صفحات برای کتابهای فیزیکی، حجم فایل برای کتابهای الکترونیکی، و مدتزمان برای کتابهای صوتی. الگوی Abstract Document به سیستم کتابخانه اجازه میدهد تا این فرمتهای متنوع را بهصورت انعطافپذیر مدیریت کند. با استفاده از این الگو، سیستم میتواند ویژگیها را بهصورت پویا ذخیره و بازیابی کند، بدون نیاز به ساختار سفت و سخت برای هر نوع کتاب، و این کار افزودن فرمتها یا ویژگیهای جدید را در آینده بدون تغییرات عمده در کد آسان میسازد.
+
+به زبان ساده
+
+> الگوی Abstract Document اجازه میدهد ویژگیهایی به اشیاء متصل شوند بدون اینکه خود آن اشیاء از آن اطلاع داشته باشند.
+
+ویکیپدیا میگوید
+
+> یک الگوی طراحی ساختاری شیءگرا برای سازماندهی اشیاء در کلید-مقدارهایی با تایپ آزاد و ارائه دادهها از طریق نمای تایپ است. هدف این الگو دستیابی به انعطافپذیری بالا بین اجزا در یک زبان strongly typed است که در آن بتوان ویژگیهای جدیدی را بهصورت پویا به ساختار درختی اشیاء اضافه کرد، بدون از دست دادن پشتیبانی از type safety. این الگو از traits برای جداسازی ویژگیهای مختلف یک کلاس در اینترفیسهای متفاوت استفاده میکند.
+
+نمودار کلاس
+
+
+
+## مثال برنامهنویسی از الگوی Abstract Document در جاوا
+
+فرض کنید یک خودرو داریم که از قطعات مختلفی تشکیل شده است. اما نمیدانیم آیا این خودرو خاص واقعاً همه قطعات را دارد یا فقط برخی از آنها. خودروهای ما پویا و بسیار انعطافپذیر هستند.
+
+بیایید ابتدا کلاسهای پایه `Document` و `AbstractDocument` را تعریف کنیم. این کلاسها اساساً یک شیء را قادر میسازند تا یک نقشه از ویژگیها و هر تعداد شیء فرزند را نگه دارد.
+
+```java
+public interface Document {
+
+ Void put(String key, Object value);
+
+ Object get(String key);
+
+ Stream children(String key, Function, T> constructor);
+}
+
+public abstract class AbstractDocument implements Document {
+
+ private final Map properties;
+
+ protected AbstractDocument(Map properties) {
+ Objects.requireNonNull(properties, "properties map is required");
+ this.properties = properties;
+ }
+
+ @Override
+ public Void put(String key, Object value) {
+ properties.put(key, value);
+ return null;
+ }
+
+ @Override
+ public Object get(String key) {
+ return properties.get(key);
+ }
+
+ @Override
+ public Stream children(String key, Function, T> constructor) {
+ return Stream.ofNullable(get(key))
+ .filter(Objects::nonNull)
+ .map(el -> (List>) el)
+ .findAny()
+ .stream()
+ .flatMap(Collection::stream)
+ .map(constructor);
+ }
+
+ // Other properties and methods...
+}
+```
+در ادامه، یک enum به نام Property و مجموعهای از واسطها برای type، price، model و parts تعریف میکنیم. این کار به ما اجازه میدهد یک واسط با ظاهر استاتیک برای کلاس Car ایجاد کنیم.
+```java
+public enum Property {
+
+ PARTS, TYPE, PRICE, MODEL
+}
+
+public interface HasType extends Document {
+
+ default Optional getType() {
+ return Optional.ofNullable((String) get(Property.TYPE.toString()));
+ }
+}
+
+public interface HasPrice extends Document {
+
+ default Optional getPrice() {
+ return Optional.ofNullable((Number) get(Property.PRICE.toString()));
+ }
+}
+
+public interface HasModel extends Document {
+
+ default Optional getModel() {
+ return Optional.ofNullable((String) get(Property.MODEL.toString()));
+ }
+}
+
+public interface HasParts extends Document {
+
+ default Stream getParts() {
+ return children(Property.PARTS.toString(), Part::new);
+ }
+}
+
+public class Part extends AbstractDocument implements HasType, HasModel, HasPrice {
+
+ public Part(Map properties) {
+ super(properties);
+ }
+}
+```
+اکنون آمادهایم تا کلاس Car را معرفی کنیم.
+```java
+public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts {
+
+ public Car(Map properties) {
+ super(properties);
+ }
+}
+```
+و در نهایت، نحوه ساخت و استفاده از Car را در یک مثال کامل میبینید.
+```java
+ public static void main(String[] args) {
+ LOGGER.info("Constructing parts and car");
+
+ var wheelProperties = Map.of(
+ Property.TYPE.toString(), "wheel",
+ Property.MODEL.toString(), "15C",
+ Property.PRICE.toString(), 100L);
+
+ var doorProperties = Map.of(
+ Property.TYPE.toString(), "door",
+ Property.MODEL.toString(), "Lambo",
+ Property.PRICE.toString(), 300L);
+
+ var carProperties = Map.of(
+ Property.MODEL.toString(), "300SL",
+ Property.PRICE.toString(), 10000L,
+ Property.PARTS.toString(), List.of(wheelProperties, doorProperties));
+
+ var car = new Car(carProperties);
+
+ LOGGER.info("Here is our car:");
+ LOGGER.info("-> model: {}", car.getModel().orElseThrow());
+ LOGGER.info("-> price: {}", car.getPrice().orElseThrow());
+ LOGGER.info("-> parts: ");
+ car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}",
+ p.getType().orElse(null),
+ p.getModel().orElse(null),
+ p.getPrice().orElse(null))
+ );
+}
+```
+خروجی برنامه:
+```
+07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car
+07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car:
+07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> model: 300SL
+07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> price: 10000
+07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> parts:
+07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100
+07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300
+```
+
+ ### چه زمانی از الگوی Abstract Document در جاوا استفاده کنیم؟
+
+الگوی طراحی Abstract Document بهویژه در سناریوهایی مفید است که نیاز به مدیریت انواع مختلفی از اسناد در جاوا وجود دارد که برخی ویژگیها یا رفتارهای مشترک دارند، ولی ویژگیها یا رفتارهای خاص خود را نیز دارند. در ادامه چند سناریوی مناسب برای این الگو آورده شده است:
+
+* سیستمهای مدیریت محتوا (CMS): ممکن است انواع مختلفی از محتوا مانند مقاله، تصویر، ویدئو و... وجود داشته باشد. هر نوع محتوا ویژگیهای مشترکی مثل تاریخ ایجاد، نویسنده و تگها دارد، ولی همچنین ویژگیهای خاصی مثل ابعاد تصویر یا مدتزمان ویدئو.
+
+* سیستمهای فایل: اگر یک سیستم فایل طراحی میکنید که باید انواع مختلف فایل مانند اسناد، تصاویر، فایلهای صوتی و دایرکتوریها را مدیریت کند، این الگو میتواند راهی یکپارچه برای دسترسی به ویژگیهایی مانند اندازه فایل یا تاریخ ایجاد، فراهم کند و در عین حال ویژگیهای خاص هر نوع فایل را هم مدیریت کند.
+
+* سیستمهای تجارت الکترونیک: یک پلتفرم فروش آنلاین ممکن است محصولات مختلفی داشته باشد مثل محصولات فیزیکی، فایلهای دیجیتال، و اشتراکها. این محصولات ویژگیهایی مثل نام، قیمت و توضیح را به اشتراک میگذارند، ولی ویژگیهای خاصی هم دارند مانند وزن حمل برای محصولات فیزیکی یا لینک دانلود برای دیجیتالها.
+
+* سیستمهای سوابق پزشکی: در مراقبت سلامت، پرونده بیماران ممکن است دادههای مختلفی مثل مشخصات فردی، سوابق پزشکی، نتایج آزمایشها و نسخهها را شامل شود. این الگو میتواند ویژگیهای مشترک مثل شماره بیمار یا تاریخ تولد را مدیریت کند و همزمان ویژگیهای خاصی مثل نتایج آزمایش یا داروهای تجویزی را هم پوشش دهد.
+
+* مدیریت پیکربندی: هنگام کار با تنظیمات پیکربندی نرمافزار، ممکن است انواع مختلفی از عناصر پیکربندی وجود داشته باشد، هر یک با ویژگیهای خاص خود. این الگو میتواند برای مدیریت این عناصر مفید باشد.
+
+* پلتفرمهای آموزشی: سیستمهای آموزشی ممکن است انواع مختلفی از منابع یادگیری داشته باشند مثل محتوای متنی، ویدیوها، آزمونها و تمرینها. ویژگیهای مشترکی مثل عنوان، نویسنده و تاریخ انتشار وجود دارد، ولی ویژگیهای خاصی مانند مدت ویدیو یا مهلت تحویل تمرین نیز ممکن است وجود داشته باشد.
+
+* ابزارهای مدیریت پروژه: در برنامههای مدیریت پروژه، ممکن است انواع مختلفی از وظایف مانند آیتمهای to-do، milestoneها و issueها داشته باشید. این الگو میتواند برای مدیریت ویژگیهای عمومی مانند نام وظیفه و مسئول آن استفاده شود و در عین حال ویژگیهای خاص مانند تاریخ milestone یا اولویت issue را نیز پوشش دهد.
+
+* اسناد ساختار ویژگیهای متنوع و در حال تحول دارند.
+
+* افزودن ویژگیهای جدید بهصورت پویا یک نیاز رایج است.
+
+* جداسازی دسترسی به داده از فرمتهای خاص حیاتی است.
+
+* نگهداریپذیری و انعطافپذیری برای کد اهمیت دارد.
+
+ایده اصلی پشت الگوی Abstract Document فراهم کردن روشی انعطافپذیر و قابل توسعه برای مدیریت انواع مختلف اسناد یا موجودیتها با ویژگیهای مشترک و خاص است. با تعریف یک واسط مشترک و پیادهسازی آن در انواع مختلف اسناد، میتوان به شیوهای منظم و یکپارچه برای مدیریت ساختارهای پیچیده داده دست یافت.
+### مزایا و معایب الگوی Abstract Document
+
+مزایا:
+
+* انعطافپذیری: پشتیبانی از ساختارهای متنوع اسناد و ویژگیها.
+
+* قابلیت توسعه: افزودن ویژگیهای جدید بدون شکستن کد موجود.
+
+* نگهداریپذیری: ارتقاء کد تمیز و قابل تطبیق بهواسطه جداسازی وظایف.
+
+* قابلیت استفاده مجدد: نمای دید تایپشده باعث استفاده مجدد از کد برای دسترسی به نوع خاصی از ویژگی میشود.
+
+معایب:
+
+* پیچیدگی: نیاز به تعریف واسطها و نماها، که باعث اضافه شدن سربار پیادهسازی میشود.
+
+* کارایی: ممکن است سربار کمی نسبت به دسترسی مستقیم به داده داشته باشد.
+
+
+منابع و اعتبارها
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+
+* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525)
+
+* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)] (https://amzn.to/49zRP4R)
+
+* [Patterns of Enterprise Application Architecture] (https://amzn.to/3WfKBPR)
+
+* [Abstract Document Pattern (Wikipedia)] (https://en.wikipedia.org/wiki/Abstract_Document_Pattern)
+
+* [Dealing with Properties (Martin Fowler)] (http://martinfowler.com/apsupp/properties.pdf)
diff --git a/localization/fa/abstract-document/etc/abstract-document.png b/localization/fa/abstract-document/etc/abstract-document.png
new file mode 100644
index 000000000000..6bc0b29a4e77
Binary files /dev/null and b/localization/fa/abstract-document/etc/abstract-document.png differ
diff --git a/localization/fa/abstract-factory/README.md b/localization/fa/abstract-factory/README.md
new file mode 100644
index 000000000000..85dce3437ba4
--- /dev/null
+++ b/localization/fa/abstract-factory/README.md
@@ -0,0 +1,223 @@
+---
+title: "الگوی طراحی Abstract Factory در جاوا: مهارت در ایجاد شیء با ظرافت"
+shortTitle: Abstract Factory
+description: "با مثالهای دنیای واقعی، دیاگرامهای کلاس و آموزشها، الگوی Abstract Factory را در جاوا بیاموزید. منظور، کاربرد، مزایا و نمونههای واقعی آن را درک کنید و دانش طراحی الگوهایتان را افزایش دهید."
+category: Creational
+language: fa
+tag:
+ - Abstraction
+ - Decoupling
+ - Gang of Four
+ - Instantiation
+ - Polymorphism
+---
+
+## همچنین به این عنوان شناخته میشود
+
+* کیت
+
+## هدف از الگوی طراحی Abstract Factory
+
+الگوی Abstract Factory در جاوا یک واسط برای ایجاد خانوادههایی از اشیای مرتبط یا وابسته فراهم میکند بدون آنکه کلاسهای مشخص آنها را تعیین کند، و این کار موجب افزایش مدولاریتی و انعطافپذیری در طراحی نرمافزار میشود.
+
+## توضیح دقیق الگوی Abstract Factory با مثالهای دنیای واقعی
+
+مثال دنیای واقعی
+
+> تصور کنید یک شرکت مبلمان وجود دارد که از الگوی Abstract Factory در جاوا برای تولید سبکهای مختلف مبلمان استفاده میکند: مدرن، ویکتوریایی و روستایی. هر سبک شامل محصولاتی مانند صندلیها، میزها و کاناپهها است. برای اطمینان از یکنواختی در هر سبک، شرکت از یک الگوی Abstract Factory استفاده میکند.
+>
+> در این سناریو، Abstract Factory یک واسط برای ایجاد خانوادههایی از اشیای مبلمان مرتبط (صندلیها، میزها، کاناپهها) است. هر Factory مشخص (کارخانهی مبلمان مدرن، کارخانهی مبلمان ویکتوریایی، کارخانهی مبلمان روستایی) این واسط را پیادهسازی میکند و مجموعهای از محصولات مطابق با سبک خاص ایجاد میکند. به این ترتیب، مشتریان میتوانند یک مجموعه کامل از مبلمان مدرن یا ویکتوریایی ایجاد کنند بدون اینکه نگران جزئیات ساخت آنها باشند. این باعث حفظ یکنواختی سبک میشود و امکان تغییر آسان سبک مبلمان را فراهم میکند.
+
+به زبان ساده
+
+> کارخانهای از کارخانهها؛ یک Factory یا کارخانه که مجموعهای از کارخانههای مرتبط یا وابسته را بدون مشخص کردن کلاسهای concrete آنها گروهبندی میکند.
+
+ویکیپدیا میگوید
+
+> الگوی Abstract Factory راهی برای کپسوله کردن مجموعهای از کارخانههای منحصر به فرد با یک تم مشترک بدون تعیین کلاسهای concrete آنها فراهم میکند.
+
+دیاگرام کلاس
+
+
+
+## مثال برنامهنویسی از Abstract Factory در جاوا
+
+برای ایجاد یک پادشاهی با استفاده از الگوی Abstract Factory در جاوا، ما به اشیایی با یک تم مشترک نیاز داریم. یک پادشاهی اِلف (Elf) به یک پادشاه اِلف، یک قلعهی اِلف، و یک ارتش اِلف نیاز دارد، در حالی که یک پادشاهی اورک (Orc) به یک پادشاه اورک، یک قلعهی اورک، و یک ارتش اورک نیاز دارد. بین اشیای موجود در پادشاهی وابستگی وجود دارد.
+
+ترجمهی مثال پادشاهی بالا. ابتدا ما برخی واسطها و پیادهسازیهایی برای اشیای موجود در پادشاهی داریم:
+
+```java
+public interface Castle {
+ String getDescription();
+}
+
+public interface King {
+ String getDescription();
+}
+
+public interface Army {
+ String getDescription();
+}
+
+// Elven implementations ->
+public class ElfCastle implements Castle {
+ static final String DESCRIPTION = "This is the elven castle!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+public class ElfKing implements King {
+ static final String DESCRIPTION = "This is the elven king!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+public class ElfArmy implements Army {
+ static final String DESCRIPTION = "This is the elven Army!";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+
+// Orcish implementations similarly -> ...
+```
+
+سپس واسط و پیادهسازیهای کارخانهی پادشاهی را داریم:
+
+```java
+public interface KingdomFactory {
+ Castle createCastle();
+ King createKing();
+ Army createArmy();
+}
+
+public class ElfKingdomFactory implements KingdomFactory {
+
+ @Override
+ public Castle createCastle() {
+ return new ElfCastle();
+ }
+
+ @Override
+ public King createKing() {
+ return new ElfKing();
+ }
+
+ @Override
+ public Army createArmy() {
+ return new ElfArmy();
+ }
+}
+
+// Orcish implementations similarly -> ...
+```
+
+اکنون میتوانیم یک کارخانه برای کارخانههای مختلف پادشاهی طراحی کنیم. در این مثال، ما `FactoryMaker` را ایجاد کردیم که مسئول برگرداندن یک نمونه از `ElfKingdomFactory` یا `OrcKingdomFactory` است. مشتری می تواند از `FactoryMaker` برای ایجاد کارخانه concrete مورد نظر استفاده کند که به نوبه خود اشیاء concrete مختلف (مشتق شده از ارتش، پادشاه، قلعه) را تولید میکند. در این مثال، ما همچنین از یک enum برای پارامتری کردن نوع کارخانه پادشاهی که مشتری درخواست خواهد کرد استفاده کردیم.
+
+```java
+public static class FactoryMaker {
+
+ public enum KingdomType {
+ ELF, ORC
+ }
+
+ public static KingdomFactory makeFactory(KingdomType type) {
+ return switch (type) {
+ case ELF -> new ElfKingdomFactory();
+ case ORC -> new OrcKingdomFactory();
+ };
+ }
+}
+```
+
+نمونهای از تابع اصلی برنامه:
+
+```java
+LOGGER.info("elf kingdom");
+createKingdom(Kingdom.FactoryMaker.KingdomType.ELF);
+LOGGER.info(kingdom.getArmy().getDescription());
+LOGGER.info(kingdom.getCastle().getDescription());
+LOGGER.info(kingdom.getKing().getDescription());
+
+LOGGER.info("orc kingdom");
+createKingdom(Kingdom.FactoryMaker.KingdomType.ORC);
+LOGGER.info(kingdom.getArmy().getDescription());
+LOGGER.info(kingdom.getCastle().getDescription());
+LOGGER.info(kingdom.getKing().getDescription());
+```
+
+خروجی برنامه:
+
+```
+07:35:46.340 [main] INFO com.iluwatar.abstractfactory.App -- elf kingdom
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven army!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven castle!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the elven king!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- orc kingdom
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc army!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc castle!
+07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king!
+```
+
+## چه زمانی باید از الگوی Abstract Factory در جاوا استفاده کرد؟
+
+* زمانی که سیستم باید مستقل از نحوهی ایجاد، ترکیب و نمایش محصولاتش باشد.
+* زمانی که نیاز به پیکربندی سیستم با یکی از چند خانواده محصول دارید.
+* زمانی که باید خانوادهای از اشیای مرتبط با هم استفاده شوند، برای اطمینان از یکنواختی.
+* زمانی که میخواهید کتابخانهای از محصولات را فراهم کنید و فقط واسطهای آنها را نمایان کنید، نه پیادهسازیها را.
+* زمانی که طول عمر وابستگیها کوتاهتر از مصرفکننده باشد.
+* زمانی که نیاز به ساخت وابستگیها با مقادیر یا پارامترهای زمان اجرا باشد.
+* زمانی که باید در زمان اجرا انتخاب کنید که کدام خانواده از محصول را استفاده کنید.
+* زمانی که افزودن محصولات یا خانواده های جدید نباید نیاز به تغییر در کد موجود داشته باشد.
+
+## آموزشهای الگوی Abstract Factory در جاوا
+
+* [Abstract Factory Design Pattern in Java (DigitalOcean)](https://www.digitalocean.com/community/tutorials/abstract-factory-design-pattern-in-java)
+* [Abstract Factory (Refactoring Guru)](https://refactoring.guru/design-patterns/abstract-factory)
+
+## مزایا و معایب الگوی Abstract Factory
+
+مزایا:
+
+> * انعطافپذیری: به راحتی میتوان خانوادههای محصول را تعویض کرد بدون تغییر کد.
+
+> * جداسازی (Decoupling): کد مشتری فقط با واسطهای انتزاعی کار میکند که باعث قابلیت حمل و نگهداری میشود.
+
+> * قابلیت استفاده مجدد: کارخانههای انتزاعی و محصولات امکان استفاده مجدد از مؤلفهها را فراهم میکنند.
+
+> * قابلیت نگهداری: تغییرات در خانوادههای محصول محلی شده و بهروزرسانی را سادهتر میکند.
+
+معایب:
+
+> * پیچیدگی: تعریف واسطهای انتزاعی و کارخانههای مشخص سربار اولیه ایجاد میکند.
+
+> * غیرمستقیم بودن: کد مشتری از طریق کارخانهها با محصولات کار میکند که ممکن است شفافیت را کاهش دهد.
+
+## نمونههای واقعی استفاده از الگوی Abstract Factory در جاوا
+
+* کلاسهای `LookAndFeel` در Java Swing برای ارائه گزینه های مختلف look-and-feel
+* پیادهسازیهای مختلف در Java AWT برای ایجاد اجزای مختلف GUI
+* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html)
+* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--)
+* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--)
+
+## الگوهای طراحی مرتبط با جاوا
+
+* الگوی [Factory Method](https://java-design-patterns.com/patterns/factory-method/): الگوی کارخانهی انتزاعی از روشهای کارخانهای برای ایجاد محصولات استفاده میکند.
+* الگوی [Singleton](https://java-design-patterns.com/patterns/singleton/): کلاسهای کارخانهی انتزاعی اغلب به صورت Singleton پیادهسازی میشوند.
+* الگوی [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): مشابه کارخانهی انتزاعی اما بر پیکربندی و مدیریت مجموعهای از اشیای مرتبط تمرکز دارد.
+
+## منابع و ارجاعات
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI)
+* [Design Patterns in Java](https://amzn.to/3Syw0vC)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq)
+* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U)
diff --git a/localization/fa/abstract-factory/etc/abstract-factory.urm.png b/localization/fa/abstract-factory/etc/abstract-factory.urm.png
new file mode 100644
index 000000000000..836858a2c652
Binary files /dev/null and b/localization/fa/abstract-factory/etc/abstract-factory.urm.png differ
diff --git a/localization/fa/active-object/README.md b/localization/fa/active-object/README.md
new file mode 100644
index 000000000000..7c105b072aaa
--- /dev/null
+++ b/localization/fa/active-object/README.md
@@ -0,0 +1,220 @@
+---
+title: "الگوی Active Object در جاوا: دستیابی به پردازش ناهمگام کارآمد"
+shortTitle: Active Object
+description: "با الگوی طراحی Active Object در جاوا آشنا شوید. این راهنما رفتار ناهمگام، همزمانی (concurrency) و مثالهای کاربردی برای بهبود عملکرد برنامههای جاوای شما را پوشش میدهد."
+category: Concurrency
+language: fa
+tag:
+ - Asynchronous
+ - Decoupling
+ - Messaging
+ - Synchronization
+ - Thread management
+---
+
+## هدف الگوی طراحی Active Object
+
+الگوی Active Object روشی مطمئن برای پردازش ناهمگام در جاوا فراهم میکند که به پاسخگو بودن برنامهها و مدیریت مؤثر threadها کمک میکند. این الگو با محصور کردن وظایف در شیءهایی که هر کدام thread و صف پیام مخصوص خود را دارند، به این هدف میرسد. این جداسازی باعث میشود thread اصلی پاسخگو باقی بماند و مشکلاتی مانند دستکاری مستقیم threadها یا دسترسی به وضعیت مشترک (shared state) به وجود نیاید.
+
+## توضیح کامل الگوی Active Object با مثالهای دنیای واقعی
+
+مثال دنیای واقعی
+
+> تصور کنید در یک رستوران شلوغ، مشتریان سفارش خود را به گارسونها میسپارند. بهجای آنکه گارسونها خودشان به آشپزخانه بروند و غذا را آماده کنند، سفارشها را روی کاغذهایی مینویسند و به یک هماهنگکننده میدهند. این هماهنگکننده گروهی از سرآشپزها را مدیریت میکند که غذاها را به صورت ناهمگام آماده میکنند. هرگاه آشپزی آزاد شود، سفارش بعدی را از صف برمیدارد، غذا را آماده میکند و پس از آن گارسون را برای سرو غذا مطلع میسازد.
+>
+> در این قیاس، گارسونها نماینده threadهای کلاینت هستند، هماهنگکننده نقش زمانبند (scheduler) را ایفا میکند، و آشپزها نمایانگر اجرای متدها در threadهای جداگانه هستند. این ساختار باعث میشود گارسونها بتوانند بدون مسدود شدن توسط فرایند آمادهسازی غذا، سفارشهای بیشتری دریافت کنند—درست مانند اینکه الگوی Active Object، فراخوانی متد را از اجرای آن جدا میکند تا همزمانی (concurrency) را افزایش دهد.
+
+به زبان ساده
+
+> الگوی Active Object، اجرای متد را از فراخوانی آن جدا میکند تا در برنامههای چندریسمانی (multithreaded)، همزمانی و پاسخگویی بهتری فراهم شود.
+
+طبق تعریف ویکیپدیا
+
+> الگوی طراحی Active Object اجرای متد را از فراخوانی آن جدا میکند، برای شیءهایی که هرکدام thread کنترل مخصوص به خود را دارند. هدف، معرفی همزمانی با استفاده از فراخوانی متد بهصورت ناهمگام و یک زمانبند برای مدیریت درخواستها است.
+>
+> این الگو شامل شش جزء کلیدی است:
+>
+> * یک proxy، که رابطی برای کلاینتها با متدهای عمومی فراهم میکند.
+> * یک interface که درخواست متد برای شیء فعال (active object) را تعریف میکند.
+> * فهرستی از درخواستهای معلق از سوی کلاینتها.
+> * یک زمانبند (scheduler) که تصمیم میگیرد کدام درخواست بعدی اجرا شود.
+> * پیادهسازی متد شیء فعال.
+> * یک callback یا متغیر برای اینکه کلاینت نتیجه را دریافت کند.
+
+نمودار توالی
+
+
+
+## مثال برنامهنویسی از Active Object در جاوا
+
+این بخش نحوه عملکرد الگوی Active Object در جاوا را توضیح میدهد و کاربرد آن در مدیریت وظایف ناهمگام و کنترل همزمانی را نشان میدهد.
+
+اورکها به دلیل ذات وحشی و غیرقابل مهارشان شناخته میشوند. بهنظر میرسد هرکدام thread کنترل مخصوص خود را دارند. برای پیادهسازی یک موجود که دارای سازوکار thread مستقل خود باشد و فقط API را در اختیار قرار دهد نه اجرای داخلی را، میتوان از الگوی Active Object استفاده کرد.
+
+```java
+public abstract class ActiveCreature {
+ private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
+
+ private BlockingQueue requests;
+
+ private String name;
+
+ private Thread thread;
+
+ public ActiveCreature(String name) {
+ this.name = name;
+ this.requests = new LinkedBlockingQueue();
+ thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ try {
+ requests.take().run();
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ }
+ }
+ }
+ }
+ );
+ thread.start();
+ }
+
+ public void eat() throws InterruptedException {
+ requests.put(new Runnable() {
+ @Override
+ public void run() {
+ logger.info("{} is eating!", name());
+ logger.info("{} has finished eating!", name());
+ }
+ }
+ );
+ }
+
+ public void roam() throws InterruptedException {
+ requests.put(new Runnable() {
+ @Override
+ public void run() {
+ logger.info("{} has started to roam the wastelands.", name());
+ }
+ }
+ );
+ }
+
+ public String name() {
+ return this.name;
+ }
+}
+```
+
+میتوان دید هر کلاسی که از ActiveCreature ارثبری کند، دارای thread کنترل مختص به خود برای فراخوانی و اجرای متدها خواهد بود.
+
+برای مثال، کلاس Orc:
+
+```java
+public class Orc extends ActiveCreature {
+
+ public Orc(String name) {
+ super(name);
+ }
+}
+```
+اکنون میتوان چند موجود مانند orc ایجاد کرد، به آنها دستور داد که بخورند و پرسه بزنند، و آنها این دستورات را در thread مختص به خود اجرا میکنند:
+
+```java
+public class App implements Runnable {
+
+ private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
+
+ private static final int NUM_CREATURES = 3;
+
+ public static void main(String[] args) {
+ var app = new App();
+ app.run();
+ }
+
+ @Override
+ public void run() {
+ List creatures = new ArrayList<>();
+ try {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.add(new Orc(Orc.class.getSimpleName() + i));
+ creatures.get(i).eat();
+ creatures.get(i).roam();
+ }
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ logger.error(e.getMessage());
+ Thread.currentThread().interrupt();
+ } finally {
+ for (int i = 0; i < NUM_CREATURES; i++) {
+ creatures.get(i).kill(0);
+ }
+ }
+ }
+}
+```
+
+خروجی برنامه:
+
+```
+09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating!
+09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating!
+09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating!
+09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating!
+09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating!
+09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands.
+09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating!
+09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands.
+09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands.
+```
+
+چه زمانی از الگوی Active Object در جاوا استفاده کنیم؟
+
+از الگوی Active Object در جاوا استفاده کنید زمانی که:
+> * نیاز دارید وظایف ناهمگام را بدون مسدود کردن thread اصلی مدیریت کنید تا عملکرد و پاسخگویی بهتری داشته باشید.
+> * نیاز به تعامل ناهمگام با منابع خارجی دارید.
+> * میخواهید پاسخگویی برنامه را افزایش دهید.
+> * نیاز به مدیریت وظایف همزمان بهصورت ماژولار و قابل نگهداری دارید.
+
+آموزشهای Java برای الگوی Active Object
+> [Android and Java Concurrency: The Active Object Pattern (Douglas Schmidt)]((https://www.youtube.com/watch?v=Cd8t2u5Qmvc))
+
+کاربردهای دنیای واقعی الگوی Active Object در جاوا
+
+> سیستمهای معاملات بلادرنگ که درخواستها بهصورت ناهمگام پردازش میشوند.
+> که در آن وظایف طولانی در پسزمینه اجرا میشوند بدون آنکه رابط کاربری را متوقف کنند.
+> رابطهای کاربری گرافیکی (GUI)
+> برنامهنویسی بازیها برای مدیریت بهروزرسانیهای همزمان وضعیت بازی یا محاسبات هوش مصنوعی.
+
+مزایا و ملاحظات الگوی Active Object
+
+با مزایا و معایب استفاده از الگوی Active Object در جاوا آشنا شوید؛ از جمله بهبود ایمنی threadها و ملاحظات سربار احتمالی (overhead).
+
+> مزایا:
+>
+> * پاسخگویی بهتر thread اصلی.
+> * محصورسازی مسائل مربوط به همزمانی درون شیءها.
+> * بهبود سازماندهی کد و قابلیت نگهداری.
+> * فراهمسازی ایمنی در برابر شرایط بحرانی (thread safety) و جلوگیری از مشکلات وضعیت مشترک.
+
+> معایب:
+>
+> * سربار اضافی به دلیل ارسال پیام و مدیریت threadها.
+> * برای تمام سناریوهای همزمانی مناسب نیست.
+
+الگوهای طراحی مرتبط در جاوا
+
+> * [Command](https://java-design-patterns.com/patterns/command/): درخواست را بهعنوان یک شیء کپسوله میکند، مشابه روشی که Active Object فراخوانی متد را کپسوله میکند.
+> * [Promise](https://java-design-patterns.com/patterns/promise/): راهی برای دریافت نتیجه یک فراخوانی متد ناهمگام فراهم میکند؛ اغلب همراه با Active Object استفاده میشود.
+> * [Proxy](https://java-design-patterns.com/patterns/proxy/): الگوی Active Object میتواند از proxy برای مدیریت فراخوانیهای متد بهصورت ناهمگام استفاده کند.
+
+منابع و مراجع
+
+> * [Design Patterns: Elements of Reusable Object Software](https://amzn.to/3HYqrBE)
+> * [Concurrent Programming in Java: Design Principles and Patterns](https://amzn.to/498SRVq)
+> * [Java Concurrency in Practice](https://amzn.to/4aRMruW)
+> * [Learning Concurrent Programming in Scala](https://amzn.to/3UE07nV)
+> * [Pattern Languages of Program Design 3](https://amzn.to/3OI1j61)
+> * [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V)
+
diff --git a/localization/fa/active-object/etc/active-object-sequence-diagram.png b/localization/fa/active-object/etc/active-object-sequence-diagram.png
new file mode 100644
index 000000000000..b725d9b07b6d
Binary files /dev/null and b/localization/fa/active-object/etc/active-object-sequence-diagram.png differ
diff --git a/localization/fa/active-object/etc/active-object.urm.png b/localization/fa/active-object/etc/active-object.urm.png
new file mode 100644
index 000000000000..c14f66144ee2
Binary files /dev/null and b/localization/fa/active-object/etc/active-object.urm.png differ
diff --git a/localization/fa/active-object/etc/active-object.urm.puml b/localization/fa/active-object/etc/active-object.urm.puml
new file mode 100644
index 000000000000..3fc3c8e1e921
--- /dev/null
+++ b/localization/fa/active-object/etc/active-object.urm.puml
@@ -0,0 +1,25 @@
+@startuml
+package com.iluwatar.activeobject {
+ abstract class ActiveCreature {
+ - logger : Logger
+ - name : String
+ - requests : BlockingQueue
+ - thread : Thread
+ + ActiveCreature(name : String)
+ + eat()
+ + name() : String
+ + roam()
+ }
+ class App {
+ - creatures : Integer
+ - logger : Logger
+ + App()
+ + main(args : String[]) {static}
+ + run()
+ }
+ class Orc {
+ + Orc(name : String)
+ }
+}
+Orc --|> ActiveCreature
+@enduml
\ No newline at end of file
diff --git a/localization/fa/factory/README.md b/localization/fa/factory/README.md
new file mode 100644
index 000000000000..db41813464e3
--- /dev/null
+++ b/localization/fa/factory/README.md
@@ -0,0 +1,155 @@
+---
+title: "الگوی factory در جاوا: سادهسازی ایجاد اشیاء"
+shortTitle: factory
+description: "الگوی طراحی factory در جاوا را با مثالها و توضیحات دقیق بیاموزید. یاد بگیرید چگونه با استفاده از الگوی factory کدی انعطافپذیر و مقیاسپذیر ایجاد کنید. مناسب برای توسعهدهندگانی که به دنبال بهبود مهارتهای طراحی شیءگرا هستند."
+category: structural
+language: fa
+tag:
+ - Abstraction
+ - Encapsulation
+ - Gang of Four
+ - Instantiation
+ - Polymorphism
+---
+
+## هدف از الگوی طراحی factory
+
+الگوی طراحی factory در جاوا یک الگوی ساختاری است که یک رابط برای ایجاد یک شیء تعریف میکند اما به زیرکلاسها اجازه میدهد نوع اشیائی را که ایجاد خواهند شد تغییر دهند. این الگو انعطافپذیری و مقیاسپذیری را در کد شما ترویج میدهد.
+
+## توضیح دقیق الگوی factory با مثالهای دنیای واقعی
+
+### مثال دنیای واقعی
+
+> تصور کنید در یک نانوایی انواع مختلف کیکها با استفاده از الگوی طراحی factory ساخته میشوند. `CakeFactory` فرآیند ایجاد را مدیریت میکند و امکان افزودن آسان انواع جدید کیکها را بدون تغییر در فرآیند اصلی فراهم میکند. `CakeFactory` میتواند انواع مختلفی از کیکها مانند کیک شکلاتی، کیک وانیلی و کیک توتفرنگی تولید کند. به جای اینکه کارکنان نانوایی به صورت دستی مواد اولیه را انتخاب کنند و دستورالعملهای خاصی را برای هر نوع کیک دنبال کنند، از `CakeFactory` برای مدیریت فرآیند استفاده میکنند. مشتری فقط نوع کیک را درخواست میکند و `CakeFactory` مواد اولیه و دستورالعمل مناسب را تعیین کرده و نوع خاصی از کیک را ایجاد میکند. این تنظیم به نانوایی اجازه میدهد تا انواع جدید کیکها را به راحتی اضافه کند بدون اینکه فرآیند اصلی تغییر کند، که این امر انعطافپذیری و مقیاسپذیری را ترویج میدهد.
+
+### تعریف ویکیپدیا
+
+> الگوی factory یک شیء برای ایجاد اشیاء دیگر است – به طور رسمی، factory یک تابع یا متدی است که اشیاء با نمونهها یا کلاسهای مختلف را بازمیگرداند.
+
+### نمودار توالی
+
+
+
+## مثال برنامهنویسی از الگوی factory در جاوا
+
+تصور کنید یک کیمیاگر قصد دارد سکههایی تولید کند. کیمیاگر باید بتواند هم سکههای طلا و هم سکههای مسی ایجاد کند و تغییر بین آنها باید بدون تغییر در کد موجود امکانپذیر باشد. الگوی factory این امکان را فراهم میکند با ارائه یک متد ایجاد استاتیک که میتوان آن را با پارامترهای مرتبط فراخوانی کرد.
+
+در جاوا، میتوانید الگوی factory را با تعریف یک رابط `Coin` و پیادهسازیهای آن `GoldCoin` و `CopperCoin` پیادهسازی کنید. کلاس `CoinFactory` یک متد استاتیک `getCoin` ارائه میدهد تا اشیاء سکه را بر اساس نوع ایجاد کند.
+
+```java
+public interface Coin {
+ String getDescription();
+}
+```
+
+```java
+public class GoldCoin implements Coin {
+
+ static final String DESCRIPTION = "This is a gold coin.";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+```
+
+```java
+public class CopperCoin implements Coin {
+
+ static final String DESCRIPTION = "This is a copper coin.";
+
+ @Override
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+}
+```
+
+کد زیر انواع سکههایی که پشتیبانی میشوند (`GoldCoin` و `CopperCoin`) را نشان میدهد.
+
+```java
+@RequiredArgsConstructor
+@Getter
+public enum CoinType {
+
+ COPPER(CopperCoin::new),
+ GOLD(GoldCoin::new);
+
+ private final Supplier constructor;
+}
+```
+
+سپس متد استاتیک `getCoin` برای ایجاد اشیاء سکه در کلاس factory `CoinFactory` کپسوله شده است.
+
+```java
+public class CoinFactory {
+
+ public static Coin getCoin(CoinType type) {
+ return type.getConstructor().get();
+ }
+}
+```
+
+اکنون، در کد کلاینت، میتوانیم انواع مختلفی از سکهها را با استفاده از کلاس factory تولید کنیم.
+
+```java
+public static void main(String[] args) {
+ LOGGER.info("The alchemist begins his work.");
+ var coin1 = CoinFactory.getCoin(CoinType.COPPER);
+ var coin2 = CoinFactory.getCoin(CoinType.GOLD);
+ LOGGER.info(coin1.getDescription());
+ LOGGER.info(coin2.getDescription());
+}
+```
+
+خروجی برنامه:
+
+```
+06:19:53.530 [main] INFO com.iluwatar.factory.App -- The alchemist begins his work.
+06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a copper coin.
+06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a gold coin.
+```
+
+## زمان استفاده از الگوی factory در جاوا
+
+* از الگوی طراحی factory در جاوا زمانی استفاده کنید که کلاس از قبل نوع دقیق و وابستگیهای اشیائی که نیاز به ایجاد آن دارد را نمیداند.
+* زمانی که یک متد یکی از چندین کلاس ممکن که یک کلاس والد مشترک دارند را بازمیگرداند و میخواهد منطق انتخاب شیء را کپسوله کند.
+* این الگو معمولاً هنگام طراحی فریمورکها یا کتابخانهها برای ارائه بهترین انعطافپذیری و جداسازی از انواع کلاسهای خاص استفاده میشود.
+
+## کاربردهای دنیای واقعی الگوی factory در جاوا
+
+> * [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--)
+> * [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-)
+> * [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--)
+> * [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-)
+> * این مورد [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) اشیاء singleton مختلف را بر اساس یک پروتکل بازمیگرداند
+> * [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E))
+> * [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) و متدهای مشابه دیگر.
+>
+> * کتابخانهی JavaFX از الگوهای factory برای ایجاد کنترلهای مختلف رابط کاربری متناسب با نیازهای محیط کاربر استفاده میکند.
+
+## مزایا و معایب الگوی factory
+
+### مزایا:
+
+> * پیادهسازی الگوی factory در برنامه جاوای شما، وابستگی بین پیادهسازی و کلاسهایی که استفاده میکند را کاهش میدهد.
+> * از [اصل Open/Closed](https://java-design-patterns.com/principles/#open-closed-principle) پشتیبانی میکند، زیرا سیستم میتواند انواع جدیدی را بدون تغییر کد موجود معرفی کند.
+
+### معایب:
+
+> * کد میتواند به دلیل معرفی چندین کلاس اضافی پیچیدهتر شود.
+> * استفاده بیش از حد میتواند کد را کمتر خوانا کند اگر پیچیدگی ایجاد اشیاء کم یا غیرضروری باشد.
+
+## الگوهای طراحی مرتبط با جاوا
+
+> * الگوی [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): میتوان آن را نوعی factory در نظر گرفت که با گروهی از محصولات کار میکند.
+> * الگوی [Singleton](https://java-design-patterns.com/patterns/singleton/): اغلب همراه با factory استفاده میشود تا اطمینان حاصل شود که یک کلاس تنها یک نمونه دارد.
+> * الگوی [Builder](https://java-design-patterns.com/patterns/builder/): ساخت یک شیء پیچیده را از نمایش آن جدا میکند، مشابه نحوهای که factoryها مدیریت نمونهسازی را انجام میدهند.
+> * الگوی [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): یک factory از محتوای غیرقابل تغییر با رابطهای builder و factory جداگانه است.
+
+## منابع و اعتبارات
+
+* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y)
+* [Effective Java](https://amzn.to/4cGk2Jz)
+* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG)
diff --git a/localization/fa/factory/etc/factory-sequence-diagram.png b/localization/fa/factory/etc/factory-sequence-diagram.png
new file mode 100644
index 000000000000..260bea92f247
Binary files /dev/null and b/localization/fa/factory/etc/factory-sequence-diagram.png differ
diff --git a/microservices-self-registration/README.md b/microservices-self-registration/README.md
new file mode 100644
index 000000000000..bfc837817c57
--- /dev/null
+++ b/microservices-self-registration/README.md
@@ -0,0 +1,236 @@
+---
+title: "Microservices Self-Registration Pattern in Java with Spring Boot and Eureka"
+shortTitle: Microservices Pattern - Self-Registration
+description: "Dynamically register and discover Java microservices using Spring Boot and Eureka for resilient, scalable communication."
+category: Service Discovery
+language: en
+tag:
+ - Microservices
+ - Self-Registration
+ - Service Discovery
+ - Eureka
+ - Spring Boot
+ - Spring Cloud
+ - Java
+ - Dynamic Configuration
+ - Resilience
+---
+
+## Intent of Microservices Self-Registration Pattern
+
+The intent of the Self-Registration pattern is to enable microservices to automatically announce their presence and location to a central registry (like Eureka) upon startup, simplifying service discovery and allowing other services to find and communicate with them without manual configuration or hardcoded addresses. This promotes dynamic and resilient microservices architectures.
+
+## What's in the Project
+
+This project demonstrates the Microservices Self-Registration pattern using Java, Spring Boot (version 3.4.4), and Eureka for service discovery. It consists of three main components: a Eureka Server and two simple microservices, a Greeting Service and a Context Service, which discover and communicate with each other.
+
+### Project Structure
+* **`eureka-server`:** The central service registry where microservices register themselves.
+* **`greeting-service`:** A simple microservice that provides a greeting.
+* **`context-service`:** A microservice that consumes the greeting from the Greeting Service and adds context.
+
+ The **Eureka Server** acts as the discovery service. Microservices register themselves with the Eureka Server, providing their network location.
+
+ package com.example.eurekaserver;
+
+ import org.springframework.boot.SpringApplication;
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
+ import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
+
+ @SpringBootApplication
+ @EnableEurekaServer
+ public class EurekaServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(EurekaServerApplication.class, args);
+ }
+ }
+
+ The **Greeting Service** is a simple microservice that exposes an endpoint to retrieve a greeting.
+
+ package com.example.greetingservice;
+
+ import org.springframework.boot.SpringApplication;
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
+ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ public class GreetingServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GreetingServiceApplication.class, args);
+ }
+ }
+
+ Greeting Controller
+
+ package com.example.greetingservice.controller;
+
+ import org.springframework.web.bind.annotation.GetMapping;
+ import org.springframework.web.bind.annotation.RestController;
+
+ @RestController
+ public class GreetingController {
+
+ @GetMapping("/greeting")
+ public String getGreeting() {
+ return "Hello";
+ }
+ }
+
+The **Context Service** consumes the greeting from the Greeting Service using OpenFeign and adds contextual information.
+
+ package com.example.contextservice;
+
+ import org.springframework.boot.SpringApplication;
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
+ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+ import org.springframework.cloud.openfeign.EnableFeignClients;
+
+ @SpringBootApplication
+ @EnableDiscoveryClient
+ @EnableFeignClients
+ public class ContextServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ContextServiceApplication.class, args);
+ }
+ }
+
+ Feign Client : Spring Cloud OpenFeign is a declarative HTTP client that makes it easier to consume RESTful web services in your Spring Cloud applications. Instead of writing the boilerplate code for making HTTP requests, you simply declare interface with annotations that describe the web service you want to consume.
+
+ package com.example.contextservice.client;
+
+ import org.springframework.cloud.openfeign.FeignClient;
+ import org.springframework.web.bind.annotation.GetMapping;
+
+ @FeignClient(name = "greeting-service")
+ public interface GreetingServiceClient {
+
+ @GetMapping("/greeting")
+ String getGreeting();
+ }
+
+ Context Controller
+
+ package com.example.contextservice.controller;
+
+ import com.example.contextservice.client.GreetingServiceClient;
+ import org.springframework.beans.factory.annotation.Autowired;
+ import org.springframework.beans.factory.annotation.Value;
+ import org.springframework.web.bind.annotation.GetMapping;
+ import org.springframework.web.bind.annotation.RestController;
+
+ @RestController
+ public class ContextController {
+
+ @Autowired
+ private GreetingServiceClient greetingServiceClient;
+
+ @Value("${user.region}")
+ private String userRegion;
+
+ @GetMapping("/context")
+ public String getContext() {
+ String greeting = greetingServiceClient.getGreeting();
+ return "The Greeting Service says: " + greeting + " from " + userRegion + "!";
+ }
+ }
+
+ 1. Both the Greeting Service and the Context Service register themselves with the Eureka Server upon startup using the _@EnableDiscoveryClient_ annotation.
+ 2. The Context Service, annotated with _@EnableFeignClients_, uses the GreetingServiceClient interface with _@FeignClient(name = "greeting-service")_ to declare its intent to communicate with the service named "greeting-service" in Eureka.
+ 3. When the /context endpoint of the Context Service is accessed, it calls the _getGreeting()_ method of the GreetingServiceClient.
+ 4. OpenFeign, leveraging the service discovery information from Eureka, resolves the network location of an available instance of the Greeting Service and makes an HTTP GET request to its /greeting endpoint.
+ 5. The Greeting Service responds with "Hello", and the Context Service then adds the configured user.region to the response.
+
+ This project utilizes Spring Boot Actuator, which is included as a dependency, to provide health check endpoints for each microservice. These endpoints (e.g., /actuator/health) can be used by Eureka Server to monitor the health of the registered instances.
+
+## Steps to use for this Project
+
+Prerequisites:
+ - Java Development Kit (JDK): Make sure you have a compatible JDK installed (ideally Java 17 or later, as Spring Boot 3.x requires it).
+ - Maven or Gradle: You'll need either Maven (if you chose Maven during Spring Initializr setup) or Gradle (if you chose Gradle) installed on your system.
+ - An IDE (Optional but Recommended): IntelliJ IDEA, Eclipse, or Spring Tool Suite (STS) can make it easier to work with the project.
+ - Web Browser: You'll need a web browser to access the Eureka dashboard and the microservice endpoints.
+
+Step :
+ - You'll need to build each microservice individually. Navigate to the root directory of each project in your terminal or command prompt and run the appropriate build command:
+ _cd eurekaserver
+ mvn clean install
+ cd ../greetingservice
+ mvn clean install
+ cd ../contextservice
+ mvn clean install_
+Step :
+ - Navigate to the root directory of your eurekaserver project in your terminal or command prompt
+ _mvn spring-boot:run_
+ - Wait for the Eureka Server application to start. You should see logs in the console indicating that it has started on port 8761 (as configured).
+ - Open your web browser and go to http://localhost:8761/. You should see the Eureka Server dashboard. Initially, the list of registered instances will be empty.
+Step :
+ - Run the Greeting Service
+ - Open a new terminal or command prompt.
+ - Navigate to the root directory of your greetingservice project.
+ - Run the Spring Boot application: _mvn spring-boot:run_
+ - Wait for the Greeting Service to start. You should see logs indicating that it has registered with the Eureka Server.
+ - Go back to your Eureka Server dashboard in the browser (http://localhost:8761/). You should now see GREETINGSERVICE listed under the "Instances currently registered with Eureka". Its status should be "UP".
+Step :
+ - Run the Context Service
+ - Open a new terminal or command prompt.
+ - Navigate to the root directory of your contextservice project.
+ - Run the Spring Boot application: _mvn spring-boot:run_
+ - Wait for the Context Service to start. You should see logs indicating that it has registered with the Eureka Server.
+ - Go back to your Eureka Server dashboard in the browser (http://localhost:8761/). You should now see CONTEXTSERVICE listed under the "Instances currently registered with Eureka". Its status should be "UP".
+STEP :
+ - Test the Greeting Service Directly: Open your web browser and go to http://localhost:8081/greeting. You should see the output: Hello.
+ - Test the Context Service (which calls the Greeting Service): Open your web browser and go to http://localhost:8082/context. You should see the output: The Greeting Service says: Hello from Chennai, Tamil Nadu, India!. This confirms that the Context Service successfully discovered and called the Greeting Service through Eureka.
+
+Optional: Check Health Endpoints
+
+You can also verify the health status of each service using Spring Boot Actuator:
+ - Greeting Service Health: http://localhost:8081/actuator/health (should return {"status":"UP"})
+ - Context Service Health: http://localhost:8082/actuator/health (should return {"status":"UP"})
+ - Eureka Server Health: http://localhost:8761/actuator/health (should return {"status":"UP"})
+
+## When to use Microservices Self-Registration Pattern
+
+ - **Dynamic Environments:** When your microservices are frequently deployed, scaled up or down, or their network locations (IP addresses and ports) change often. This is common in cloud-based or containerized environments (like Docker and Kubernetes).
+ - **Large Number of Services:** As the number of microservices in your system grows, manually managing their configurations and dependencies becomes complex and error-prone. Self-registration automates this process.
+ - **Need for Automatic Service** Discovery: When services need to find and communicate with each other without hardcoding network locations. This allows for greater flexibility and reduces coupling.
+ - **Implementing Load Balancing:** Service registries like Eureka often integrate with load balancers, enabling them to automatically distribute traffic across available instances of a service that have registered themselves.
+ - **Improving System Resilience:** If a service instance fails, the registry will eventually be updated (through heartbeats or health checks), and other services can discover and communicate with the remaining healthy instances.
+ - **DevOps Automation:** This pattern aligns well with DevOps practices, allowing for more automated deployment and management of microservices.
+
+## Real-World Applications of Self-Registration pattern
+
+ - E-Commerce platforms have numerous independent services for product catalogs, order processing, payments, shipping, etc. Self-registration allows these services to dynamically discover and communicate with each other as the system scales during peak loads or as new features are deployed.
+ - Streaming services rely on many microservices for user authentication, content delivery networks (CDNs), recommendation engines, billing systems, etc. Self-registration helps these services adapt to varying user demands and infrastructure changes.
+ - Social media These platforms use microservices for managing user profiles, timelines, messaging, advertising, and more. Self-registration enables these services to scale independently and handle the massive traffic they experience.
+
+## Advantages
+
+ - Microservices can dynamically locate and communicate with each other without needing to know their specific network addresses beforehand. This is crucial in dynamic environments where IP addresses and ports can change frequently.
+ - Reduces the need for manual configuration of service locations in each microservice. Services don't need to be updated every time another service's location changes.
+ - Scaling microservices up or down becomes easier. New instances automatically register themselves with the service registry, making them immediately discoverable by other services without manual intervention.
+ - If a service instance fails, it will eventually stop sending heartbeats to the registry and will be removed. Consumers can then discover and connect to other healthy instances, improving the system's overall resilience.
+ - Services are less tightly coupled as they don't have direct dependencies on the physical locations of other services. This makes deployments and updates more flexible.
+ - Service registries often integrate with load balancers. When a new service instance registers, the load balancer can automatically include it in the pool of available instances, distributing traffic effectively.
+ - Microservices can be deployed across different environments (development, testing, production) without significant changes to their discovery mechanism, as long as they are configured to connect to the appropriate service registry for that environment.
+
+## Trade-offs
+
+ - Introducing a service registry adds another component to your system that needs to be set up, managed, and monitored. This increases the overall complexity of the infrastructure.
+ - The service registry itself becomes a critical component. If the service registry becomes unavailable, it can disrupt communication between microservices. High availability for the service registry is therefore essential.
+ - Microservices need to communicate with the service registry for registration, sending heartbeats, and querying for other services. This can lead to increased network traffic.
+ - There might be a slight delay between when a microservice instance starts and when it becomes fully registered and discoverable in the service registry. This needs to be considered, especially during scaling events.
+ - You need to consider how your microservices will behave if they fail to register with the service registry upon startup. Robust error handling and retry mechanisms are often necessary.
+ - Microservices need to include and configure client libraries (like the Eureka Discovery Client) to interact with the service registry. This adds a dependency to your application code.
+ - In distributed service registries, ensuring consistency of the registry data across all nodes can be a challenge. Different registries might have different consistency models (e.g., eventual consistency).
+
+## References
+
+ - Microservices Patterns: https://microservices.io/
+ - Eureka Documentation: https://github.com/Netflix/eureka | https://spring.io/projects/spring-cloud-netflix
+ - Spring Boot Documentation: https://spring.io/projects/spring-boot
+ - Spring Cloud OpenFeignDocumentation: https://spring.io/projects/spring-cloud-openfeign
+ - Spring Boot Actuator Documentation: https://www.baeldung.com/spring-boot-actuators
\ No newline at end of file
diff --git a/microservices-self-registration/application.log.2025-04-09.0.gz b/microservices-self-registration/application.log.2025-04-09.0.gz
new file mode 100644
index 000000000000..d51965d73d7a
Binary files /dev/null and b/microservices-self-registration/application.log.2025-04-09.0.gz differ
diff --git a/microservices-self-registration/contextservice/.gitattributes b/microservices-self-registration/contextservice/.gitattributes
new file mode 100644
index 000000000000..3b41682ac579
--- /dev/null
+++ b/microservices-self-registration/contextservice/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/microservices-self-registration/contextservice/.gitignore b/microservices-self-registration/contextservice/.gitignore
new file mode 100644
index 000000000000..549e00a2a96f
--- /dev/null
+++ b/microservices-self-registration/contextservice/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz
new file mode 100644
index 000000000000..6ceb03b833a7
Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-05.0.gz differ
diff --git a/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz
new file mode 100644
index 000000000000..eb2a63ce194e
Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-07.0.gz differ
diff --git a/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz b/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz
new file mode 100644
index 000000000000..bd773dc8ba59
Binary files /dev/null and b/microservices-self-registration/contextservice/application.log.2025-04-09.0.gz differ
diff --git a/microservices-self-registration/contextservice/pom.xml b/microservices-self-registration/contextservice/pom.xml
new file mode 100644
index 000000000000..ea6d105bd06f
--- /dev/null
+++ b/microservices-self-registration/contextservice/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.4
+
+ com.learning
+ contextservice
+ 0.0.1-SNAPSHOT
+ contextservice
+ contextservice
+
+
+ 2024.0.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java
new file mode 100644
index 000000000000..eb22d094ffce
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/ContextserviceApplication.java
@@ -0,0 +1,17 @@
+package com.learning.contextservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableFeignClients
+public class ContextserviceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ContextserviceApplication.class, args);
+ }
+
+}
diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java
new file mode 100644
index 000000000000..0226fc50e803
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/MyCustomHealthCheck.java
@@ -0,0 +1,42 @@
+package com.learning.contextservice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+
+@Component("myCustomHealthCheck")
+public class MyCustomHealthCheck implements HealthIndicator {
+
+ private static final Logger log = LoggerFactory.getLogger(MyCustomHealthCheck.class);
+
+ private volatile boolean isHealthy = true;
+
+ @Scheduled(fixedRate = 5000) // Run every 5 seconds
+ public void updateHealthStatus() {
+ // Perform checks here to determine the current health
+ // For example, check database connectivity, external service availability, etc.
+ isHealthy = performHealthCheck();
+ log.info("Update health status : {}", isHealthy);
+ }
+
+ boolean performHealthCheck() {
+ boolean current = System.currentTimeMillis() % 10000 < 5000; // Simulate fluctuating health
+ log.debug("Performing health check, current status: {}", current);
+ return current; // Simulate fluctuating health
+ }
+
+ @Override
+ public Health health() {
+ if (isHealthy) {
+ log.info("Health check successful, service is UP");
+ return Health.up().withDetail("message", "Service is running and scheduled checks are OK").build();
+ } else {
+ log.warn("Health check failed, service is DOWN");
+ return Health.down().withDetail("error", "Scheduled health checks failed").build();
+ }
+ }
+}
diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java
new file mode 100644
index 000000000000..367a3bdd496c
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/client/GreetingServiceClient.java
@@ -0,0 +1,11 @@
+package com.learning.contextservice.client;
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.GetMapping;
+
+@FeignClient(name = "greetingservice")
+public interface GreetingServiceClient {
+
+ @GetMapping("/greeting")
+ String getGreeting();
+}
diff --git a/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java
new file mode 100644
index 000000000000..5ad8969e0871
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/main/java/com/learning/contextservice/controller/ContextController.java
@@ -0,0 +1,26 @@
+package com.learning.contextservice.controller;
+
+import com.learning.contextservice.client.GreetingServiceClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class ContextController {
+
+ private final GreetingServiceClient greetingServiceClient;
+ private final String userRegion;
+
+ @Autowired
+ public ContextController(GreetingServiceClient greetingServiceClient, @Value("${user.region}") String userRegion) {
+ this.greetingServiceClient = greetingServiceClient;
+ this.userRegion = userRegion;
+ }
+
+ @GetMapping("/context")
+ public String getContext() {
+ String greeting = greetingServiceClient.getGreeting();
+ return "The Greeting Service says: "+greeting+" from "+userRegion;
+ }
+}
diff --git a/microservices-self-registration/contextservice/src/main/resources/application.yml b/microservices-self-registration/contextservice/src/main/resources/application.yml
new file mode 100644
index 000000000000..dfef73bbbeec
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/main/resources/application.yml
@@ -0,0 +1,25 @@
+server:
+ port: 8082
+
+spring:
+ application:
+ name: contextservice
+
+eureka:
+ client:
+ service-url.defaultZone: http://localhost:8761/eureka
+
+user:
+ region: Chennai, Tamil Nadu, India
+
+management:
+ endpoint:
+ health:
+ show-details: always
+ web:
+ exposure:
+ include: health
+
+logging:
+ file:
+ name: application.log
diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java
new file mode 100644
index 000000000000..f11da867cda0
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextControllerTest.java
@@ -0,0 +1,49 @@
+package com.learning.contextservice;
+
+import com.learning.contextservice.client.GreetingServiceClient;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@SpringBootTest(classes = ContextserviceApplication.class)
+@AutoConfigureMockMvc
+@Import(TestConfig.class)
+class ContextControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @MockitoBean
+ private GreetingServiceClient greetingServiceClient;
+
+ @Value("${user.region}")
+ private String userRegion;
+
+ @Test
+ void shouldReturnContextGreeting() throws Exception{
+ Mockito.when(greetingServiceClient.getGreeting()).thenReturn("Mocked Hello");
+
+ mockMvc.perform(MockMvcRequestBuilders.get("/context")
+ .accept(MediaType.TEXT_PLAIN))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("The Greeting Service says: Mocked Hello from Chennai, Tamil Nadu, India"));
+ }
+
+ @Test
+ void shouldReturnContextServiceHealthStatusUp() throws Exception {
+ mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string(Matchers.containsString("\"status\":\"UP\"")));
+ }
+}
diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java
new file mode 100644
index 000000000000..a5d5c869c664
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/ContextserviceApplicationTests.java
@@ -0,0 +1,17 @@
+package com.learning.contextservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class ContextserviceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ // This is a basic integration test that checks if the Spring Application Context loads successfully.
+ // If the context loads without any exceptions, the test is considered passing.
+ // It is often left empty as the act of loading the context is the primary verification.
+ // You can add specific assertions here if you want to verify the presence or state of certain beans.
+ }
+
+}
diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java
new file mode 100644
index 000000000000..f378f46f59df
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/TestConfig.java
@@ -0,0 +1,17 @@
+package com.learning.contextservice;
+
+import com.learning.contextservice.client.GreetingServiceClient;
+import org.mockito.Mockito;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TestConfig {
+
+ @Bean
+ public GreetingServiceClient greetingServiceClient() {
+ GreetingServiceClient mockClient = Mockito.mock(GreetingServiceClient.class);
+ Mockito.when(mockClient.getGreeting()).thenReturn("Mocked Hello");
+ return mockClient;
+ }
+}
diff --git a/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java
new file mode 100644
index 000000000000..129209469827
--- /dev/null
+++ b/microservices-self-registration/contextservice/src/test/java/com/learning/contextservice/myCustomHealthCheckTest.java
@@ -0,0 +1,33 @@
+package com.learning.contextservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.boot.actuate.health.Status;
+import static org.junit.jupiter.api.Assertions.*;
+
+class MyCustomHealthCheckTest {
+
+ @Test
+ void testHealthUp() {
+ MyCustomHealthCheck healthCheck = new MyCustomHealthCheck();
+ // Simulate a healthy state
+ ReflectionTestUtils.setField(healthCheck, "isHealthy", true);
+ Health health = healthCheck.health();
+ assertEquals(Status.UP, health.getStatus());
+ assertTrue(health.getDetails().containsKey("message"));
+ assertEquals("Service is running and scheduled checks are OK", health.getDetails().get("message"));
+ }
+
+ @Test
+ void testHealthDown() {
+ MyCustomHealthCheck healthCheck = new MyCustomHealthCheck();
+ // Simulate an unhealthy state
+ ReflectionTestUtils.setField(healthCheck, "isHealthy", false);
+ Health health = healthCheck.health();
+ assertEquals(Status.DOWN, health.getStatus());
+ assertTrue(health.getDetails().containsKey("error"));
+ assertEquals("Scheduled health checks failed", health.getDetails().get("error"));
+ }
+
+}
\ No newline at end of file
diff --git a/microservices-self-registration/eurekaserver/.gitattributes b/microservices-self-registration/eurekaserver/.gitattributes
new file mode 100644
index 000000000000..3b41682ac579
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/microservices-self-registration/eurekaserver/.gitignore b/microservices-self-registration/eurekaserver/.gitignore
new file mode 100644
index 000000000000..549e00a2a96f
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/microservices-self-registration/eurekaserver/pom.xml b/microservices-self-registration/eurekaserver/pom.xml
new file mode 100644
index 000000000000..b1a4b26cf4f4
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/pom.xml
@@ -0,0 +1,55 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.4
+
+ com.learning
+ eurekaserver
+ 0.0.1-SNAPSHOT
+ eurekaserver
+ eurekaserver
+
+
+ 2024.0.1
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-server
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java b/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java
new file mode 100644
index 000000000000..80b3d904ff4c
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/src/main/java/com/learning/eurekaserver/EurekaserverApplication.java
@@ -0,0 +1,15 @@
+package com.learning.eurekaserver;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
+
+@SpringBootApplication
+@EnableEurekaServer
+public class EurekaserverApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(EurekaserverApplication.class, args);
+ }
+
+}
diff --git a/microservices-self-registration/eurekaserver/src/main/resources/application.yml b/microservices-self-registration/eurekaserver/src/main/resources/application.yml
new file mode 100644
index 000000000000..51f8a815d251
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/src/main/resources/application.yml
@@ -0,0 +1,10 @@
+server:
+ port: 8761
+
+eureka:
+ client:
+ register-with-eureka: false
+ fetch-registry: false
+ server:
+ enable-self-preservation: true
+
diff --git a/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java b/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java
new file mode 100644
index 000000000000..b5150fefa940
--- /dev/null
+++ b/microservices-self-registration/eurekaserver/src/test/java/com/learning/eurekaserver/EurekaserverApplicationTests.java
@@ -0,0 +1,17 @@
+package com.learning.eurekaserver;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class EurekaserverApplicationTests {
+
+ @Test
+ void contextLoads() {
+ // This is a basic integration test that checks if the Spring Application Context loads successfully.
+ // If the context loads without any exceptions, the test is considered passing.
+ // It is often left empty as the act of loading the context is the primary verification.
+ // You can add specific assertions here if you want to verify the presence or state of certain beans.
+ }
+
+}
diff --git a/microservices-self-registration/greetingservice/.gitattributes b/microservices-self-registration/greetingservice/.gitattributes
new file mode 100644
index 000000000000..3b41682ac579
--- /dev/null
+++ b/microservices-self-registration/greetingservice/.gitattributes
@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf
diff --git a/microservices-self-registration/greetingservice/.gitignore b/microservices-self-registration/greetingservice/.gitignore
new file mode 100644
index 000000000000..549e00a2a96f
--- /dev/null
+++ b/microservices-self-registration/greetingservice/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz
new file mode 100644
index 000000000000..93d6a2e62ac1
Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-05.0.gz differ
diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz
new file mode 100644
index 000000000000..40c96f702853
Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-07.0.gz differ
diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz
new file mode 100644
index 000000000000..59f2cbc3c8df
Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-09.0.gz differ
diff --git a/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz b/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz
new file mode 100644
index 000000000000..62d73c3020c1
Binary files /dev/null and b/microservices-self-registration/greetingservice/application.log.2025-04-11.0.gz differ
diff --git a/microservices-self-registration/greetingservice/pom.xml b/microservices-self-registration/greetingservice/pom.xml
new file mode 100644
index 000000000000..45988a145866
--- /dev/null
+++ b/microservices-self-registration/greetingservice/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.4
+
+ com.learning
+ greetingservice
+ 0.0.1-SNAPSHOT
+ greetingservice
+ greetingservice
+
+
+ 2024.0.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ 1.18.38
+ provided
+
+
+ org.springframework.cloud
+ spring-cloud-starter-netflix-eureka-client
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java
new file mode 100644
index 000000000000..7a549a084284
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/GreetingserviceApplication.java
@@ -0,0 +1,17 @@
+package com.learning.greetingservice;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@EnableDiscoveryClient
+@ComponentScan("com.learning.greetingservice.controller")
+public class GreetingserviceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(GreetingserviceApplication.class, args);
+ }
+
+}
diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java
new file mode 100644
index 000000000000..218a4ad002d4
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/MyCustomHealthCheck.java
@@ -0,0 +1,41 @@
+package com.learning.greetingservice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.boot.actuate.health.HealthIndicator;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component("myCustomHealthCheck")
+public class MyCustomHealthCheck implements HealthIndicator {
+
+ private static final Logger log = LoggerFactory.getLogger(MyCustomHealthCheck.class);
+
+ private volatile boolean isHealthy = true;
+
+ @Scheduled(fixedRate = 5000) // Run every 5 seconds
+ public void updateHealthStatus() {
+ // Perform checks here to determine the current health
+ // For example, check database connectivity, external service availability, etc.
+ isHealthy = performHealthCheck();
+ log.info("Update health status : {}", isHealthy);
+ }
+
+ boolean performHealthCheck() {
+ boolean current = System.currentTimeMillis() % 10000 < 5000; // Simulate fluctuating health
+ log.debug("Performing health check, current status: {}", current);
+ return current; // Simulate fluctuating health
+ }
+
+ @Override
+ public Health health() {
+ if (isHealthy) {
+ log.info("Health check successful, service is UP");
+ return Health.up().withDetail("message", "Service is running and scheduled checks are OK").build();
+ } else {
+ log.warn("Health check failed, service is DOWN");
+ return Health.down().withDetail("error", "Scheduled health checks failed").build();
+ }
+ }
+}
diff --git a/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java
new file mode 100644
index 000000000000..ea385beb1abe
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/main/java/com/learning/greetingservice/controller/GreetingsController.java
@@ -0,0 +1,13 @@
+package com.learning.greetingservice.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class GreetingsController {
+
+ @GetMapping("/greeting")
+ public String getGreeting() {
+ return "Hello";
+ }
+}
diff --git a/microservices-self-registration/greetingservice/src/main/resources/application.yml b/microservices-self-registration/greetingservice/src/main/resources/application.yml
new file mode 100644
index 000000000000..adcfac884c2b
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/main/resources/application.yml
@@ -0,0 +1,22 @@
+server:
+ port: 8081
+
+spring:
+ application:
+ name: greetingservice
+eureka:
+ client:
+ service-url.defaultZone: http://localhost:8761/eureka
+
+management:
+ endpoint:
+ health:
+ show-details: always
+ web:
+ exposure:
+ include: health
+
+logging:
+ file:
+ name: application.log
+
diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java
new file mode 100644
index 000000000000..945898278aa9
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/GreetingserviceApplicationTests.java
@@ -0,0 +1,17 @@
+package com.learning.greetingservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class GreetingserviceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ // This is a basic integration test that checks if the Spring Application Context loads successfully.
+ // If the context loads without any exceptions, the test is considered passing.
+ // It is often left empty as the act of loading the context is the primary verification.
+ // You can add specific assertions here if you want to verify the presence or state of certain beans.
+ }
+
+}
diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java
new file mode 100644
index 000000000000..8ba8b2a30f93
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/MyCustomHealthCheckTest.java
@@ -0,0 +1,22 @@
+package com.learning.greetingservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.actuate.health.Health;
+import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.boot.actuate.health.Status;
+import static org.junit.jupiter.api.Assertions.*;
+
+class MyCustomHealthCheckTest {
+
+ @Test
+ void testHealthUp() {
+ MyCustomHealthCheck healthCheck = new MyCustomHealthCheck();
+ // Simulate a healthy state
+ ReflectionTestUtils.setField(healthCheck, "isHealthy", true);
+ Health health = healthCheck.health();
+ assertEquals(Status.UP, health.getStatus());
+ assertTrue(health.getDetails().containsKey("message"));
+ assertEquals("Service is running and scheduled checks are OK", health.getDetails().get("message"));
+ }
+
+}
\ No newline at end of file
diff --git a/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java
new file mode 100644
index 000000000000..5ae98c8b6aeb
--- /dev/null
+++ b/microservices-self-registration/greetingservice/src/test/java/com/learning/greetingservice/controller/GreetingControllerTest.java
@@ -0,0 +1,36 @@
+package com.learning.greetingservice.controller;
+
+import com.learning.greetingservice.GreetingserviceApplication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+@SpringBootTest(classes = GreetingserviceApplication.class)
+@AutoConfigureMockMvc
+@ActiveProfiles("test")
+class GreetingControllerTest {
+
+ @Autowired
+ private MockMvc mockMvc;
+
+ @Test
+ void shouldReturnGreeting() throws Exception{
+ mockMvc.perform(MockMvcRequestBuilders.get("/greeting")
+ .accept(MediaType.TEXT_PLAIN))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string("Hello"));
+ }
+
+ @Test
+ void shouldReturnHealthStatusUp() throws Exception{
+ mockMvc.perform(MockMvcRequestBuilders.get("/actuator/health"))
+ .andExpect(MockMvcResultMatchers.status().isOk())
+ .andExpect(MockMvcResultMatchers.content().string(org.hamcrest.Matchers.containsString("\"status\":\"UP\"")));
+ }
+}
diff --git a/microservices-self-registration/pom.xml b/microservices-self-registration/pom.xml
new file mode 100644
index 000000000000..4b708cb0eb12
--- /dev/null
+++ b/microservices-self-registration/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ java-design-patterns
+ com.iluwatar
+ 1.26.0-SNAPSHOT
+
+ 4.0.0
+ microservices-self-registration
+ pom
+
+ eurekaserver
+ greetingservice
+ contextservice
+
+
+
+ 21
+ ${java.version}
+ ${java.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 46429c75af7c..82d15bdf1d74 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,7 +48,7 @@
0.8.13
1.4
- 4.7.0
+ 4.17.0
2.11.0
6.0.0
1.1.0
@@ -59,11 +59,11 @@
3.5.2
- 4.6
+ 5.0.0
3.14.0
- 5.0.0.4389
+ 5.1.0.4751
https://sonarcloud.io
iluwatar
iluwatar_java-design-patterns
@@ -106,6 +106,7 @@
converter
curiously-recurring-template-pattern
currying
+ dao-factory
data-access-object
data-bus
data-locality
@@ -166,6 +167,7 @@
microservices-distributed-tracing
microservices-idempotent-consumer
microservices-log-aggregation
+ microservices-self-registration
model-view-controller
model-view-intent
model-view-presenter
@@ -230,6 +232,7 @@
table-module
template-method
templateview
+ thread-pool-executor
throttling
tolerant-reader
trampoline
@@ -240,9 +243,12 @@
update-method
value-object
version-number
+ view-helper
virtual-proxy
visitor
- backpressure
+ backpressure
+ actor-model
+ rate-limiting-pattern