diff --git a/src/main/java/de/rwu/easydrop/core/Core.java b/src/main/java/de/rwu/easydrop/core/Core.java index a33c14e..6e6111b 100644 --- a/src/main/java/de/rwu/easydrop/core/Core.java +++ b/src/main/java/de/rwu/easydrop/core/Core.java @@ -9,13 +9,17 @@ import org.slf4j.LoggerFactory; import org.sqlite.SQLiteDataSource; import de.rwu.easydrop.api.client.DataSourceFactory; +import de.rwu.easydrop.api.dto.OfferDTO; import de.rwu.easydrop.data.connector.OfferPersistenceInterface; import de.rwu.easydrop.data.connector.ProductPersistenceInterface; import de.rwu.easydrop.data.connector.SQLiteConnector; +import de.rwu.easydrop.data.connector.TransactionPersistenceInterface; import de.rwu.easydrop.model.Offer; import de.rwu.easydrop.model.ProductCatalogue; +import de.rwu.easydrop.service.mapping.OfferMapper; import de.rwu.easydrop.service.processing.OfferIdentifier; import de.rwu.easydrop.service.processing.OfferProvisioner; +import de.rwu.easydrop.service.processing.TransactionHandler; import de.rwu.easydrop.service.retriever.CatalogueRetriever; import de.rwu.easydrop.service.retriever.ProductRetriever; import de.rwu.easydrop.service.writer.CatalogueWriter; @@ -57,6 +61,7 @@ public final class Core { ProductRetriever retriever = new ProductRetriever(dataSourceFactory, pdb); CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever); CatalogueWriter catWriter = new CatalogueWriter(pdb); + TransactionPersistenceInterface tpi = new SQLiteConnector(new SQLiteDataSource()); LOGGER.info("Loading catalogues"); catRetriever.loadCatalogues(); @@ -70,8 +75,12 @@ public final class Core { provis.runProvisioner(identifiedOffers); LOGGER.info("Creating transactions"); - // Transaction logic! + TransactionHandler txHandler = new TransactionHandler(tpi); + for (OfferDTO o : odb.getOffers()) { + txHandler.turnOfferToTransaction(OfferMapper.mapOfferFromDTO(o)); + } - LOGGER.info("Done!"); + tpi.outputTransactionsToLog(); + tpi.outputSummaryToLog(); } } diff --git a/src/main/java/de/rwu/easydrop/data/connector/OfferPersistenceInterface.java b/src/main/java/de/rwu/easydrop/data/connector/OfferPersistenceInterface.java index 5d2284b..d0f04d8 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/OfferPersistenceInterface.java +++ b/src/main/java/de/rwu/easydrop/data/connector/OfferPersistenceInterface.java @@ -1,7 +1,14 @@ package de.rwu.easydrop.data.connector; +import java.util.List; + import de.rwu.easydrop.api.dto.OfferDTO; +/** + * Interface to offer persistence. + * + * @since 0.3.0 + */ public interface OfferPersistenceInterface { /** * Writes a ProductDTO to persistence. @@ -19,7 +26,21 @@ public interface OfferPersistenceInterface { OfferDTO getOfferDTOById(String offerId); /** - * Deletes all data from persistence. + * Gets all offerDTOs from persistence. + * + * @return offerDTOs */ - void clearData(); + List getOffers(); + + /** + * Deletes all offer data from persistence. + */ + void clearOfferData(); + + /** + * Deletes an offer from persistence. + * + * @param offerId + */ + void deleteOfferById(String offerId); } diff --git a/src/main/java/de/rwu/easydrop/data/connector/ProductPersistenceInterface.java b/src/main/java/de/rwu/easydrop/data/connector/ProductPersistenceInterface.java index b5a1c1a..45ec2bd 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/ProductPersistenceInterface.java +++ b/src/main/java/de/rwu/easydrop/data/connector/ProductPersistenceInterface.java @@ -24,7 +24,7 @@ public interface ProductPersistenceInterface { ProductDTO getProductDTOById(String productId); /** - * Deletes all data from persistence. + * Deletes all product data from persistence. */ - void clearData(); + void clearProductData(); } diff --git a/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java b/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java index 4a9e492..c38d63b 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java +++ b/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java @@ -5,13 +5,19 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sqlite.SQLiteDataSource; import de.rwu.easydrop.api.dto.OfferDTO; import de.rwu.easydrop.api.dto.ProductDTO; +import de.rwu.easydrop.api.dto.TransactionDTO; import de.rwu.easydrop.exception.PersistenceException; import de.rwu.easydrop.model.Webshop; +import de.rwu.easydrop.util.FormattingUtil; /** * Allows connecting to a SQLite Database. @@ -19,7 +25,18 @@ import de.rwu.easydrop.model.Webshop; * @since 0.2.0 */ public final class SQLiteConnector implements - ProductPersistenceInterface, OfferPersistenceInterface { + ProductPersistenceInterface, OfferPersistenceInterface, + TransactionPersistenceInterface { + /** + * Base for calculating percentages. + */ + private static final int PERCENT = 100; + + /** + * Logging instance. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(SQLiteConnector.class); + /** * SQLite Database. */ @@ -41,6 +58,11 @@ public final class SQLiteConnector implements */ private static final String LAST_UPDATE_COL_NAME = "lastUpdate"; + /** + * Name of 'offerId' column. + */ + private static final String OFFERID_COL_NAME = "offerId"; + /** * Creates instance. * @@ -89,6 +111,16 @@ public final class SQLiteConnector implements + ")"); createOffers.close(); + Statement createTransactions = connection.createStatement(); + createTransactions.execute( + "CREATE TABLE IF NOT EXISTS transactions (" + + "offerId TEXT, " + + "earnings REAL, " + + "volume REAL, " + + "transactionTime TEXT" + + ")"); + createTransactions.close(); + // Close the connection connection.close(); } catch (SQLException e) { @@ -105,7 +137,7 @@ public final class SQLiteConnector implements String query = "INSERT INTO products (" + "dataOrigin, productId, currentPrice, merchant, " + "deliveryPrice, available, lastUpdate" - + ") VALUES (" + + ") VALUES ( " + "?, ?, ?, ?, ?, ?, ?" + ")"; @@ -123,7 +155,8 @@ public final class SQLiteConnector implements statement.executeUpdate(); } catch (SQLException e) { - throw new PersistenceException("Something went wrong while saving to SQLite", e); + throw new PersistenceException( + "Something went wrong while saving product to SQLite", e); } } @@ -156,7 +189,8 @@ public final class SQLiteConnector implements } } } catch (SQLException e) { - throw new PersistenceException("Something went wrong while reading from SQLite"); + throw new PersistenceException( + "Something went wrong while reading product from SQLite"); } return dto; @@ -168,16 +202,9 @@ public final class SQLiteConnector implements * @throws SQLException */ public void clearData() { - try (Connection connection = db.getConnection(); - Statement statement = connection.createStatement()) { - - String productsQuery = "DELETE FROM products;"; - String offersQuery = "DELETE FROM offers;"; - statement.executeUpdate(productsQuery); - statement.executeUpdate(offersQuery); - } catch (SQLException e) { - throw new PersistenceException("Something went wrong while clearing the database", e); - } + clearProductData(); + clearOfferData(); + clearTXData(); } /** @@ -202,17 +229,18 @@ public final class SQLiteConnector implements ProductDTO targetProduct = dto.getTargetProduct(); statement.setString(++index, dto.getOfferId()); - statement.setString(++index, sourceProduct.getProductId()); statement.setString(++index, sourceProduct.getDataOrigin().toString()); + statement.setString(++index, sourceProduct.getProductId()); statement.setDouble(++index, sourceProduct.getCurrentPrice()); - statement.setString(++index, targetProduct.getProductId()); statement.setString(++index, targetProduct.getDataOrigin().toString()); + statement.setString(++index, targetProduct.getProductId()); statement.setDouble(++index, targetProduct.getCurrentPrice()); statement.setString(++index, dto.getLastUpdate()); statement.executeUpdate(); } catch (SQLException e) { - throw new PersistenceException("Something went wrong while saving to SQLite", e); + throw new PersistenceException( + "Something went wrong while saving offer to SQLite", e); } } @@ -234,24 +262,186 @@ public final class SQLiteConnector implements try (ResultSet resultSet = statement.executeQuery()) { if (resultSet.next()) { dto = new OfferDTO(); - ProductDTO srcProduct = new ProductDTO( - resultSet.getString("sourceId"), - Webshop.fromString(resultSet.getString("sourceWebshop"))); - srcProduct.setCurrentPrice(resultSet.getDouble("sourcePrice")); - ProductDTO targetProduct = new ProductDTO( - resultSet.getString("targetId"), - Webshop.fromString(resultSet.getString("targetWebshop"))); - srcProduct.setCurrentPrice(resultSet.getDouble("targetPrice")); - dto.setOfferId(resultSet.getString("offerId")); + ProductDTO srcProduct = getProductDTOById(resultSet.getString("sourceId")); + ProductDTO targetProduct = getProductDTOById(resultSet.getString("targetId")); + dto.setOfferId(resultSet.getString(OFFERID_COL_NAME)); dto.setSourceProduct(srcProduct); dto.setTargetProduct(targetProduct); dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME)); } } } catch (SQLException e) { - throw new PersistenceException("Something went wrong while reading from SQLite", e); + throw new PersistenceException( + "Something went wrong while reading offer from SQLite", e); } return dto; } + + @Override + public void writeTX(final TransactionDTO dto) { + String query = "INSERT INTO transactions (" + + "offerId, volume, earnings, transactionTime" + + ") VALUES (" + + "?, ?, ?, ?" + + ")"; + + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + int index = 0; + + statement.setString(++index, dto.getOfferId()); + statement.setDouble(++index, dto.getVolume()); + statement.setDouble(++index, dto.getEarnings()); + statement.setString(++index, dto.getTransactionTime()); + + statement.executeUpdate(); + } catch (SQLException e) { + throw new PersistenceException( + "Something went wrong while saving transaction to SQLite", e); + } + } + + @Override + public void outputTransactionsToLog() { + String query = "SELECT * FROM transactions t " + + "LEFT JOIN offers o ON t.offerId = o.offerId " + + "ORDER BY transactionTime ASC"; + StringBuilder sb = new StringBuilder(); + int index = 0; + sb.append("Transaction History:"); + + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + sb.append("\n"); + sb.append(String.format("%3d", ++index)); + sb.append(String.format(" - %s - ", resultSet.getString("transactionTime"))); + sb.append(String.format("+%s (%s) - %s", + FormattingUtil.formatEuro(resultSet.getDouble("earnings")), + FormattingUtil.formatEuro(resultSet.getDouble("volume")), + resultSet.getString(OFFERID_COL_NAME))); + } + } + } catch (SQLException e) { + throw new PersistenceException( + "Something went wrong while reading transaction from SQLite", e); + } + + String logString = sb.toString(); + LOGGER.info(logString); + } + + @Override + public void outputSummaryToLog() { + String query = "SELECT SUM(earnings) AS earnings, SUM(volume) AS volume FROM transactions;"; + StringBuilder sb = new StringBuilder(); + sb.append("Summary:\n"); + + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + double earnings = resultSet.getDouble("earnings"); + double volume = resultSet.getDouble("volume"); + String avgMargin = String.valueOf( + Math.round(earnings / volume * PERCENT)) + "%"; + + String earningsFormatted = FormattingUtil.formatEuro(earnings); + String volumeFormatted = FormattingUtil.formatEuro(volume); + + sb.append(String.format("Total earnings: %s, ", earningsFormatted)); + sb.append(String.format("total volume: %s ", volumeFormatted)); + sb.append(String.format("(average margin %s)", avgMargin)); + } + } + } catch (SQLException e) { + throw new PersistenceException( + "Something went wrong while reading transaction summary from SQLite", e); + } + + String logString = sb.toString(); + LOGGER.info(logString); + } + + @Override + public void clearTXData() { + flushTable("transactions"); + } + + @Override + public void clearOfferData() { + flushTable("offers"); + } + + @Override + public void clearProductData() { + flushTable("products"); + } + + /** + * Flushes all data from the specified table. + * + * @param table + */ + private void flushTable(final String table) { + try (Connection connection = db.getConnection(); + Statement statement = connection.createStatement()) { + if (table.matches("[\\w]+")) { + String query = "DELETE FROM " + table + ";"; + statement.executeUpdate(query); + } else { + throw new PersistenceException("Table name contains illegal characters"); + } + } catch (SQLException e) { + throw new PersistenceException("Something went wrong while clearing the database", e); + } + } + + @Override + public void deleteOfferById(final String offerId) { + String query = "DELETE FROM offers WHERE offerID = ?;"; + + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, offerId); + + statement.executeUpdate(); + } catch (SQLException e) { + throw new PersistenceException( + "Something went wrong while deleting offer from SQLite", e); + } + } + + @Override + public List getOffers() { + List list = new ArrayList<>(); + String query = "SELECT * FROM offers"; + + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + OfferDTO dto = new OfferDTO(); + ProductDTO srcProduct = getProductDTOById(resultSet.getString("sourceId")); + ProductDTO targetProduct = getProductDTOById(resultSet.getString("targetId")); + dto.setOfferId(resultSet.getString(OFFERID_COL_NAME)); + dto.setSourceProduct(srcProduct); + dto.setTargetProduct(targetProduct); + dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME)); + + list.add(dto); + } + } + } catch (SQLException e) { + throw new PersistenceException( + "Something went wrong while reading offers from SQLite", e); + } + + return list; + } } diff --git a/src/main/java/de/rwu/easydrop/data/connector/TransactionPersistenceInterface.java b/src/main/java/de/rwu/easydrop/data/connector/TransactionPersistenceInterface.java new file mode 100644 index 0000000..8a930a3 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/connector/TransactionPersistenceInterface.java @@ -0,0 +1,32 @@ +package de.rwu.easydrop.data.connector; + +import de.rwu.easydrop.api.dto.TransactionDTO; + +/** + * Persistence interface for transactions. + * + * @since 0.3.0 + */ +public interface TransactionPersistenceInterface { + /** + * Writes a ProductDTO to persistence. + * + * @param dto + */ + void writeTX(TransactionDTO dto); + + /** + * Writes transactions to log. + */ + void outputTransactionsToLog(); + + /** + * Writes transaction summary (total volume and earnings) to log. + */ + void outputSummaryToLog(); + + /** + * Deletes all transaction data from persistence. + */ + void clearTXData(); +} diff --git a/src/main/java/de/rwu/easydrop/exception/InvalidOfferException.java b/src/main/java/de/rwu/easydrop/exception/InvalidOfferException.java index f4d2c0b..aefe676 100644 --- a/src/main/java/de/rwu/easydrop/exception/InvalidOfferException.java +++ b/src/main/java/de/rwu/easydrop/exception/InvalidOfferException.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.exception; +/** + * Exception that signifies the data of an Offer are invalid. + * + * @since 0.3.0 + */ public class InvalidOfferException extends RuntimeException { /** * Throws an exception that signifies the data of an Offer are invalid. diff --git a/src/main/java/de/rwu/easydrop/exception/InvalidTransactionException.java b/src/main/java/de/rwu/easydrop/exception/InvalidTransactionException.java new file mode 100644 index 0000000..66442a4 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/exception/InvalidTransactionException.java @@ -0,0 +1,27 @@ +package de.rwu.easydrop.exception; + +/** + * Exception that signifies the data of a Transaction are invalid. + * + * @since 0.3.0 + */ +public class InvalidTransactionException extends RuntimeException { + /** + * Throws an exception that signifies the data of an Offer are invalid. + * + * @param message + */ + public InvalidTransactionException(final String message) { + super(message); + } + + /** + * Throws an exception that signifies the data of an Offer are invalid. + * + * @param message + * @param cause + */ + public InvalidTransactionException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/de/rwu/easydrop/service/processing/TransactionHandler.java b/src/main/java/de/rwu/easydrop/service/processing/TransactionHandler.java new file mode 100644 index 0000000..928625c --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/processing/TransactionHandler.java @@ -0,0 +1,47 @@ +package de.rwu.easydrop.service.processing; + +import de.rwu.easydrop.api.dto.TransactionDTO; +import de.rwu.easydrop.data.connector.TransactionPersistenceInterface; +import de.rwu.easydrop.model.Offer; +import de.rwu.easydrop.model.Transaction; +import de.rwu.easydrop.service.mapping.TransactionMapper; +import de.rwu.easydrop.util.Timestamp; + +/** + * Handles transactions. + * + * @since 0.3.0 + */ +public class TransactionHandler { + /** + * Transaction persistence. + */ + private TransactionPersistenceInterface txPersistence; + + /** + * Creates new transaction handler. + * + * @param txdb transaction persistence + */ + public TransactionHandler(final TransactionPersistenceInterface txdb) { + this.txPersistence = txdb; + } + + /** + * Creates transaction from offer. + * + * @param offer Offer + */ + public void turnOfferToTransaction(final Offer offer) { + Transaction tx = new Transaction(); + tx.setOfferId(offer.getOfferId()); + tx.setVolume(offer.getTargetProduct().getCurrentPrice()); + tx.setEarnings(offer.getTargetProduct().getCurrentPrice() + - offer.getSourceProduct().getCurrentPrice()); + tx.setTransactionTime(Timestamp.now()); + + // Write transaction to persistence + TransactionDTO txDTO = TransactionMapper.mapTXToDTO(tx); + txPersistence.writeTX(txDTO); + } +} diff --git a/src/main/java/de/rwu/easydrop/service/validation/TransactionValidator.java b/src/main/java/de/rwu/easydrop/service/validation/TransactionValidator.java new file mode 100644 index 0000000..b73e435 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/validation/TransactionValidator.java @@ -0,0 +1,42 @@ +package de.rwu.easydrop.service.validation; + +import de.rwu.easydrop.exception.InvalidTransactionException; +import de.rwu.easydrop.model.Transaction; + +/** + * Validates transactions. + * + * @since 0.3.0 + */ +public final class TransactionValidator { + /** + * Private constructor to prevent unwanted instantiation. + * + * @throws UnsupportedOperationException always + */ + private TransactionValidator() throws UnsupportedOperationException { + throw new UnsupportedOperationException("This is a validator class, don't instantiate it."); + } + + /** + * Makes sure a transaction does not contain invalid information. + * + * @param tx the Transaction + */ + public static void validate(final Transaction tx) { + try { + if (tx.getOfferId().equals("")) { + throw new InvalidTransactionException("Offer ID cannot be empty"); + } + if (tx.getEarnings() == 0) { + throw new InvalidTransactionException("Earnings can't be zero"); + } + if (tx.getVolume() == 0) { + throw new InvalidTransactionException("Volume can't be zero"); + } + } catch (NullPointerException e) { + throw new InvalidTransactionException( + "Required information is missing in the transaction", e); + } + } +} diff --git a/src/main/java/de/rwu/easydrop/service/writer/TransactionWriter.java b/src/main/java/de/rwu/easydrop/service/writer/TransactionWriter.java new file mode 100644 index 0000000..ff649c0 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/writer/TransactionWriter.java @@ -0,0 +1,33 @@ +package de.rwu.easydrop.service.writer; + +import de.rwu.easydrop.api.dto.TransactionDTO; +import de.rwu.easydrop.data.connector.TransactionPersistenceInterface; +import de.rwu.easydrop.model.Transaction; +import de.rwu.easydrop.service.mapping.TransactionMapper; +import de.rwu.easydrop.service.validation.TransactionValidator; + +public class TransactionWriter { + /** + * Persistence. + */ + private TransactionPersistenceInterface persistence; + + /** + * @param newPersistence the persistence to set + */ + public TransactionWriter(final TransactionPersistenceInterface newPersistence) { + this.persistence = newPersistence; + } + + /** + * Validates and saves a transaction to persistence. + * + * @param tx Transaction + */ + public void writeTXToPersistence(final Transaction tx) { + TransactionDTO dto = TransactionMapper.mapTXToDTO(tx); + TransactionValidator.validate(tx); + + persistence.writeTX(dto); + } +}