Skip to content

Implement DAO Factory pattern [Closed] #3275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions dao-factory/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
The MIT License
Copyright © 2014-2022 Ilkka Seppälä
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.26.0-SNAPSHOT</version>
</parent>

<artifactId>dao-factory</artifactId>

<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-legacy</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
124 changes: 124 additions & 0 deletions dao-factory/src/main/java/com/iluwatar/daofactory/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import java.io.Serializable;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;

@Slf4j
public class App {

public static void main(String[] args) {
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
CustomerDAO customerDAO = daoFactory.createCustomerDAO();

// Perform CRUD H2 Database
if (customerDAO instanceof H2CustomerDAO h2CustomerDAO) {
h2CustomerDAO.deleteSchema();
h2CustomerDAO.createSchema();
}
Customer<Long> customerInmemory1 = new Customer<>(1L, "Green");
Customer<Long> customerInmemory2 = new Customer<>(2L, "Red");
Customer<Long> customerInmemory3 = new Customer<>(3L, "Blue");
Customer<Long> customerUpdateInmemory = new Customer<>(1L, "Yellow");

LOGGER.debug("H2 - Create customer");
performCreateCustomer(
customerDAO, List.of(customerInmemory1, customerInmemory2, customerInmemory3));
LOGGER.debug("H2 - Update customer");
performUpdateCustomer(customerDAO, customerUpdateInmemory);
LOGGER.debug("H2 - Delete customer");
performDeleteCustomer(customerDAO, 3L);
deleteSchema(customerDAO);

// Perform CRUD MongoDb
daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
customerDAO = daoFactory.createCustomerDAO();
ObjectId idCustomerMongo1 = new ObjectId();
ObjectId idCustomerMongo2 = new ObjectId();
Customer<ObjectId> customer4 = new Customer<>(idCustomerMongo1, "Masca");
Customer<ObjectId> customer5 = new Customer<>(idCustomerMongo2, "Elliot");
Customer<ObjectId> customerUpdateMongo = new Customer<>(idCustomerMongo2, "Henry");

LOGGER.debug("Mongo - Create customer");
performCreateCustomer(customerDAO, List.of(customer4, customer5));
LOGGER.debug("Mongo - Update customer");
performUpdateCustomer(customerDAO, customerUpdateMongo);
LOGGER.debug("Mongo - Delete customer");
performDeleteCustomer(customerDAO, idCustomerMongo2);
deleteSchema(customerDAO);

// Perform CRUD Flat file
daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
customerDAO = daoFactory.createCustomerDAO();
Customer<Long> customerFlatFile1 = new Customer<>(1L, "Duc");
Customer<Long> customerFlatFile2 = new Customer<>(2L, "Quang");
Customer<Long> customerFlatFile3 = new Customer<>(3L, "Nhat");
Customer<Long> customerUpdateFlatFile = new Customer<>(1L, "Thanh");
LOGGER.debug("Flat file - Create customer");
performCreateCustomer(
customerDAO, List.of(customerFlatFile1, customerFlatFile2, customerFlatFile3));
LOGGER.debug("Flat file - Update customer");
performUpdateCustomer(customerDAO, customerUpdateFlatFile);
LOGGER.debug("Flat file - Delete customer");
performDeleteCustomer(customerDAO, 3L);
deleteSchema(customerDAO);
}

public static void deleteSchema(CustomerDAO customerDAO) {
customerDAO.deleteSchema();
}

public static <T extends Serializable> void performCreateCustomer(
CustomerDAO<T> customerDAO, List<Customer<T>> customerList) {
for (Customer<T> customer : customerList) {
customerDAO.save(customer);
}
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}

public static <T extends Serializable> void performUpdateCustomer(
CustomerDAO<T> customerDAO, Customer<T> customerUpdate) {
customerDAO.update(customerUpdate);
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}

public static <T extends Serializable> void performDeleteCustomer(
CustomerDAO<T> customerDAO, T customerId) {
customerDAO.delete(customerId);
List<Customer<T>> customers = customerDAO.findAll();
for (Customer<T> customer : customers) {
LOGGER.debug(customer.toString());
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
47 changes: 47 additions & 0 deletions dao-factory/src/main/java/com/iluwatar/daofactory/Customer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

/**
* A customer generic POJO that represents the data that can be stored in any supported data source.
* This class is designed t work with various ID types (e.g., Long, String, or ObjectId) through
* generic, making it adaptable to different persistence system.
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Customer<T extends Serializable> implements Serializable {
private T id;
private String name;
}
85 changes: 85 additions & 0 deletions dao-factory/src/main/java/com/iluwatar/daofactory/CustomerDAO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import java.io.Serializable;
import java.util.List;
import java.util.Optional;

/**
* The Data Access Object (DAO) pattern provides an abstraction layer between the application and
* the database. It encapsulates data access logic, allowing the application to work with domain
* objects instead of direct database operations.
*
* <p>Implementations handle specific storage mechanisms (e.g., in-memory, databases) while keeping
* client code unchanged.
*
* @see H2CustomerDAO
* @see MongoCustomerDAO
* @see FlatFileCustomerDAO
*/
public interface CustomerDAO<T extends Serializable> {
/**
* Persist the given customer
*
* @param customer the customer to persist
*/
void save(Customer<T> customer);

/**
* Update the given customer
*
* @param customer the customer to update
*/
void update(Customer<T> customer);

/**
* Delete the customer with the given id
*
* @param id the id of the customer to delete
*/
void delete(T id);

/**
* Find all customers
*
* @return a list of customers
*/
List<Customer<T>> findAll();

/**
* Find the customer with the given id
*
* @param id the id of the customer to find
* @return the customer with the given id
*/
Optional<Customer<T>> findById(T id);

/**
* Delete the customer schema. After executing the statements, this function will be called to
* clean up the data and delete the records.
*/
void deleteSchema();
}
45 changes: 45 additions & 0 deletions dao-factory/src/main/java/com/iluwatar/daofactory/DAOFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

/**
* An abstract factory class that provides a way to create concrete DAO (Data Access Object)
* factories for different data sources types (e.g., H2, Mongo, FlatFile).
*
* <p>This class follows the Abstract Factory design pattern, allowing applications to retrieve the
* approriate DAO implementation without being tightly coupled to a specific data source.
*
* @see H2DataSourceFactory
* @see MongoDataSourceFactory
* @see FlatFileDataSourceFactory
*/
public abstract class DAOFactory {
/**
* Retrieves a {@link CustomerDAO} implementation specific to the underlying data source..
*
* @return A data source-specific implementation of {@link CustomerDAO}
*/
public abstract CustomerDAO createCustomerDAO();
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>This class acts as an entry point to obtain DAO factories for different storage mechanisms
* such as relational databases (e.g., H2), document stores (e.g., MongoDB), or file-based systems.
* It uses the {@link DataSourceType} enumeration to determine which concrete factory to
* instantiate.
*
* <p>Example usage:
*
* <pre>{@code
* DAOFactory factory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
* }</pre>
*/
public class DAOFactoryProvider {

private DAOFactoryProvider() {}

/**
* Returns a concrete {@link DAOFactory} intance based on the specified data source type.
*
* @param dataSourceType The type of data source for which a factory is needed. Supported values:
* {@code H2}, {@code Mongo}, {@code FlatFile}
* @return A {@link DAOFactory} implementation corresponding to the given data source type.
* @throws IllegalArgumentException if the given data source type is not supported.
*/
public static DAOFactory getDataSource(DataSourceType dataSourceType) {
return switch (dataSourceType) {
case H2 -> new H2DataSourceFactory();
case MONGO -> new MongoDataSourceFactory();
case FLAT_FILE -> new FlatFileDataSourceFactory();
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

/** Enumerates the types of data sources supported by the application. */
public enum DataSourceType {
H2,
MONGO,
FLAT_FILE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* A Flat File implementation of {@link CustomerDAO}, which store the customer data in a JSON file
* at path {@code ~/Desktop/customer.json}.
*/
@Slf4j
@RequiredArgsConstructor
public class FlatFileCustomerDAO implements CustomerDAO<Long> {
private final Path filePath;
private final Gson gson;
Type customerListType = new TypeToken<List<Customer<Long>>>() {}.getType();

protected Reader createReader(Path filePath) throws IOException {
return new FileReader(filePath.toFile());
}

protected Writer createWriter(Path filePath) throws IOException {
return new FileWriter(filePath.toFile());
}

/** {@inheritDoc} */
@Override
public void save(Customer<Long> customer) {
List<Customer<Long>> customers = new LinkedList<>();
if (filePath.toFile().exists()) {
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
}
customers.add(customer);
try (Writer writer = createWriter(filePath)) {
gson.toJson(customers, writer);
} catch (IOException ex) {
throw new CustomException("Failed to write customer data", ex);
}
}

/** {@inheritDoc} */
@Override
public void update(Customer<Long> customer) {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> customers;
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
customers.stream()
.filter(c -> c.getId().equals(customer.getId()))
.findFirst()
.ifPresentOrElse(
c -> c.setName(customer.getName()),
() -> {
throw new CustomException("Customer not found with id: " + customer.getId());
});
try (Writer writer = createWriter(filePath)) {
gson.toJson(customers, writer);
} catch (IOException ex) {
throw new CustomException("Failed to write customer data", ex);
}
}

/** {@inheritDoc} */
@Override
public void delete(Long id) {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> customers;
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
Customer<Long> customerToRemove =
customers.stream()
.filter(c -> c.getId().equals(id))
.findFirst()
.orElseThrow(() -> new CustomException("Customer not found with id: " + id));
customers.remove(customerToRemove);
try (Writer writer = createWriter(filePath)) {
gson.toJson(customers, writer);
} catch (IOException ex) {
throw new CustomException("Failed to write customer data", ex);
}
}

/** {@inheritDoc} */
@Override
public List<Customer<Long>> findAll() {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> customers;
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
return customers;
}

/** {@inheritDoc} */
@Override
public Optional<Customer<Long>> findById(Long id) {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
List<Customer<Long>> customers = null;
try (Reader reader = createReader(filePath)) {
customers = gson.fromJson(reader, customerListType);
} catch (IOException ex) {
throw new CustomException("Failed to read customer data", ex);
}
return customers.stream().filter(c -> c.getId().equals(id)).findFirst();
}

/** {@inheritDoc} */
@Override
public void deleteSchema() {
if (!filePath.toFile().exists()) {
throw new CustomException("File not found");
}
try {
Files.delete(filePath);
} catch (IOException ex) {
throw new CustomException("Failed to delete customer data");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.nio.file.Path;
import java.nio.file.Paths;

/** FlatFileDataSourceFactory concrete factory. */
public class FlatFileDataSourceFactory extends DAOFactory {
private static final String FILE_PATH =
System.getProperty("user.home") + "/Desktop/customer.json";

@Override
public CustomerDAO<Long> createCustomerDAO() {
Path filePath = Paths.get(FILE_PATH);
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
return new FlatFileCustomerDAO(filePath, gson);
}
}
179 changes: 179 additions & 0 deletions dao-factory/src/main/java/com/iluwatar/daofactory/H2CustomerDAO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.sql.DataSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
* An implementation of {@link CustomerDAO} that uses H2 database (http://www.h2database.com/) which
* is an in-memory database and data will lost after application exits.
*/
@Slf4j
@RequiredArgsConstructor
public class H2CustomerDAO implements CustomerDAO<Long> {
private final DataSource dataSource;
private static final String INSERT_CUSTOMER = "INSERT INTO customer(id, name) VALUES (?, ?)";
private static final String UPDATE_CUSTOMER = "UPDATE customer SET name = ? WHERE id = ?";
private static final String DELETE_CUSTOMER = "DELETE FROM customer WHERE id = ?";
private static final String SELECT_CUSTOMER_BY_ID =
"SELECT customer.id, customer.name FROM customer WHERE id= ?";
private static final String SELECT_ALL_CUSTOMERS = "SELECT customer.* FROM customer";
private static final String CREATE_SCHEMA =
"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";

/** {@inheritDoc} */
@Override
public void save(Customer<Long> customer) {
try (Connection connection = dataSource.getConnection();
PreparedStatement saveStatement = connection.prepareStatement(INSERT_CUSTOMER)) {
saveStatement.setLong(1, customer.getId());
saveStatement.setString(2, customer.getName());
saveStatement.execute();
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}

/** {@inheritDoc} */
@Override
public void update(Customer<Long> customer) {
if (Objects.isNull(customer) || Objects.isNull(customer.getId())) {
throw new CustomException("Custome null or customer id null");
}
try (Connection connection = dataSource.getConnection();
PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
PreparedStatement updateStatement = connection.prepareStatement(UPDATE_CUSTOMER)) {
selectStatement.setLong(1, customer.getId());
try (ResultSet resultSet = selectStatement.executeQuery()) {
if (!resultSet.next()) {
throw new CustomException("Customer not found with id: " + customer.getId());
}
}
updateStatement.setString(1, customer.getName());
updateStatement.setLong(2, customer.getId());
updateStatement.executeUpdate();
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}

/** {@inheritDoc} */
@Override
public void delete(Long id) {
if (Objects.isNull(id)) {
throw new CustomException("Customer id null");
}
try (Connection connection = dataSource.getConnection();
PreparedStatement selectStatement = connection.prepareStatement(SELECT_CUSTOMER_BY_ID);
PreparedStatement deleteStatement = connection.prepareStatement(DELETE_CUSTOMER)) {
selectStatement.setLong(1, id);
try (ResultSet resultSet = selectStatement.executeQuery()) {
if (!resultSet.next()) {
throw new CustomException("Customer not found with id: " + id);
}
}
deleteStatement.setLong(1, id);
deleteStatement.execute();
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}

/** {@inheritDoc} */
@Override
public List<Customer<Long>> findAll() {
List<Customer<Long>> customers = new LinkedList<>();
try (Connection connection = dataSource.getConnection();
PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_CUSTOMERS)) {
try (ResultSet resultSet = selectStatement.executeQuery()) {
while (resultSet.next()) {
Long idCustomer = resultSet.getLong("id");
String nameCustomer = resultSet.getString("name");
customers.add(new Customer<>(idCustomer, nameCustomer));
}
}
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
return customers;
}

/** {@inheritDoc} */
@Override
public Optional<Customer<Long>> findById(Long id) {
if (Objects.isNull(id)) {
throw new CustomException("Customer id null");
}
Customer<Long> customer = null;
try (Connection connection = dataSource.getConnection();
PreparedStatement selectByIdStatement =
connection.prepareStatement(SELECT_CUSTOMER_BY_ID)) {
selectByIdStatement.setLong(1, id);
try (ResultSet resultSet = selectByIdStatement.executeQuery()) {
while (resultSet.next()) {
Long idCustomer = resultSet.getLong("id");
String nameCustomer = resultSet.getString("name");
customer = new Customer<>(idCustomer, nameCustomer);
}
}
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
return Optional.ofNullable(customer);
}

/** Create customer schema. */
public void createSchema() {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.execute(CREATE_SCHEMA);
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}

/** {@inheritDoc}} */
@Override
public void deleteSchema() {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement(); ) {
statement.execute(DROP_SCHEMA);
} catch (SQLException e) {
throw new CustomException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import javax.sql.DataSource;
import org.h2.jdbcx.JdbcDataSource;

/** H2DataSourceFactory concrete factory. */
public class H2DataSourceFactory extends DAOFactory {
private static final String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
private static final String USER = "sa";
private static final String PASS = "";

@Override
public CustomerDAO createCustomerDAO() {
return new H2CustomerDAO(createDataSource());
}

private DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
dataSource.setUser(USER);
dataSource.setPassword(PASS);
return dataSource;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;
import com.mongodb.client.result.DeleteResult;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;

/** An implementation of {@link CustomerDAO} that uses MongoDB (https://www.mongodb.com/) */
@Slf4j
@RequiredArgsConstructor
public class MongoCustomerDAO implements CustomerDAO<ObjectId> {
private final MongoCollection<Document> customerCollection;

/** {@inheritDoc} */
@Override
public void save(Customer<ObjectId> customer) {
Document customerDocument = new Document("_id", customer.getId());
customerDocument.append("name", customer.getName());
customerCollection.insertOne(customerDocument);
}

/** {@inheritDoc} */
@Override
public void update(Customer<ObjectId> customer) {
Document updateQuery = new Document("_id", customer.getId());
Bson update = Updates.set("name", customer.getName());
customerCollection.updateOne(updateQuery, update);
}

/** {@inheritDoc} */
@Override
public void delete(ObjectId objectId) {
Bson deleteQuery = Filters.eq("_id", objectId);
DeleteResult deleteResult = customerCollection.deleteOne(deleteQuery);
if (deleteResult.getDeletedCount() == 0) {
throw new CustomException("Delete failed: No document found with id: " + objectId);
}
}

/** {@inheritDoc} */
@Override
public List<Customer<ObjectId>> findAll() {
List<Customer<ObjectId>> customers = new LinkedList<>();
FindIterable<Document> customerDocuments = customerCollection.find();
for (Document customerDocument : customerDocuments) {
Customer<ObjectId> customer =
new Customer<>(
(ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
customers.add(customer);
}
return customers;
}

/** {@inheritDoc} */
@Override
public Optional<Customer<ObjectId>> findById(ObjectId objectId) {
Bson filter = Filters.eq("_id", objectId);
Document customerDocument = customerCollection.find(filter).first();
Customer<ObjectId> customerResult = null;
if (customerDocument != null) {
customerResult =
new Customer<>(
(ObjectId) customerDocument.get("_id"), customerDocument.getString("name"));
}
return Optional.ofNullable(customerResult);
}

/** {@inheritDoc} */
@Override
public void deleteSchema() {
customerCollection.drop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
import org.bson.types.ObjectId;

/** MongoDataSourceFactory concrete factory. */
public class MongoDataSourceFactory extends DAOFactory {
private static final String CONN_STR = "mongodb://localhost:27017/";
private static final String DB_NAME = "dao_factory";
private static final String COLLECTION_NAME = "customer";

@Override
public CustomerDAO<ObjectId> createCustomerDAO() {
try {
MongoClient mongoClient = MongoClients.create(CONN_STR);
MongoDatabase database = mongoClient.getDatabase(DB_NAME);
MongoCollection<Document> customerCollection = database.getCollection(COLLECTION_NAME);
return new MongoCustomerDAO(customerCollection);
} catch (CustomException e) {
throw new CustomException("Error: " + e);
}
}
}
94 changes: 94 additions & 0 deletions dao-factory/src/test/java/com/iluwatar/daofactory/AppTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.List;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/** {@link App} */
class AppTest {
/** Test perform CRUD in main class */
private CustomerDAO<Long> mockLongCustomerDAO;

private CustomerDAO<ObjectId> mockObjectIdCustomerDAO;

@BeforeEach
void setUp() {
mockLongCustomerDAO = mock(CustomerDAO.class);
mockObjectIdCustomerDAO = mock(CustomerDAO.class);
}

@Test
void testPerformCreateCustomerWithLongId() {
Customer<Long> c1 = new Customer<>(1L, "Test1");
Customer<Long> c2 = new Customer<>(2L, "Test2");

when(mockLongCustomerDAO.findAll()).thenReturn(List.of(c1, c2));

App.performCreateCustomer(mockLongCustomerDAO, List.of(c1, c2));

verify(mockLongCustomerDAO).save(c1);
verify(mockLongCustomerDAO).save(c2);
verify(mockLongCustomerDAO).findAll();
}

@Test
void testPerformUpdateCustomerWithObjectId() {
ObjectId id = new ObjectId();
Customer<ObjectId> updatedCustomer = new Customer<>(id, "Updated");

when(mockObjectIdCustomerDAO.findAll()).thenReturn(List.of(updatedCustomer));

App.performUpdateCustomer(mockObjectIdCustomerDAO, updatedCustomer);

verify(mockObjectIdCustomerDAO).update(updatedCustomer);
verify(mockObjectIdCustomerDAO).findAll();
}

@Test
void testPerformDeleteCustomerWithLongId() {
Long id = 100L;
Customer<Long> remainingCustomer = new Customer<>(1L, "Remaining");

when(mockLongCustomerDAO.findAll()).thenReturn(List.of(remainingCustomer));

App.performDeleteCustomer(mockLongCustomerDAO, id);

verify(mockLongCustomerDAO).delete(id);
verify(mockLongCustomerDAO).findAll();
}

@Test
void testDeleteSchema() {
App.deleteSchema(mockLongCustomerDAO);
verify(mockLongCustomerDAO).deleteSchema();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import static org.junit.jupiter.api.Assertions.assertInstanceOf;

import org.junit.jupiter.api.Test;

/** {@link DAOFactory} */
class DAOFactoryTest {

@Test
void verifyH2CustomerDAOCreation() {
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.H2);
var customerDAO = daoFactory.createCustomerDAO();
assertInstanceOf(H2CustomerDAO.class, customerDAO);
}

@Test
void verifyMongoCustomerDAOCreation() {
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.MONGO);
var customerDAO = daoFactory.createCustomerDAO();
assertInstanceOf(MongoCustomerDAO.class, customerDAO);
}

@Test
void verifyFlatFileCustomerDAOCreation() {
var daoFactory = DAOFactoryProvider.getDataSource(DataSourceType.FLAT_FILE);
var customerDAO = daoFactory.createCustomerDAO();
assertInstanceOf(FlatFileCustomerDAO.class, customerDAO);
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

/** Tests {@link H2CustomerDAO} */
class H2CustomerDAOTest {
private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
private static final String USER = "sa";
private static final String PASS = "";
private static final String CREATE_SCHEMA =
"CREATE TABLE IF NOT EXISTS customer (id BIGINT PRIMARY KEY, name VARCHAR(255))";
private static final String DROP_SCHEMA = "DROP TABLE IF EXISTS customer";
private final Customer<Long> existingCustomer = new Customer<>(1L, "Nguyen");
private H2CustomerDAO h2CustomerDAO;

@BeforeEach
void createSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
var statement = connection.createStatement()) {
statement.execute(CREATE_SCHEMA);
}
}

@AfterEach
void deleteSchema() throws SQLException {
try (var connection = DriverManager.getConnection(DB_URL, USER, PASS);
var statement = connection.createStatement()) {
statement.execute(DROP_SCHEMA);
}
}

/** Class test for scenario connect with datasource succeed */
@Nested
class ConnectionSucceed {

@BeforeEach
void setUp() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
dataSource.setUser(USER);
dataSource.setPassword(PASS);
h2CustomerDAO = new H2CustomerDAO(dataSource);
assertDoesNotThrow(() -> h2CustomerDAO.save(existingCustomer));
var customer = h2CustomerDAO.findById(existingCustomer.getId());
assertTrue(customer.isPresent());
assertEquals(customer.get().getName(), existingCustomer.getName());
assertEquals(customer.get().getId(), existingCustomer.getId());
}

@Nested
class SaveCustomer {
@Test
void givenValidCustomer_whenSaveCustomer_thenAddSucceed() {
var customer = new Customer<>(2L, "Duc");
assertDoesNotThrow(() -> h2CustomerDAO.save(customer));
var customerInDb = h2CustomerDAO.findById(customer.getId());
assertTrue(customerInDb.isPresent());
assertEquals(customerInDb.get().getName(), customer.getName());
assertEquals(customerInDb.get().getId(), customer.getId());
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(2, customers.size());
}

@Test
void givenIdCustomerDuplicated_whenSaveCustomer_thenThrowException() {
var customer = new Customer<>(existingCustomer.getId(), "Duc");
assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(1, customers.size());
}
}

@Nested
class UpdateCustomer {
@Test
void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
assertDoesNotThrow(() -> h2CustomerDAO.update(customerUpdate));
var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
assertTrue(customerInDb.isPresent());
assertEquals(customerInDb.get().getName(), customerUpdate.getName());
}

@Test
void givenIdCustomerNotExist_whenUpdateCustomer_thenThrowException() {
var customerUpdate = new Customer<>(100L, "Duc");
var customerInDb = h2CustomerDAO.findById(customerUpdate.getId());
assertTrue(customerInDb.isEmpty());
assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
}

@Test
void givenNull_whenUpdateCustomer_thenThrowException() {
assertThrows(CustomException.class, () -> h2CustomerDAO.update(null));
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(1, customers.size());
}
}

@Nested
class DeleteCustomer {
@Test
void givenValidId_whenDeleteCustomer_thenDeleteSucceed() {
assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
assertTrue(customerInDb.isEmpty());
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(0, customers.size());
}

@Test
void givenIdCustomerNotExist_whenDeleteCustomer_thenThrowException() {
var customerInDb = h2CustomerDAO.findById(100L);
assertTrue(customerInDb.isEmpty());
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(100L));
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(1, customers.size());
assertEquals(existingCustomer.getName(), customers.get(0).getName());
assertEquals(existingCustomer.getId(), customers.get(0).getId());
}

@Test
void givenNull_whenDeleteCustomer_thenThrowException() {
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(null));
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(1, customers.size());
assertEquals(existingCustomer.getName(), customers.get(0).getName());
}
}

@Nested
class FindAllCustomers {
@Test
void givenNonCustomerInDb_whenFindAllCustomer_thenReturnEmptyList() {
assertDoesNotThrow(() -> h2CustomerDAO.delete(existingCustomer.getId()));
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(0, customers.size());
}

@Test
void givenCustomerExistInDb_whenFindAllCustomer_thenReturnCustomers() {
List<Customer<Long>> customers = h2CustomerDAO.findAll();
assertEquals(1, customers.size());
assertEquals(existingCustomer.getName(), customers.get(0).getName());
assertEquals(existingCustomer.getId(), customers.get(0).getId());
}
}

@Nested
class FindCustomerById {
@Test
void givenValidId_whenFindById_thenReturnCustomer() {
var customerInDb = h2CustomerDAO.findById(existingCustomer.getId());
assertTrue(customerInDb.isPresent());
assertEquals(existingCustomer.getName(), customerInDb.get().getName());
assertEquals(existingCustomer.getId(), customerInDb.get().getId());
}

@Test
void givenIdCustomerNotExist_whenFindById_thenReturnEmpty() {
var customerNotExist = h2CustomerDAO.findById(100L);
assertTrue(customerNotExist.isEmpty());
}

@Test
void givenNull_whenFindById_thenThrowException() {
assertThrows(CustomException.class, () -> h2CustomerDAO.findById(null));
}
}

@Nested
class CreateSchema {
@Test
void whenCreateSchema_thenNotThrowException() {
assertDoesNotThrow(() -> h2CustomerDAO.createSchema());
}
}

@Nested
class DeleteSchema {
@Test
void whenDeleteSchema_thenNotThrowException() {
assertDoesNotThrow(() -> h2CustomerDAO.deleteSchema());
}
}
}

/** Class test with scenario connect with data source failed */
@Nested
class ConnectionFailed {
private static final String EXCEPTION_CAUSE = "Connection not available";

@BeforeEach
void setUp() throws SQLException {
h2CustomerDAO = new H2CustomerDAO(mockedDataSource());
}

private DataSource mockedDataSource() throws SQLException {
var mockedDataSource = mock(DataSource.class);
var mockedConnection = mock(Connection.class);
var exception = new SQLException(EXCEPTION_CAUSE);
doThrow(exception).when(mockedConnection).prepareStatement(Mockito.anyString());
doThrow(exception).when(mockedConnection).createStatement();
doReturn(mockedConnection).when(mockedDataSource).getConnection();
return mockedDataSource;
}

@Test
void givenValidCustomer_whenSaveCustomer_thenThrowException() {
var customer = new Customer<>(2L, "Duc");
CustomException exception =
assertThrows(CustomException.class, () -> h2CustomerDAO.save(customer));
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void givenValidCustomer_whenUpdateCustomer_thenThrowException() {
var customerUpdate = new Customer<>(existingCustomer.getId(), "Duc");
CustomException exception =
assertThrows(CustomException.class, () -> h2CustomerDAO.update(customerUpdate));
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void givenValidId_whenDeleteCustomer_thenThrowException() {
Long idCustomer = existingCustomer.getId();
CustomException exception =
assertThrows(CustomException.class, () -> h2CustomerDAO.delete(idCustomer));
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void whenFindAll_thenThrowException() {
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::findAll);
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void whenFindById_thenThrowException() {
Long idCustomer = existingCustomer.getId();
CustomException exception =
assertThrows(CustomException.class, () -> h2CustomerDAO.findById(idCustomer));
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void whenCreateSchema_thenThrowException() {
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::createSchema);
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}

@Test
void whenDeleteSchema_thenThrowException() {
CustomException exception = assertThrows(CustomException.class, h2CustomerDAO::deleteSchema);
assertEquals(EXCEPTION_CAUSE, exception.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
*
* The MIT License
* Copyright © 2014-2022 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.iluwatar.daofactory;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.model.Filters;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import java.util.List;
import java.util.Optional;
import org.bson.BsonDocument;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.Test;

/** Tests {@link MongoCustomerDAO} */
class MongoCustomerDAOTest {
MongoCollection<Document> customerCollection = mock(MongoCollection.class);
MongoCustomerDAO mongoCustomerDAO = new MongoCustomerDAO(customerCollection);

@Test
void givenValidCustomer_whenSaveCustomer_thenSaveSucceed() {
Customer<ObjectId> customer = new Customer<>(new ObjectId(), "John");
mongoCustomerDAO.save(customer);
verify(customerCollection)
.insertOne(
argThat(
document ->
document.get("_id").equals(customer.getId())
&& document.get("name").equals(customer.getName())));
}

@Test
void givenValidCustomer_whenUpdateCustomer_thenUpdateSucceed() {
ObjectId customerId = new ObjectId();
Customer<ObjectId> customerUpdated = new Customer<>(customerId, "John");
when(customerCollection.updateOne(any(Bson.class), any(Bson.class)))
.thenReturn(UpdateResult.acknowledged(1L, 1L, null));
mongoCustomerDAO.update(customerUpdated);
verify(customerCollection)
.updateOne(
argThat(
(Bson filter) -> {
Document filterDoc = (Document) filter;
return filterDoc.getObjectId("_id").equals(customerId);
}),
argThat(
(Bson update) -> {
BsonDocument bsonDoc = update.toBsonDocument();
BsonDocument setDoc = bsonDoc.getDocument("$set");
return setDoc.getString("name").getValue().equals(customerUpdated.getName());
}));
}

@Test
void givenValidObjectId_whenDeleteCustomer_thenDeleteSucceed() {
ObjectId customerId = new ObjectId();
when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(1));
mongoCustomerDAO.delete(customerId);
verify(customerCollection)
.deleteOne(
argThat(
(Bson filter) -> {
BsonDocument filterDoc = filter.toBsonDocument();
return filterDoc.getObjectId("_id").getValue().equals(customerId);
}));
}

@Test
void givenIdNotExist_whenDeleteCustomer_thenThrowException() {
ObjectId customerId = new ObjectId();
when(customerCollection.deleteOne(any(Bson.class))).thenReturn(DeleteResult.acknowledged(0));
assertThrows(CustomException.class, () -> mongoCustomerDAO.delete(customerId));
verify(customerCollection)
.deleteOne(
argThat(
(Bson filter) -> {
BsonDocument filterDoc = filter.toBsonDocument();
return filterDoc.getObjectId("_id").getValue().equals(customerId);
}));
}

@Test
void findAll_thenReturnAllCustomers() {
FindIterable<Document> findIterable = mock(FindIterable.class);
MongoCursor<Document> cursor = mock(MongoCursor.class);
Document customerDoc1 = new Document("_id", new ObjectId()).append("name", "Duc");
Document customerDoc2 = new Document("_id", new ObjectId()).append("name", "Thanh");
when(customerCollection.find()).thenReturn(findIterable);
when(findIterable.iterator()).thenReturn(cursor);
when(cursor.hasNext()).thenReturn(true, true, false);
when(cursor.next()).thenReturn(customerDoc1, customerDoc2);
List<Customer<ObjectId>> customerList = mongoCustomerDAO.findAll();
assertEquals(2, customerList.size());
verify(customerCollection).find();
}

@Test
void givenValidId_whenFindById_thenReturnCustomer() {
FindIterable<Document> findIterable = mock(FindIterable.class);
ObjectId customerId = new ObjectId();
String customerName = "Duc";
Document customerDoc = new Document("_id", customerId).append("name", customerName);
when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
when(findIterable.first()).thenReturn(customerDoc);

Optional<Customer<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
assertTrue(customer.isPresent());
assertEquals(customerId, customer.get().getId());
assertEquals(customerName, customer.get().getName());
}

@Test
void givenNotExistingId_whenFindById_thenReturnEmpty() {
FindIterable<Document> findIterable = mock(FindIterable.class);
ObjectId customerId = new ObjectId();
when(customerCollection.find(Filters.eq("_id", customerId))).thenReturn(findIterable);
when(findIterable.first()).thenReturn(null);
Optional<Customer<ObjectId>> customer = mongoCustomerDAO.findById(customerId);
assertTrue(customer.isEmpty());
verify(customerCollection).find(Filters.eq("_id", customerId));
}

@Test
void whenDeleteSchema_thenDeleteCollection() {
mongoCustomerDAO.deleteSchema();
verify(customerCollection).drop();
}
}