refactor: rename and delete some patterns (#2970)

This commit is contained in:
Ilkka Seppälä
2024-05-23 17:57:46 +03:00
committed by GitHub
parent 399970f8d1
commit 962e8898e0
94 changed files with 972 additions and 3619 deletions
@@ -0,0 +1,125 @@
/*
* 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.dao;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.h2.jdbcx.JdbcDataSource;
/**
* Data Access Object (DAO) is an object that provides an abstract interface to some type of
* database or other persistence mechanism. By mapping application calls to the persistence layer,
* DAO provide some specific data operations without exposing details of the database. This
* isolation supports the Single responsibility principle. It separates what data accesses the
* application needs, in terms of domain-specific objects and data types (the public interface of
* the DAO), from how these needs can be satisfied with a specific DBMS.
*
* <p>With the DAO pattern, we can use various method calls to retrieve/add/delete/update data
* without directly interacting with the data source. The below example demonstrates basic CRUD
* operations: select, add, update, and delete.
*/
@Slf4j
public class App {
private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): ";
/**
* Program entry point.
*
* @param args command line args.
* @throws Exception if any error occurs.
*/
public static void main(final String[] args) throws Exception {
final var inMemoryDao = new InMemoryCustomerDao();
performOperationsUsing(inMemoryDao);
final var dataSource = createDataSource();
createSchema(dataSource);
final var dbDao = new DbCustomerDao(dataSource);
performOperationsUsing(dbDao);
deleteSchema(dataSource);
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}
private static void performOperationsUsing(final CustomerDao customerDao) throws Exception {
addCustomers(customerDao);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(customer -> LOGGER.info(customer.toString()));
}
LOGGER.info("customerDao.getCustomerById(2): " + customerDao.getById(2));
final var customer = new Customer(4, "Dan", "Danson");
customerDao.add(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
customer.setFirstName("Daniel");
customer.setLastName("Danielson");
customerDao.update(customer);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(cust -> LOGGER.info(cust.toString()));
}
customerDao.delete(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
}
private static void addCustomers(CustomerDao customerDao) throws Exception {
for (var customer : generateSampleCustomers()) {
customerDao.add(customer);
}
}
/**
* Generate customers.
*
* @return list of customers.
*/
public static List<Customer> generateSampleCustomers() {
final var customer1 = new Customer(1, "Adam", "Adamson");
final var customer2 = new Customer(2, "Bob", "Bobson");
final var customer3 = new Customer(3, "Carl", "Carlson");
return List.of(customer1, customer2, customer3);
}
}
@@ -0,0 +1,40 @@
/*
* 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.dao;
import java.io.Serial;
/**
* Custom exception.
*/
public class CustomException extends Exception {
@Serial
private static final long serialVersionUID = 1L;
public CustomException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,48 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* A customer POJO that represents the data that will be read from the data source.
*/
@Setter
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor
public class Customer {
@EqualsAndHashCode.Include
private int id;
private String firstName;
private String lastName;
}
@@ -0,0 +1,92 @@
/*
* 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.dao;
import java.util.Optional;
import java.util.stream.Stream;
/**
* In an application the Data Access Object (DAO) is a part of Data access layer. It is an object
* that provides an interface to some type of persistence mechanism. By mapping application calls to
* the persistence layer, DAO provides some specific data operations without exposing details of the
* database. This isolation supports the Single responsibility principle. It separates what data
* accesses the application needs, in terms of domain-specific objects and data types (the public
* interface of the DAO), from how these needs can be satisfied with a specific DBMS, database
* schema, etc.
*
* <p>Any change in the way data is stored and retrieved will not change the client code as the
* client will be using interface and need not worry about exact source.
*
* @see InMemoryCustomerDao
* @see DbCustomerDao
*/
public interface CustomerDao {
/**
* Get all customers.
*
* @return all the customers as a stream. The stream may be lazily or eagerly evaluated based on
* the implementation. The stream must be closed after use.
* @throws Exception if any error occurs.
*/
Stream<Customer> getAll() throws Exception;
/**
* Get customer as Optional by id.
*
* @param id unique identifier of the customer.
* @return an optional with customer if a customer with unique identifier <code>id</code> exists,
* empty optional otherwise.
* @throws Exception if any error occurs.
*/
Optional<Customer> getById(int id) throws Exception;
/**
* Add a customer.
*
* @param customer the customer to be added.
* @return true if customer is successfully added, false if customer already exists.
* @throws Exception if any error occurs.
*/
boolean add(Customer customer) throws Exception;
/**
* Update a customer.
*
* @param customer the customer to be updated.
* @return true if customer exists and is successfully updated, false otherwise.
* @throws Exception if any error occurs.
*/
boolean update(Customer customer) throws Exception;
/**
* Delete a customer.
*
* @param customer the customer to be deleted.
* @return true if customer exists and is successfully deleted, false otherwise.
* @throws Exception if any error occurs.
*/
boolean delete(Customer customer) throws Exception;
}
@@ -0,0 +1,41 @@
/*
* 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.dao;
/**
* Customer Schema SQL Class.
*/
public final class CustomerSchemaSql {
private CustomerSchemaSql() {
}
public static final String CREATE_SCHEMA_SQL =
"CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), "
+ "LNAME VARCHAR(100))";
public static final String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS";
}
@@ -0,0 +1,183 @@
/*
* 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.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* An implementation of {@link CustomerDao} that persists customers in RDBMS.
*/
@Slf4j
@RequiredArgsConstructor
public class DbCustomerDao implements CustomerDao {
private final DataSource dataSource;
/**
* Get all customers as Java Stream.
*
* @return a lazily populated stream of customers. Note the stream returned must be closed to free
* all the acquired resources. The stream keeps an open connection to the database till it is
* complete or is closed manually.
*/
@Override
public Stream<Customer> getAll() throws Exception {
try {
var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR
var resultSet = statement.executeQuery(); // NOSONAR
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Customer>(Long.MAX_VALUE,
Spliterator.ORDERED) {
@Override
public boolean tryAdvance(Consumer<? super Customer> action) {
try {
if (!resultSet.next()) {
return false;
}
action.accept(createCustomer(resultSet));
return true;
} catch (SQLException e) {
throw new RuntimeException(e); // NOSONAR
}
}
}, false).onClose(() -> mutedClose(connection, statement, resultSet));
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
private void mutedClose(Connection connection, PreparedStatement statement, ResultSet resultSet) {
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
LOGGER.info("Exception thrown " + e.getMessage());
}
}
private Customer createCustomer(ResultSet resultSet) throws SQLException {
return new Customer(resultSet.getInt("ID"),
resultSet.getString("FNAME"),
resultSet.getString("LNAME"));
}
/**
* {@inheritDoc}
*/
@Override
public Optional<Customer> getById(int id) throws Exception {
ResultSet resultSet = null;
try (var connection = getConnection();
var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, id);
resultSet = statement.executeQuery();
if (resultSet.next()) {
return Optional.of(createCustomer(resultSet));
} else {
return Optional.empty();
}
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
} finally {
if (resultSet != null) {
resultSet.close();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean add(Customer customer) throws Exception {
if (getById(customer.getId()).isPresent()) {
return false;
}
try (var connection = getConnection();
var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) {
statement.setInt(1, customer.getId());
statement.setString(2, customer.getFirstName());
statement.setString(3, customer.getLastName());
statement.execute();
return true;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean update(Customer customer) throws Exception {
try (var connection = getConnection();
var statement =
connection
.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) {
statement.setString(1, customer.getFirstName());
statement.setString(2, customer.getLastName());
statement.setInt(3, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean delete(Customer customer) throws Exception {
try (var connection = getConnection();
var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) {
statement.setInt(1, customer.getId());
return statement.executeUpdate() > 0;
} catch (SQLException ex) {
throw new CustomException(ex.getMessage(), ex);
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.dao;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory and
* data is lost when the application exits.
* <br>
* This implementation is useful as temporary database or for testing.
*/
public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>();
/**
* An eagerly evaluated stream of customers stored in memory.
*/
@Override
public Stream<Customer> getAll() {
return idToCustomer.values().stream();
}
@Override
public Optional<Customer> getById(final int id) {
return Optional.ofNullable(idToCustomer.get(id));
}
@Override
public boolean add(final Customer customer) {
if (getById(customer.getId()).isPresent()) {
return false;
}
idToCustomer.put(customer.getId(), customer);
return true;
}
@Override
public boolean update(final Customer customer) {
return idToCustomer.replace(customer.getId(), customer) != null;
}
@Override
public boolean delete(final Customer customer) {
return idToCustomer.remove(customer.getId()) != null;
}
}
@@ -0,0 +1,46 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.dao;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
/**
* Tests that DAO example runs without errors.
*/
class AppTest {
/**
* Issue: Add at least one assertion to this test case.
* Solution: Inserted assertion to check whether the execution of the main method in {@link App#main(String[])}
* throws an exception.
*/
@Test
void shouldExecuteDaoWithoutException() {
assertDoesNotThrow(() -> App.main(new String[]{}));
}
}
@@ -0,0 +1,95 @@
/*
* 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.dao;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* Tests {@link Customer}.
*/
class CustomerTest {
private Customer customer;
private static final int ID = 1;
private static final String FIRSTNAME = "Winston";
private static final String LASTNAME = "Churchill";
@BeforeEach
void setUp() {
customer = new Customer(ID, FIRSTNAME, LASTNAME);
}
@Test
void getAndSetId() {
final var newId = 2;
customer.setId(newId);
assertEquals(newId, customer.getId());
}
@Test
void getAndSetFirstname() {
final var newFirstname = "Bill";
customer.setFirstName(newFirstname);
assertEquals(newFirstname, customer.getFirstName());
}
@Test
void getAndSetLastname() {
final var newLastname = "Clinton";
customer.setLastName(newLastname);
assertEquals(newLastname, customer.getLastName());
}
@Test
void notEqualWithDifferentId() {
final var newId = 2;
final var otherCustomer = new Customer(newId, FIRSTNAME, LASTNAME);
assertNotEquals(customer, otherCustomer);
assertNotEquals(customer.hashCode(), otherCustomer.hashCode());
}
@Test
void equalsWithSameObjectValues() {
final var otherCustomer = new Customer(ID, FIRSTNAME, LASTNAME);
assertEquals(customer, otherCustomer);
assertEquals(customer.hashCode(), otherCustomer.hashCode());
}
@Test
void equalsWithSameObjects() {
assertEquals(customer, customer);
assertEquals(customer.hashCode(), customer.hashCode());
}
@Test
void testToString() {
assertEquals(String.format("Customer(id=%s, firstName=%s, lastName=%s)",
customer.getId(), customer.getFirstName(), customer.getLastName()), customer.toString());
}
}
@@ -0,0 +1,263 @@
/*
* 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.dao;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
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 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 DbCustomerDao}.
*/
class DbCustomerDaoTest {
private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
private DbCustomerDao dao;
private final Customer existingCustomer = new Customer(1, "Freddy", "Krueger");
/**
* Creates customers schema.
*
* @throws SQLException if there is any error while creating schema.
*/
@BeforeEach
void createSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL);
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
/**
* Represents the scenario where DB connectivity is present.
*/
@Nested
class ConnectionSuccess {
/**
* Setup for connection success scenario.
*
* @throws Exception if any error occurs.
*/
@BeforeEach
void setUp() throws Exception {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
dao = new DbCustomerDao(dataSource);
var result = dao.add(existingCustomer);
assertTrue(result);
}
/**
* Represents the scenario when DAO operations are being performed on a non-existing customer.
*/
@Nested
class NonExistingCustomer {
@Test
void addingShouldResultInSuccess() throws Exception {
try (var allCustomers = dao.getAll()) {
assumeTrue(allCustomers.count() == 1);
}
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.add(nonExistingCustomer);
assertTrue(result);
assertCustomerCountIs(2);
assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get());
}
@Test
void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.delete(nonExistingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
}
@Test
void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingId = getNonExistingCustomerId();
final var newFirstname = "Douglas";
final var newLastname = "MacArthur";
final var customer = new Customer(nonExistingId, newFirstname, newLastname);
var result = dao.update(customer);
assertFalse(result);
assertFalse(dao.getById(nonExistingId).isPresent());
}
@Test
void retrieveShouldReturnNoCustomer() throws Exception {
assertFalse(dao.getById(getNonExistingCustomerId()).isPresent());
}
}
/**
* Represents a scenario where DAO operations are being performed on an already existing
* customer.
*/
@Nested
class ExistingCustomer {
@Test
void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception {
var existingCustomer = new Customer(1, "Freddy", "Krueger");
var result = dao.add(existingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
assertEquals(existingCustomer, dao.getById(existingCustomer.getId()).get());
}
@Test
void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception {
var result = dao.delete(existingCustomer);
assertTrue(result);
assertCustomerCountIs(0);
assertFalse(dao.getById(existingCustomer.getId()).isPresent());
}
@Test
void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws
Exception {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
final var customer = new Customer(existingCustomer.getId(), newFirstname, newLastname);
var result = dao.update(customer);
assertTrue(result);
final var cust = dao.getById(existingCustomer.getId()).get();
assertEquals(newFirstname, cust.getFirstName());
assertEquals(newLastname, cust.getLastName());
}
}
}
/**
* Represents a scenario where DB connectivity is not present due to network issue, or DB service
* unavailable.
*/
@Nested
class ConnectivityIssue {
private static final String EXCEPTION_CAUSE = "Connection not available";
/**
* setup a connection failure scenario.
*
* @throws SQLException if any error occurs.
*/
@BeforeEach
void setUp() throws SQLException {
dao = new DbCustomerDao(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());
doReturn(mockedConnection).when(mockedDataSource).getConnection();
return mockedDataSource;
}
@Test
void addingACustomerFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.add(new Customer(2, "Bernard", "Montgomery")));
}
@Test
void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() {
assertThrows(Exception.class, () -> dao.delete(existingCustomer));
}
@Test
void updatingACustomerFailsWithFeedbackToTheClient() {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
assertThrows(Exception.class, () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)));
}
@Test
void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.getById(existingCustomer.getId()));
}
@Test
void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() {
assertThrows(Exception.class, () -> dao.getAll());
}
}
/**
* Delete customer schema for fresh setup per test.
*
* @throws SQLException if any error occurs.
*/
@AfterEach
void deleteSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL);
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private void assertCustomerCountIs(int count) throws Exception {
try (var allCustomers = dao.getAll()) {
assertEquals(count, allCustomers.count());
}
}
/**
* An arbitrary number which does not correspond to an active Customer id.
*
* @return an int of a customer id which doesn't exist
*/
private int getNonExistingCustomerId() {
return 999;
}
}
@@ -0,0 +1,161 @@
/*
* 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.dao;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
/**
* Tests {@link InMemoryCustomerDao}.
*/
class InMemoryCustomerDaoTest {
private InMemoryCustomerDao dao;
private static final Customer CUSTOMER = new Customer(1, "Freddy", "Krueger");
@BeforeEach
void setUp() {
dao = new InMemoryCustomerDao();
assertTrue(dao.add(CUSTOMER));
}
/**
* Represents the scenario when the DAO operations are being performed on a non-existent
* customer.
*/
@Nested
class NonExistingCustomer {
@Test
void addingShouldResultInSuccess() throws Exception {
try (var allCustomers = dao.getAll()) {
assumeTrue(allCustomers.count() == 1);
}
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.add(nonExistingCustomer);
assertTrue(result);
assertCustomerCountIs(2);
assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get());
}
@Test
void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception {
final var nonExistingCustomer = new Customer(2, "Robert", "Englund");
var result = dao.delete(nonExistingCustomer);
assertFalse(result);
assertCustomerCountIs(1);
}
@Test
void updateShouldBeFailureAndNotAffectExistingCustomers() {
final var nonExistingId = getNonExistingCustomerId();
final var newFirstname = "Douglas";
final var newLastname = "MacArthur";
final var customer = new Customer(nonExistingId, newFirstname, newLastname);
var result = dao.update(customer);
assertFalse(result);
assertFalse(dao.getById(nonExistingId).isPresent());
}
@Test
void retrieveShouldReturnNoCustomer() throws Exception {
assertFalse(dao.getById(getNonExistingCustomerId()).isPresent());
}
}
/**
* Represents the scenario when the DAO operations are being performed on an already existing
* customer.
*/
@Nested
class ExistingCustomer {
@Test
void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception {
var result = dao.add(CUSTOMER);
assertFalse(result);
assertCustomerCountIs(1);
assertEquals(CUSTOMER, dao.getById(CUSTOMER.getId()).get());
}
@Test
void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception {
var result = dao.delete(CUSTOMER);
assertTrue(result);
assertCustomerCountIs(0);
assertFalse(dao.getById(CUSTOMER.getId()).isPresent());
}
@Test
void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws
Exception {
final var newFirstname = "Bernard";
final var newLastname = "Montgomery";
final var customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname);
var result = dao.update(customer);
assertTrue(result);
final var cust = dao.getById(CUSTOMER.getId()).get();
assertEquals(newFirstname, cust.getFirstName());
assertEquals(newLastname, cust.getLastName());
}
@Test
void retriveShouldReturnTheCustomer() {
var optionalCustomer = dao.getById(CUSTOMER.getId());
assertTrue(optionalCustomer.isPresent());
assertEquals(CUSTOMER, optionalCustomer.get());
}
}
/**
* An arbitrary number which does not correspond to an active Customer id.
*
* @return an int of a customer id which doesn't exist
*/
private int getNonExistingCustomerId() {
return 999;
}
private void assertCustomerCountIs(int count) throws Exception {
try (var allCustomers = dao.getAll()) {
assertEquals(count, allCustomers.count());
}
}
}