From 50fbd1046e67d52d614dd58c47bb507ac34da438 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Mon, 21 Apr 2025 01:39:13 +0700 Subject: [PATCH 01/10] feat: implement dao factory --- dao-factory/README.md | 0 dao-factory/pom.xml | 20 +++++++++ .../com/iluwatar/daofactory/Customer.java | 10 +++++ .../com/iluwatar/daofactory/CustomerDAO.java | 10 +++++ .../com/iluwatar/daofactory/DAOFactory.java | 10 +++++ .../iluwatar/daofactory/DataSourceEnum.java | 11 +++++ .../daofactory/FlatFileCustomerDAO.java | 11 +++++ .../daofactory/FlatFileDataSourceFactory.java | 10 +++++ .../iluwatar/daofactory/H2CustomerDAO.java | 10 +++++ .../daofactory/H2DataSourceFactory.java | 10 +++++ .../iluwatar/daofactory/MongoCustomerDAO.java | 43 +++++++++++++++++++ .../daofactory/MongoDataSourceFactory.java | 15 +++++++ .../java/com/iluwatar/daofactory/AppTest.java | 10 +++++ .../com/iluwatar/daofactory/CustomerTest.java | 11 +++++ .../iluwatar/daofactory/DAOFactoryTest.java | 11 +++++ .../daofactory/FlatFileCustomerDAOTest.java | 10 +++++ .../daofactory/H2CustomerDAOTest.java | 11 +++++ .../daofactory/MongoCustomerDAOTest.java | 11 +++++ 18 files changed, 224 insertions(+) create mode 100644 dao-factory/README.md create mode 100644 dao-factory/pom.xml create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java diff --git a/dao-factory/README.md b/dao-factory/README.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml new file mode 100644 index 000000000000..137e7e49f5ad --- /dev/null +++ b/dao-factory/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + dao-factory + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java new file mode 100644 index 000000000000..7d06de6723c4 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:22 + * Filename : Customer + */public class Customer { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java new file mode 100644 index 000000000000..ea7a88498782 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:16 + * Filename : CustomerDAO + */public interface CustomerDAO { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java new file mode 100644 index 000000000000..99cf7507a719 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:16 + * Filename : DAOFactory + */public interface DAOFactory { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java new file mode 100644 index 000000000000..7d08423f5512 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 17/04/2025 + * Time : 00:21 + * Filename : DataSourceEnum + */ +public class DataSourceEnum { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java new file mode 100644 index 000000000000..0706bb555040 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:25 + * Filename : FlatFileCustomerDAO + */ +public class FlatFileCustomerDAO { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java new file mode 100644 index 000000000000..f710d39cf01d --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:19 + * Filename : FlatFileDataSourceFactory + */public class FlatFileDataSourceFactory { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java new file mode 100644 index 000000000000..0aa8659a12c3 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:26 + * Filename : H2CustomerDAO + */public class H2CustomerDAO { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java new file mode 100644 index 000000000000..14141beb07b7 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:18 + * Filename : H2DataSourceFactory + */public class H2DataSourceFactory { +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java new file mode 100644 index 000000000000..0109cf22fb3d --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -0,0 +1,43 @@ +package com.iluwatar.daofactory; + +import org.dizitart.no2.collection.Document; +import java.util.Optional; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:26 + * Filename : NitriteCustomerDAO + */ +public class MongoCustomerDAO implements CustomerDAO { + @Override + public void save(Customer customer) { + + } + + @Override + public void update(Customer customer) { + + } + + @Override + public void delete(Long id) { + + } + + @Override + public Optional findById(Long id) { + return Optional.empty(); + } + +// @Override +// public List findAll() { +// return List.of(); +// } +// +// @Override +// public Optional findById(Long id) { +// return Optional.empty(); +// } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java new file mode 100644 index 000000000000..f06c0f72501b --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -0,0 +1,15 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:19 + * Filename : NitriteDataSourceFactory + */ +public class MongoDataSourceFactory extends DAOFactory { + @Override + public CustomerDAO getCustomerDAO() { + return new MongoCustomerDAO(); + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java new file mode 100644 index 000000000000..3a2e4ce79f55 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:02 + * Filename : AppTest + */public class AppTest { +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java new file mode 100644 index 000000000000..90e94f57b471 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:03 + * Filename : CustomerTest + */ +public class CustomerTest { +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java new file mode 100644 index 000000000000..e4a3dc197ce4 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:01 + * Filename : DAOFactoryTest + */ +public class DAOFactoryTest { +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java new file mode 100644 index 000000000000..20ef721200e2 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:05 + * Filename : FlatFileCustomerDAOTest + */public class FlatFileCustomerDAOTest { +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java new file mode 100644 index 000000000000..54ac4495df38 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:05 + * Filename : H2CustomerDAOTest + */ +public class H2CustomerDAOTest { +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java new file mode 100644 index 000000000000..253dc3a89305 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -0,0 +1,11 @@ +package com.iluwatar.daofactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 21/04/2025 + * Time : 01:05 + * Filename : MongoCustomerDAOTest + */ +public class MongoCustomerDAOTest { +} From fd0f8558733d1eee60d3472032b5501112f7ae31 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Fri, 25 Apr 2025 16:38:02 +0700 Subject: [PATCH 02/10] feat: done implement dao factory --- dao-factory/pom.xml | 28 +++ .../java/com/iluwatar/daofactory/App.java | 110 ++++++++++ .../com/iluwatar/daofactory/Customer.java | 17 +- .../com/iluwatar/daofactory/CustomerDAO.java | 17 +- .../com/iluwatar/daofactory/DAOFactory.java | 12 +- .../iluwatar/daofactory/DataSourceEnum.java | 5 +- .../daofactory/FlatFileCustomerDAO.java | 124 +++++++++++- .../daofactory/FlatFileDataSourceFactory.java | 7 +- .../iluwatar/daofactory/H2CustomerDAO.java | 191 +++++++++++++++++- .../daofactory/H2DataSourceFactory.java | 7 +- .../iluwatar/daofactory/MongoCustomerDAO.java | 85 ++++++-- dao-factory/src/main/resources/logback.xml | 10 + .../java/com/iluwatar/daofactory/AppTest.java | 3 +- .../daofactory/FlatFileCustomerDAOTest.java | 3 +- pom.xml | 3 +- 15 files changed, 593 insertions(+), 29 deletions(-) create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/App.java create mode 100644 dao-factory/src/main/resources/logback.xml diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml index 137e7e49f5ad..b06fd29d8db3 100644 --- a/dao-factory/pom.xml +++ b/dao-factory/pom.xml @@ -17,4 +17,32 @@ UTF-8 + + + com.h2database + h2 + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.mongodb + bson + + + org.mongodb + mongodb-driver-legacy + + + com.google.code.gson + gson + + + \ No newline at end of file diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java new file mode 100644 index 000000000000..aaa91a106569 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -0,0 +1,110 @@ +package com.iluwatar.daofactory; + +import java.sql.SQLException; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by: IntelliJ IDEA + * User : dthanh + * Date : 16/04/2025 + * Time : 23:08 + * Filename : ${NAME} + */ +@Slf4j +public class App { + + private static final Logger logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + + + + public static void main(String[] args) throws SQLException { + final var h2DAO = DAOFactory.getDataSource(DataSourceEnum.H2); + final var mongoDAO = DAOFactory.getDataSource(DataSourceEnum.Mongo); + final var flatFileDAO = DAOFactory.getDataSource(DataSourceEnum.FlatFile); + + final var h2CustomerDAO = h2DAO.getCustomerDAO(); + final var mongoCustomerDAO = mongoDAO.getCustomerDAO(); + final var flatFileCustomerDAO = flatFileDAO.getCustomerDAO(); + + // Perform CRUD H2 Database + Customer customerInmemory1 = new Customer<>(1L, "Green"); + Customer customerInmemory2 = new Customer<>(2L, "Red"); + Customer customerInmemory3 = new Customer<>(3L, "Blue"); + Customer customerUpdateInmemory = new Customer<>(1L, "Yellow"); + + LOGGER.debug("H2 - Create customer"); + performCreateCustomer(h2CustomerDAO, + List.of(customerInmemory1, customerInmemory2, customerInmemory3)); + LOGGER.debug("H2 - Update customer"); + performUpdateCustomer(h2CustomerDAO, customerUpdateInmemory); + LOGGER.debug("H2 - Delete customer"); + performDeleteCustomer(h2CustomerDAO, 3L); + deleteSchema(h2CustomerDAO); + + // Perform CRUD MongoDb + ObjectId idCustomerMongo1 = new ObjectId(); + ObjectId idCustomerMongo2 = new ObjectId(); + Customer customer4 = new Customer<>(idCustomerMongo1, "Masca"); + Customer customer5 = new Customer<>(idCustomerMongo2, "Elliot"); + Customer customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry"); + + LOGGER.debug("Mongo - Create customer"); + performCreateCustomer(mongoCustomerDAO, List.of(customer4, customer5)); + LOGGER.debug("Mongo - Update customer"); + performUpdateCustomer(mongoCustomerDAO, customerUpdateMongo); + LOGGER.debug("Mongo - Delete customer"); + performDeleteCustomer(mongoCustomerDAO, idCustomerMongo2); + deleteSchema(mongoCustomerDAO); + + // Perform CRUD Flat file + Customer customerFlatFile1 = new Customer<>(1L, "Duc"); + Customer customerFlatFile2 = new Customer<>(2L, "Quang"); + Customer customerFlatFile3 = new Customer<>(3L, "Nhat"); + Customer customerUpdateFlatFile = new Customer<>(1L, "Thanh"); + LOGGER.debug("Flat file - Create customer"); + performCreateCustomer(flatFileCustomerDAO, + List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + LOGGER.debug("Flat file - Update customer"); + performUpdateCustomer(flatFileCustomerDAO, customerUpdateFlatFile); + LOGGER.debug("Flat file - Delete customer"); + performDeleteCustomer(flatFileCustomerDAO, 3L); + deleteSchema(flatFileCustomerDAO); + } + + public static void deleteSchema(CustomerDAO customerDAO) { + customerDAO.deleteSchema(); + } + + public static void performCreateCustomer(CustomerDAO customerDAO, + List> customerList) { + for (Customer customer : customerList) { + customerDAO.save(customer); + } + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } + + public static void performUpdateCustomer(CustomerDAO customerDAO, + Customer customerUpdate) { + customerDAO.update(customerUpdate); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.error(customer.toString()); + } + } + + public static void performDeleteCustomer(CustomerDAO customerDAO, + ID customerId) { + customerDAO.delete(customerId); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } +} \ No newline at end of file diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java index 7d06de6723c4..65a0cc67637d 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -1,10 +1,25 @@ package com.iluwatar.daofactory; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + /** * Created by: IntelliJ IDEA * User : dthanh * Date : 16/04/2025 * Time : 23:22 * Filename : Customer - */public class Customer { + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Customer implements Serializable { + private T id; + private String name; } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java index ea7a88498782..c9c336471263 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -1,10 +1,25 @@ package com.iluwatar.daofactory; +import java.util.List; +import java.util.Optional; + /** * Created by: IntelliJ IDEA * User : dthanh * Date : 16/04/2025 * Time : 23:16 * Filename : CustomerDAO - */public interface CustomerDAO { + */ +public interface CustomerDAO { + void save(Customer customer); + + void update(Customer customer); + + void delete(ID id); + + List> findAll(); + + Optional> findById(ID id); + + void deleteSchema(); } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java index 99cf7507a719..e53c0e46547b 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -6,5 +6,15 @@ * Date : 16/04/2025 * Time : 23:16 * Filename : DAOFactory - */public interface DAOFactory { + */ +public abstract class DAOFactory { + public static DAOFactory getDataSource(DataSourceEnum dataSourceEnum) { + return switch (dataSourceEnum) { + case H2 -> new H2DataSourceFactory(); + case Mongo -> new MongoDataSourceFactory(); + case FlatFile -> new FlatFileDataSourceFactory(); + }; + } + + public abstract CustomerDAO getCustomerDAO(); } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java index 7d08423f5512..57172b0a6ff5 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java @@ -7,5 +7,8 @@ * Time : 00:21 * Filename : DataSourceEnum */ -public class DataSourceEnum { +public enum DataSourceEnum { + H2, + Mongo, + FlatFile } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java index 0706bb555040..f207e8dfe6ac 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -1,5 +1,21 @@ package com.iluwatar.daofactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +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.Path; +import java.nio.file.Paths; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; + /** * Created by: IntelliJ IDEA * User : dthanh @@ -7,5 +23,111 @@ * Time : 23:25 * Filename : FlatFileCustomerDAO */ -public class FlatFileCustomerDAO { +@Slf4j +public class FlatFileCustomerDAO implements CustomerDAO { + Path filePath = Paths.get(System.getProperty("user.home"), "Desktop", "customer.json"); + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create(); + Type customerListType = new TypeToken>>() { + }.getType(); + + @Override + public void save(Customer customer) { + List> customers = new LinkedList<>(); + if (filePath.toFile().exists() && filePath.toFile().length() > 0) { + try (Reader reader = new FileReader(filePath.toFile())) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + } + } + customers.add(customer); + try (Writer writer = new FileWriter(filePath.toFile())) { + gson.toJson(customers, writer); + } catch (IOException ex) { + LOGGER.error("Error writing JSON: {}", ex.getMessage(), ex); + } + } + + @Override + public void update(Customer customer) { + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); + } + List> customers = new LinkedList<>(); + try (Reader reader = new FileReader(filePath.toFile())) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + } + customers.stream() + .filter(c -> c.getId() + .equals(customer.getId())) + .findFirst() + .ifPresentOrElse( + c -> c.setName(customer.getName()), + () -> { + throw new RuntimeException("Customer not found with id: " + customer.getId()); + } + ); + } + + @Override + public void delete(Long id) { + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); + } + List> customers = new LinkedList<>(); + try (Reader reader = new FileReader(filePath.toFile())) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + } + Customer customerToRemove = customers.stream().filter(c -> c.getId().equals(id)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Customer not found with id: " + id)); + customers.remove(customerToRemove); + } + + @Override + public List> findAll() { + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); + } + List> customers = null; + try (Reader reader = new FileReader(filePath.toFile())) { + customers = gson.fromJson(reader, customerListType); + + } catch (IOException ex) { + LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + } + return customers; + } + + @Override + public Optional> findById(Long id) { + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); + } + List> customers = null; + try (Reader reader = new FileReader(filePath.toFile())) { + customers = gson.fromJson(reader, customerListType); + + } catch (IOException ex) { + LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + } + return customers.stream() + .filter(c -> c.getId() + .equals(id)) + .findFirst(); + } + + @Override + public void deleteSchema() { + if (filePath.toFile().exists()) { + filePath.toFile().delete(); + } + } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java index f710d39cf01d..72742d60b1cf 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -6,5 +6,10 @@ * Date : 16/04/2025 * Time : 23:19 * Filename : FlatFileDataSourceFactory - */public class FlatFileDataSourceFactory { + */ +public class FlatFileDataSourceFactory extends DAOFactory { + @Override + public CustomerDAO getCustomerDAO() { + return new FlatFileCustomerDAO(); + } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java index 0aa8659a12c3..3f323dd2a200 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -1,10 +1,199 @@ package com.iluwatar.daofactory; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.extern.slf4j.Slf4j; + /** * Created by: IntelliJ IDEA * User : dthanh * Date : 16/04/2025 * Time : 23:26 * Filename : H2CustomerDAO - */public class H2CustomerDAO { + */ +@Slf4j +public class H2CustomerDAO implements CustomerDAO { + static final String JDBC_DRIVER = "org.h2.Driver"; + static final String DB_URL = "jdbc:h2:~/test"; + + // Database credentials + static final String USER = "sa"; + static final String PASS = ""; + + 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 * FROM customer WHERE id= ?"; + private static final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer"; + + @Override + public void save(Customer customer) { + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + Class.forName(JDBC_DRIVER); + connection = DriverManager.getConnection(DB_URL, USER, PASS); + preparedStatement = connection.prepareStatement( + "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"); + preparedStatement.execute(); + preparedStatement = connection.prepareStatement(INSERT_CUSTOMER); + preparedStatement.setLong(1, customer.getId()); + preparedStatement.setString(2, customer.getName()); + preparedStatement.execute(); + connection.commit(); + } catch (SQLException | ClassNotFoundException e) { + try { + connection.rollback(); + } catch (SQLException ex) { + LOGGER.error("Exception occurred: " + e); + } + } finally { + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred: " + e); + } + } + } + + @Override + public void update(Customer customer) { + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + Class.forName(JDBC_DRIVER); + connection = DriverManager.getConnection(DB_URL, USER, PASS); + preparedStatement = connection.prepareStatement(UPDATE_CUSTOMER); + preparedStatement.setString(1, customer.getName()); + preparedStatement.setLong(2, customer.getId()); + preparedStatement.execute(); + } catch (SQLException | ClassNotFoundException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } finally { + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } + } + } + + @Override + public void delete(Long id) { + Connection connection = null; + PreparedStatement preparedStatement = null; + try { + Class.forName(JDBC_DRIVER); + connection = DriverManager.getConnection(DB_URL, USER, PASS); + preparedStatement = connection.prepareStatement(DELETE_CUSTOMER); + preparedStatement.setLong(1, id); + preparedStatement.execute(); + } catch (SQLException | ClassNotFoundException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } finally { + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } + } + } + + @Override + public List> findAll() { + Connection connection = null; + PreparedStatement preparedStatement = null; + List> customers = new LinkedList<>(); + try { + Class.forName(JDBC_DRIVER); + connection = DriverManager.getConnection(DB_URL, USER, PASS); + preparedStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS); + ResultSet resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customers.add(new Customer<>(idCustomer, nameCustomer)); + } + resultSet.close(); + } catch (SQLException | ClassNotFoundException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } finally { + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } + } + return customers; + } + + @Override + public Optional> findById(Long id) { + Connection connection = null; + PreparedStatement preparedStatement = null; + Customer customer = null; + try { + Class.forName(JDBC_DRIVER); + connection = DriverManager.getConnection(DB_URL, USER, PASS); + preparedStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); + preparedStatement.setLong(1, id); + ResultSet resultSet = preparedStatement.executeQuery(); + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customer = new Customer<>(idCustomer, nameCustomer); + } + resultSet.close(); + } catch (SQLException | ClassNotFoundException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } finally { + try { + if (preparedStatement != null) { + preparedStatement.close(); + } + if (connection != null) { + connection.close(); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred: " + e.getMessage()); + } + } + return Optional.ofNullable(customer); + } + + @Override + public void deleteSchema() { + try (var connection = DriverManager.getConnection(DB_URL, USER, PASS)) { + var statement = connection.createStatement(); + statement.execute("DROP TABLE customer"); + } catch (SQLException e) { + throw new RuntimeException("Error while deleting schema", e); + } + } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index 14141beb07b7..eec637c1f13a 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -6,5 +6,10 @@ * Date : 16/04/2025 * Time : 23:18 * Filename : H2DataSourceFactory - */public class H2DataSourceFactory { + */ +public class H2DataSourceFactory extends DAOFactory { + @Override + public CustomerDAO getCustomerDAO() { + return new H2CustomerDAO(); + } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java index 0109cf22fb3d..5d8ca0bc1e34 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -1,7 +1,19 @@ package com.iluwatar.daofactory; -import org.dizitart.no2.collection.Document; +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; /** * Created by: IntelliJ IDEA @@ -10,34 +22,73 @@ * Time : 23:26 * Filename : NitriteCustomerDAO */ -public class MongoCustomerDAO implements CustomerDAO { - @Override - public void save(Customer customer) { +@Slf4j +public class MongoCustomerDAO implements CustomerDAO { + static String connString = "mongodb://localhost:27017/"; + static MongoClient mongoClient = null; + static MongoDatabase database = null; + static MongoCollection customerCollection = null; + + static { + try { + mongoClient = MongoClients.create(connString); + database = mongoClient.getDatabase("dao_factory"); + customerCollection = database.getCollection("customer"); + } catch (RuntimeException e) { + LOGGER.error("Error: %s", e); + } } + @Override - public void update(Customer customer) { + public void save(Customer customer) { + Document customerDocument = new Document("_id", customer.getId()); + customerDocument.append("name", customer.getName()); + customerCollection.insertOne(customerDocument); + } + @Override + public void update(Customer customer) { + Document updateQuery = new Document("_id", customer.getId()); + Bson update = Updates.set("name", customer.getName()); + customerCollection.updateOne(updateQuery, update); } @Override - public void delete(Long id) { + public void delete(ObjectId objectId) { + Bson deleteQuery = Filters.eq("_id", objectId); + customerCollection.deleteOne(deleteQuery); + } + @Override + public List> findAll() { + List> customers = new LinkedList<>(); + FindIterable customerDocuments = customerCollection.find(); + for (Document customerDocument : customerDocuments) { + Customer customer = new Customer<>((ObjectId) customerDocument.get("_id"), + customerDocument.getString("name")); + customers.add(customer); + } + return customers; } @Override - public Optional findById(Long id) { - return Optional.empty(); + public Optional> findById(ObjectId objectId) { + Bson filter = Filters.eq("_id", objectId); + Document customerDocument = customerCollection.find(filter) + .first(); + Customer customerResult = null; + if (customerDocument != null) { + customerResult = + new Customer<>((ObjectId) customerDocument.get("_id"), + customerDocument.getString("name")); + } + return Optional.ofNullable(customerResult); } -// @Override -// public List findAll() { -// return List.of(); -// } -// -// @Override -// public Optional findById(Long id) { -// return Optional.empty(); -// } + @Override + public void deleteSchema() { + database.drop(); + } } diff --git a/dao-factory/src/main/resources/logback.xml b/dao-factory/src/main/resources/logback.xml new file mode 100644 index 000000000000..e0c061f15b8f --- /dev/null +++ b/dao-factory/src/main/resources/logback.xml @@ -0,0 +1,10 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java index 3a2e4ce79f55..0d167d6536ca 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -6,5 +6,6 @@ * Date : 21/04/2025 * Time : 01:02 * Filename : AppTest - */public class AppTest { + */ +public class AppTest { } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java index 20ef721200e2..48a8e752520f 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -6,5 +6,6 @@ * Date : 21/04/2025 * Time : 01:05 * Filename : FlatFileCustomerDAOTest - */public class FlatFileCustomerDAOTest { + */ +public class FlatFileCustomerDAOTest { } diff --git a/pom.xml b/pom.xml index 8337c97966da..3f9628396bb5 100644 --- a/pom.xml +++ b/pom.xml @@ -231,7 +231,6 @@ table-module template-method templateview - thread-pool-executor throttling tolerant-reader trampoline @@ -246,7 +245,7 @@ visitor backpressure actor-model - + dao-factory From 2960794199fa5528b2f90e01e1e26c06b3f9bfe3 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 3 May 2025 20:07:46 +0700 Subject: [PATCH 03/10] test: Add unit test dao-factory --- dao-factory/README.md | 27 ++ dao-factory/etc/dao-factory.puml | 78 +++ dao-factory/pom.xml | 20 +- .../java/com/iluwatar/daofactory/App.java | 84 ++-- .../com/iluwatar/daofactory/Customer.java | 8 +- .../com/iluwatar/daofactory/CustomerDAO.java | 57 ++- .../com/iluwatar/daofactory/DAOFactory.java | 34 +- .../iluwatar/daofactory/DataSourceEnum.java | 14 - .../iluwatar/daofactory/DataSourceType.java | 10 + .../daofactory/FlatFileCustomerDAO.java | 93 ++-- .../daofactory/FlatFileDataSourceFactory.java | 21 +- .../iluwatar/daofactory/H2CustomerDAO.java | 242 ++++------ .../daofactory/H2DataSourceFactory.java | 25 +- .../iluwatar/daofactory/MongoCustomerDAO.java | 54 ++- .../daofactory/MongoDataSourceFactory.java | 27 +- dao-factory/src/main/resources/logback.xml | 4 +- .../java/com/iluwatar/daofactory/AppTest.java | 18 +- .../com/iluwatar/daofactory/CustomerTest.java | 11 - .../iluwatar/daofactory/DAOFactoryTest.java | 32 +- .../daofactory/FlatFileCustomerDAOTest.java | 443 +++++++++++++++++- .../daofactory/H2CustomerDAOTest.java | 280 ++++++++++- .../daofactory/MongoCustomerDAOTest.java | 134 +++++- 22 files changed, 1364 insertions(+), 352 deletions(-) create mode 100644 dao-factory/etc/dao-factory.puml delete mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java delete mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java diff --git a/dao-factory/README.md b/dao-factory/README.md index e69de29bb2d1..2338e52d1918 100644 --- a/dao-factory/README.md +++ b/dao-factory/README.md @@ -0,0 +1,27 @@ +## Also known as + + + +## Intent of Data Access Object Factory Design Pattern + + +## Detailed Explanation of Data Access Object Factory Pattern with Real-World Examples + +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 + +## When to Use the Data Access Object Factory Pattern in Java + +## Data Access Object Factory Pattern Java Tutorials + + +## Benefits and Trade-offs of Data Access Object Factory Pattern + +## Real-World Applications of Data Access Object Factory Pattern in Java + +## Related Java Design Patterns + +## References and Credits diff --git a/dao-factory/etc/dao-factory.puml b/dao-factory/etc/dao-factory.puml new file mode 100644 index 000000000000..04f113ac173b --- /dev/null +++ b/dao-factory/etc/dao-factory.puml @@ -0,0 +1,78 @@ +@startuml +package com.iluwatar.daofactory { + class App { + {static} void main(String[] args) + {static} void performCreateCustomer(CustomerDAO customerDAO, \n List> customerList) + {static} void performUpdateCustomer(CustomerDAO customerDAO, \n Customer customerUpdate) + {static} void performDeleteCustomer(CustomerDAO customerDAO, \n T id) + {static} void deleteSchema(CustomerDAO customerDAO) + } + + class Customer { + T id + String name + } + + interface CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(ID id) + List> findAll() + Optional> findById(ID id) + void deleteSchema() + } + + abstract class DAOFactory { + {static} DAOFactory getDataSource(DataSourceType dataType) + {abstract} CustomerDAO createCustomerDAO() + } + + enum DataSourceType { + H2 + Mongo + FlatFile + } + + class FlatFileCustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(Long id) + List> findAll() + Optional> findById(Long id) + void deleteSchema() + } + + class H2CustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(Long id) + List> findAll() + Optional> findById(Long id) + void deleteSchema() + } + + class FlatFileDataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + class H2DataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + class MongoCustomerDAO implements CustomerDAO { + void save(Customer customer) + void update(Customer customer) + void delete(ObjectId id) + List> findAll() + Optional> findById(ObjectId id) + void deleteSchema() + } + class MongoDataSourceFactory extends DAOFactory { + CustomerDAO createCustomerDAO() + } + + DataSourceType ..+ DAOFactory + DAOFactory ..+ App + App --> Customer + } +@enduml \ No newline at end of file diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml index b06fd29d8db3..5aea50b00f93 100644 --- a/dao-factory/pom.xml +++ b/dao-factory/pom.xml @@ -31,10 +31,10 @@ ch.qos.logback logback-classic - - org.mongodb - bson - + + + + org.mongodb mongodb-driver-legacy @@ -43,6 +43,18 @@ com.google.code.gson gson + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.mockito + mockito-core + test + \ No newline at end of file diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java index aaa91a106569..d011d0168c80 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -1,51 +1,39 @@ package com.iluwatar.daofactory; -import java.sql.SQLException; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:08 - * Filename : ${NAME} - */ @Slf4j public class App { - private static final Logger logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); - - - public static void main(String[] args) throws SQLException { - final var h2DAO = DAOFactory.getDataSource(DataSourceEnum.H2); - final var mongoDAO = DAOFactory.getDataSource(DataSourceEnum.Mongo); - final var flatFileDAO = DAOFactory.getDataSource(DataSourceEnum.FlatFile); - - final var h2CustomerDAO = h2DAO.getCustomerDAO(); - final var mongoCustomerDAO = mongoDAO.getCustomerDAO(); - final var flatFileCustomerDAO = flatFileDAO.getCustomerDAO(); + public static void main(String[] args) { + var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); + var customerDAO = daoFactory.createCustomerDAO(); // Perform CRUD H2 Database + if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) { + h2CustomerDAO.deleteSchema(); + h2CustomerDAO.createSchema(); + } Customer customerInmemory1 = new Customer<>(1L, "Green"); Customer customerInmemory2 = new Customer<>(2L, "Red"); Customer customerInmemory3 = new Customer<>(3L, "Blue"); Customer customerUpdateInmemory = new Customer<>(1L, "Yellow"); LOGGER.debug("H2 - Create customer"); - performCreateCustomer(h2CustomerDAO, + performCreateCustomer(customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3)); LOGGER.debug("H2 - Update customer"); - performUpdateCustomer(h2CustomerDAO, customerUpdateInmemory); + performUpdateCustomer(customerDAO, customerUpdateInmemory); LOGGER.debug("H2 - Delete customer"); - performDeleteCustomer(h2CustomerDAO, 3L); - deleteSchema(h2CustomerDAO); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); // Perform CRUD MongoDb + daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + customerDAO = daoFactory.createCustomerDAO(); ObjectId idCustomerMongo1 = new ObjectId(); ObjectId idCustomerMongo2 = new ObjectId(); Customer customer4 = new Customer<>(idCustomerMongo1, "Masca"); @@ -53,57 +41,59 @@ public static void main(String[] args) throws SQLException { Customer customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry"); LOGGER.debug("Mongo - Create customer"); - performCreateCustomer(mongoCustomerDAO, List.of(customer4, customer5)); + performCreateCustomer(customerDAO, List.of(customer4, customer5)); LOGGER.debug("Mongo - Update customer"); - performUpdateCustomer(mongoCustomerDAO, customerUpdateMongo); + performUpdateCustomer(customerDAO, customerUpdateMongo); LOGGER.debug("Mongo - Delete customer"); - performDeleteCustomer(mongoCustomerDAO, idCustomerMongo2); - deleteSchema(mongoCustomerDAO); + performDeleteCustomer(customerDAO, idCustomerMongo2); + deleteSchema(customerDAO); // Perform CRUD Flat file + daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + customerDAO = daoFactory.createCustomerDAO(); Customer customerFlatFile1 = new Customer<>(1L, "Duc"); Customer customerFlatFile2 = new Customer<>(2L, "Quang"); Customer customerFlatFile3 = new Customer<>(3L, "Nhat"); Customer customerUpdateFlatFile = new Customer<>(1L, "Thanh"); LOGGER.debug("Flat file - Create customer"); - performCreateCustomer(flatFileCustomerDAO, + performCreateCustomer(customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); LOGGER.debug("Flat file - Update customer"); - performUpdateCustomer(flatFileCustomerDAO, customerUpdateFlatFile); + performUpdateCustomer(customerDAO, customerUpdateFlatFile); LOGGER.debug("Flat file - Delete customer"); - performDeleteCustomer(flatFileCustomerDAO, 3L); - deleteSchema(flatFileCustomerDAO); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); } public static void deleteSchema(CustomerDAO customerDAO) { customerDAO.deleteSchema(); } - public static void performCreateCustomer(CustomerDAO customerDAO, - List> customerList) { - for (Customer customer : customerList) { + public static void performCreateCustomer(CustomerDAO customerDAO, + List> customerList) { + for (Customer customer : customerList) { customerDAO.save(customer); } - List> customers = customerDAO.findAll(); - for (Customer customer : customers) { + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { LOGGER.debug(customer.toString()); } } - public static void performUpdateCustomer(CustomerDAO customerDAO, - Customer customerUpdate) { + public static void performUpdateCustomer(CustomerDAO customerDAO, + Customer customerUpdate) { customerDAO.update(customerUpdate); - List> customers = customerDAO.findAll(); - for (Customer customer : customers) { - LOGGER.error(customer.toString()); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); } } - public static void performDeleteCustomer(CustomerDAO customerDAO, - ID customerId) { + public static void performDeleteCustomer(CustomerDAO customerDAO, + T customerId) { customerDAO.delete(customerId); - List> customers = customerDAO.findAll(); - for (Customer customer : customers) { + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { LOGGER.debug(customer.toString()); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java index 65a0cc67637d..ed46a27ff957 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -8,11 +8,9 @@ import lombok.ToString; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:22 - * Filename : Customer + * 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 diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java index c9c336471263..93595ca2e89b 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -4,22 +4,57 @@ import java.util.Optional; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:16 - * Filename : CustomerDAO + * The Data Access Object (DAO) pattern provides an abstraction layer between the application + * and the database. It encapsulates data access logic, allowing the application to work + * with domain objects instead of direct database operations. + * + *

Implementations handle specific storage mechanisms (e.g., in-memory, databases) + * while keeping client code unchanged. + * + * @see H2CustomerDAO + * @see MongoCustomerDAO + * @see FlatFileCustomerDAO */ -public interface CustomerDAO { - void save(Customer customer); +public interface CustomerDAO { + /** + * Persist the given customer + * + * @param customer the customer to persist + */ + void save(Customer customer); - void update(Customer customer); + /** + * Update the given customer + * + * @param customer the customer to update + */ + void update(Customer customer); - void delete(ID id); + /** + * Delete the customer with the given id + * + * @param id the id of the customer to delete + */ + void delete(T id); - List> findAll(); + /** + * Find all customers + * + * @return a list of customers + */ + List> findAll(); - Optional> findById(ID id); + /** + * Find the customer with the given id + * + * @param id the id of the customer to find + * @return the customer with the given id + */ + Optional> findById(T id); + /** + * Delete the customer schema. After executing the statements, + * this function will be called to clean up the data and delete the records. + */ void deleteSchema(); } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java index e53c0e46547b..0432cfa603e2 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -1,20 +1,38 @@ package com.iluwatar.daofactory; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:16 - * Filename : DAOFactory + * An abstract factory class that provides a way to create concrete DAO (Data Access Object) + * factories for different data sources types (e.g., H2, Mongo, FlatFile). + *

+ * This class follows the Abstract Factory design pattern, allowing applications to retrieve + * the approriate DAO implementation without being tightly coupled to a specific data source. + *

+ * + * @see H2DataSourceFactory + * @see MongoDataSourceFactory + * @see FlatFileDataSourceFactory */ public abstract class DAOFactory { - public static DAOFactory getDataSource(DataSourceEnum dataSourceEnum) { - return switch (dataSourceEnum) { + /** + * 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 FlatFile -> new FlatFileDataSourceFactory(); }; } - public abstract CustomerDAO getCustomerDAO(); + /** + * Retrieves a {@link CustomerDAO} implementation specific to the underlying data source.. + * + * @return A data source-specific implementation of {@link CustomerDAO} + */ + public abstract CustomerDAO createCustomerDAO(); } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java deleted file mode 100644 index 57172b0a6ff5..000000000000 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceEnum.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.iluwatar.daofactory; - -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 17/04/2025 - * Time : 00:21 - * Filename : DataSourceEnum - */ -public enum DataSourceEnum { - H2, - Mongo, - FlatFile -} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java new file mode 100644 index 000000000000..aca5aae8278f --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -0,0 +1,10 @@ +package com.iluwatar.daofactory; + +/** + * Enumerates the types of data sources supported by the application. + */ +public enum DataSourceType { + H2, + Mongo, + FlatFile +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java index f207e8dfe6ac..55e9423aff30 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -1,7 +1,6 @@ package com.iluwatar.daofactory; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import java.io.FileReader; import java.io.FileWriter; @@ -10,57 +9,66 @@ import java.io.Writer; import java.lang.reflect.Type; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; import java.util.Optional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:25 - * Filename : FlatFileCustomerDAO + * 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 { - Path filePath = Paths.get(System.getProperty("user.home"), "Desktop", "customer.json"); - Gson gson = new GsonBuilder() - .setPrettyPrinting() - .serializeNulls() - .create(); + private final Path filePath; + private final Gson gson; Type customerListType = new TypeToken>>() { }.getType(); + protected Reader createReader(Path filePath) throws IOException { + return new FileReader(filePath.toFile()); + } + + protected Writer createWriter(Path filePath) throws IOException { + return new FileWriter(filePath.toFile()); + } + + /** + * {@inheritDoc} + */ @Override public void save(Customer customer) { List> customers = new LinkedList<>(); - if (filePath.toFile().exists() && filePath.toFile().length() > 0) { - try (Reader reader = new FileReader(filePath.toFile())) { + if (filePath.toFile().exists()) { + try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to read customer data", ex); } } customers.add(customer); - try (Writer writer = new FileWriter(filePath.toFile())) { + try (Writer writer = createWriter(filePath)) { gson.toJson(customers, writer); } catch (IOException ex) { - LOGGER.error("Error writing JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to write customer data", ex); } } + /** + * {@inheritDoc} + */ @Override public void update(Customer customer) { if (!filePath.toFile().exists()) { throw new RuntimeException("File not found"); } - List> customers = new LinkedList<>(); - try (Reader reader = new FileReader(filePath.toFile())) { + List> customers; + try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to read customer data", ex); } customers.stream() .filter(c -> c.getId() @@ -72,51 +80,68 @@ public void update(Customer customer) { throw new RuntimeException("Customer not found with id: " + customer.getId()); } ); + try (Writer writer = createWriter(filePath)) { + gson.toJson(customers, writer); + } catch (IOException ex) { + throw new RuntimeException("Failed to write customer data", ex); + } } + /** + * {@inheritDoc} + */ @Override public void delete(Long id) { if (!filePath.toFile().exists()) { throw new RuntimeException("File not found"); } - List> customers = new LinkedList<>(); - try (Reader reader = new FileReader(filePath.toFile())) { + List> customers; + try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to read customer data", ex); } Customer customerToRemove = customers.stream().filter(c -> c.getId().equals(id)) .findFirst() .orElseThrow(() -> new RuntimeException("Customer not found with id: " + id)); customers.remove(customerToRemove); + try (Writer writer = createWriter(filePath)) { + gson.toJson(customers, writer); + } catch (IOException ex) { + throw new RuntimeException("Failed to write customer data", ex); + } } + /** + * {@inheritDoc} + */ @Override public List> findAll() { if (!filePath.toFile().exists()) { throw new RuntimeException("File not found"); } - List> customers = null; - try (Reader reader = new FileReader(filePath.toFile())) { + List> customers; + try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); - } catch (IOException ex) { - LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to read customer data", ex); } return customers; } + /** + * {@inheritDoc} + */ @Override public Optional> findById(Long id) { if (!filePath.toFile().exists()) { throw new RuntimeException("File not found"); } List> customers = null; - try (Reader reader = new FileReader(filePath.toFile())) { + try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); - } catch (IOException ex) { - LOGGER.error("Error while reading JSON: {}", ex.getMessage(), ex); + throw new RuntimeException("Failed to read customer data", ex); } return customers.stream() .filter(c -> c.getId() @@ -124,10 +149,14 @@ public Optional> findById(Long id) { .findFirst(); } + /** + * {@inheritDoc} + */ @Override public void deleteSchema() { - if (filePath.toFile().exists()) { - filePath.toFile().delete(); + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); } + filePath.toFile().delete(); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java index 72742d60b1cf..b0f9d78f831a 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -1,15 +1,22 @@ package com.iluwatar.daofactory; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.nio.file.Path; +import java.nio.file.Paths; + /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:19 - * Filename : FlatFileDataSourceFactory + * FlatFileDataSourceFactory concrete factory. */ public class FlatFileDataSourceFactory extends DAOFactory { + private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json"; @Override - public CustomerDAO getCustomerDAO() { - return new FlatFileCustomerDAO(); + public CustomerDAO createCustomerDAO() { + Path filePath = Paths.get(FILE_PATH); + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create(); + return new FlatFileCustomerDAO(filePath, gson); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java index 3f323dd2a200..22bfc38df835 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -1,199 +1,161 @@ package com.iluwatar.daofactory; import java.sql.Connection; -import java.sql.DriverManager; 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.Optional; +import javax.sql.DataSource; +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:26 - * Filename : H2CustomerDAO + * 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 { - static final String JDBC_DRIVER = "org.h2.Driver"; - static final String DB_URL = "jdbc:h2:~/test"; - - // Database credentials - static final String USER = "sa"; - static final String PASS = ""; - - 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 * FROM customer WHERE id= ?"; - private static final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer"; + 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"; + /** + * {@inheritDoc} + */ @Override public void save(Customer customer) { - Connection connection = null; - PreparedStatement preparedStatement = null; - try { - Class.forName(JDBC_DRIVER); - connection = DriverManager.getConnection(DB_URL, USER, PASS); - preparedStatement = connection.prepareStatement( - "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"); - preparedStatement.execute(); - preparedStatement = connection.prepareStatement(INSERT_CUSTOMER); - preparedStatement.setLong(1, customer.getId()); - preparedStatement.setString(2, customer.getName()); - preparedStatement.execute(); - connection.commit(); - } catch (SQLException | ClassNotFoundException e) { - try { - connection.rollback(); - } catch (SQLException ex) { - LOGGER.error("Exception occurred: " + e); - } - } finally { - try { - if (preparedStatement != null) { - preparedStatement.close(); - } - if (connection != null) { - connection.close(); - } - } catch (SQLException e) { - LOGGER.error("Exception occurred: " + e); - } + 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 RuntimeException(e.getMessage(), e); } } + /** + * {@inheritDoc} + */ @Override public void update(Customer customer) { - Connection connection = null; - PreparedStatement preparedStatement = null; - try { - Class.forName(JDBC_DRIVER); - connection = DriverManager.getConnection(DB_URL, USER, PASS); - preparedStatement = connection.prepareStatement(UPDATE_CUSTOMER); - preparedStatement.setString(1, customer.getName()); - preparedStatement.setLong(2, customer.getId()); - preparedStatement.execute(); - } catch (SQLException | ClassNotFoundException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); - } finally { - try { - if (preparedStatement != null) { - preparedStatement.close(); - } - if (connection != null) { - connection.close(); + 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 RuntimeException("Customer not found with id: " + customer.getId()); } - } catch (SQLException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); } + updateStatement.setString(1, customer.getName()); + updateStatement.setLong(2, customer.getId()); + updateStatement.executeUpdate(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); } } + /** + * {@inheritDoc} + */ @Override public void delete(Long id) { - Connection connection = null; - PreparedStatement preparedStatement = null; - try { - Class.forName(JDBC_DRIVER); - connection = DriverManager.getConnection(DB_URL, USER, PASS); - preparedStatement = connection.prepareStatement(DELETE_CUSTOMER); - preparedStatement.setLong(1, id); - preparedStatement.execute(); - } catch (SQLException | ClassNotFoundException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); - } finally { - try { - if (preparedStatement != null) { - preparedStatement.close(); + 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 RuntimeException("Customer not found with id: " + id); } - if (connection != null) { - connection.close(); - } - } catch (SQLException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); } + deleteStatement.setLong(1, id); + deleteStatement.execute(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); } } + /** + * {@inheritDoc} + */ @Override public List> findAll() { - Connection connection = null; - PreparedStatement preparedStatement = null; List> customers = new LinkedList<>(); - try { - Class.forName(JDBC_DRIVER); - connection = DriverManager.getConnection(DB_URL, USER, PASS); - preparedStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS); - ResultSet resultSet = preparedStatement.executeQuery(); - while (resultSet.next()) { - Long idCustomer = resultSet.getLong("id"); - String nameCustomer = resultSet.getString("name"); - customers.add(new Customer<>(idCustomer, nameCustomer)); - } - resultSet.close(); - } catch (SQLException | ClassNotFoundException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); - } finally { - try { - if (preparedStatement != null) { - preparedStatement.close(); - } - if (connection != null) { - connection.close(); + 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) { - LOGGER.error("Exception occurred: " + e.getMessage()); } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); } return customers; } + /** + * {@inheritDoc} + */ @Override public Optional> findById(Long id) { - Connection connection = null; - PreparedStatement preparedStatement = null; Customer customer = null; - try { - Class.forName(JDBC_DRIVER); - connection = DriverManager.getConnection(DB_URL, USER, PASS); - preparedStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); - preparedStatement.setLong(1, id); - ResultSet resultSet = preparedStatement.executeQuery(); - while (resultSet.next()) { - Long idCustomer = resultSet.getLong("id"); - String nameCustomer = resultSet.getString("name"); - customer = new Customer<>(idCustomer, nameCustomer); - } - resultSet.close(); - } catch (SQLException | ClassNotFoundException e) { - LOGGER.error("Exception occurred: " + e.getMessage()); - } finally { - try { - if (preparedStatement != null) { - preparedStatement.close(); - } - if (connection != null) { - connection.close(); + 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) { - LOGGER.error("Exception occurred: " + e.getMessage()); } + } catch (SQLException e) { + throw new RuntimeException(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 RuntimeException(e.getMessage(), e); + } + } + + /** + * {@inheritDoc}} + */ @Override public void deleteSchema() { - try (var connection = DriverManager.getConnection(DB_URL, USER, PASS)) { - var statement = connection.createStatement(); - statement.execute("DROP TABLE customer"); + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement();) { + statement.execute(DROP_SCHEMA); } catch (SQLException e) { - throw new RuntimeException("Error while deleting schema", e); + throw new RuntimeException(e.getMessage(), e); } } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index eec637c1f13a..c6bb9a38ba5e 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -1,15 +1,26 @@ package com.iluwatar.daofactory; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; + /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:18 - * Filename : H2DataSourceFactory + * H2DataSourceFactory concrete factory. */ 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 getCustomerDAO() { - return new H2CustomerDAO(); + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + return dataSource; } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java index 5d8ca0bc1e34..bc1fd1befa5c 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -1,46 +1,30 @@ package com.iluwatar.daofactory; import com.mongodb.client.FindIterable; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; 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; /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:26 - * Filename : NitriteCustomerDAO + * An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */ @Slf4j +@RequiredArgsConstructor public class MongoCustomerDAO implements CustomerDAO { - static String connString = "mongodb://localhost:27017/"; - static MongoClient mongoClient = null; - static MongoDatabase database = null; - static MongoCollection customerCollection = null; - - static { - try { - mongoClient = MongoClients.create(connString); - database = mongoClient.getDatabase("dao_factory"); - customerCollection = database.getCollection("customer"); - } catch (RuntimeException e) { - LOGGER.error("Error: %s", e); - } - - } - + private final MongoCollection customerCollection; + /** + * {@inheritDoc} + */ @Override public void save(Customer customer) { Document customerDocument = new Document("_id", customer.getId()); @@ -48,6 +32,9 @@ public void save(Customer customer) { customerCollection.insertOne(customerDocument); } + /** + * {@inheritDoc} + */ @Override public void update(Customer customer) { Document updateQuery = new Document("_id", customer.getId()); @@ -55,12 +42,21 @@ public void update(Customer customer) { customerCollection.updateOne(updateQuery, update); } + /** + * {@inheritDoc} + */ @Override public void delete(ObjectId objectId) { Bson deleteQuery = Filters.eq("_id", objectId); - customerCollection.deleteOne(deleteQuery); + DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery); + if (deleteResult.getDeletedCount() == 0) { + throw new RuntimeException("Delete failed: No document found with id: " + objectId); + } } + /** + * {@inheritDoc} + */ @Override public List> findAll() { List> customers = new LinkedList<>(); @@ -73,6 +69,9 @@ public List> findAll() { return customers; } + /** + * {@inheritDoc} + */ @Override public Optional> findById(ObjectId objectId) { Bson filter = Filters.eq("_id", objectId); @@ -87,8 +86,11 @@ public Optional> findById(ObjectId objectId) { return Optional.ofNullable(customerResult); } + /** + * {@inheritDoc} + */ @Override public void deleteSchema() { - database.drop(); + customerCollection.drop(); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java index f06c0f72501b..e895f182d583 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -1,15 +1,28 @@ 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; + /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 16/04/2025 - * Time : 23:19 - * Filename : NitriteDataSourceFactory + * MongoDataSourceFactory concrete factory. */ 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 getCustomerDAO() { - return new MongoCustomerDAO(); + public CustomerDAO createCustomerDAO() { + try { + MongoClient mongoClient = MongoClients.create(CONN_STR); + MongoDatabase database = mongoClient.getDatabase(DB_NAME); + MongoCollection customerCollection = database.getCollection(COLLECTION_NAME); + return new MongoCustomerDAO(customerCollection); + } catch (RuntimeException e) { + throw new RuntimeException("Error: " + e); + } } } diff --git a/dao-factory/src/main/resources/logback.xml b/dao-factory/src/main/resources/logback.xml index e0c061f15b8f..f82341ebb2ab 100644 --- a/dao-factory/src/main/resources/logback.xml +++ b/dao-factory/src/main/resources/logback.xml @@ -4,7 +4,9 @@ %d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{15}) %logger{36} - %msg%n - + + + \ No newline at end of file diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java index 0d167d6536ca..c5f531b190e7 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -1,11 +1,13 @@ package com.iluwatar.daofactory; -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:02 - * Filename : AppTest - */ -public class AppTest { +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + + +class AppTest { + @Test + void shouldExecuteDaoWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java deleted file mode 100644 index 90e94f57b471..000000000000 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/CustomerTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.iluwatar.daofactory; - -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:03 - * Filename : CustomerTest - */ -public class CustomerTest { -} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java index e4a3dc197ce4..8a3313fc9f8a 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -1,11 +1,29 @@ package com.iluwatar.daofactory; -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:01 - * Filename : DAOFactoryTest - */ +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + public class DAOFactoryTest { + + @Test + void verifyH2CustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); + var customerDAO = daoFactory.createCustomerDAO(); + assertTrue(customerDAO instanceof H2CustomerDAO); + } + + @Test + void verifyMongoCustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + var customerDAO = daoFactory.createCustomerDAO(); + assertTrue(customerDAO instanceof MongoCustomerDAO); + } + + @Test + void verifyFlatFileCustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + var customerDAO = daoFactory.createCustomerDAO(); + assertTrue(customerDAO instanceof FlatFileCustomerDAO); + } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java index 48a8e752520f..3e870997afd2 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -1,11 +1,440 @@ package com.iluwatar.daofactory; -/** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:05 - * Filename : FlatFileCustomerDAOTest - */ + +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.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.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; + public class FlatFileCustomerDAOTest { + private Path filePath; + private File file; + private Gson gson; + + private final Type customerListType = new TypeToken>>() { + }.getType(); + private final Customer existingCustomer = new Customer<>(1L, "Thanh"); + private FlatFileCustomerDAO flatFileCustomerDAO; + private FileReader fileReader; + private FileWriter fileWriter; + + @BeforeEach + void setUp() { + filePath = mock(Path.class); + file = mock(File.class); + gson = mock(Gson.class); + fileReader = mock(FileReader.class); + fileWriter = mock(FileWriter.class); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; + when(filePath.toFile()).thenReturn(file); + } + + @Nested + class Save { + @Test + void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() { + when(file.exists()).thenReturn(false); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).toJson(argThat( + (List> list) -> list.size() == 1 && + list.getFirst().equals(existingCustomer)), + eq(fileWriter)); + } + + @Test + void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson).toJson(argThat((List> list) -> list.size() == 1 && + list.getFirst().equals(existingCustomer)), eq(fileWriter)); + } + + @Test + void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() { + List> customers = new LinkedList<>(); + customers.add(new Customer<>(2L, "Duc")); + customers.add(new Customer<>(3L, "Nguyen")); + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(customers); + + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson).toJson(argThat((List> list) -> list.size() == 3), + eq(fileWriter)); + } + + + @Test + void whenReadFails_thenThrowException() { + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + when(file.exists()).thenReturn(true); + assertThrows(RuntimeException.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(RuntimeException.class, () -> flatFileCustomerDAO.save(existingCustomer)); + } + } + + + @Nested + class Update { + @Test + void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(RuntimeException.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(RuntimeException.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(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; + flatFileCustomerDAO.update(existingCustomer); + verify(gson).toJson(argThat((List> customers) -> + customers.size() == 1 && customers.stream() + .anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh")) + ) + , eq(fileWriter)); + } + + @Test + void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(2L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + } + } + + @Nested + class Delete { + @Test + void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(RuntimeException.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(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); + } + + @Test + void whenWriteFails_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + throw new IOException("Failed to write file"); + } + }; + assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); + } + + @Test + void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + + flatFileCustomerDAO.delete(1L); + assertEquals(1, existingListCustomer.size()); + verify(gson).toJson(argThat((List> customers) -> + customers.stream().noneMatch(c -> c.getId().equals(1L) && + c.getName().equals("Quang"))), + eq(fileWriter)); + } + + @Test + void givenIdNotExist_whenDeleteCustomer_thenThrowException() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(3L)); + } + } + + @Nested + class FindAll { + @Test + void givenFileNotExist_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(RuntimeException.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(RuntimeException.class, () -> flatFileCustomerDAO.findAll()); + } + + @Test + void givenEmptyCustomer_thenReturnEmptyList() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + List> customers = flatFileCustomerDAO.findAll(); + assertEquals(0, customers.size()); + verify(gson).fromJson(fileReader, customerListType); + } + + @Test + void givenCustomerExist_thenReturnCustomerList() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + List> customers = flatFileCustomerDAO.findAll(); + assertEquals(2, customers.size()); + } + } + + @Nested + class FindById { + + @Test + void givenFilePathNotExist_whenFindById_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(RuntimeException.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(RuntimeException.class, () -> flatFileCustomerDAO.findById(1L)); + } + + @Test + void givenIdCustomerExist_whenFindById_thenReturnCustomer() { + when(file.exists()).thenReturn(true); + List> existingListCustomer = new LinkedList<>(); + existingListCustomer.add(new Customer<>(1L, "Quang")); + existingListCustomer.add(new Customer<>(2L, "Thanh")); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); + Optional> customer = flatFileCustomerDAO.findById(1L); + assertTrue(customer.isPresent()); + assertEquals("Quang", customer.get().getName()); + } + + @Test + void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + Optional> customers = flatFileCustomerDAO.findById(1L); + assertTrue(customers.isEmpty()); + } + } + + @Nested + class DeleteSchema { + @Test + void givenFilePathExist_thenDeleteFile() { + when(file.exists()).thenReturn(true); + flatFileCustomerDAO.deleteSchema(); + verify(file, times(1)).delete(); + } + + @Test + void givenFilePathNotExist_thenThrowException() { + when(file.exists()).thenReturn(false); + assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.deleteSchema()); + verify(file, times(0)).delete(); + } + } } + + diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java index 54ac4495df38..52e935b57ce4 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -1,11 +1,281 @@ 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; + /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:05 - * Filename : H2CustomerDAOTest + * Tests {@link H2CustomerDAO} */ public class H2CustomerDAOTest { + private final String DB_URL = "jdbc:h2:~/test"; + private final String USER = "sa"; + private final String PASS = ""; + 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"; + private final Customer existingCustomer = new Customer<>(1L, "Nguyen"); + private H2CustomerDAO h2CustomerDAO; + + @BeforeEach + void createSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); + var statement = connection.createStatement()) { + statement.execute(CREATE_SCHEMA); + } + } + + @AfterEach + void deleteSchema() throws SQLException { + try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); + var statement = connection.createStatement()) { + statement.execute(DROP_SCHEMA); + } + } + + @Nested + class ConnectionSucceed { + + @BeforeEach + void setUp() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + h2CustomerDAO = new H2CustomerDAO(dataSource); +// h2CustomerDAO.setDataSource(dataSource); + assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer)); + var customer = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customer.isPresent()); + assertEquals(customer.get().getName(), existingCustomer.getName()); + assertEquals(customer.get().getId(), existingCustomer.getId()); + } + + @Nested + class SaveCustomer { + @Test + void givenValidCustomer_whenSaveCustomer_thenAddSucceed() { + var customer = new Customer<>(2L, "Duc"); + assertDoesNotThrow(() -> h2CustomerDAO.save(customer)); + var customerInDb = h2CustomerDAO.findById(customer.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(customerInDb.get().getName(), customer.getName()); + assertEquals(customerInDb.get().getId(), customer.getId()); + List> customers = h2CustomerDAO.findAll(); + assertEquals(2, customers.size()); + } + + @Test + void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() { + var customer = new Customer<>(existingCustomer.getId(), "Duc"); + assertThrows(RuntimeException.class, () -> h2CustomerDAO.save(customer)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + } + } + + @Nested + class UpdateCustomer { + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc"); + assertDoesNotThrow(() -> h2CustomerDAO.update(customerUpdate)); + var customerInDb = h2CustomerDAO.findById(customerUpdate.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(customerInDb.get().getName(), customerUpdate.getName()); + } + + @Test + void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { + var customerUpdate = new Customer<>(100L, "Duc"); + var customerInDb = h2CustomerDAO.findById(customerUpdate.getId()); + assertTrue(customerInDb.isEmpty()); + assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(customerUpdate)); + } + + @Test + void givenNull_whenUpdateCustomer_thenThrowException() { + assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(null)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + } + } + + @Nested + class DeleteCustomer { + @Test + void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { + assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId())); + var customerInDb = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customerInDb.isEmpty()); + List> customers = h2CustomerDAO.findAll(); + assertEquals(0, customers.size()); + } + + @Test + void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() { + var customerInDb = h2CustomerDAO.findById(100L); + assertTrue(customerInDb.isEmpty()); + assertThrows(RuntimeException.class, () -> h2CustomerDAO.delete(100L)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + assertEquals(existingCustomer.getId(), customers.get(0).getId()); + } + + @Test + void givenNull_whenDeleteCustomer_thenThrowException() { + assertThrows(RuntimeException.class, () -> h2CustomerDAO.delete(null)); + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + } + } + + @Nested + class FindAllCustomers { + @Test + void givenNonCustomerInDb_whenFindAllCustomer_thenReturnEmptyList() { + assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId())); + List> customers = h2CustomerDAO.findAll(); + assertEquals(0, customers.size()); + } + + @Test + void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() { + List> customers = h2CustomerDAO.findAll(); + assertEquals(1, customers.size()); + assertEquals(existingCustomer.getName(), customers.get(0).getName()); + assertEquals(existingCustomer.getId(), customers.get(0).getId()); + } + } + + @Nested + class FindCustomerById { + @Test + void givenValidId_whenFindById_thenReturnCustomer() { + var customerInDb = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(existingCustomer.getName(), customerInDb.get().getName()); + assertEquals(existingCustomer.getId(), customerInDb.get().getId()); + } + + @Test + void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { + var customerNotExist = h2CustomerDAO.findById(100L); + assertTrue(customerNotExist.isEmpty()); + } + + @Test + void givenNull_whenFindById_thenThrowException() { + assertThrows(RuntimeException.class, () -> h2CustomerDAO.findById(null)); + } + } + + @Nested + class CreateSchema { + @Test + void whenCreateSchema_thenNotThrowException() { + assertDoesNotThrow(() -> h2CustomerDAO.createSchema()); + } + } + + @Nested + class DeleteSchema { + @Test + void whenDeleteSchema_thenNotThrowException() { + assertDoesNotThrow(() -> h2CustomerDAO.deleteSchema()); + } + } + } + + @Nested + class ConnectionFailed { + private final String EXCEPTION_CAUSE = "Connection not available"; + + @BeforeEach + void setUp() throws SQLException { + h2CustomerDAO = new H2CustomerDAO(mockedDataSource()); +// h2CustomerDAO.setDataSource(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"); + RuntimeException exception = + assertThrows(RuntimeException.class, () -> h2CustomerDAO.save(customer)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenThrowException() { + var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc"); + RuntimeException exception = + assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(customerUpdate)); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void givenValidId_whenDeleteCustomer_thenThrowException() { + RuntimeException exception = + assertThrows(RuntimeException.class, + () -> h2CustomerDAO.delete(existingCustomer.getId())); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenFindAll_thenThrowException() { + RuntimeException exception = assertThrows(RuntimeException.class, h2CustomerDAO::findAll); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenFindById_thenThrowException() { + RuntimeException exception = + assertThrows(RuntimeException.class, + () -> h2CustomerDAO.findById(existingCustomer.getId())); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenCreateSchema_thenThrowException() { + RuntimeException exception = + assertThrows(RuntimeException.class, h2CustomerDAO::createSchema); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + + @Test + void whenDeleteSchema_thenThrowException() { + RuntimeException exception = + assertThrows(RuntimeException.class, h2CustomerDAO::deleteSchema); + assertEquals(EXCEPTION_CAUSE, exception.getMessage()); + } + } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java index 253dc3a89305..1a5f1f81c5f6 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -1,11 +1,135 @@ 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** - * Created by: IntelliJ IDEA - * User : dthanh - * Date : 21/04/2025 - * Time : 01:05 - * Filename : MongoCustomerDAOTest + * Tests {@link MongoCustomerDAO} */ public class MongoCustomerDAOTest { + private static final Logger log = LoggerFactory.getLogger(MongoCustomerDAOTest.class); + MongoCollection customerCollection = mock(MongoCollection.class); + MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection); + + @Test + void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() { + Customer customer = new Customer<>(new ObjectId(), "John"); + mongoCustomerDAO.save(customer); + verify(customerCollection).insertOne(argThat( + document -> document.get("_id").equals(customer.getId()) && + document.get("name").equals(customer.getName()))); + } + + @Test + void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { + ObjectId customerId = new ObjectId(); + Customer customerUpdated = new Customer<>(customerId, "John"); + when(customerCollection.updateOne(any(Bson.class), any(Bson.class))).thenReturn( + UpdateResult.acknowledged(1L, 1L, null)); + mongoCustomerDAO.update(customerUpdated); + verify(customerCollection).updateOne( + argThat((Bson filter) -> { + Document filterDoc = (Document) filter; + return filterDoc.getObjectId("_id").equals(customerId); + }), + argThat((Bson update) -> { + BsonDocument bsonDoc = update.toBsonDocument(); + BsonDocument setDoc = bsonDoc.getDocument("$set"); + return setDoc.getString("name").getValue().equals(customerUpdated.getName()); + }) + ); + } + + @Test + void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() { + ObjectId customerId = new ObjectId(); + when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(1)); + mongoCustomerDAO.delete(customerId); + verify(customerCollection).deleteOne(argThat((Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); + } + + @Test + void givenIdNotExist_whenDeleteCustomer_thenThrowException() { + ObjectId customerId = new ObjectId(); + when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0)); + assertThrows(RuntimeException.class, () -> mongoCustomerDAO.delete(customerId)); + verify(customerCollection).deleteOne(argThat((Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); + } + + @Test + void findAll_thenReturnAllCustomers() { + FindIterable findIterable = mock(FindIterable.class); + MongoCursor cursor = mock(MongoCursor.class); + Document customerDoc1 = new Document("_id", new ObjectId()).append("name", "Duc"); + Document customerDoc2 = new Document("_id", new ObjectId()).append("name", "Thanh"); + when(customerCollection.find()).thenReturn(findIterable); + when(findIterable.iterator()).thenReturn(cursor); + when(cursor.hasNext()).thenReturn(true, true, false); + when(cursor.next()).thenReturn(customerDoc1, customerDoc2); + List> customerList = mongoCustomerDAO.findAll(); + assertEquals(2, customerList.size()); + verify(customerCollection).find(); + } + + @Test + void givenValidId_whenFindById_thenReturnCustomer() { + FindIterable findIterable = mock(FindIterable.class); + ObjectId customerId = new ObjectId(); + String customerName = "Duc"; + Document customerDoc = new Document("_id", customerId).append("name", customerName); + when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn( + findIterable); + when(findIterable.first()).thenReturn(customerDoc); + + Optional> customer = mongoCustomerDAO.findById(customerId); + assertTrue(customer.isPresent()); + assertEquals(customerId, customer.get().getId()); + assertEquals(customerName, customer.get().getName()); + } + + @Test + void givenNotExistingId_whenFindById_thenReturnEmpty() { + FindIterable findIterable = mock(FindIterable.class); + ObjectId customerId = new ObjectId(); + when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn( + findIterable); + when(findIterable.first()).thenReturn(null); + Optional> customer = mongoCustomerDAO.findById(customerId); + assertTrue(customer.isEmpty()); + verify(customerCollection).find(Filters.eq("_id", customerId)); + } + + @Test + void whenDeleteSchema_thenDeleteCollection() { + mongoCustomerDAO.deleteSchema(); + verify(customerCollection).drop(); + } } From 525ad3fcb649b38d88085b4e16c34a88151af6e4 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sun, 4 May 2025 19:05:06 +0700 Subject: [PATCH 04/10] doc: add document for dao-factory --- dao-factory/README.md | 335 +++++++++++++++- dao-factory/etc/dao-factory.png | Bin 0 -> 113583 bytes dao-factory/etc/dao-factory.puml | 4 - .../java/com/iluwatar/daofactory/App.java | 22 +- .../com/iluwatar/daofactory/Customer.java | 4 +- .../com/iluwatar/daofactory/CustomerDAO.java | 14 +- .../com/iluwatar/daofactory/DAOFactory.java | 11 +- .../iluwatar/daofactory/DataSourceType.java | 4 +- .../daofactory/FlatFileCustomerDAO.java | 46 +-- .../daofactory/FlatFileDataSourceFactory.java | 10 +- .../iluwatar/daofactory/H2CustomerDAO.java | 55 +-- .../daofactory/H2DataSourceFactory.java | 4 +- .../iluwatar/daofactory/MongoCustomerDAO.java | 40 +- .../daofactory/MongoDataSourceFactory.java | 4 +- .../java/com/iluwatar/daofactory/AppTest.java | 5 +- .../iluwatar/daofactory/DAOFactoryTest.java | 11 +- .../daofactory/FlatFileCustomerDAOTest.java | 371 ++++++++++-------- .../daofactory/H2CustomerDAOTest.java | 23 +- .../daofactory/MongoCustomerDAOTest.java | 69 ++-- 19 files changed, 667 insertions(+), 365 deletions(-) create mode 100644 dao-factory/etc/dao-factory.png diff --git a/dao-factory/README.md b/dao-factory/README.md index 2338e52d1918..c3ee3c5e893d 100644 --- a/dao-factory/README.md +++ b/dao-factory/README.md @@ -1,27 +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 implements Serializable { +private T id; +private String name; +} +``` + +Next, we define a CustomerDAO interface that outlines the standard CRUD operations on the Customer model. This interface will have three concrete implementations, each corresponding to a specific data source: H2 in-memory database, MongoDB, and JSON file. + +``` java +public interface CustomerDAO { + + void save(Customer customer); + + void update(Customer customer); + + void delete(T id); + + List> findAll(); + + Optional> findById(T id); +} +``` + +Here is the implementations + +``` java +@Slf4j +@RequiredArgsConstructor +public class H2CustomerDAO implements CustomerDAO { +private final DataSource dataSource; +private final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)"; +private final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?"; +private final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?"; +private final String SELECT_CUSTOMER_BY_ID = "SELECT * FROM customer WHERE id= ?"; +private final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer"; +private final String CREATE_SCHEMA = +"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"; +private final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + + @Override + public void save(Customer customer) { + // Implement operation save for H2 + } + + @Override + public void update(Customer customer) { + // Implement operation save for H2 + } + + @Override + public void delete(Long id) { + // Implement operation delete for H2 + } + + @Override + public List> findAll() { + // Implement operation find all for H2 + } + + @Override + public Optional> findById(Long id) { + // Implement operation find by id for H2 + } +} +``` + +``` java +@Slf4j +@RequiredArgsConstructor +public class MongoCustomerDAO implements CustomerDAO { +private final MongoCollection customerCollection; + + // Implement CRUD operation with MongoDB data source +} +``` + +``` java +@Slf4j +@RequiredArgsConstructor +public class FlatFileCustomerDAO implements CustomerDAO { + private final Path filePath; + private final Gson gson; + Type customerListType = new TypeToken>>() { + }.getType(); + + // Implement CRUD operation with Flat file data source +} +``` + +After that, we create an abstract class DAOFactory that defines two key methods: a static method getDataSource() and an abstract method createCustomerDAO(). + +- The getDataSource() method is a factory selector—it returns a concrete DAOFactory instance based on the type of data source requested. + +- Each subclass of DAOFactory will implement the createCustomerDAO() method to provide the corresponding CustomerDAO implementation. + +``` java +public abstract class DAOFactory { + public static DAOFactory getDataSource(DataSourceType dataSourceType) { + return switch (dataSourceType) { + case H2 -> new H2DataSourceFactory(); + case Mongo -> new MongoDataSourceFactory(); + case FlatFile -> new FlatFileDataSourceFactory(); + }; + } + + public abstract CustomerDAO createCustomerDAO(); +} +``` + +We then implement three specific factory classes: + +H2DataSourceFactory for H2 in-memory RDBMS +``` java +public class H2DataSourceFactory extends DAOFactory { + private final String DB_URL = "jdbc:h2:~/test"; + private final String USER = "sa"; + private final String PASS = ""; + + @Override + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + return dataSource; + } +} +``` + +MongoDataSourceFactory for MongoDB +``` java +public class MongoDataSourceFactory extends DAOFactory { + private final String CONN_STR = "mongodb://localhost:27017/"; + private final String DB_NAME = "dao_factory"; + private final String COLLECTION_NAME = "customer"; + + @Override + public CustomerDAO createCustomerDAO() { + try { + MongoClient mongoClient = MongoClients.create(CONN_STR); + MongoDatabase database = mongoClient.getDatabase(DB_NAME); + MongoCollection customerCollection = database.getCollection(COLLECTION_NAME); + return new MongoCustomerDAO(customerCollection); + } catch (RuntimeException e) { + throw new RuntimeException("Error: " + e); + } + } +} +``` + +FlatFileDataSourceFactory for flat file storage using JSON +``` java +public class FlatFileDataSourceFactory extends DAOFactory { + private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json"; + @Override + public CustomerDAO createCustomerDAO() { + Path filePath = Paths.get(FILE_PATH); + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .create(); + return new FlatFileCustomerDAO(filePath, gson); + } +} +``` + +Finally, in the main function of client code, we will demonstrate CRUD operations on the Customer using three data source type. +``` java + // Perform CRUD H2 Database + LOGGER.debug("H2 - Create customer"); + performCreateCustomer(customerDAO, + List.of(customerInmemory1, customerInmemory2, customerInmemory3)); + LOGGER.debug("H2 - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateInmemory); + LOGGER.debug("H2 - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); + + // Perform CRUD MongoDb + daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + customerDAO = daoFactory.createCustomerDAO(); + LOGGER.debug("Mongo - Create customer"); + performCreateCustomer(customerDAO, List.of(customer4, customer5)); + LOGGER.debug("Mongo - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateMongo); + LOGGER.debug("Mongo - Delete customer"); + performDeleteCustomer(customerDAO, idCustomerMongo2); + deleteSchema(customerDAO); + + // Perform CRUD Flat file + daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + customerDAO = daoFactory.createCustomerDAO(); + LOGGER.debug("Flat file - Create customer"); + performCreateCustomer(customerDAO, + List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + LOGGER.debug("Flat file - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateFlatFile); + LOGGER.debug("Flat file - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); +``` + +The program output +``` java +17:17:24.368 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Create customer +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Green) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue) +17:17:24.514 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Update customer +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Blue) +17:17:24.573 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - H2 - Delete customer +17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Yellow) +17:17:24.632 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Red) +17:17:24.747 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Create customer +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Elliot) +17:17:24.834 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Update customer +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c2, name=Henry) +17:17:24.845 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Mongo - Delete customer +17:17:24.850 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=68173eb4c840286dbc2bc5c1, name=Masca) +17:17:24.876 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Create customer +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Duc) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat) +17:17:24.895 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Update customer +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=3, name=Nhat) +17:17:24.897 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Flat file - Delete customer +17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=1, name=Thanh) +17:17:24.898 [main] DEBUG c.i.d.App com.iluwatar.daofactory.App - Customer(id=2, name=Quang) +``` ## When to Use the Data Access Object Factory Pattern in Java +Use the DAO Factory Pattern when: + +* The application needs to support multiple types of storage (RDBMS, NoSQL, file system, etc.) with minimal changes to business logic. +* You want to abstract and isolate persistence logic from the core application logic. +* You aim to make your data access layer pluggable and easy to extend with new storage technologies. +* You want to enable easier unit testing and dependency injection by providing mock implementations of DAOs. +* Runtime configuration (e.g., via environment variables or application settings) determines which data source to use. + ## Data Access Object Factory Pattern Java Tutorials +* [Core J2EE Patterns - Data Access Object (Oracle)](https://www.oracle.com/java/technologies/dataaccessobject.html) +* [DAO Factories: Java Design Patterns (Youtube)](https://www.youtube.com/watch?v=5HGe9s9qM-o) +* [Java Design Patterns and Architecture (CaveofProgramming)](https://caveofprogramming.teachable.com/courses/2084/lectures/39549) + +## Real-World Applications of Data Access Object Factory Pattern in Java + +* Enterprise Java Applications: Where switching between test, dev, and production databases is common (e.g., MySQL ↔ MongoDB ↔ In-Memory). +* Spring Data JPA & Repository Abstraction: Though Spring provides its own abstraction, the concept is similar to DAO factory for modular and pluggable persistence. +* Microservices with Varying Storage Backends: Different microservices might store data in SQL, NoSQL, or even flat files; using a DAO Factory per service ensures consistency. +* Data Integration Tools: Tools that support importing/exporting from various formats (CSV, JSON, databases) often use DAO factories behind the scenes. +* Framework-Level Implementations: Custom internal frameworks where persistence layers need to support multiple database types. ## Benefits and Trade-offs of Data Access Object Factory Pattern -## Real-World Applications of Data Access Object Factory Pattern in Java +Benefits: + +* Abstraction of Data Source Logic: Client code interacts only with DAO interfaces, completely decoupled from how and where the data is stored. +* Flexibility in Persistence Strategy: Easily switch between databases (e.g., H2, MongoDB, flat files) by changing the factory configuration. +* Improved Maintainability: Storage logic for each data source is encapsulated within its own DAO implementation and factory, making it easier to update or extend. +* Code Reusability: Common data access logic (e.g., CRUD operations) can be reused across different implementations and projects. +* Testability: DAOs and factories can be mocked or stubbed easily, which supports unit testing and dependency injection. + +Trade-offs: +* Increased Complexity: Introducing abstract DAOs and multiple factory classes adds structural complexity to the codebase. +* Boilerplate Code: Requires defining many interfaces and implementations, even for simple data access needs. +* Less Transparent Behavior: Since clients access DAOs indirectly via factories, understanding the concrete data source behavior may require deeper inspection. ## Related Java Design Patterns +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): DAO Factory is a concrete application of the Factory Pattern, used to create DAO objects in a flexible way. +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): When supporting multiple data sources (e.g., MySQLDAO, OracleDAO), DAO Factory can act as an Abstract Factory. +* [Data Access Object (DAO)](https://java-design-patterns.com/patterns/data-access-object/): The core pattern managed by DAO Factory, it separates data access logic from business logic. +* [Singleton](https://java-design-patterns.com/patterns/singleton/): DAO Factory is often implemented as a Singleton to ensure only one instance manages DAO creation. +* [Service Locator](https://java-design-patterns.com/patterns/service-locator/): Can be used alongside DAO Factory to retrieve DAO services efficiently. +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): In frameworks like Spring, DAOs are typically injected into the service layer instead of being retrieved from a factory. + + ## References and Credits + +* [DAO Factory - J2EE Design Patterns Book](https://www.oreilly.com/library/view/j2ee-design-patterns/0596004273/re15.html) +* [DAO Factory patterns with Hibernate](http://www.giuseppeurso.eu/en/dao-factory-patterns-with-hibernate/) +* [Design Patterns - Java Means DURGA SOFT](https://www.scribd.com/document/407219980/2-DAO-Factory-Design-Pattern) +* [Generic DAO pattern - Hibernate](https://in.relation.to/2005/09/09/generic-dao-pattern-with-jdk-50/) + \ No newline at end of file diff --git a/dao-factory/etc/dao-factory.png b/dao-factory/etc/dao-factory.png new file mode 100644 index 0000000000000000000000000000000000000000..d93547d799575ecf42efa9c27aac4e16c437723b GIT binary patch literal 113583 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 void performCreateCustomer(CustomerDAO customerDAO, \n List> customerList) - {static} void performUpdateCustomer(CustomerDAO customerDAO, \n Customer customerUpdate) - {static} void performDeleteCustomer(CustomerDAO customerDAO, \n T id) - {static} void deleteSchema(CustomerDAO customerDAO) } class Customer { diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java index d011d0168c80..c879ad111a94 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -7,7 +7,6 @@ @Slf4j public class App { - public static void main(String[] args) { var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); var customerDAO = daoFactory.createCustomerDAO(); @@ -23,8 +22,8 @@ public static void main(String[] args) { Customer customerUpdateInmemory = new Customer<>(1L, "Yellow"); LOGGER.debug("H2 - Create customer"); - performCreateCustomer(customerDAO, - List.of(customerInmemory1, customerInmemory2, customerInmemory3)); + performCreateCustomer( + customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3)); LOGGER.debug("H2 - Update customer"); performUpdateCustomer(customerDAO, customerUpdateInmemory); LOGGER.debug("H2 - Delete customer"); @@ -56,8 +55,8 @@ public static void main(String[] args) { Customer customerFlatFile3 = new Customer<>(3L, "Nhat"); Customer customerUpdateFlatFile = new Customer<>(1L, "Thanh"); LOGGER.debug("Flat file - Create customer"); - performCreateCustomer(customerDAO, - List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + performCreateCustomer( + customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); LOGGER.debug("Flat file - Update customer"); performUpdateCustomer(customerDAO, customerUpdateFlatFile); LOGGER.debug("Flat file - Delete customer"); @@ -69,8 +68,8 @@ public static void deleteSchema(CustomerDAO customerDAO) { customerDAO.deleteSchema(); } - public static void performCreateCustomer(CustomerDAO customerDAO, - List> customerList) { + public static void performCreateCustomer( + CustomerDAO customerDAO, List> customerList) { for (Customer customer : customerList) { customerDAO.save(customer); } @@ -80,8 +79,8 @@ public static void performCreateCustomer(CustomerDAO customerDAO, } } - public static void performUpdateCustomer(CustomerDAO customerDAO, - Customer customerUpdate) { + public static void performUpdateCustomer( + CustomerDAO customerDAO, Customer customerUpdate) { customerDAO.update(customerUpdate); List> customers = customerDAO.findAll(); for (Customer customer : customers) { @@ -89,12 +88,11 @@ public static void performUpdateCustomer(CustomerDAO customerDAO, } } - public static void performDeleteCustomer(CustomerDAO customerDAO, - T customerId) { + public static void performDeleteCustomer(CustomerDAO customerDAO, T customerId) { customerDAO.delete(customerId); List> customers = customerDAO.findAll(); for (Customer customer : customers) { LOGGER.debug(customer.toString()); } } -} \ No newline at end of file +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java index ed46a27ff957..0df38bdffd60 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -9,8 +9,8 @@ /** * 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. + * 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 diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java index 93595ca2e89b..4e913a75a666 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -4,12 +4,12 @@ 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. + * The Data Access Object (DAO) pattern provides an abstraction layer between the application and + * the database. It encapsulates data access logic, allowing the application to work with domain + * objects instead of direct database operations. * - *

Implementations handle specific storage mechanisms (e.g., in-memory, databases) - * while keeping client code unchanged. + *

Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping + * client code unchanged. * * @see H2CustomerDAO * @see MongoCustomerDAO @@ -53,8 +53,8 @@ public interface CustomerDAO { Optional> findById(T id); /** - * Delete the customer schema. After executing the statements, - * this function will be called to clean up the data and delete the records. + * Delete the customer schema. After executing the statements, this function will be called to + * clean up the data and delete the records. */ void deleteSchema(); } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java index 0432cfa603e2..772c4c0d4a5b 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -3,10 +3,9 @@ /** * An abstract factory class that provides a way to create concrete DAO (Data Access Object) * factories for different data sources types (e.g., H2, Mongo, FlatFile). - *

- * This class follows the Abstract Factory design pattern, allowing applications to retrieve - * the approriate DAO implementation without being tightly coupled to a specific data source. - *

+ * + *

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 @@ -16,8 +15,8 @@ public abstract class DAOFactory { /** * 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} + * @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. */ diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java index aca5aae8278f..f309afe1e088 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -1,8 +1,6 @@ package com.iluwatar.daofactory; -/** - * Enumerates the types of data sources supported by the application. - */ +/** Enumerates the types of data sources supported by the application. */ public enum DataSourceType { H2, Mongo, diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java index 55e9423aff30..bcdcda3d2dee 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -24,8 +24,7 @@ public class FlatFileCustomerDAO implements CustomerDAO { private final Path filePath; private final Gson gson; - Type customerListType = new TypeToken>>() { - }.getType(); + Type customerListType = new TypeToken>>() {}.getType(); protected Reader createReader(Path filePath) throws IOException { return new FileReader(filePath.toFile()); @@ -35,9 +34,7 @@ protected Writer createWriter(Path filePath) throws IOException { return new FileWriter(filePath.toFile()); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void save(Customer customer) { List> customers = new LinkedList<>(); @@ -56,9 +53,7 @@ public void save(Customer customer) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void update(Customer customer) { if (!filePath.toFile().exists()) { @@ -71,15 +66,13 @@ public void update(Customer customer) { throw new RuntimeException("Failed to read customer data", ex); } customers.stream() - .filter(c -> c.getId() - .equals(customer.getId())) + .filter(c -> c.getId().equals(customer.getId())) .findFirst() .ifPresentOrElse( c -> c.setName(customer.getName()), () -> { throw new RuntimeException("Customer not found with id: " + customer.getId()); - } - ); + }); try (Writer writer = createWriter(filePath)) { gson.toJson(customers, writer); } catch (IOException ex) { @@ -87,9 +80,7 @@ public void update(Customer customer) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void delete(Long id) { if (!filePath.toFile().exists()) { @@ -101,9 +92,11 @@ public void delete(Long id) { } catch (IOException ex) { throw new RuntimeException("Failed to read customer data", ex); } - Customer customerToRemove = customers.stream().filter(c -> c.getId().equals(id)) - .findFirst() - .orElseThrow(() -> new RuntimeException("Customer not found with id: " + id)); + Customer customerToRemove = + customers.stream() + .filter(c -> c.getId().equals(id)) + .findFirst() + .orElseThrow(() -> new RuntimeException("Customer not found with id: " + id)); customers.remove(customerToRemove); try (Writer writer = createWriter(filePath)) { gson.toJson(customers, writer); @@ -112,9 +105,7 @@ public void delete(Long id) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List> findAll() { if (!filePath.toFile().exists()) { @@ -129,9 +120,7 @@ public List> findAll() { return customers; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional> findById(Long id) { if (!filePath.toFile().exists()) { @@ -143,15 +132,10 @@ public Optional> findById(Long id) { } catch (IOException ex) { throw new RuntimeException("Failed to read customer data", ex); } - return customers.stream() - .filter(c -> c.getId() - .equals(id)) - .findFirst(); + return customers.stream().filter(c -> c.getId().equals(id)).findFirst(); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void deleteSchema() { if (!filePath.toFile().exists()) { diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java index b0f9d78f831a..9e89a5a07214 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -5,18 +5,14 @@ import java.nio.file.Path; import java.nio.file.Paths; -/** - * FlatFileDataSourceFactory concrete factory. - */ +/** FlatFileDataSourceFactory concrete factory. */ 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(); + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); return new FlatFileCustomerDAO(filePath, gson); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java index 22bfc38df835..f001e68e255e 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -9,14 +9,12 @@ import java.util.List; import java.util.Optional; import javax.sql.DataSource; -import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; -import lombok.Setter; 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. + * 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 @@ -31,13 +29,11 @@ public class H2CustomerDAO implements CustomerDAO { "CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))"; private final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void save(Customer customer) { try (Connection connection = dataSource.getConnection(); - PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) { + PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) { saveStatement.setLong(1, customer.getId()); saveStatement.setString(2, customer.getName()); saveStatement.execute(); @@ -46,14 +42,12 @@ public void save(Customer customer) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void update(Customer customer) { try (Connection connection = dataSource.getConnection(); - PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); - PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) { + 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()) { @@ -68,15 +62,12 @@ public void update(Customer customer) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void delete(Long id) { try (Connection connection = dataSource.getConnection(); - PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); - PreparedStatement deleteStatement = connection.prepareStatement(DELETE_CUSTOMER) - ) { + 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()) { @@ -90,14 +81,12 @@ public void delete(Long id) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List> findAll() { List> customers = new LinkedList<>(); try (Connection connection = dataSource.getConnection(); - PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) { + PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) { try (ResultSet resultSet = selectStatement.executeQuery()) { while (resultSet.next()) { Long idCustomer = resultSet.getLong("id"); @@ -111,15 +100,13 @@ public List> findAll() { return customers; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional> findById(Long id) { Customer customer = null; try (Connection connection = dataSource.getConnection(); - PreparedStatement selectByIdStatement = connection.prepareStatement( - SELECT_CUSTOMER_BY_ID)) { + PreparedStatement selectByIdStatement = + connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) { selectByIdStatement.setLong(1, id); try (ResultSet resultSet = selectByIdStatement.executeQuery()) { while (resultSet.next()) { @@ -134,25 +121,21 @@ public Optional> findById(Long id) { return Optional.ofNullable(customer); } - /** - * Create customer schema. - */ + /** Create customer schema. */ public void createSchema() { try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { + Statement statement = connection.createStatement()) { statement.execute(CREATE_SCHEMA); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } } - /** - * {@inheritDoc}} - */ + /** {@inheritDoc}} */ @Override public void deleteSchema() { try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement();) { + Statement statement = connection.createStatement(); ) { statement.execute(DROP_SCHEMA); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index c6bb9a38ba5e..41995f934c29 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -3,9 +3,7 @@ import javax.sql.DataSource; import org.h2.jdbcx.JdbcDataSource; -/** - * H2DataSourceFactory concrete factory. - */ +/** H2DataSourceFactory concrete factory. */ public class H2DataSourceFactory extends DAOFactory { private final String DB_URL = "jdbc:h2:~/test"; private final String USER = "sa"; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java index bc1fd1befa5c..5d729e0ab2ed 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -14,17 +14,13 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; -/** - * An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) - */ +/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */ @Slf4j @RequiredArgsConstructor public class MongoCustomerDAO implements CustomerDAO { private final MongoCollection customerCollection; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void save(Customer customer) { Document customerDocument = new Document("_id", customer.getId()); @@ -32,9 +28,7 @@ public void save(Customer customer) { customerCollection.insertOne(customerDocument); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void update(Customer customer) { Document updateQuery = new Document("_id", customer.getId()); @@ -42,9 +36,7 @@ public void update(Customer customer) { customerCollection.updateOne(updateQuery, update); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void delete(ObjectId objectId) { Bson deleteQuery = Filters.eq("_id", objectId); @@ -54,41 +46,35 @@ public void delete(ObjectId objectId) { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List> findAll() { List> customers = new LinkedList<>(); FindIterable customerDocuments = customerCollection.find(); for (Document customerDocument : customerDocuments) { - Customer customer = new Customer<>((ObjectId) customerDocument.get("_id"), - customerDocument.getString("name")); + Customer customer = + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); customers.add(customer); } return customers; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional> findById(ObjectId objectId) { Bson filter = Filters.eq("_id", objectId); - Document customerDocument = customerCollection.find(filter) - .first(); + Document customerDocument = customerCollection.find(filter).first(); Customer customerResult = null; if (customerDocument != null) { customerResult = - new Customer<>((ObjectId) customerDocument.get("_id"), - customerDocument.getString("name")); + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); } return Optional.ofNullable(customerResult); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void deleteSchema() { customerCollection.drop(); diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java index e895f182d583..c3824c8ecaf6 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -6,9 +6,7 @@ import com.mongodb.client.MongoDatabase; import org.bson.Document; -/** - * MongoDataSourceFactory concrete factory. - */ +/** MongoDataSourceFactory concrete factory. */ public class MongoDataSourceFactory extends DAOFactory { private final String CONN_STR = "mongodb://localhost:27017/"; private final String DB_NAME = "dao_factory"; diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java index c5f531b190e7..ae6b9722906b 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -1,11 +1,12 @@ package com.iluwatar.daofactory; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; +/** {@link App} */ class AppTest { + /** Test ensure that no exception when execute main function */ @Test void shouldExecuteDaoWithoutException() { assertDoesNotThrow(() -> App.main(new String[] {})); diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java index 8a3313fc9f8a..f4875c6af24b 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -1,29 +1,30 @@ package com.iluwatar.daofactory; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import org.junit.jupiter.api.Test; -public class DAOFactoryTest { +/** {@link DAOFactory} */ +class DAOFactoryTest { @Test void verifyH2CustomerDAOCreation() { var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); var customerDAO = daoFactory.createCustomerDAO(); - assertTrue(customerDAO instanceof H2CustomerDAO); + assertInstanceOf(H2CustomerDAO.class, customerDAO); } @Test void verifyMongoCustomerDAOCreation() { var daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); var customerDAO = daoFactory.createCustomerDAO(); - assertTrue(customerDAO instanceof MongoCustomerDAO); + assertInstanceOf(MongoCustomerDAO.class, customerDAO); } @Test void verifyFlatFileCustomerDAOCreation() { var daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); var customerDAO = daoFactory.createCustomerDAO(); - assertTrue(customerDAO instanceof FlatFileCustomerDAO); + assertInstanceOf(FlatFileCustomerDAO.class, customerDAO); } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java index 3e870997afd2..101579a28e9b 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -1,6 +1,5 @@ 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; @@ -29,13 +28,13 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +/** {@link FlatFileCustomerDAO} */ public class FlatFileCustomerDAOTest { private Path filePath; private File file; private Gson gson; - private final Type customerListType = new TypeToken>>() { - }.getType(); + private final Type customerListType = new TypeToken>>() {}.getType(); private final Customer existingCustomer = new Customer<>(1L, "Thanh"); private FlatFileCustomerDAO flatFileCustomerDAO; private FileReader fileReader; @@ -48,20 +47,22 @@ void setUp() { 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; - } - }; + 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 @@ -69,10 +70,12 @@ void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() { when(file.exists()).thenReturn(false); flatFileCustomerDAO.save(existingCustomer); - verify(gson).toJson(argThat( - (List> list) -> list.size() == 1 && - list.getFirst().equals(existingCustomer)), - eq(fileWriter)); + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); } @Test @@ -82,8 +85,12 @@ void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() { flatFileCustomerDAO.save(existingCustomer); verify(gson).fromJson(fileReader, customerListType); - verify(gson).toJson(argThat((List> list) -> list.size() == 1 && - list.getFirst().equals(existingCustomer)), eq(fileWriter)); + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); } @Test @@ -97,24 +104,23 @@ void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() { flatFileCustomerDAO.save(existingCustomer); verify(gson).fromJson(fileReader, customerListType); - verify(gson).toJson(argThat((List> list) -> list.size() == 3), - eq(fileWriter)); + verify(gson).toJson(argThat((List> list) -> list.size() == 3), eq(fileWriter)); } - @Test void whenReadFails_thenThrowException() { - flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { - @Override - protected Reader createReader(Path filePath) throws IOException { - throw new IOException("Failed to read file"); - } - - @Override - protected Writer createWriter(Path filePath) { - return fileWriter; - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.save(existingCustomer)); } @@ -122,23 +128,24 @@ protected Writer createWriter(Path filePath) { @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"); - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.save(existingCustomer)); } } - + /** Class test with scenario Update Customer */ @Nested class Update { @Test @@ -150,39 +157,43 @@ void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() { @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; - } - }; + 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(RuntimeException.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"); - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } @@ -192,23 +203,27 @@ void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { List> existingListCustomer = new LinkedList<>(); existingListCustomer.add(new Customer<>(1L, "Quang")); when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); - flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { - @Override - protected Reader createReader(Path filePath) { - return fileReader; - } - - @Override - protected Writer createWriter(Path filePath) throws IOException { - return fileWriter; - } - }; + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) throws IOException { + return fileWriter; + } + }; flatFileCustomerDAO.update(existingCustomer); - verify(gson).toJson(argThat((List> customers) -> - customers.size() == 1 && customers.stream() - .anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh")) - ) - , eq(fileWriter)); + verify(gson) + .toJson( + argThat( + (List> customers) -> + customers.size() == 1 + && customers.stream() + .anyMatch(c -> c.getId().equals(1L) && c.getName().equals("Thanh"))), + eq(fileWriter)); } @Test @@ -217,21 +232,23 @@ void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { List> existingListCustomer = new LinkedList<>(); existingListCustomer.add(new Customer<>(2L, "Quang")); when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); - flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { - @Override - protected Reader createReader(Path filePath) { - return fileReader; - } - - @Override - protected Writer createWriter(Path filePath) { - return fileWriter; - } - }; + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } } + /** Class test with scenario Delete Customer */ @Nested class Delete { @Test @@ -243,17 +260,18 @@ void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() { @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; - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); } @@ -263,17 +281,18 @@ void whenWriteFails_thenThrowException() { List> existingListCustomer = new LinkedList<>(); existingListCustomer.add(new Customer<>(1L, "Quang")); when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(existingListCustomer); - flatFileCustomerDAO = new FlatFileCustomerDAO(filePath, gson) { - @Override - protected Reader createReader(Path filePath) { - return fileReader; - } - - @Override - protected Writer createWriter(Path filePath) throws IOException { - throw new IOException("Failed to write file"); - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); } @@ -284,24 +303,28 @@ void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { 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 = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; flatFileCustomerDAO.delete(1L); assertEquals(1, existingListCustomer.size()); - verify(gson).toJson(argThat((List> customers) -> - customers.stream().noneMatch(c -> c.getId().equals(1L) && - c.getName().equals("Quang"))), - eq(fileWriter)); + verify(gson) + .toJson( + argThat( + (List> customers) -> + customers.stream() + .noneMatch(c -> c.getId().equals(1L) && c.getName().equals("Quang"))), + eq(fileWriter)); } @Test @@ -311,21 +334,23 @@ void givenIdNotExist_whenDeleteCustomer_thenThrowException() { 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 = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) { + return fileReader; + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(3L)); } } + /** Class test with scenario Find All Customer */ @Nested class FindAll { @Test @@ -337,17 +362,18 @@ void givenFileNotExist_thenThrowException() { @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; - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.findAll()); } @@ -372,6 +398,7 @@ void givenCustomerExist_thenReturnCustomerList() { } } + /** Class test with scenario Find By Id Customer */ @Nested class FindById { @@ -384,17 +411,18 @@ void givenFilePathNotExist_whenFindById_thenThrowException() { @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; - } - }; + 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(RuntimeException.class, () -> flatFileCustomerDAO.findById(1L)); } @@ -419,6 +447,7 @@ void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { } } + /** Clas test with scenario Delete schema */ @Nested class DeleteSchema { @Test @@ -436,5 +465,3 @@ void givenFilePathNotExist_thenThrowException() { } } } - - diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java index 52e935b57ce4..6fc801ea1e08 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -20,9 +20,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -/** - * Tests {@link H2CustomerDAO} - */ +/** Tests {@link H2CustomerDAO} */ public class H2CustomerDAOTest { private final String DB_URL = "jdbc:h2:~/test"; private final String USER = "sa"; @@ -36,7 +34,7 @@ public class H2CustomerDAOTest { @BeforeEach void createSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CREATE_SCHEMA); } } @@ -44,11 +42,12 @@ void createSchema() throws SQLException { @AfterEach void deleteSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL, USER, PASS); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(DROP_SCHEMA); } } + /** Class test for scenario connect with datasource succeed */ @Nested class ConnectionSucceed { @@ -59,7 +58,6 @@ void setUp() { dataSource.setUser(USER); dataSource.setPassword(PASS); h2CustomerDAO = new H2CustomerDAO(dataSource); -// h2CustomerDAO.setDataSource(dataSource); assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer)); var customer = h2CustomerDAO.findById(existingCustomer.getId()); assertTrue(customer.isPresent()); @@ -205,6 +203,7 @@ void whenDeleteSchema_thenNotThrowException() { } } + /** Class test with scenario connect with data source failed */ @Nested class ConnectionFailed { private final String EXCEPTION_CAUSE = "Connection not available"; @@ -212,15 +211,13 @@ class ConnectionFailed { @BeforeEach void setUp() throws SQLException { h2CustomerDAO = new H2CustomerDAO(mockedDataSource()); -// h2CustomerDAO.setDataSource(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).prepareStatement(Mockito.anyString()); doThrow(exception).when(mockedConnection).createStatement(); doReturn(mockedConnection).when(mockedDataSource).getConnection(); return mockedDataSource; @@ -245,8 +242,8 @@ void givenValidCustomer_whenUpdateCustomer_thenThrowException() { @Test void givenValidId_whenDeleteCustomer_thenThrowException() { RuntimeException exception = - assertThrows(RuntimeException.class, - () -> h2CustomerDAO.delete(existingCustomer.getId())); + assertThrows( + RuntimeException.class, () -> h2CustomerDAO.delete(existingCustomer.getId())); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @@ -259,8 +256,8 @@ void whenFindAll_thenThrowException() { @Test void whenFindById_thenThrowException() { RuntimeException exception = - assertThrows(RuntimeException.class, - () -> h2CustomerDAO.findById(existingCustomer.getId())); + assertThrows( + RuntimeException.class, () -> h2CustomerDAO.findById(existingCustomer.getId())); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java index 1a5f1f81c5f6..c70cd49373d2 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -25,9 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Tests {@link MongoCustomerDAO} - */ +/** Tests {@link MongoCustomerDAO} */ public class MongoCustomerDAOTest { private static final Logger log = LoggerFactory.getLogger(MongoCustomerDAOTest.class); MongoCollection customerCollection = mock(MongoCollection.class); @@ -37,29 +35,34 @@ public class MongoCustomerDAOTest { void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() { Customer customer = new Customer<>(new ObjectId(), "John"); mongoCustomerDAO.save(customer); - verify(customerCollection).insertOne(argThat( - document -> document.get("_id").equals(customer.getId()) && - document.get("name").equals(customer.getName()))); + verify(customerCollection) + .insertOne( + argThat( + document -> + document.get("_id").equals(customer.getId()) + && document.get("name").equals(customer.getName()))); } @Test void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() { ObjectId customerId = new ObjectId(); Customer customerUpdated = new Customer<>(customerId, "John"); - when(customerCollection.updateOne(any(Bson.class), any(Bson.class))).thenReturn( - UpdateResult.acknowledged(1L, 1L, null)); + 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()); - }) - ); + 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 @@ -67,10 +70,13 @@ 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); - })); + verify(customerCollection) + .deleteOne( + argThat( + (Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); } @Test @@ -78,10 +84,13 @@ void givenIdNotExist_whenDeleteCustomer_thenThrowException() { ObjectId customerId = new ObjectId(); when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0)); assertThrows(RuntimeException.class, () -> mongoCustomerDAO.delete(customerId)); - verify(customerCollection).deleteOne(argThat((Bson filter) -> { - BsonDocument filterDoc = filter.toBsonDocument(); - return filterDoc.getObjectId("_id").getValue().equals(customerId); - })); + verify(customerCollection) + .deleteOne( + argThat( + (Bson filter) -> { + BsonDocument filterDoc = filter.toBsonDocument(); + return filterDoc.getObjectId("_id").getValue().equals(customerId); + })); } @Test @@ -105,8 +114,7 @@ void givenValidId_whenFindById_thenReturnCustomer() { 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(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable); when(findIterable.first()).thenReturn(customerDoc); Optional> customer = mongoCustomerDAO.findById(customerId); @@ -119,8 +127,7 @@ void givenValidId_whenFindById_thenReturnCustomer() { void givenNotExistingId_whenFindById_thenReturnEmpty() { FindIterable findIterable = mock(FindIterable.class); ObjectId customerId = new ObjectId(); - when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn( - findIterable); + when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable); when(findIterable.first()).thenReturn(null); Optional> customer = mongoCustomerDAO.findById(customerId); assertTrue(customer.isEmpty()); From abd7ef8da5f68e07e9bd3770e2a971932eb158c2 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Wed, 7 May 2025 00:57:13 +0700 Subject: [PATCH 05/10] fix: h2 inmemory database to pass CI --- .../main/java/com/iluwatar/daofactory/H2DataSourceFactory.java | 2 +- .../test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index 41995f934c29..a291c3977bf0 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -5,7 +5,7 @@ /** H2DataSourceFactory concrete factory. */ public class H2DataSourceFactory extends DAOFactory { - private final String DB_URL = "jdbc:h2:~/test"; + private final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; private final String USER = "sa"; private final String PASS = ""; diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java index 6fc801ea1e08..7cc7533e287b 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -22,7 +22,7 @@ /** Tests {@link H2CustomerDAO} */ public class H2CustomerDAOTest { - private final String DB_URL = "jdbc:h2:~/test"; + private final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; private final String USER = "sa"; private final String PASS = ""; private final String CREATE_SCHEMA = From 0a24bfdbfc90d0632209480c7449aaafce076709 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 14:22:46 +0700 Subject: [PATCH 06/10] fix: unit test pipeline --- dao-factory/pom.xml | 4 -- .../java/com/iluwatar/daofactory/AppTest.java | 63 +++++++++++++++++-- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml index 5aea50b00f93..9543ce636003 100644 --- a/dao-factory/pom.xml +++ b/dao-factory/pom.xml @@ -31,10 +31,6 @@ ch.qos.logback logback-classic - - - - org.mongodb mongodb-driver-legacy diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java index ae6b9722906b..0d663be5ed72 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -1,14 +1,69 @@ package com.iluwatar.daofactory; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +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 ensure that no exception when execute main function */ + /** Test perform CRUD in main class */ + private CustomerDAO mockLongCustomerDAO; + private CustomerDAO mockObjectIdCustomerDAO; + + @BeforeEach + void setUp() { + mockLongCustomerDAO = mock(CustomerDAO.class); + mockObjectIdCustomerDAO = mock(CustomerDAO.class); + } + + @Test + void testPerformCreateCustomerWithLongId() { + Customer c1 = new Customer<>(1L, "Test1"); + Customer c2 = new Customer<>(2L, "Test2"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2)); + + App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2)); + + verify(mockLongCustomerDAO).save(c1); + verify(mockLongCustomerDAO).save(c2); + verify(mockLongCustomerDAO).findAll(); + } + + @Test + void testPerformUpdateCustomerWithObjectId() { + ObjectId id = new ObjectId(); + Customer updatedCustomer = new Customer<>(id, "Updated"); + + when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer)); + + App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer); + + verify(mockObjectIdCustomerDAO).update(updatedCustomer); + verify(mockObjectIdCustomerDAO).findAll(); + } + + @Test + void testPerformDeleteCustomerWithLongId() { + Long id = 100L; + Customer remainingCustomer = new Customer<>(1L, "Remaining"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer)); + + App.performDeleteCustomer(mockLongCustomerDAO, id); + + verify(mockLongCustomerDAO).delete(id); + verify(mockLongCustomerDAO).findAll(); + } + @Test - void shouldExecuteDaoWithoutException() { - assertDoesNotThrow(() -> App.main(new String[] {})); + void testDeleteSchema() { + App.deleteSchema(mockLongCustomerDAO); + verify(mockLongCustomerDAO).deleteSchema(); } } From 7c83fec637e0690a8284dcde8b3073eb7dcde015 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 15:14:08 +0700 Subject: [PATCH 07/10] add license --- dao-factory/pom.xml | 26 ++++++++++++ .../java/com/iluwatar/daofactory/App.java | 24 +++++++++++ .../com/iluwatar/daofactory/Customer.java | 24 +++++++++++ .../com/iluwatar/daofactory/CustomerDAO.java | 24 +++++++++++ .../com/iluwatar/daofactory/DAOFactory.java | 24 +++++++++++ .../iluwatar/daofactory/DataSourceType.java | 24 +++++++++++ .../daofactory/FlatFileCustomerDAO.java | 31 +++++++++++++- .../daofactory/FlatFileDataSourceFactory.java | 24 +++++++++++ .../iluwatar/daofactory/H2CustomerDAO.java | 24 +++++++++++ .../daofactory/H2DataSourceFactory.java | 24 +++++++++++ .../iluwatar/daofactory/MongoCustomerDAO.java | 24 +++++++++++ .../daofactory/MongoDataSourceFactory.java | 24 +++++++++++ .../java/com/iluwatar/daofactory/AppTest.java | 25 +++++++++++ .../iluwatar/daofactory/DAOFactoryTest.java | 24 +++++++++++ .../daofactory/FlatFileCustomerDAOTest.java | 41 +++++++++++++++++-- .../daofactory/H2CustomerDAOTest.java | 24 +++++++++++ .../daofactory/MongoCustomerDAOTest.java | 24 +++++++++++ 17 files changed, 430 insertions(+), 5 deletions(-) diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml index 9543ce636003..2e771e736688 100644 --- a/dao-factory/pom.xml +++ b/dao-factory/pom.xml @@ -1,4 +1,30 @@ + diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java index c879ad111a94..3fcb6c0ebbef 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import java.util.List; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java index 0df38bdffd60..d4bfec9153db 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import java.io.Serializable; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java index 4e913a75a666..bd21878dad35 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import java.util.List; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java index 772c4c0d4a5b..5049006037fe 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; /** diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java index f309afe1e088..6538737a6c2e 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; /** Enumerates the types of data sources supported by the application. */ diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java index bcdcda3d2dee..9dcace753f49 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import com.google.gson.Gson; @@ -8,6 +32,7 @@ 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; @@ -141,6 +166,10 @@ public void deleteSchema() { if (!filePath.toFile().exists()) { throw new RuntimeException("File not found"); } - filePath.toFile().delete(); + try { + Files.delete(filePath); + } catch (IOException ex) { + throw new RuntimeException("Failed to delete customer data"); + } } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java index 9e89a5a07214..ae4e19faa6d7 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import com.google.gson.Gson; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java index f001e68e255e..8bc4a307e31f 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import java.sql.Connection; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index a291c3977bf0..9c9297970d40 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import javax.sql.DataSource; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java index 5d729e0ab2ed..87e930ba09a2 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import com.mongodb.client.FindIterable; diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java index c3824c8ecaf6..dbb17c2b4154 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import com.mongodb.client.MongoClient; diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java index 0d663be5ed72..12efea42bdc6 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import static org.mockito.Mockito.mock; @@ -13,6 +37,7 @@ class AppTest { /** Test perform CRUD in main class */ private CustomerDAO mockLongCustomerDAO; + private CustomerDAO mockObjectIdCustomerDAO; @BeforeEach diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java index f4875c6af24b..05f5b62675d5 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import static org.junit.jupiter.api.Assertions.assertInstanceOf; diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java index 101579a28e9b..894757208145 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -7,6 +31,7 @@ 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; @@ -20,6 +45,7 @@ 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; @@ -27,6 +53,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; /** {@link FlatFileCustomerDAO} */ public class FlatFileCustomerDAOTest { @@ -453,15 +480,21 @@ class DeleteSchema { @Test void givenFilePathExist_thenDeleteFile() { when(file.exists()).thenReturn(true); - flatFileCustomerDAO.deleteSchema(); - verify(file, times(1)).delete(); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + flatFileCustomerDAO.deleteSchema(); + mockedFiles.verify(() -> Files.delete(filePath), times(1)); + } } @Test void givenFilePathNotExist_thenThrowException() { when(file.exists()).thenReturn(false); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.deleteSchema()); - verify(file, times(0)).delete(); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.deleteSchema()); + mockedFiles.verify(() -> Files.delete(filePath), times(0)); + } } } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java index 7cc7533e287b..de4bb3d08213 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java index c70cd49373d2..efd78d528b09 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -1,3 +1,27 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.daofactory; import static org.junit.jupiter.api.Assertions.assertEquals; From f9263cc4e4c4fb7ece3dec42dafbb03ef11e476c Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 19:14:41 +0700 Subject: [PATCH 08/10] fix SonarQube --- .../java/com/iluwatar/daofactory/App.java | 18 +++--- .../iluwatar/daofactory/CustomException.java | 36 +++++++++++ .../com/iluwatar/daofactory/Customer.java | 2 +- .../com/iluwatar/daofactory/CustomerDAO.java | 3 +- .../com/iluwatar/daofactory/DAOFactory.java | 16 ----- .../daofactory/DAOFactoryProvider.java | 61 +++++++++++++++++++ .../iluwatar/daofactory/DataSourceType.java | 4 +- .../daofactory/FlatFileCustomerDAO.java | 32 +++++----- .../daofactory/FlatFileDataSourceFactory.java | 5 +- .../iluwatar/daofactory/H2CustomerDAO.java | 43 ++++++++----- .../daofactory/H2DataSourceFactory.java | 6 +- .../iluwatar/daofactory/MongoCustomerDAO.java | 2 +- .../daofactory/MongoDataSourceFactory.java | 13 ++-- .../iluwatar/daofactory/DAOFactoryTest.java | 6 +- .../daofactory/FlatFileCustomerDAOTest.java | 32 +++++----- .../daofactory/H2CustomerDAOTest.java | 54 ++++++++-------- .../daofactory/MongoCustomerDAOTest.java | 7 +-- 17 files changed, 216 insertions(+), 124 deletions(-) create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java index 3fcb6c0ebbef..b80d3c5ac56a 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/App.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -24,6 +24,7 @@ */ package com.iluwatar.daofactory; +import java.io.Serializable; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -32,8 +33,8 @@ public class App { public static void main(String[] args) { - var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); - var customerDAO = daoFactory.createCustomerDAO(); + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2); + CustomerDAO customerDAO = daoFactory.createCustomerDAO(); // Perform CRUD H2 Database if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) { @@ -55,7 +56,7 @@ public static void main(String[] args) { deleteSchema(customerDAO); // Perform CRUD MongoDb - daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO); customerDAO = daoFactory.createCustomerDAO(); ObjectId idCustomerMongo1 = new ObjectId(); ObjectId idCustomerMongo2 = new ObjectId(); @@ -72,7 +73,7 @@ public static void main(String[] args) { deleteSchema(customerDAO); // Perform CRUD Flat file - daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE); customerDAO = daoFactory.createCustomerDAO(); Customer customerFlatFile1 = new Customer<>(1L, "Duc"); Customer customerFlatFile2 = new Customer<>(2L, "Quang"); @@ -88,11 +89,11 @@ public static void main(String[] args) { deleteSchema(customerDAO); } - public static void deleteSchema(CustomerDAO customerDAO) { + public static void deleteSchema(CustomerDAO customerDAO) { customerDAO.deleteSchema(); } - public static void performCreateCustomer( + public static void performCreateCustomer( CustomerDAO customerDAO, List> customerList) { for (Customer customer : customerList) { customerDAO.save(customer); @@ -103,7 +104,7 @@ public static void performCreateCustomer( } } - public static void performUpdateCustomer( + public static void performUpdateCustomer( CustomerDAO customerDAO, Customer customerUpdate) { customerDAO.update(customerUpdate); List> customers = customerDAO.findAll(); @@ -112,7 +113,8 @@ public static void performUpdateCustomer( } } - public static void performDeleteCustomer(CustomerDAO customerDAO, T customerId) { + public static void performDeleteCustomer( + CustomerDAO customerDAO, T customerId) { customerDAO.delete(customerId); List> customers = customerDAO.findAll(); for (Customer customer : customers) { diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java new file mode 100644 index 000000000000..9559e765c7d4 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomException.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +/** Customer exception */ +public class CustomException extends RuntimeException { + public CustomException(String message) { + super(message); + } + + public CustomException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java index d4bfec9153db..95b675487d27 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -41,7 +41,7 @@ @NoArgsConstructor @AllArgsConstructor @ToString -public class Customer implements Serializable { +public class Customer implements Serializable { private T id; private String name; } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java index bd21878dad35..34316b4c49af 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -24,6 +24,7 @@ */ package com.iluwatar.daofactory; +import java.io.Serializable; import java.util.List; import java.util.Optional; @@ -39,7 +40,7 @@ * @see MongoCustomerDAO * @see FlatFileCustomerDAO */ -public interface CustomerDAO { +public interface CustomerDAO { /** * Persist the given customer * diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java index 5049006037fe..e7d33186bec5 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java @@ -36,22 +36,6 @@ * @see FlatFileDataSourceFactory */ public abstract class DAOFactory { - /** - * 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 FlatFile -> new FlatFileDataSourceFactory(); - }; - } - /** * Retrieves a {@link CustomerDAO} implementation specific to the underlying data source.. * diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java new file mode 100644 index 000000000000..f01fbf75ac47 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +/** + * {@code DAOFactoryProvider} is a utility class responsible for providing concrete implementations + * of the {@link DAOFactory} interface based on the specified data source type. + * + *

This class acts as an entry point to obtain DAO factories for different storage mechanisms + * such as relational databases (e.g., H2), document stores (e.g., MongoDB), or file-based systems. + * It uses the {@link DataSourceType} enumeration to determine which concrete factory to + * instantiate. + * + *

Example usage: + * + *

{@code
+ * DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
+ * }
+ */ +public class DAOFactoryProvider { + + private DAOFactoryProvider() {} + + /** + * Returns a concrete {@link DAOFactory} intance based on the specified data source type. + * + * @param dataSourceType The type of data source for which a factory is needed. Supported values: + * {@code H2}, {@code Mongo}, {@code FlatFile} + * @return A {@link DAOFactory} implementation corresponding to the given data source type. + * @throws IllegalArgumentException if the given data source type is not supported. + */ + public static DAOFactory getDataSource(DataSourceType dataSourceType) { + return switch (dataSourceType) { + case H2 -> new H2DataSourceFactory(); + case MONGO -> new MongoDataSourceFactory(); + case FLAT_FILE -> new FlatFileDataSourceFactory(); + }; + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java index 6538737a6c2e..da01d451f09e 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -27,6 +27,6 @@ /** Enumerates the types of data sources supported by the application. */ public enum DataSourceType { H2, - Mongo, - FlatFile + MONGO, + FLAT_FILE } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java index 9dcace753f49..8f1f1f144f77 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -67,14 +67,14 @@ public void save(Customer customer) { try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - throw new RuntimeException("Failed to read customer data", 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 RuntimeException("Failed to write customer data", ex); + throw new CustomException("Failed to write customer data", ex); } } @@ -82,13 +82,13 @@ public void save(Customer customer) { @Override public void update(Customer customer) { if (!filePath.toFile().exists()) { - throw new RuntimeException("File not found"); + throw new CustomException("File not found"); } List> customers; try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - throw new RuntimeException("Failed to read customer data", ex); + throw new CustomException("Failed to read customer data", ex); } customers.stream() .filter(c -> c.getId().equals(customer.getId())) @@ -96,12 +96,12 @@ public void update(Customer customer) { .ifPresentOrElse( c -> c.setName(customer.getName()), () -> { - throw new RuntimeException("Customer not found with id: " + customer.getId()); + throw new CustomException("Customer not found with id: " + customer.getId()); }); try (Writer writer = createWriter(filePath)) { gson.toJson(customers, writer); } catch (IOException ex) { - throw new RuntimeException("Failed to write customer data", ex); + throw new CustomException("Failed to write customer data", ex); } } @@ -109,24 +109,24 @@ public void update(Customer customer) { @Override public void delete(Long id) { if (!filePath.toFile().exists()) { - throw new RuntimeException("File not found"); + throw new CustomException("File not found"); } List> customers; try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - throw new RuntimeException("Failed to read customer data", ex); + throw new CustomException("Failed to read customer data", ex); } Customer customerToRemove = customers.stream() .filter(c -> c.getId().equals(id)) .findFirst() - .orElseThrow(() -> new RuntimeException("Customer not found with id: " + id)); + .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 RuntimeException("Failed to write customer data", ex); + throw new CustomException("Failed to write customer data", ex); } } @@ -134,13 +134,13 @@ public void delete(Long id) { @Override public List> findAll() { if (!filePath.toFile().exists()) { - throw new RuntimeException("File not found"); + throw new CustomException("File not found"); } List> customers; try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - throw new RuntimeException("Failed to read customer data", ex); + throw new CustomException("Failed to read customer data", ex); } return customers; } @@ -149,13 +149,13 @@ public List> findAll() { @Override public Optional> findById(Long id) { if (!filePath.toFile().exists()) { - throw new RuntimeException("File not found"); + throw new CustomException("File not found"); } List> customers = null; try (Reader reader = createReader(filePath)) { customers = gson.fromJson(reader, customerListType); } catch (IOException ex) { - throw new RuntimeException("Failed to read customer data", ex); + throw new CustomException("Failed to read customer data", ex); } return customers.stream().filter(c -> c.getId().equals(id)).findFirst(); } @@ -164,12 +164,12 @@ public Optional> findById(Long id) { @Override public void deleteSchema() { if (!filePath.toFile().exists()) { - throw new RuntimeException("File not found"); + throw new CustomException("File not found"); } try { Files.delete(filePath); } catch (IOException ex) { - throw new RuntimeException("Failed to delete customer data"); + throw new CustomException("Failed to delete customer data"); } } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java index ae4e19faa6d7..f423376703b5 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -31,10 +31,11 @@ /** FlatFileDataSourceFactory concrete factory. */ public class FlatFileDataSourceFactory extends DAOFactory { - private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json"; + private static final String FILE_PATH = + System.getProperty("user.home") + "/Desktop/customer.json"; @Override - public CustomerDAO createCustomerDAO() { + public CustomerDAO createCustomerDAO() { Path filePath = Paths.get(FILE_PATH); Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); return new FlatFileCustomerDAO(filePath, gson); diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java index 8bc4a307e31f..fe027426391c 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -31,6 +31,7 @@ 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; @@ -44,14 +45,15 @@ @RequiredArgsConstructor public class H2CustomerDAO implements CustomerDAO { private final DataSource dataSource; - private final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)"; - private final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?"; - private final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?"; - private final String SELECT_CUSTOMER_BY_ID = "SELECT * FROM customer WHERE id= ?"; - private final String SELECT_ALL_CUSTOMERS = "SELECT * FROM customer"; - private final String CREATE_SCHEMA = + 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 final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; /** {@inheritDoc} */ @Override @@ -62,46 +64,52 @@ public void save(Customer customer) { saveStatement.setString(2, customer.getName()); saveStatement.execute(); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } } /** {@inheritDoc} */ @Override public void update(Customer customer) { + if (Objects.isNull(customer) || Objects.isNull(customer.getId())) { + throw new CustomException("Custome null or customer id null"); + } try (Connection connection = dataSource.getConnection(); PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID); PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) { selectStatement.setLong(1, customer.getId()); try (ResultSet resultSet = selectStatement.executeQuery()) { if (!resultSet.next()) { - throw new RuntimeException("Customer not found with id: " + customer.getId()); + 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 RuntimeException(e.getMessage(), 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 RuntimeException("Customer not found with id: " + id); + throw new CustomException("Customer not found with id: " + id); } } deleteStatement.setLong(1, id); deleteStatement.execute(); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } } @@ -119,7 +127,7 @@ public List> findAll() { } } } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } return customers; } @@ -127,6 +135,9 @@ public List> findAll() { /** {@inheritDoc} */ @Override public Optional> findById(Long id) { + if (Objects.isNull(id)) { + throw new CustomException("Customer id null"); + } Customer customer = null; try (Connection connection = dataSource.getConnection(); PreparedStatement selectByIdStatement = @@ -140,7 +151,7 @@ public Optional> findById(Long id) { } } } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } return Optional.ofNullable(customer); } @@ -151,7 +162,7 @@ public void createSchema() { Statement statement = connection.createStatement()) { statement.execute(CREATE_SCHEMA); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } } @@ -162,7 +173,7 @@ public void deleteSchema() { Statement statement = connection.createStatement(); ) { statement.execute(DROP_SCHEMA); } catch (SQLException e) { - throw new RuntimeException(e.getMessage(), e); + throw new CustomException(e.getMessage(), e); } } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java index 9c9297970d40..dbb39dd98f3b 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -29,9 +29,9 @@ /** H2DataSourceFactory concrete factory. */ public class H2DataSourceFactory extends DAOFactory { - private final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; - private final String USER = "sa"; - private final String PASS = ""; + 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() { diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java index 87e930ba09a2..1870f61e85fd 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -66,7 +66,7 @@ public void delete(ObjectId objectId) { Bson deleteQuery = Filters.eq("_id", objectId); DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery); if (deleteResult.getDeletedCount() == 0) { - throw new RuntimeException("Delete failed: No document found with id: " + objectId); + throw new CustomException("Delete failed: No document found with id: " + objectId); } } diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java index dbb17c2b4154..5a7b1f1b1ece 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -29,22 +29,23 @@ 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 final String CONN_STR = "mongodb://localhost:27017/"; - private final String DB_NAME = "dao_factory"; - private final String COLLECTION_NAME = "customer"; + private static final String CONN_STR = "mongodb://localhost:27017/"; + private static final String DB_NAME = "dao_factory"; + private static final String COLLECTION_NAME = "customer"; @Override - public CustomerDAO createCustomerDAO() { + public CustomerDAO createCustomerDAO() { try { MongoClient mongoClient = MongoClients.create(CONN_STR); MongoDatabase database = mongoClient.getDatabase(DB_NAME); MongoCollection customerCollection = database.getCollection(COLLECTION_NAME); return new MongoCustomerDAO(customerCollection); - } catch (RuntimeException e) { - throw new RuntimeException("Error: " + e); + } catch (CustomException e) { + throw new CustomException("Error: " + e); } } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java index 05f5b62675d5..f8aaf199762d 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -33,21 +33,21 @@ class DAOFactoryTest { @Test void verifyH2CustomerDAOCreation() { - var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2); var customerDAO = daoFactory.createCustomerDAO(); assertInstanceOf(H2CustomerDAO.class, customerDAO); } @Test void verifyMongoCustomerDAOCreation() { - var daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO); var customerDAO = daoFactory.createCustomerDAO(); assertInstanceOf(MongoCustomerDAO.class, customerDAO); } @Test void verifyFlatFileCustomerDAOCreation() { - var daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE); var customerDAO = daoFactory.createCustomerDAO(); assertInstanceOf(FlatFileCustomerDAO.class, customerDAO); } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java index 894757208145..470964f4217a 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -56,7 +56,7 @@ import org.mockito.MockedStatic; /** {@link FlatFileCustomerDAO} */ -public class FlatFileCustomerDAOTest { +class FlatFileCustomerDAOTest { private Path filePath; private File file; private Gson gson; @@ -149,7 +149,7 @@ protected Writer createWriter(Path filePath) { } }; when(file.exists()).thenReturn(true); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.save(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer)); } @Test @@ -168,7 +168,7 @@ protected Writer createWriter(Path filePath) throws IOException { } }; when(file.exists()).thenReturn(true); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.save(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.save(existingCustomer)); } } @@ -178,7 +178,7 @@ class Update { @Test void givenFilePathNotExist_whenUpdateCustomer_thenThrowException() { when(file.exists()).thenReturn(false); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } @Test @@ -196,7 +196,7 @@ protected Writer createWriter(Path filePath) throws IOException { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } @Test @@ -221,7 +221,7 @@ protected Writer createWriter(Path filePath) throws IOException { throw new IOException("Failed to write file"); } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } @Test @@ -271,7 +271,7 @@ protected Writer createWriter(Path filePath) { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.update(existingCustomer)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.update(existingCustomer)); } } @@ -281,7 +281,7 @@ class Delete { @Test void givenFilePathNotExist_whenDeleteCustomer_thenThrowException() { when(file.exists()).thenReturn(false); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); } @Test @@ -299,7 +299,7 @@ protected Writer createWriter(Path filePath) { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); } @Test @@ -320,7 +320,7 @@ protected Writer createWriter(Path filePath) throws IOException { throw new IOException("Failed to write file"); } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(1L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(1L)); } @Test @@ -373,7 +373,7 @@ protected Writer createWriter(Path filePath) { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.delete(3L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.delete(3L)); } } @@ -383,7 +383,7 @@ class FindAll { @Test void givenFileNotExist_thenThrowException() { when(file.exists()).thenReturn(false); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.findAll()); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll()); } @Test @@ -401,7 +401,7 @@ protected Writer createWriter(Path filePath) { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.findAll()); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findAll()); } @Test @@ -432,7 +432,7 @@ class FindById { @Test void givenFilePathNotExist_whenFindById_thenThrowException() { when(file.exists()).thenReturn(false); - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.findById(1L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L)); } @Test @@ -450,7 +450,7 @@ protected Writer createWriter(Path filePath) { return fileWriter; } }; - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.findById(1L)); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.findById(1L)); } @Test @@ -492,7 +492,7 @@ void givenFilePathNotExist_thenThrowException() { when(file.exists()).thenReturn(false); try (MockedStatic mockedFiles = mockStatic(Files.class)) { - assertThrows(RuntimeException.class, () -> flatFileCustomerDAO.deleteSchema()); + assertThrows(CustomException.class, () -> flatFileCustomerDAO.deleteSchema()); mockedFiles.verify(() -> Files.delete(filePath), times(0)); } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java index de4bb3d08213..ce7def36e5bc 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -45,13 +45,13 @@ import org.mockito.Mockito; /** Tests {@link H2CustomerDAO} */ -public class H2CustomerDAOTest { - private final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; - private final String USER = "sa"; - private final String PASS = ""; - private final String CREATE_SCHEMA = +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 final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; + private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer"; private final Customer existingCustomer = new Customer<>(1L, "Nguyen"); private H2CustomerDAO h2CustomerDAO; @@ -106,7 +106,7 @@ void givenValidCustomer_whenSaveCustomer_thenAddSucceed() { @Test void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() { var customer = new Customer<>(existingCustomer.getId(), "Duc"); - assertThrows(RuntimeException.class, () -> h2CustomerDAO.save(customer)); + assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer)); List> customers = h2CustomerDAO.findAll(); assertEquals(1, customers.size()); } @@ -128,12 +128,12 @@ void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() { var customerUpdate = new Customer<>(100L, "Duc"); var customerInDb = h2CustomerDAO.findById(customerUpdate.getId()); assertTrue(customerInDb.isEmpty()); - assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(customerUpdate)); + assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate)); } @Test void givenNull_whenUpdateCustomer_thenThrowException() { - assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(null)); + assertThrows(CustomException.class, () -> h2CustomerDAO.update(null)); List> customers = h2CustomerDAO.findAll(); assertEquals(1, customers.size()); } @@ -154,7 +154,7 @@ void givenValidId_whenDeleteCustomer_thenDeleteSucceed() { void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() { var customerInDb = h2CustomerDAO.findById(100L); assertTrue(customerInDb.isEmpty()); - assertThrows(RuntimeException.class, () -> h2CustomerDAO.delete(100L)); + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(100L)); List> customers = h2CustomerDAO.findAll(); assertEquals(1, customers.size()); assertEquals(existingCustomer.getName(), customers.get(0).getName()); @@ -163,7 +163,7 @@ void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() { @Test void givenNull_whenDeleteCustomer_thenThrowException() { - assertThrows(RuntimeException.class, () -> h2CustomerDAO.delete(null)); + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(null)); List> customers = h2CustomerDAO.findAll(); assertEquals(1, customers.size()); assertEquals(existingCustomer.getName(), customers.get(0).getName()); @@ -206,7 +206,7 @@ void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() { @Test void givenNull_whenFindById_thenThrowException() { - assertThrows(RuntimeException.class, () -> h2CustomerDAO.findById(null)); + assertThrows(CustomException.class, () -> h2CustomerDAO.findById(null)); } } @@ -230,7 +230,7 @@ void whenDeleteSchema_thenNotThrowException() { /** Class test with scenario connect with data source failed */ @Nested class ConnectionFailed { - private final String EXCEPTION_CAUSE = "Connection not available"; + private static final String EXCEPTION_CAUSE = "Connection not available"; @BeforeEach void setUp() throws SQLException { @@ -250,52 +250,50 @@ private DataSource mockedDataSource() throws SQLException { @Test void givenValidCustomer_whenSaveCustomer_thenThrowException() { var customer = new Customer<>(2L, "Duc"); - RuntimeException exception = - assertThrows(RuntimeException.class, () -> h2CustomerDAO.save(customer)); + 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"); - RuntimeException exception = - assertThrows(RuntimeException.class, () -> h2CustomerDAO.update(customerUpdate)); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate)); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @Test void givenValidId_whenDeleteCustomer_thenThrowException() { - RuntimeException exception = - assertThrows( - RuntimeException.class, () -> h2CustomerDAO.delete(existingCustomer.getId())); + Long idCustomer = existingCustomer.getId(); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.delete(idCustomer)); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @Test void whenFindAll_thenThrowException() { - RuntimeException exception = assertThrows(RuntimeException.class, h2CustomerDAO::findAll); + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::findAll); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @Test void whenFindById_thenThrowException() { - RuntimeException exception = - assertThrows( - RuntimeException.class, () -> h2CustomerDAO.findById(existingCustomer.getId())); + Long idCustomer = existingCustomer.getId(); + CustomException exception = + assertThrows(CustomException.class, () -> h2CustomerDAO.findById(idCustomer)); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @Test void whenCreateSchema_thenThrowException() { - RuntimeException exception = - assertThrows(RuntimeException.class, h2CustomerDAO::createSchema); + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::createSchema); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } @Test void whenDeleteSchema_thenThrowException() { - RuntimeException exception = - assertThrows(RuntimeException.class, h2CustomerDAO::deleteSchema); + CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::deleteSchema); assertEquals(EXCEPTION_CAUSE, exception.getMessage()); } } diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java index efd78d528b09..c56e72c30389 100644 --- a/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -46,12 +46,9 @@ import org.bson.conversions.Bson; import org.bson.types.ObjectId; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** Tests {@link MongoCustomerDAO} */ -public class MongoCustomerDAOTest { - private static final Logger log = LoggerFactory.getLogger(MongoCustomerDAOTest.class); +class MongoCustomerDAOTest { MongoCollection customerCollection = mock(MongoCollection.class); MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection); @@ -107,7 +104,7 @@ void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() { void givenIdNotExist_whenDeleteCustomer_thenThrowException() { ObjectId customerId = new ObjectId(); when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0)); - assertThrows(RuntimeException.class, () -> mongoCustomerDAO.delete(customerId)); + assertThrows(CustomException.class, () -> mongoCustomerDAO.delete(customerId)); verify(customerCollection) .deleteOne( argThat( From 9afad6de49e41ab30126ec0032b2cafda7613482 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 17 May 2025 16:46:58 +0700 Subject: [PATCH 09/10] add pom.xml --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 3f9628396bb5..0a099acb6179 100644 --- a/pom.xml +++ b/pom.xml @@ -231,6 +231,7 @@ table-module template-method templateview + thread-pool-executor throttling tolerant-reader trampoline From 2a66d97afe4e7356e68da7fcd4bff095801641b9 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Mon, 26 May 2025 23:53:33 +0700 Subject: [PATCH 10/10] fix: throw exception + refactor --- .../main/java/com/iluwatar/daofactory/DAOFactoryProvider.java | 1 + pom.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java index f01fbf75ac47..08585622d00d 100644 --- a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactoryProvider.java @@ -56,6 +56,7 @@ public static DAOFactory getDataSource(DataSourceType dataSourceType) { case H2 -> new H2DataSourceFactory(); case MONGO -> new MongoDataSourceFactory(); case FLAT_FILE -> new FlatFileDataSourceFactory(); + default -> throw new IllegalArgumentException("Unsupported data source type"); }; } } diff --git a/pom.xml b/pom.xml index 0a099acb6179..7b40fdb7f57a 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,7 @@ converter curiously-recurring-template-pattern currying + dao-factory data-access-object data-bus data-locality @@ -246,7 +247,7 @@ visitor backpressure actor-model - dao-factory +