#75 Added transaction logic

This commit is contained in:
Marvin Scham
2023-06-28 00:38:02 +02:00
parent 19c796b457
commit a2337ed3ad
10 changed files with 439 additions and 33 deletions

View File

@@ -9,13 +9,17 @@ import org.slf4j.LoggerFactory;
import org.sqlite.SQLiteDataSource; import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory; 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.OfferPersistenceInterface;
import de.rwu.easydrop.data.connector.ProductPersistenceInterface; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.data.connector.SQLiteConnector; 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.Offer;
import de.rwu.easydrop.model.ProductCatalogue; 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.OfferIdentifier;
import de.rwu.easydrop.service.processing.OfferProvisioner; 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.CatalogueRetriever;
import de.rwu.easydrop.service.retriever.ProductRetriever; import de.rwu.easydrop.service.retriever.ProductRetriever;
import de.rwu.easydrop.service.writer.CatalogueWriter; import de.rwu.easydrop.service.writer.CatalogueWriter;
@@ -57,6 +61,7 @@ public final class Core {
ProductRetriever retriever = new ProductRetriever(dataSourceFactory, pdb); ProductRetriever retriever = new ProductRetriever(dataSourceFactory, pdb);
CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever); CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
CatalogueWriter catWriter = new CatalogueWriter(pdb); CatalogueWriter catWriter = new CatalogueWriter(pdb);
TransactionPersistenceInterface tpi = new SQLiteConnector(new SQLiteDataSource());
LOGGER.info("Loading catalogues"); LOGGER.info("Loading catalogues");
catRetriever.loadCatalogues(); catRetriever.loadCatalogues();
@@ -70,8 +75,12 @@ public final class Core {
provis.runProvisioner(identifiedOffers); provis.runProvisioner(identifiedOffers);
LOGGER.info("Creating transactions"); 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();
} }
} }

View File

@@ -1,7 +1,14 @@
package de.rwu.easydrop.data.connector; package de.rwu.easydrop.data.connector;
import java.util.List;
import de.rwu.easydrop.api.dto.OfferDTO; import de.rwu.easydrop.api.dto.OfferDTO;
/**
* Interface to offer persistence.
*
* @since 0.3.0
*/
public interface OfferPersistenceInterface { public interface OfferPersistenceInterface {
/** /**
* Writes a ProductDTO to persistence. * Writes a ProductDTO to persistence.
@@ -19,7 +26,21 @@ public interface OfferPersistenceInterface {
OfferDTO getOfferDTOById(String offerId); OfferDTO getOfferDTOById(String offerId);
/** /**
* Deletes all data from persistence. * Gets all offerDTOs from persistence.
*
* @return offerDTOs
*/ */
void clearData(); List<OfferDTO> getOffers();
/**
* Deletes all offer data from persistence.
*/
void clearOfferData();
/**
* Deletes an offer from persistence.
*
* @param offerId
*/
void deleteOfferById(String offerId);
} }

View File

@@ -24,7 +24,7 @@ public interface ProductPersistenceInterface {
ProductDTO getProductDTOById(String productId); ProductDTO getProductDTOById(String productId);
/** /**
* Deletes all data from persistence. * Deletes all product data from persistence.
*/ */
void clearData(); void clearProductData();
} }

View File

@@ -5,13 +5,19 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; 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 org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.dto.OfferDTO; import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.exception.PersistenceException; import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.model.Webshop; import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.FormattingUtil;
/** /**
* Allows connecting to a SQLite Database. * Allows connecting to a SQLite Database.
@@ -19,7 +25,18 @@ import de.rwu.easydrop.model.Webshop;
* @since 0.2.0 * @since 0.2.0
*/ */
public final class SQLiteConnector implements 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. * SQLite Database.
*/ */
@@ -41,6 +58,11 @@ public final class SQLiteConnector implements
*/ */
private static final String LAST_UPDATE_COL_NAME = "lastUpdate"; private static final String LAST_UPDATE_COL_NAME = "lastUpdate";
/**
* Name of 'offerId' column.
*/
private static final String OFFERID_COL_NAME = "offerId";
/** /**
* Creates instance. * Creates instance.
* *
@@ -89,6 +111,16 @@ public final class SQLiteConnector implements
+ ")"); + ")");
createOffers.close(); 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 // Close the connection
connection.close(); connection.close();
} catch (SQLException e) { } catch (SQLException e) {
@@ -123,7 +155,8 @@ public final class SQLiteConnector implements
statement.executeUpdate(); statement.executeUpdate();
} catch (SQLException e) { } 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) { } 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; return dto;
@@ -168,16 +202,9 @@ public final class SQLiteConnector implements
* @throws SQLException * @throws SQLException
*/ */
public void clearData() { public void clearData() {
try (Connection connection = db.getConnection(); clearProductData();
Statement statement = connection.createStatement()) { clearOfferData();
clearTXData();
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);
}
} }
/** /**
@@ -202,17 +229,18 @@ public final class SQLiteConnector implements
ProductDTO targetProduct = dto.getTargetProduct(); ProductDTO targetProduct = dto.getTargetProduct();
statement.setString(++index, dto.getOfferId()); statement.setString(++index, dto.getOfferId());
statement.setString(++index, sourceProduct.getProductId());
statement.setString(++index, sourceProduct.getDataOrigin().toString()); statement.setString(++index, sourceProduct.getDataOrigin().toString());
statement.setString(++index, sourceProduct.getProductId());
statement.setDouble(++index, sourceProduct.getCurrentPrice()); statement.setDouble(++index, sourceProduct.getCurrentPrice());
statement.setString(++index, targetProduct.getProductId());
statement.setString(++index, targetProduct.getDataOrigin().toString()); statement.setString(++index, targetProduct.getDataOrigin().toString());
statement.setString(++index, targetProduct.getProductId());
statement.setDouble(++index, targetProduct.getCurrentPrice()); statement.setDouble(++index, targetProduct.getCurrentPrice());
statement.setString(++index, dto.getLastUpdate()); statement.setString(++index, dto.getLastUpdate());
statement.executeUpdate(); statement.executeUpdate();
} catch (SQLException e) { } 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()) { try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) { if (resultSet.next()) {
dto = new OfferDTO(); dto = new OfferDTO();
ProductDTO srcProduct = new ProductDTO( ProductDTO srcProduct = getProductDTOById(resultSet.getString("sourceId"));
resultSet.getString("sourceId"), ProductDTO targetProduct = getProductDTOById(resultSet.getString("targetId"));
Webshop.fromString(resultSet.getString("sourceWebshop"))); dto.setOfferId(resultSet.getString(OFFERID_COL_NAME));
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"));
dto.setSourceProduct(srcProduct); dto.setSourceProduct(srcProduct);
dto.setTargetProduct(targetProduct); dto.setTargetProduct(targetProduct);
dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME)); dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME));
} }
} }
} catch (SQLException e) { } 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; 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<OfferDTO> getOffers() {
List<OfferDTO> 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;
}
} }

View File

@@ -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();
}

View File

@@ -1,5 +1,10 @@
package de.rwu.easydrop.exception; package de.rwu.easydrop.exception;
/**
* Exception that signifies the data of an Offer are invalid.
*
* @since 0.3.0
*/
public class InvalidOfferException extends RuntimeException { public class InvalidOfferException extends RuntimeException {
/** /**
* Throws an exception that signifies the data of an Offer are invalid. * Throws an exception that signifies the data of an Offer are invalid.

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}