From 4e1c7c66f6a53c813af9baa9fca9a2a74b2ed94b Mon Sep 17 00:00:00 2001 From: letdtcode Date: Wed, 7 May 2025 00:57:13 +0700 Subject: [PATCH 1/4] fix: h2 inmemory database to pass CI --- .../daofactory/H2DataSourceFactory.java | 24 ++ .../daofactory/H2CustomerDAOTest.java | 278 ++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java 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..a291c3977bf0 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2DataSourceFactory.java @@ -0,0 +1,24 @@ +package com.iluwatar.daofactory; + +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; + +/** 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 = ""; + + @Override + public CustomerDAO createCustomerDAO() { + return new H2CustomerDAO(createDataSource()); + } + + private DataSource createDataSource() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + return dataSource; + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java new file mode 100644 index 000000000000..7cc7533e287b --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/H2CustomerDAOTest.java @@ -0,0 +1,278 @@ +package com.iluwatar.daofactory; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.List; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** Tests {@link H2CustomerDAO} */ +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 = + "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); + } + } + + /** Class test for scenario connect with datasource succeed */ + @Nested + class ConnectionSucceed { + + @BeforeEach + void setUp() { + var dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dataSource.setUser(USER); + dataSource.setPassword(PASS); + h2CustomerDAO = new H2CustomerDAO(dataSource); + assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer)); + var customer = h2CustomerDAO.findById(existingCustomer.getId()); + assertTrue(customer.isPresent()); + assertEquals(customer.get().getName(), existingCustomer.getName()); + assertEquals(customer.get().getId(), existingCustomer.getId()); + } + + @Nested + class SaveCustomer { + @Test + void givenValidCustomer_whenSaveCustomer_thenAddSucceed() { + var customer = new Customer<>(2L, "Duc"); + assertDoesNotThrow(() -> h2CustomerDAO.save(customer)); + var customerInDb = h2CustomerDAO.findById(customer.getId()); + assertTrue(customerInDb.isPresent()); + assertEquals(customerInDb.get().getName(), customer.getName()); + assertEquals(customerInDb.get().getId(), customer.getId()); + List> customers = h2CustomerDAO.findAll(); + assertEquals(2, customers.size()); + } + + @Test + void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() { + var customer = new Customer<>(existingCustomer.getId(), "Duc"); + assertThrows(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()); + } + } + } + + /** Class test with scenario connect with data source failed */ + @Nested + class ConnectionFailed { + private final String EXCEPTION_CAUSE = "Connection not available"; + + @BeforeEach + void setUp() throws SQLException { + h2CustomerDAO = new H2CustomerDAO(mockedDataSource()); + } + + private DataSource mockedDataSource() throws SQLException { + var mockedDataSource = mock(DataSource.class); + var mockedConnection = mock(Connection.class); + var exception = new SQLException(EXCEPTION_CAUSE); + doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString()); + doThrow(exception).when(mockedConnection).createStatement(); + doReturn(mockedConnection).when(mockedDataSource).getConnection(); + return mockedDataSource; + } + + @Test + void givenValidCustomer_whenSaveCustomer_thenThrowException() { + var customer = new Customer<>(2L, "Duc"); + 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()); + } + } +} From bc3941f2d90e10485f92e47f7c9c3884604c2e0d Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 14:22:46 +0700 Subject: [PATCH 2/4] fix: unit test pipeline --- dao-factory/pom.xml | 56 +++++++++++++++ .../java/com/iluwatar/daofactory/AppTest.java | 69 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 dao-factory/pom.xml create mode 100644 dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java diff --git a/dao-factory/pom.xml b/dao-factory/pom.xml new file mode 100644 index 000000000000..9543ce636003 --- /dev/null +++ b/dao-factory/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + dao-factory + + + 21 + 21 + UTF-8 + + + + + com.h2database + h2 + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.mongodb + mongodb-driver-legacy + + + com.google.code.gson + gson + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java new file mode 100644 index 000000000000..0d663be5ed72 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java @@ -0,0 +1,69 @@ +package com.iluwatar.daofactory; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** {@link App} */ +class AppTest { + /** Test perform CRUD in main class */ + private CustomerDAO mockLongCustomerDAO; + private CustomerDAO mockObjectIdCustomerDAO; + + @BeforeEach + void setUp() { + mockLongCustomerDAO = mock(CustomerDAO.class); + mockObjectIdCustomerDAO = mock(CustomerDAO.class); + } + + @Test + void testPerformCreateCustomerWithLongId() { + Customer c1 = new Customer<>(1L, "Test1"); + Customer c2 = new Customer<>(2L, "Test2"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2)); + + App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2)); + + verify(mockLongCustomerDAO).save(c1); + verify(mockLongCustomerDAO).save(c2); + verify(mockLongCustomerDAO).findAll(); + } + + @Test + void testPerformUpdateCustomerWithObjectId() { + ObjectId id = new ObjectId(); + Customer updatedCustomer = new Customer<>(id, "Updated"); + + when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer)); + + App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer); + + verify(mockObjectIdCustomerDAO).update(updatedCustomer); + verify(mockObjectIdCustomerDAO).findAll(); + } + + @Test + void testPerformDeleteCustomerWithLongId() { + Long id = 100L; + Customer remainingCustomer = new Customer<>(1L, "Remaining"); + + when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer)); + + App.performDeleteCustomer(mockLongCustomerDAO, id); + + verify(mockLongCustomerDAO).delete(id); + verify(mockLongCustomerDAO).findAll(); + } + + @Test + void testDeleteSchema() { + App.deleteSchema(mockLongCustomerDAO); + verify(mockLongCustomerDAO).deleteSchema(); + } +} From b7d580c185baf4d8558b9ef0f4c1a5f91ba46a67 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 15:14:08 +0700 Subject: [PATCH 3/4] add license --- dao-factory/pom.xml | 26 + .../java/com/iluwatar/daofactory/App.java | 122 +++++ .../com/iluwatar/daofactory/Customer.java | 47 ++ .../com/iluwatar/daofactory/CustomerDAO.java | 84 +++ .../com/iluwatar/daofactory/DAOFactory.java | 61 +++ .../iluwatar/daofactory/DataSourceType.java | 32 ++ .../daofactory/FlatFileCustomerDAO.java | 175 ++++++ .../daofactory/FlatFileDataSourceFactory.java | 42 ++ .../iluwatar/daofactory/H2CustomerDAO.java | 168 ++++++ .../daofactory/H2DataSourceFactory.java | 24 + .../iluwatar/daofactory/MongoCustomerDAO.java | 106 ++++ .../daofactory/MongoDataSourceFactory.java | 50 ++ .../java/com/iluwatar/daofactory/AppTest.java | 25 + .../iluwatar/daofactory/DAOFactoryTest.java | 54 ++ .../daofactory/FlatFileCustomerDAOTest.java | 500 ++++++++++++++++++ .../daofactory/H2CustomerDAOTest.java | 24 + .../daofactory/MongoCustomerDAOTest.java | 166 ++++++ 17 files changed, 1706 insertions(+) create mode 100644 dao-factory/src/main/java/com/iluwatar/daofactory/App.java 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/DataSourceType.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/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/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/MongoCustomerDAOTest.java 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 new file mode 100644 index 000000000000..3fcb6c0ebbef --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/App.java @@ -0,0 +1,122 @@ +/* + * 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; +import lombok.extern.slf4j.Slf4j; +import org.bson.types.ObjectId; + +@Slf4j +public class App { + + 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( + 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(); + ObjectId idCustomerMongo1 = new ObjectId(); + ObjectId idCustomerMongo2 = new ObjectId(); + Customer customer4 = new Customer<>(idCustomerMongo1, "Masca"); + Customer customer5 = new Customer<>(idCustomerMongo2, "Elliot"); + Customer customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry"); + + LOGGER.debug("Mongo - Create customer"); + performCreateCustomer(customerDAO, List.of(customer4, customer5)); + LOGGER.debug("Mongo - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateMongo); + LOGGER.debug("Mongo - Delete customer"); + performDeleteCustomer(customerDAO, idCustomerMongo2); + deleteSchema(customerDAO); + + // Perform CRUD Flat file + daoFactory = 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( + customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3)); + LOGGER.debug("Flat file - Update customer"); + performUpdateCustomer(customerDAO, customerUpdateFlatFile); + LOGGER.debug("Flat file - Delete customer"); + performDeleteCustomer(customerDAO, 3L); + deleteSchema(customerDAO); + } + + public static void deleteSchema(CustomerDAO customerDAO) { + customerDAO.deleteSchema(); + } + + public static void performCreateCustomer( + CustomerDAO customerDAO, List> customerList) { + for (Customer customer : customerList) { + customerDAO.save(customer); + } + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } + + public static void performUpdateCustomer( + CustomerDAO customerDAO, Customer customerUpdate) { + customerDAO.update(customerUpdate); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } + + public static void performDeleteCustomer(CustomerDAO customerDAO, T customerId) { + customerDAO.delete(customerId); + List> customers = customerDAO.findAll(); + for (Customer customer : customers) { + LOGGER.debug(customer.toString()); + } + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java new file mode 100644 index 000000000000..d4bfec9153db --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java @@ -0,0 +1,47 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * A customer generic POJO that represents the data that can be stored in any supported data source. + * This class is designed t work with various ID types (e.g., Long, String, or ObjectId) through + * generic, making it adaptable to different persistence system. + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class Customer implements Serializable { + private T id; + private String name; +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java new file mode 100644 index 000000000000..bd21878dad35 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java @@ -0,0 +1,84 @@ +/* + * 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; +import java.util.Optional; + +/** + * The Data Access Object (DAO) pattern provides an abstraction layer between the application and + * the database. It encapsulates data access logic, allowing the application to work with domain + * objects instead of direct database operations. + * + *

Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping + * client code unchanged. + * + * @see H2CustomerDAO + * @see MongoCustomerDAO + * @see FlatFileCustomerDAO + */ +public interface CustomerDAO { + /** + * Persist the given customer + * + * @param customer the customer to persist + */ + void save(Customer customer); + + /** + * Update the given customer + * + * @param customer the customer to update + */ + void update(Customer customer); + + /** + * Delete the customer with the given id + * + * @param id the id of the customer to delete + */ + void delete(T id); + + /** + * Find all customers + * + * @return a list of customers + */ + List> findAll(); + + /** + * Find the customer with the given id + * + * @param id the id of the customer to find + * @return the customer with the given id + */ + Optional> findById(T id); + + /** + * Delete the customer schema. After executing the statements, this function will be called to + * clean up the data and delete the records. + */ + void deleteSchema(); +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java new file mode 100644 index 000000000000..5049006037fe --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.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; + +/** + * 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 { + /** + * 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.. + * + * @return A data source-specific implementation of {@link CustomerDAO} + */ + public abstract CustomerDAO createCustomerDAO(); +} 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..6538737a6c2e --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/DataSourceType.java @@ -0,0 +1,32 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +/** Enumerates the types of data sources supported by the application. */ +public enum DataSourceType { + H2, + Mongo, + 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 new file mode 100644 index 000000000000..9dcace753f49 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileCustomerDAO.java @@ -0,0 +1,175 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * A Flat File implementation of {@link CustomerDAO}, which store the customer data in a JSON file + * at path {@code ~/Desktop/customer.json}. + */ +@Slf4j +@RequiredArgsConstructor +public class FlatFileCustomerDAO implements CustomerDAO { + private final Path filePath; + private final Gson gson; + Type customerListType = new TypeToken>>() {}.getType(); + + protected Reader createReader(Path filePath) throws IOException { + return new FileReader(filePath.toFile()); + } + + protected Writer createWriter(Path filePath) throws IOException { + return new FileWriter(filePath.toFile()); + } + + /** {@inheritDoc} */ + @Override + public void save(Customer customer) { + List> customers = new LinkedList<>(); + if (filePath.toFile().exists()) { + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new RuntimeException("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); + } + } + + /** {@inheritDoc} */ + @Override + public void update(Customer customer) { + if (!filePath.toFile().exists()) { + throw new RuntimeException("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); + } + 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()); + }); + 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; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } 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)); + 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; + try (Reader reader = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException 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 = createReader(filePath)) { + customers = gson.fromJson(reader, customerListType); + } catch (IOException ex) { + throw new RuntimeException("Failed to read customer data", ex); + } + return customers.stream().filter(c -> c.getId().equals(id)).findFirst(); + } + + /** {@inheritDoc} */ + @Override + public void deleteSchema() { + if (!filePath.toFile().exists()) { + throw new RuntimeException("File not found"); + } + 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 new file mode 100644 index 000000000000..ae4e19faa6d7 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/FlatFileDataSourceFactory.java @@ -0,0 +1,42 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** FlatFileDataSourceFactory concrete factory. */ +public class FlatFileDataSourceFactory extends DAOFactory { + private final String FILE_PATH = System.getProperty("user.home") + "/Desktop/customer.json"; + + @Override + public CustomerDAO createCustomerDAO() { + Path filePath = Paths.get(FILE_PATH); + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); + return new FlatFileCustomerDAO(filePath, gson); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java new file mode 100644 index 000000000000..8bc4a307e31f --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java @@ -0,0 +1,168 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import javax.sql.DataSource; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * An implementation of {@link CustomerDAO} that uses H2 database (http://www.h2database.com/) which + * is an in-memory database and data will lost after application exits. + */ +@Slf4j +@RequiredArgsConstructor +public class H2CustomerDAO implements CustomerDAO { + private final DataSource dataSource; + private 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) { + 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) { + 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()); + } + } + 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) { + 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); + } + } + deleteStatement.setLong(1, id); + deleteStatement.execute(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + /** {@inheritDoc} */ + @Override + public List> findAll() { + List> customers = new LinkedList<>(); + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) { + try (ResultSet resultSet = selectStatement.executeQuery()) { + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customers.add(new Customer<>(idCustomer, nameCustomer)); + } + } + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + return customers; + } + + /** {@inheritDoc} */ + @Override + public Optional> findById(Long id) { + Customer customer = null; + try (Connection connection = dataSource.getConnection(); + PreparedStatement selectByIdStatement = + connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) { + selectByIdStatement.setLong(1, id); + try (ResultSet resultSet = selectByIdStatement.executeQuery()) { + while (resultSet.next()) { + Long idCustomer = resultSet.getLong("id"); + String nameCustomer = resultSet.getString("name"); + customer = new Customer<>(idCustomer, nameCustomer); + } + } + } catch (SQLException e) { + throw new 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 (Connection connection = dataSource.getConnection(); + 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 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 new file mode 100644 index 000000000000..87e930ba09a2 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoCustomerDAO.java @@ -0,0 +1,106 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.Updates; +import com.mongodb.client.result.DeleteResult; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; + +/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */ +@Slf4j +@RequiredArgsConstructor +public class MongoCustomerDAO implements CustomerDAO { + private final MongoCollection customerCollection; + + /** {@inheritDoc} */ + @Override + public void save(Customer customer) { + Document customerDocument = new Document("_id", customer.getId()); + customerDocument.append("name", customer.getName()); + customerCollection.insertOne(customerDocument); + } + + /** {@inheritDoc} */ + @Override + public void update(Customer customer) { + Document updateQuery = new Document("_id", customer.getId()); + Bson update = Updates.set("name", customer.getName()); + customerCollection.updateOne(updateQuery, update); + } + + /** {@inheritDoc} */ + @Override + public void delete(ObjectId objectId) { + Bson deleteQuery = Filters.eq("_id", objectId); + DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery); + if (deleteResult.getDeletedCount() == 0) { + throw new RuntimeException("Delete failed: No document found with id: " + objectId); + } + } + + /** {@inheritDoc} */ + @Override + public List> findAll() { + List> customers = new LinkedList<>(); + FindIterable customerDocuments = customerCollection.find(); + for (Document customerDocument : customerDocuments) { + Customer customer = + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); + customers.add(customer); + } + return customers; + } + + /** {@inheritDoc} */ + @Override + public Optional> findById(ObjectId objectId) { + Bson filter = Filters.eq("_id", objectId); + Document customerDocument = customerCollection.find(filter).first(); + Customer customerResult = null; + if (customerDocument != null) { + customerResult = + new Customer<>( + (ObjectId) customerDocument.get("_id"), customerDocument.getString("name")); + } + return Optional.ofNullable(customerResult); + } + + /** {@inheritDoc} */ + @Override + public void deleteSchema() { + customerCollection.drop(); + } +} diff --git a/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java new file mode 100644 index 000000000000..dbb17c2b4154 --- /dev/null +++ b/dao-factory/src/main/java/com/iluwatar/daofactory/MongoDataSourceFactory.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import org.bson.Document; + +/** 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 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/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 new file mode 100644 index 000000000000..05f5b62675d5 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/DAOFactoryTest.java @@ -0,0 +1,54 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +/** {@link DAOFactory} */ +class DAOFactoryTest { + + @Test + void verifyH2CustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.H2); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(H2CustomerDAO.class, customerDAO); + } + + @Test + void verifyMongoCustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.Mongo); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(MongoCustomerDAO.class, customerDAO); + } + + @Test + void verifyFlatFileCustomerDAOCreation() { + var daoFactory = DAOFactory.getDataSource(DataSourceType.FlatFile); + var customerDAO = daoFactory.createCustomerDAO(); + assertInstanceOf(FlatFileCustomerDAO.class, customerDAO); + } +} diff --git a/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java new file mode 100644 index 000000000000..894757208145 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/FlatFileCustomerDAOTest.java @@ -0,0 +1,500 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +/** {@link FlatFileCustomerDAO} */ +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); + } + + /** Class test with scenario Save Customer */ + @Nested + class Save { + @Test + void giveFilePathNotExist_whenSaveCustomer_thenCreateNewFileWithCustomer() { + when(file.exists()).thenReturn(false); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); + } + + @Test + void givenEmptyFileExist_whenSaveCustomer_thenAddCustomer() { + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(new LinkedList<>()); + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson) + .toJson( + argThat( + (List> list) -> + list.size() == 1 && list.getFirst().equals(existingCustomer)), + eq(fileWriter)); + } + + @Test + void givenFileWithCustomerExist_whenSaveCustomer_thenShouldAppendCustomer() { + List> customers = new LinkedList<>(); + customers.add(new Customer<>(2L, "Duc")); + customers.add(new Customer<>(3L, "Nguyen")); + when(file.exists()).thenReturn(true); + when(gson.fromJson(any(Reader.class), eq(customerListType))).thenReturn(customers); + + flatFileCustomerDAO.save(existingCustomer); + + verify(gson).fromJson(fileReader, customerListType); + verify(gson).toJson(argThat((List> list) -> list.size() == 3), eq(fileWriter)); + } + + @Test + void whenReadFails_thenThrowException() { + flatFileCustomerDAO = + new FlatFileCustomerDAO(filePath, gson) { + @Override + protected Reader createReader(Path filePath) throws IOException { + throw new IOException("Failed to read file"); + } + + @Override + protected Writer createWriter(Path filePath) { + return fileWriter; + } + }; + when(file.exists()).thenReturn(true); + assertThrows(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)); + } + } + + /** Class test with scenario Update Customer */ + @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)); + } + } + + /** Class test with scenario Delete Customer */ + @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)); + } + } + + /** Class test with scenario Find All Customer */ + @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()); + } + } + + /** Class test with scenario Find By Id Customer */ + @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()); + } + } + + /** Clas test with scenario Delete schema */ + @Nested + class DeleteSchema { + @Test + void givenFilePathExist_thenDeleteFile() { + when(file.exists()).thenReturn(true); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + flatFileCustomerDAO.deleteSchema(); + mockedFiles.verify(() -> Files.delete(filePath), times(1)); + } + } + + @Test + void givenFilePathNotExist_thenThrowException() { + when(file.exists()).thenReturn(false); + + try (MockedStatic mockedFiles = mockStatic(Files.class)) { + assertThrows(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 new file mode 100644 index 000000000000..efd78d528b09 --- /dev/null +++ b/dao-factory/src/test/java/com/iluwatar/daofactory/MongoCustomerDAOTest.java @@ -0,0 +1,166 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.daofactory; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Filters; +import com.mongodb.client.result.DeleteResult; +import com.mongodb.client.result.UpdateResult; +import java.util.List; +import java.util.Optional; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** 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 420ad1b7900b955357394fa2f630eecfc4b7a7b9 Mon Sep 17 00:00:00 2001 From: letdtcode Date: Sat, 10 May 2025 19:14:41 +0700 Subject: [PATCH 4/4] 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(