#53 Implemented SQLite product data persistence + tests

This commit is contained in:
Marvin Scham
2023-06-07 23:17:59 +02:00
parent 0a42a38016
commit 5a6ff71839
27 changed files with 880 additions and 34 deletions

View File

@@ -1,14 +1,20 @@
package de.rwu.easydrop;
import java.util.List;
import javax.naming.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.data.connector.SQLiteConnector;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.retriever.CatalogueRetriever;
import de.rwu.easydrop.service.retriever.ProductRetriever;
import de.rwu.easydrop.service.writer.CatalogueWriter;
import de.rwu.easydrop.util.Config;
import de.rwu.easydrop.util.ProductsConfig;
@@ -41,9 +47,14 @@ public final class Main {
DataSourceFactory dataSourceFactory = new DataSourceFactory(config);
ProductRetriever retriever = new ProductRetriever(dataSourceFactory);
CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
AbstractProductPersistence db = new SQLiteConnector(new SQLiteDataSource());
CatalogueWriter catWriter = new CatalogueWriter(db);
catRetriever.loadCatalogues();
for (ProductCatalogue pCat : catRetriever.getProductCatalogues()) {
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
catWriter.writeCatalogues(pCats);
for (ProductCatalogue pCat : pCats) {
String pCatStr = pCat.toString();
LOGGER.info(pCatStr);
}

View File

@@ -2,6 +2,8 @@ package de.rwu.easydrop.api.client;
import javax.naming.ConfigurationException;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.util.Config;
/**
@@ -15,6 +17,17 @@ public class DataSourceFactory {
* The data source config.
*/
private Config config;
/**
* Persistence interface.
*/
private AbstractProductPersistence persistence = null;
/**
* @param newPersistence the persistence to set
*/
public void setPersistence(final AbstractProductPersistence newPersistence) {
this.persistence = newPersistence;
}
/**
* @param newConfig the config to set
@@ -52,4 +65,17 @@ public class DataSourceFactory {
String apiKey = config.getProperty("EBAY_API_KEY");
return new EbayItemDataSource(apiUrl, apiKey);
}
/**
* Creates a persistence data source.
*
* @return ProductPersistenceInterface
*/
public AbstractProductPersistence createProductPersistenceDataSource() {
if (persistence == null) {
throw new PersistenceException("Persistence is not set");
}
return persistence;
}
}

View File

@@ -0,0 +1,34 @@
package de.rwu.easydrop.data.connector;
import de.rwu.easydrop.api.client.AbstractDataSource;
import de.rwu.easydrop.api.dto.ProductDTO;
/**
* Allows connecting to a persistent product data store.
*
* @since 0.2.0
*/
public abstract class AbstractProductPersistence extends AbstractDataSource {
/**
* Data origin.
*/
public static final String DATA_ORIGIN = "Persistence";
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
public abstract void saveProduct(ProductDTO dto);
/**
* Gets a ProductDTO from persistence.
*/
@Override
public abstract ProductDTO getProductDTOById(String productId);
/**
* Deletes all data from persistence.
*/
public abstract void clearData();
}

View File

@@ -1,10 +0,0 @@
package de.rwu.easydrop.data.connector;
/**
* Allows connecting to a SQLite Database.
*
* TODO implement
*/
public class DatabaseConnector {
}

View File

@@ -0,0 +1,173 @@
package de.rwu.easydrop.data.connector;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.PersistenceException;
/**
* Allows connecting to a SQLite Database.
*
* @since 0.2.0
*/
public final class SQLiteConnector extends AbstractProductPersistence {
/**
* Data origin.
*/
private static final String DATA_ORIGIN = "SQLite";
/**
* SQLite Database.
*/
private SQLiteDataSource db;
/**
* @param src the db to set
*/
public void setDb(final SQLiteDataSource src) {
this.db = src;
}
/**
* Path to SQLite db file.
*/
private static final String PERSISTENCE_PATH = "jdbc:sqlite:persistence.db";
/**
* Creates instance.
*
* @param src SQLite Data Source
*/
public SQLiteConnector(final SQLiteDataSource src) {
db = src;
db.setUrl(PERSISTENCE_PATH);
initializeDatabase();
}
private void initializeDatabase() {
try {
// Create a new database connection
Connection connection = db.getConnection();
// Execute SQL statements to create tables
Statement statement = connection.createStatement();
statement.execute(
"CREATE TABLE IF NOT EXISTS products ("
+ "dataOrigin TEXT, "
+ "productId TEXT, "
+ "currentPrice REAL, "
+ "merchant TEXT, "
+ "deliveryPrice REAL, "
+ "available INT, "
+ "lastupdate TEXT, "
+ "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE"
+ ")");
// Close the statement and connection
statement.close();
connection.close();
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while initializing SQLite DB", e);
}
}
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
public void saveProduct(final ProductDTO dto) {
String query = "INSERT INTO products ("
+ "dataOrigin, productId, currentPrice, merchant, "
+ "deliveryPrice, available, lastupdate"
+ ") VALUES ("
+ "?, ?, ?, ?, ?, ?, datetime('now', 'localtime')"
+ ")";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
int index = 0;
statement.setString(++index, dto.getDataOrigin());
statement.setString(++index, dto.getProductId());
statement.setDouble(++index, dto.getCurrentPrice());
statement.setString(++index, dto.getMerchant());
statement.setDouble(++index, dto.getDeliveryPrice());
statement.setBoolean(++index, dto.isAvailable());
statement.executeUpdate();
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while saving to SQLite", e);
}
}
@Override
public ProductDTO getProductDTOById(final String productId) {
String query = "SELECT * FROM products WHERE productId = ?";
ProductDTO dto = null;
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, productId);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
dto = new ProductDTO(resultSet.getString("productId"),
resultSet.getString("dataOrigin"));
dto.setCurrentPrice(resultSet.getDouble("currentPrice"));
dto.setMerchant(resultSet.getString("merchant"));
dto.setDeliveryPrice(resultSet.getDouble("deliveryPrice"));
dto.setAvailable(resultSet.getBoolean("available"));
}
}
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while reading from SQLite", e);
}
return dto;
}
/**
* Deletes all data from persistence.
*/
public void clearData() {
try (Connection connection = db.getConnection();
Statement statement = connection.createStatement()) {
String query = "DELETE FROM products";
statement.executeUpdate(query);
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while clearing the database", e);
}
}
@Override
protected String getDataOrigin() {
return DATA_ORIGIN;
}
@Override
protected String getApiKey() {
throw new UnsupportedOperationException(
this.getClass().getName() + " doesn't support getApiKey");
}
@Override
protected ProductDTO buildProductDTO(final ProductDTO product, final String json) {
throw new UnsupportedOperationException(
this.getClass().getName() + " doesn't support buildProductDTO");
}
@Override
protected URL createApiUrl(final String productIdentifier) {
throw new UnsupportedOperationException(
this.getClass().getName() + " doesn't support createApiUrl");
}
}

View File

@@ -1,6 +1,6 @@
/**
* Connectors for databases.
*
* TODO implement
* @since 0.2.0
*/
package de.rwu.easydrop.data.connector;

View File

@@ -1,10 +0,0 @@
package de.rwu.easydrop.data.dao;
/**
* Product data access object.
*
* TODO implement
*/
public class ProductDAO {
}

View File

@@ -1,6 +0,0 @@
/**
* Data access objects for business objects created from persistence.
*
* TODO implement
*/
package de.rwu.easydrop.data.dao;

View File

@@ -1,6 +1,6 @@
/**
* Structure for business objects and persisting their info.
*
* TODO implement
* @since 0.2.0
*/
package de.rwu.easydrop.data;

View File

@@ -0,0 +1,27 @@
package de.rwu.easydrop.exception;
/**
* Exception that signifies a problem with data persistence.
*
* @since 0.2.0
*/
public class PersistenceException extends RuntimeException {
/**
* Throws an exception that signifies the data of a Product are invalid.
*
* @param message
*/
public PersistenceException(final String message) {
super(message);
}
/**
* Throws an exception that signifies the data of a Product are invalid.
*
* @param message
* @param cause
*/
public PersistenceException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,16 +1,17 @@
package de.rwu.easydrop.service.mapping;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Product;
/**
* Maps between Product, ProductDAO and ProductDTO.
* Maps between Product, ProductDTO and ProductDTO.
*
* @since 0.2.0
*
* @see Product
* @see ProductDTO
* @see ProductDAO
* @see ProductDTO
*/
public final class ProductMapper {
@@ -41,4 +42,21 @@ public final class ProductMapper {
return product;
}
/**
* Creates a ProductDTO object from a corresponding Product.
*
* @param product Product
* @return ProductDTO
*/
public static ProductDTO mapProductToDTO(final Product product) {
ProductDTO dto = new ProductDTO(product.getProductId(), product.getDataOrigin());
dto.setAvailable(product.isAvailable());
dto.setCurrentPrice(product.getCurrentPrice());
dto.setDeliveryPrice(product.getDeliveryPrice());
dto.setMerchant(product.getMerchant());
return dto;
}
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import javax.naming.ConfigurationException;
import de.rwu.easydrop.exception.InvalidProductException;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.util.ProductsConfig;
@@ -60,10 +61,12 @@ public class CatalogueRetriever {
newProduct.setDataOrigin(product.getDataOrigin());
newProduct.setProductId(product.getProductId());
if (product.getDataOrigin().equals("Amazon")) {
if (newProduct.getDataOrigin().equals("Amazon")) {
newProduct = productRetriever.getProductFromAmazon(product.getProductId());
} else if (product.getDataOrigin().equals("eBay")) {
} else if (newProduct.getDataOrigin().equals("eBay")) {
newProduct = productRetriever.getProductFromEbay(product.getProductId());
} else {
throw new InvalidProductException("Product data origin is invalid");
}
newProductCatalogue.addProduct(newProduct);

View File

@@ -4,6 +4,7 @@ import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator;
@@ -64,4 +65,20 @@ public class ProductRetriever {
return product;
}
/**
* Retrieves a product from persistence.
*
* @param productId
* @return Product from persistence
*/
public Product getProductFromPersistence(final String productId) {
AbstractProductPersistence src = dataSourceFactory.createProductPersistenceDataSource();
ProductDTO dto = src.getProductDTOById(productId);
Product product = ProductMapper.mapProductFromDTO(dto);
ProductValidator.validate(product);
return product;
}
}

View File

@@ -0,0 +1,47 @@
package de.rwu.easydrop.service.writer;
import java.util.List;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator;
/**
* Writes data for all products of multiple catalogues to persistence.
*
* @since 0.2.0
*/
public final class CatalogueWriter {
/**
* Holds a persistence reference.
*/
private AbstractProductPersistence persistence;
/**
* Creates new instance.
*
* @param newPersistence
*/
public CatalogueWriter(final AbstractProductPersistence newPersistence) {
persistence = newPersistence;
}
/**
* Writes all products of specified catalogues to persistence.
*
* @param catalogues
*/
public void writeCatalogues(final List<ProductCatalogue> catalogues) {
for (ProductCatalogue pCat : catalogues) {
for (Product product : pCat.getProducts()) {
ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto);
}
}
}
}

View File

@@ -0,0 +1,38 @@
package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator;
/**
* Wrapper for writing product info to persistence.
*
* @since 0.2.0
*/
public class ProductWriter {
/**
* Persistence.
*/
private AbstractProductPersistence persistence;
/**
* @param newPersistence the persistence to set
*/
public void setPersistence(final AbstractProductPersistence newPersistence) {
this.persistence = newPersistence;
}
/**
* Validates and saves product to persistence.
*
* @param product
*/
public void writeProductToPersistence(final Product product) {
ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto);
}
}

View File

@@ -0,0 +1,6 @@
/**
* Writes Objects to a data store.
*
* @since 0.2.0
*/
package de.rwu.easydrop.service.writer;