feat: Implement DAO Factory pattern (#3282)

* feat: implement dao factory

* feat: done implement dao factory

* test: Add unit test dao-factory

* doc: add document for dao-factory

* fix: h2 inmemory database to pass CI

* fix: unit test pipeline

* add license

* fix SonarQube

* add pom.xml

* fix: throw exception + refactor
This commit is contained in:
Thanh Nguyen Duc
2026-02-08 21:11:47 +02:00
committed by GitHub
parent fecf7e7a82
commit f92e6829c3
24 changed files with 2673 additions and 0 deletions
+360
View File
@@ -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
![Data Access Object Factory class diagram](./etc/dao-factory.png "Data Access Object Factory 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<T> 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<T> {
void save(Customer<T> customer);
void update(Customer<T> customer);
void delete(T id);
List<Customer<T>> findAll();
Optional<Customer<T>> findById(T id);
}
```
Here is the implementations
``` java
@Slf4j
@RequiredArgsConstructor
public class H2CustomerDAO implements CustomerDAO<Long> {
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<Long> customer) {
// Implement operation save for H2
}
@Override
public void update(Customer<Long> customer) {
// Implement operation save for H2
}
@Override
public void delete(Long id) {
// Implement operation delete for H2
}
@Override
public List<Customer<Long>> findAll() {
// Implement operation find all for H2
}
@Override
public Optional<Customer<Long>> findById(Long id) {
// Implement operation find by id for H2
}
}
```
``` java
@Slf4j
@RequiredArgsConstructor
public class MongoCustomerDAO implements CustomerDAO<ObjectId> {
private final MongoCollection<Document> customerCollection;
// Implement CRUD operation with MongoDB data source
}
```
``` java
@Slf4j
@RequiredArgsConstructor
public class FlatFileCustomerDAO implements CustomerDAO<Long> {
private final Path filePath;
private final Gson gson;
Type customerListType = new TypeToken<List<Customer<Long>>>() {
}.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<Document> 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/)
Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

+74
View File
@@ -0,0 +1,74 @@
@startuml
package com.iluwatar.daofactory {
class App {
{static} void main(String[] args)
}
class Customer<T> {
T id
String name
}
interface CustomerDAO<ID> {
void save(Customer<ID> customer)
void update(Customer<ID> customer)
void delete(ID id)
List<Customer<ID>> findAll()
Optional<Customer<ID>> 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<Long> {
void save(Customer<Long> customer)
void update(Customer<Long> customer)
void delete(Long id)
List<Customer<Long>> findAll()
Optional<Customer<Long>> findById(Long id)
void deleteSchema()
}
class H2CustomerDAO implements CustomerDAO<Long> {
void save(Customer<Long> customer)
void update(Customer<Long> customer)
void delete(Long id)
List<Customer<Long>> findAll()
Optional<Customer<Long>> findById(Long id)
void deleteSchema()
}
class FlatFileDataSourceFactory extends DAOFactory {
CustomerDAO createCustomerDAO()
}
class H2DataSourceFactory extends DAOFactory {
CustomerDAO createCustomerDAO()
}
class MongoCustomerDAO implements CustomerDAO<ObjectId> {
void save(Customer<ObjectId> customer)
void update(Customer<ObjectId> customer)
void delete(ObjectId id)
List<Customer<ObjectId>> findAll()
Optional<Customer<ObjectId>> findById(ObjectId id)
void deleteSchema()
}
class MongoDataSourceFactory extends DAOFactory {
CustomerDAO createCustomerDAO()
}
DataSourceType ..+ DAOFactory
DAOFactory ..+ App
App --> Customer
}
@enduml
+82
View File
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>
<artifactId>dao-factory</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-legacy</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -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<Long> customerInmemory1 = new Customer<>(1L, "Green");
Customer<Long> customerInmemory2 = new Customer<>(2L, "Red");
Customer<Long> customerInmemory3 = new Customer<>(3L, "Blue");
Customer<Long> 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<ObjectId> customer4 = new Customer<>(idCustomerMongo1, "Masca");
Customer<ObjectId> customer5 = new Customer<>(idCustomerMongo2, "Elliot");
Customer<ObjectId> 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<Long> customerFlatFile1 = new Customer<>(1L, "Duc");
Customer<Long> customerFlatFile2 = new Customer<>(2L, "Quang");
Customer<Long> customerFlatFile3 = new Customer<>(3L, "Nhat");
Customer<Long> 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 <T extends Serializable> void performCreateCustomer(
CustomerDAO<T> customerDAO, List<Customer<T>> customerList) {
for (Customer<T> customer : customerList) {
customerDAO.save(customer);
}
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}
public static <T extends Serializable> void performUpdateCustomer(
CustomerDAO<T> customerDAO, Customer<T> customerUpdate) {
customerDAO.update(customerUpdate);
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}
public static <T extends Serializable> void performDeleteCustomer(
CustomerDAO<T> customerDAO, T customerId) {
customerDAO.delete(customerId);
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}
}
@@ -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);
}
}
@@ -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<T extends Serializable> implements Serializable {
private T id;
private String name;
}
@@ -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.
*
* <p>Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping
* client code unchanged.
*
* @see H2CustomerDAO
* @see MongoCustomerDAO
* @see FlatFileCustomerDAO
*/
public interface CustomerDAO<T extends Serializable> {
/**
* Persist the given customer
*
* @param customer the customer to persist
*/
void save(Customer<T> customer);
/**
* Update the given customer
*
* @param customer the customer to update
*/
void update(Customer<T> 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<Customer<T>> 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<Customer<T>> 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();
}
@@ -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).
*
* <p>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();
}
@@ -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.
*
* <p>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.
*
* <p>Example usage:
*
* <pre>{@code
* DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
* }</pre>
*/
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");
};
}
}
@@ -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
}
@@ -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<Long> {
private final Path filePath;
private final Gson gson;
Type customerListType = new TypeToken<List<Customer<Long>>>() {}.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<Long> customer) {
List<Customer<Long>> 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<Long> customer) {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> 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<Customer<Long>> customers;
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
Customer<Long> 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<Customer<Long>> findAll() {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> 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<Customer<Long>> findById(Long id) {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> 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");
}
}
}
@@ -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<Long> createCustomerDAO() {
Path filePath = Paths.get(FILE_PATH);
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
return new FlatFileCustomerDAO(filePath, gson);
}
}
@@ -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<Long> {
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<Long> 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<Long> 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<Customer<Long>> findAll() {
List<Customer<Long>> 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<Customer<Long>> findById(Long id) {
if (Objects.isNull(id)) {
throw new CustomException("Customer id null");
}
Customer<Long> 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);
}
}
}
@@ -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;
}
}
@@ -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<ObjectId> {
private final MongoCollection<Document> customerCollection;
/** {@inheritDoc} */
@Override
public void save(Customer<ObjectId> customer) {
Document customerDocument = new Document("_id", customer.getId());
customerDocument.append("name", customer.getName());
customerCollection.insertOne(customerDocument);
}
/** {@inheritDoc} */
@Override
public void update(Customer<ObjectId> 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<Customer<ObjectId>> findAll() {
List<Customer<ObjectId>> customers = new LinkedList<>();
FindIterable<Document> customerDocuments = customerCollection.find();
for (Document customerDocument : customerDocuments) {
Customer<ObjectId> customer =
new Customer<>(
(ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
customers.add(customer);
}
return customers;
}
/** {@inheritDoc} */
@Override
public Optional<Customer<ObjectId>> findById(ObjectId objectId) {
Bson filter = Filters.eq("_id", objectId);
Document customerDocument = customerCollection.find(filter).first();
Customer<ObjectId> 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();
}
}
@@ -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<ObjectId> createCustomerDAO() {
try {
MongoClient mongoClient = MongoClients.create(CONN_STR);
MongoDatabase database = mongoClient.getDatabase(DB_NAME);
MongoCollection<Document> customerCollection = database.getCollection(COLLECTION_NAME);
return new MongoCustomerDAO(customerCollection);
} catch (CustomException e) {
throw new CustomException("Error: " + e);
}
}
}
@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.iluwatar.daofactory" level="DEBUG"/>
<root level="ERROR">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -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<Long> mockLongCustomerDAO;
private CustomerDAO<ObjectId> mockObjectIdCustomerDAO;
@BeforeEach
void setUp() {
mockLongCustomerDAO = mock(CustomerDAO.class);
mockObjectIdCustomerDAO = mock(CustomerDAO.class);
}
@Test
void testPerformCreateCustomerWithLongId() {
Customer<Long> c1 = new Customer<>(1L, "Test1");
Customer<Long> 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<ObjectId> 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<Long> 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();
}
}
@@ -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);
}
}
@@ -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<List<Customer<Long>>>() {}.getType();
private final Customer<Long> 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<Customer<Long>> 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<Customer<Long>> list) ->
list.size() == 1 && list.getFirst().equals(existingCustomer)),
eq(fileWriter));
}
@Test
void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() {
List<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> customers = flatFileCustomerDAO.findAll();
assertEquals(0, customers.size());
verify(gson).fromJson(fileReader, customerListType);
}
@Test
void givenCustomerExist_thenReturnCustomerList() {
when(file.exists()).thenReturn(true);
List<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Long>> 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<Customer<Long>> 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<Files> mockedFiles = mockStatic(Files.class)) {
flatFileCustomerDAO.deleteSchema();
mockedFiles.verify(() -> Files.delete(filePath), times(1));
}
}
@Test
void givenFilePathNotExist_thenThrowException() {
when(file.exists()).thenReturn(false);
try (MockedStatic<Files> mockedFiles = mockStatic(Files.class)) {
assertThrows(CustomException.class, () -> flatFileCustomerDAO.deleteSchema());
mockedFiles.verify(() -> Files.delete(filePath), times(0));
}
}
}
}
@@ -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<Long> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> 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<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(0, customers.size());
}
@Test
void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() {
List<Customer<Long>> 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());
}
}
}
@@ -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<Document> customerCollection = mock(MongoCollection.class);
MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection);
@Test
void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() {
Customer<ObjectId> 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<ObjectId> 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<Document> findIterable = mock(FindIterable.class);
MongoCursor<Document> 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<Customer<ObjectId>> customerList = mongoCustomerDAO.findAll();
assertEquals(2, customerList.size());
verify(customerCollection).find();
}
@Test
void givenValidId_whenFindById_thenReturnCustomer() {
FindIterable<Document> 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<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
assertTrue(customer.isPresent());
assertEquals(customerId, customer.get().getId());
assertEquals(customerName, customer.get().getName());
}
@Test
void givenNotExistingId_whenFindById_thenReturnEmpty() {
FindIterable<Document> findIterable = mock(FindIterable.class);
ObjectId customerId = new ObjectId();
when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
when(findIterable.first()).thenReturn(null);
Optional<Customer<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
assertTrue(customer.isEmpty());
verify(customerCollection).find(Filters.eq("_id", customerId));
}
@Test
void whenDeleteSchema_thenDeleteCollection() {
mongoCustomerDAO.deleteSchema();
verify(customerCollection).drop();
}
}
+1
View File
@@ -107,6 +107,7 @@
<module>converter</module>
<module>curiously-recurring-template-pattern</module>
<module>currying</module>
<module>dao-factory</module>
<module>data-access-object</module>
<module>data-bus</module>
<module>data-locality</module>