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..f01fbf75ac47
--- /dev/null
+++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java
@@ -0,0 +1,61 @@
+/*
+ * 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();
+ };
+ }
+}
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/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();
+ }
+}