diff --git a/dao/etc/dao.png b/dao/etc/dao.png index 9fe34b97..452e72ba 100644 Binary files a/dao/etc/dao.png and b/dao/etc/dao.png differ diff --git a/dao/etc/dao.ucls b/dao/etc/dao.ucls index bf11f18b..0706837f 100644 --- a/dao/etc/dao.ucls +++ b/dao/etc/dao.ucls @@ -1,45 +1,71 @@ - - + + - - + + - - + + + + + + + + + - - + + + + + + + + + - - - - - - - - + + + + - + + + + + + + + + + + + + diff --git a/dao/pom.xml b/dao/pom.xml index 3b6fc7d1..05ab2b22 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -44,6 +44,18 @@ log4j log4j + + com.h2database + h2 + + + de.bechte.junit + junit-hierarchicalcontextrunner + + + org.mockito + mockito-core + diff --git a/dao/src/main/java/com/iluwatar/dao/App.java b/dao/src/main/java/com/iluwatar/dao/App.java index 3fa4b34d..a63289ae 100644 --- a/dao/src/main/java/com/iluwatar/dao/App.java +++ b/dao/src/main/java/com/iluwatar/dao/App.java @@ -20,49 +20,101 @@ * 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.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; + +import javax.sql.DataSource; import org.apache.log4j.Logger; +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. - *

- * With the DAO pattern, we can use various method calls to retrieve/add/delete/update data without - * directly interacting with the data. The below example demonstrates basic CRUD operations: select, - * add, update, and delete. + * + *

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. + * * */ public class App { - + private static final String DB_URL = "jdbc:h2:~/dao:customerdb"; private static Logger log = Logger.getLogger(App.class); - + /** * Program entry point. * * @param args command line args. + * @throws Exception if any error occurs. */ - public static void main(final String[] args) { - final CustomerDao customerDao = new CustomerDaoImpl(generateSampleCustomers()); - log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); - log.info("customerDao.getCusterById(2): " + customerDao.getCustomerById(2)); + public static void main(final String[] args) throws Exception { + final CustomerDao inMemoryDao = new InMemoryCustomerDao(); + performOperationsUsing(inMemoryDao); + + final DataSource dataSource = createDataSource(); + createSchema(dataSource); + final CustomerDao dbDao = new DbCustomerDao(dataSource); + performOperationsUsing(dbDao); + deleteSchema(dataSource); + } + + private static void deleteSchema(DataSource dataSource) throws SQLException { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private static void createSchema(DataSource dataSource) throws SQLException { + try (Connection connection = dataSource.getConnection(); + Statement statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); + } + } + + private static DataSource createDataSource() { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + return dataSource; + } + + private static void performOperationsUsing(final CustomerDao customerDao) throws Exception { + addCustomers(customerDao); + log.info("customerDao.getAllCustomers(): "); + try (Stream customerStream = customerDao.getAll()) { + customerStream.forEach((customer) -> log.info(customer)); + } + log.info("customerDao.getCustomerById(2): " + customerDao.getById(2)); final Customer customer = new Customer(4, "Dan", "Danson"); - customerDao.addCustomer(customer); - log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + customerDao.add(customer); + log.info("customerDao.getAllCustomers(): " + customerDao.getAll()); customer.setFirstName("Daniel"); customer.setLastName("Danielson"); - customerDao.updateCustomer(customer); - log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); - customerDao.deleteCustomer(customer); - log.info("customerDao.getAllCustomers(): " + customerDao.getAllCustomers()); + customerDao.update(customer); + log.info("customerDao.getAllCustomers(): "); + try (Stream customerStream = customerDao.getAll()) { + customerStream.forEach((cust) -> log.info(cust)); + } + customerDao.delete(customer); + log.info("customerDao.getAllCustomers(): " + customerDao.getAll()); + } + + private static void addCustomers(CustomerDao customerDao) throws Exception { + for (Customer customer : generateSampleCustomers()) { + customerDao.add(customer); + } } /** diff --git a/dao/src/main/java/com/iluwatar/dao/Customer.java b/dao/src/main/java/com/iluwatar/dao/Customer.java index 6d10fb14..d6b512dd 100644 --- a/dao/src/main/java/com/iluwatar/dao/Customer.java +++ b/dao/src/main/java/com/iluwatar/dao/Customer.java @@ -20,11 +20,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; /** - * - * Customer + * A customer POJO that represents the data that will be read from the data source. * */ public class Customer { @@ -34,7 +34,7 @@ public class Customer { private String lastName; /** - * Constructor + * Creates an instance of customer. */ public Customer(final int id, final String firstName, final String lastName) { this.id = id; @@ -73,12 +73,12 @@ public String toString() { } @Override - public boolean equals(final Object o) { + public boolean equals(final Object that) { boolean isEqual = false; - if (this == o) { + if (this == that) { isEqual = true; - } else if (o != null && getClass() == o.getClass()) { - final Customer customer = (Customer) o; + } else if (that != null && getClass() == that.getClass()) { + final Customer customer = (Customer) that; if (getId() == customer.getId()) { isEqual = true; } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java index 26b4df47..059a9e9f 100644 --- a/dao/src/main/java/com/iluwatar/dao/CustomerDao.java +++ b/dao/src/main/java/com/iluwatar/dao/CustomerDao.java @@ -20,24 +20,62 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; -import java.util.List; +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. * - * CustomerDao - * + *

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 { - List getAllCustomers(); - - Customer getCustomerById(int id); + /** + * @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 getAll() throws Exception; + + /** + * @param id unique identifier of the customer. + * @return an optional with customer if a customer with unique identifier id + * exists, empty optional otherwise. + * @throws Exception if any error occurs. + */ + Optional getById(int id) throws Exception; - void addCustomer(Customer 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; - void updateCustomer(Customer 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; - void deleteCustomer(Customer 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; } diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerDaoImpl.java b/dao/src/main/java/com/iluwatar/dao/CustomerDaoImpl.java deleted file mode 100644 index 25d4149f..00000000 --- a/dao/src/main/java/com/iluwatar/dao/CustomerDaoImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2014 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.List; - -/** - * - * The 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, database schema, etc. - * - */ -public class CustomerDaoImpl implements CustomerDao { - - // Represents the DB structure for our example so we don't have to managed it ourselves - // Note: Normally this would be in the form of an actual database and not part of the Dao Impl. - private List customers; - - public CustomerDaoImpl(final List customers) { - this.customers = customers; - } - - @Override - public List getAllCustomers() { - return customers; - } - - @Override - public Customer getCustomerById(final int id) { - Customer customer = null; - for (final Customer cus : getAllCustomers()) { - if (cus.getId() == id) { - customer = cus; - break; - } - } - return customer; - } - - @Override - public void addCustomer(final Customer customer) { - if (getCustomerById(customer.getId()) == null) { - customers.add(customer); - } - } - - - @Override - public void updateCustomer(final Customer customer) { - if (getAllCustomers().contains(customer)) { - final int index = getAllCustomers().indexOf(customer); - getAllCustomers().set(index, customer); - } - } - - @Override - public void deleteCustomer(final Customer customer) { - getAllCustomers().remove(customer); - } -} diff --git a/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java b/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java new file mode 100644 index 00000000..05707fa0 --- /dev/null +++ b/dao/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java @@ -0,0 +1,9 @@ +package com.iluwatar.dao; + +public interface CustomerSchemaSql { + + String CREATE_SCHEMA_SQL = "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " + + "LNAME VARCHAR(100))"; + + String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS"; +} diff --git a/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java new file mode 100644 index 00000000..622b5b1f --- /dev/null +++ b/dao/src/main/java/com/iluwatar/dao/DbCustomerDao.java @@ -0,0 +1,183 @@ +/** + * The MIT License + * Copyright (c) 2014 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; + +/** + * An implementation of {@link CustomerDao} that persists customers in RDBMS. + * + */ +public class DbCustomerDao implements CustomerDao { + + private final DataSource dataSource; + + /** + * Creates an instance of {@link DbCustomerDao} which uses provided dataSource + * to store and retrieve customer information. + * + * @param dataSource a non-null dataSource. + */ + public DbCustomerDao(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * @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 getAll() throws Exception { + + Connection connection; + try { + connection = getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); + ResultSet resultSet = statement.executeQuery(); + return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, + Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createCustomer(resultSet)); + return true; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + }, false).onClose(() -> mutedClose(connection)); + } catch (SQLException e) { + throw new Exception(e.getMessage(), e); + } + } + + private Connection getConnection() throws SQLException { + return dataSource.getConnection(); + } + + private void mutedClose(Connection connection) { + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private Customer createCustomer(ResultSet resultSet) throws SQLException { + return new Customer(resultSet.getInt("ID"), + resultSet.getString("FNAME"), + resultSet.getString("LNAME")); + } + + /** + * {@inheritDoc} + */ + @Override + public Optional getById(int id) throws Exception { + try (Connection connection = getConnection(); + PreparedStatement statement = + connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { + + statement.setInt(1, id); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return Optional.of(createCustomer(resultSet)); + } else { + return Optional.empty(); + } + } catch (SQLException ex) { + throw new Exception(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean add(Customer customer) throws Exception { + if (getById(customer.getId()).isPresent()) { + return false; + } + + try (Connection connection = getConnection(); + PreparedStatement 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 Exception(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean update(Customer customer) throws Exception { + try (Connection connection = getConnection(); + PreparedStatement 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 Exception(ex.getMessage(), ex); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean delete(Customer customer) throws Exception { + try (Connection connection = getConnection(); + PreparedStatement statement = + connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { + statement.setInt(1, customer.getId()); + return statement.executeUpdate() > 0; + } catch (SQLException ex) { + throw new Exception(ex.getMessage(), ex); + } + } +} diff --git a/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java new file mode 100644 index 00000000..15c63d1d --- /dev/null +++ b/dao/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -0,0 +1,73 @@ +/** + * The MIT License + * Copyright (c) 2014 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. + *
+ * This implementation is useful as temporary database or for testing. + */ +public class InMemoryCustomerDao implements CustomerDao { + + private Map idToCustomer = new HashMap<>(); + + /** + * An eagerly evaluated stream of customers stored in memory. + */ + @Override + public Stream getAll() { + return idToCustomer.values().stream(); + } + + @Override + public Optional 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; + } +} diff --git a/dao/src/test/java/com/iluwatar/dao/AppTest.java b/dao/src/test/java/com/iluwatar/dao/AppTest.java index b4caa54b..169fc046 100644 --- a/dao/src/test/java/com/iluwatar/dao/AppTest.java +++ b/dao/src/test/java/com/iluwatar/dao/AppTest.java @@ -20,18 +20,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ + package com.iluwatar.dao; import org.junit.Test; -import java.io.IOException; - /** * Tests that DAO example runs without errors. */ public class AppTest { @Test - public void test() throws IOException { + public void test() throws Exception { String[] args = {}; App.main(args); } diff --git a/dao/src/test/java/com/iluwatar/dao/CustomerDaoImplTest.java b/dao/src/test/java/com/iluwatar/dao/CustomerDaoImplTest.java deleted file mode 100644 index f3074062..00000000 --- a/dao/src/test/java/com/iluwatar/dao/CustomerDaoImplTest.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * The MIT License - * Copyright (c) 2014 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.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; - -public class CustomerDaoImplTest { - - private CustomerDaoImpl impl; - private List customers; - private static final Customer CUSTOMER = new Customer(1, "Freddy", "Krueger"); - - @Before - public void setUp() { - customers = new ArrayList<>(); - customers.add(CUSTOMER); - impl = new CustomerDaoImpl(customers); - } - - @Test - public void deleteExistingCustomer() { - assertEquals(1, impl.getAllCustomers().size()); - impl.deleteCustomer(CUSTOMER); - assertTrue(impl.getAllCustomers().isEmpty()); - } - - @Test - public void deleteNonExistingCustomer() { - final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); - impl.deleteCustomer(nonExistingCustomer); - assertEquals(1, impl.getAllCustomers().size()); - } - - @Test - public void updateExistingCustomer() { - final String newFirstname = "Bernard"; - final String newLastname = "Montgomery"; - final Customer customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); - impl.updateCustomer(customer); - final Customer cust = impl.getCustomerById(CUSTOMER.getId()); - assertEquals(newFirstname, cust.getFirstName()); - assertEquals(newLastname, cust.getLastName()); - } - - @Test - public void updateNonExistingCustomer() { - final int nonExistingId = getNonExistingCustomerId(); - final String newFirstname = "Douglas"; - final String newLastname = "MacArthur"; - final Customer customer = new Customer(nonExistingId, newFirstname, newLastname); - impl.updateCustomer(customer); - assertNull(impl.getCustomerById(nonExistingId)); - final Customer existingCustomer = impl.getCustomerById(CUSTOMER.getId()); - assertEquals(CUSTOMER.getFirstName(), existingCustomer.getFirstName()); - assertEquals(CUSTOMER.getLastName(), existingCustomer.getLastName()); - } - - @Test - public void addCustomer() { - final Customer newCustomer = new Customer(3, "George", "Patton"); - impl.addCustomer(newCustomer); - assertEquals(2, impl.getAllCustomers().size()); - } - - @Test - public void addAlreadyAddedCustomer() { - final Customer newCustomer = new Customer(3, "George", "Patton"); - impl.addCustomer(newCustomer); - assertEquals(2, impl.getAllCustomers().size()); - impl.addCustomer(newCustomer); - assertEquals(2, impl.getAllCustomers().size()); - } - - @Test - public void getExistinCustomerById() { - assertEquals(CUSTOMER, impl.getCustomerById(CUSTOMER.getId())); - } - - @Test - public void getNonExistinCustomerById() { - final int nonExistingId = getNonExistingCustomerId(); - assertNull(impl.getCustomerById(nonExistingId)); - } - - /** - * 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; - } -} diff --git a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java index 600b5ba3..6a02fd6b 100644 --- a/dao/src/test/java/com/iluwatar/dao/CustomerTest.java +++ b/dao/src/test/java/com/iluwatar/dao/CustomerTest.java @@ -20,6 +20,7 @@ * 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.Assert.assertEquals; diff --git a/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java new file mode 100644 index 00000000..08e61ebe --- /dev/null +++ b/dao/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -0,0 +1,247 @@ +package com.iluwatar.dao; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.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 java.sql.Statement; +import java.util.stream.Stream; + +import javax.sql.DataSource; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +/** + * Tests {@link DbCustomerDao}. + */ +@RunWith(HierarchicalContextRunner.class) +public class DbCustomerDaoTest { + + private static final String DB_URL = "jdbc:h2:~/dao:customerdb"; + private DbCustomerDao dao; + private Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); + + /** + * Creates customers schema. + * @throws SQLException if there is any error while creating schema. + */ + @Before + public void createSchema() throws SQLException { + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); + } + } + + /** + * Represents the scenario where DB connectivity is present. + */ + public class ConnectionSuccess { + + /** + * Setup for connection success scenario. + * @throws Exception if any error occurs. + */ + @Before + public void setUp() throws Exception { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL(DB_URL); + dao = new DbCustomerDao(dataSource); + boolean result = dao.add(existingCustomer); + assertTrue(result); + } + + /** + * Represents the scenario when DAO operations are being performed on a non existing customer. + */ + public class NonExistingCustomer { + + @Test + public void addingShouldResultInSuccess() throws Exception { + try (Stream allCustomers = dao.getAll()) { + assumeTrue(allCustomers.count() == 1); + } + + final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); + boolean result = dao.add(nonExistingCustomer); + assertTrue(result); + + assertCustomerCountIs(2); + assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get()); + } + + @Test + public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); + boolean result = dao.delete(nonExistingCustomer); + + assertFalse(result); + assertCustomerCountIs(1); + } + + @Test + public void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + final int nonExistingId = getNonExistingCustomerId(); + final String newFirstname = "Douglas"; + final String newLastname = "MacArthur"; + final Customer customer = new Customer(nonExistingId, newFirstname, newLastname); + boolean result = dao.update(customer); + + assertFalse(result); + assertFalse(dao.getById(nonExistingId).isPresent()); + } + + @Test + public void retrieveShouldReturnNoCustomer() throws Exception { + assertFalse(dao.getById(getNonExistingCustomerId()).isPresent()); + } + } + + /** + * Represents a scenario where DAO operations are being performed on an already existing + * customer. + * + */ + public class ExistingCustomer { + + @Test + public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception { + Customer existingCustomer = new Customer(1, "Freddy", "Krueger"); + + boolean result = dao.add(existingCustomer); + + assertFalse(result); + assertCustomerCountIs(1); + assertEquals(existingCustomer, dao.getById(existingCustomer.getId()).get()); + } + + @Test + public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception { + boolean result = dao.delete(existingCustomer); + + assertTrue(result); + assertCustomerCountIs(0); + assertFalse(dao.getById(existingCustomer.getId()).isPresent()); + } + + @Test + public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws Exception { + final String newFirstname = "Bernard"; + final String newLastname = "Montgomery"; + final Customer customer = new Customer(existingCustomer.getId(), newFirstname, newLastname); + boolean result = dao.update(customer); + + assertTrue(result); + + final Customer 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. + * + */ + public class ConnectivityIssue { + + private static final String EXCEPTION_CAUSE = "Connection not available"; + @Rule public ExpectedException exception = ExpectedException.none(); + + /** + * setup a connection failure scenario. + * @throws SQLException if any error occurs. + */ + @Before + public void setUp() throws SQLException { + dao = new DbCustomerDao(mockedDatasource()); + exception.expect(Exception.class); + exception.expectMessage(EXCEPTION_CAUSE); + } + + private DataSource mockedDatasource() throws SQLException { + DataSource mockedDataSource = mock(DataSource.class); + Connection mockedConnection = mock(Connection.class); + SQLException exception = new SQLException(EXCEPTION_CAUSE); + doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString()); + doReturn(mockedConnection).when(mockedDataSource).getConnection(); + return mockedDataSource; + } + + @Test + public void addingACustomerFailsWithExceptionAsFeedbackToClient() throws Exception { + dao.add(new Customer(2, "Bernard", "Montgomery")); + } + + @Test + public void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() throws Exception { + dao.delete(existingCustomer); + } + + @Test + public void updatingACustomerFailsWithFeedbackToTheClient() throws Exception { + final String newFirstname = "Bernard"; + final String newLastname = "Montgomery"; + + dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname)); + } + + @Test + public void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() throws Exception { + dao.getById(existingCustomer.getId()); + } + + @Test + public void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() throws Exception { + dao.getAll(); + } + + } + + /** + * Delete customer schema for fresh setup per test. + * @throws SQLException if any error occurs. + */ + @After + public void deleteSchema() throws SQLException { + try (Connection connection = DriverManager.getConnection(DB_URL); + Statement statement = connection.createStatement()) { + statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); + } + } + + private void assertCustomerCountIs(int count) throws Exception { + try (Stream allCustomers = dao.getAll()) { + assertTrue(allCustomers.count() == 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; + } +} diff --git a/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java new file mode 100644 index 00000000..65a087b9 --- /dev/null +++ b/dao/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -0,0 +1,164 @@ +/** + * The MIT License + * Copyright (c) 2014 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.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import de.bechte.junit.runners.context.HierarchicalContextRunner; + +/** + * Tests {@link InMemoryCustomerDao}. + */ +@RunWith(HierarchicalContextRunner.class) +public class InMemoryCustomerDaoTest { + + private InMemoryCustomerDao dao; + private static final Customer CUSTOMER = new Customer(1, "Freddy", "Krueger"); + + @Before + public void setUp() { + dao = new InMemoryCustomerDao(); + assertTrue(dao.add(CUSTOMER)); + } + + /** + * Represents the scenario when the DAO operations are being performed on a non existent + * customer. + */ + public class NonExistingCustomer { + + @Test + public void addingShouldResultInSuccess() throws Exception { + try (Stream allCustomers = dao.getAll()) { + assumeTrue(allCustomers.count() == 1); + } + + final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); + boolean result = dao.add(nonExistingCustomer); + assertTrue(result); + + assertCustomerCountIs(2); + assertEquals(nonExistingCustomer, dao.getById(nonExistingCustomer.getId()).get()); + } + + @Test + public void deletionShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + final Customer nonExistingCustomer = new Customer(2, "Robert", "Englund"); + boolean result = dao.delete(nonExistingCustomer); + + assertFalse(result); + assertCustomerCountIs(1); + } + + @Test + public void updationShouldBeFailureAndNotAffectExistingCustomers() throws Exception { + final int nonExistingId = getNonExistingCustomerId(); + final String newFirstname = "Douglas"; + final String newLastname = "MacArthur"; + final Customer customer = new Customer(nonExistingId, newFirstname, newLastname); + boolean result = dao.update(customer); + + assertFalse(result); + assertFalse(dao.getById(nonExistingId).isPresent()); + } + + @Test + public void retrieveShouldReturnNoCustomer() throws Exception { + assertFalse(dao.getById(getNonExistingCustomerId()).isPresent()); + } + } + + /** + * Represents the scenario when the DAO operations are being performed on an already existing + * customer. + */ + public class ExistingCustomer { + + @Test + public void addingShouldResultInFailureAndNotAffectExistingCustomers() throws Exception { + boolean result = dao.add(CUSTOMER); + + assertFalse(result); + assertCustomerCountIs(1); + assertEquals(CUSTOMER, dao.getById(CUSTOMER.getId()).get()); + } + + @Test + public void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception { + boolean result = dao.delete(CUSTOMER); + + assertTrue(result); + assertCustomerCountIs(0); + assertFalse(dao.getById(CUSTOMER.getId()).isPresent()); + } + + @Test + public void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws Exception { + final String newFirstname = "Bernard"; + final String newLastname = "Montgomery"; + final Customer customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); + boolean result = dao.update(customer); + + assertTrue(result); + + final Customer cust = dao.getById(CUSTOMER.getId()).get(); + assertEquals(newFirstname, cust.getFirstName()); + assertEquals(newLastname, cust.getLastName()); + } + + @Test + public void retriveShouldReturnTheCustomer() { + Optional 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 (Stream allCustomers = dao.getAll()) { + assertTrue(allCustomers.count() == count); + } + } +} diff --git a/pom.xml b/pom.xml index 57d03075..9127a865 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 19.0 1.15.1 1.10.19 + 4.12.1 abstract-factory @@ -196,6 +197,12 @@ ${systemrules.version} test + + de.bechte.junit + junit-hierarchicalcontextrunner + ${hierarchical-junit-runner-version} + test +