Connected application components

This commit is contained in:
Marvin Scham
2023-06-27 05:23:43 +02:00
parent b78fb49eb1
commit d01c4d0b1d
31 changed files with 402 additions and 561 deletions

View File

@@ -1,23 +1,8 @@
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.core.Core;
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;
/**
* Kickoff point for the service.
@@ -25,11 +10,6 @@ import de.rwu.easydrop.util.ProductsConfig;
* @since 0.1.0
*/
public final class Main {
/**
* Logger for main process.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
/**
* Prevents unwanted instantiation.
*/
@@ -43,19 +23,6 @@ public final class Main {
* @param args
*/
public static void main(final String[] args) throws ConfigurationException {
Config config = Config.getInstance();
ProductsConfig pConfig = ProductsConfig.getInstance();
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();
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
catWriter.writeCatalogues(pCats);
Core core = new Core();
core.runCore(pCats);
Core.run();
}
}

View File

@@ -9,6 +9,7 @@ import com.jayway.jsonpath.ReadContext;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.Timestamp;
/**
* Interface to an Amazon data source.
@@ -61,6 +62,7 @@ public final class AmazonProductDataSource extends AbstractDataSource {
product.setDeliveryPrice(
ctx.read(root + "shippingOptions[0].shippingCost.value.amount", double.class));
product.setMerchant(ctx.read(root + "merchant.name", String.class));
product.setLastUpdate(Timestamp.now());
} catch (PathNotFoundException e) {
// Pass, allow incomplete ProductDTO to pass for later validation
}

View File

@@ -2,8 +2,6 @@ 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;
/**
@@ -17,17 +15,6 @@ 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
@@ -65,17 +52,4 @@ 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

@@ -9,6 +9,7 @@ import com.jayway.jsonpath.ReadContext;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.Timestamp;
/**
* Interface to an eBay data source.
@@ -70,6 +71,7 @@ public final class EbayItemDataSource extends AbstractDataSource {
product.setDeliveryPrice(
ctx.read(root + "shippingOptions[0].shippingCost.value", double.class));
product.setMerchant(ctx.read(root + "seller.username", String.class));
product.setLastUpdate(Timestamp.now());
} catch (PathNotFoundException e) {
// Pass, allow incomplete ProductDTO to pass for later validation
}

View File

@@ -1,52 +1,31 @@
package de.rwu.easydrop.api.dto;
import lombok.Data;
import de.rwu.easydrop.model.Product;
import java.util.Date;
/*
* Offer data transfer object
* Offer data transfer object.
*
* @since 0.3.0
*/
@Data
public class OfferDTO {
/**
* Platform internal offer identifier.
* ID of the offer, built from identifiers of the source platforms.
*/
private String offerId;
/**
* The product that our software buys.
*/
private Product sourceProduct;
private ProductDTO sourceProduct;
/**
* The product that our software sells.
*/
private Product saleProduct;
private ProductDTO targetProduct;
/**
* Date of the creation of the offer
* NOTE: Use Timestamp?
* https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html
* Date of last update of the offer.
*/
private Date creationDate;
/**
* Date of last update of the offer on the destination website (API).
*/
private Date upDate;
/**
* Date of last check if offer is still valid.
*/
private Date checkDate;
/**
* Creates OfferDTO instance.
*
* @param newOfferId Internal Offer identifier
*/
public OfferDTO(final String newOfferId) {
this.offerId = newOfferId;
}
private String lastUpdate;
}

View File

@@ -40,6 +40,11 @@ public class ProductDTO {
*/
private boolean available;
/**
* Last update from API.
*/
private String lastUpdate;
/**
* Creates ProductDTO instance.
*

View File

@@ -4,42 +4,74 @@ 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.OfferPersistenceInterface;
import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.data.connector.SQLiteConnector;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.processing.OfferIdentifier;
import de.rwu.easydrop.service.processing.OfferProvisioner;
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;
/**
* The application core.
*
* @since 0.3.0
*/
public class Core {
public final class Core {
/**
* Offer identifier.
* Logging instance.
*/
private OfferIdentifier ident;
/**
* Offer provisioner.
*/
private OfferProvisioner provis;
private static final Logger LOGGER = LoggerFactory.getLogger(Core.class);
/**
* Constructor.
*
* @throws ConfigurationException
* Hidden Constructor.
*/
public Core() throws ConfigurationException {
this.ident = new OfferIdentifier();
this.provis = new OfferProvisioner();
private Core() {
// Hidden constructor
}
/**
* Runs the core.
*
* @param pCats
* @throws ConfigurationException
*/
public void runCore(final List<ProductCatalogue> pCats) {
public static void run() throws ConfigurationException {
LOGGER.info("Loading config...");
Config config = Config.getInstance();
ProductsConfig pConfig = ProductsConfig.getInstance();
LOGGER.info("Preparing...");
DataSourceFactory dataSourceFactory = new DataSourceFactory(config);
ProductPersistenceInterface pdb = new SQLiteConnector(new SQLiteDataSource());
OfferPersistenceInterface odb = new SQLiteConnector(new SQLiteDataSource());
ProductRetriever retriever = new ProductRetriever(dataSourceFactory, pdb);
CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
CatalogueWriter catWriter = new CatalogueWriter(pdb);
LOGGER.info("Loading catalogues");
catRetriever.loadCatalogues();
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
catWriter.writeCatalogues(pCats);
LOGGER.info("Creating offers");
OfferIdentifier ident = new OfferIdentifier();
List<Offer> identifiedOffers = ident.runIdentifier(pCats);
OfferProvisioner provis = new OfferProvisioner(odb);
provis.runProvisioner(identifiedOffers);
// LOGGER.info("Creating transactions");
// TODO: Transactions
LOGGER.info("Done!");
}
}

View File

@@ -1,83 +0,0 @@
package de.rwu.easydrop.core;
import javax.naming.ConfigurationException;
import java.util.List;
import java.util.ArrayList;
import java.util.Date;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair;
import de.rwu.easydrop.service.retriever.OfferRetriever;
import de.rwu.easydrop.service.processing.OrderManager;
import de.rwu.easydrop.exception.InvalidCatalogueException;
public class OfferIdentifier {
/**
* Logger for main process.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OfferIdentifier.class);
/**
* OfferRetriever gets the offer from persistence.
*/
private OfferRetriever offerRetriever;
/**
* OfferIdentifier identifies offers that are
* feasible to place on a target platform based on
* their margin.
*
* @throws ConfigurationException
*/
public OfferIdentifier() throws ConfigurationException {
this.offerRetriever = new OfferRetriever();
}
/**
* runIdentifier calls the price function that decides
* if it is feasible to dropship products and if so,
* at which margin.
*
* @param pCats
* @return newOffers
*/
public List<Offer> runIdentifier(final List<ProductCatalogue> pCats) {
List<Offer> identifiedOffers = new ArrayList<>();
for (ProductCatalogue pCat : pCats) {
try {
// Call price finder for all catalogue
ProductPair pair = OrderManager.getHighestMarginProducts(pCat);
Offer possibleOffer = new Offer();
possibleOffer.setCheckDate(new Date());
possibleOffer.setSourceProduct(pair.getProduct1());
possibleOffer.setSaleProduct(pair.getProduct2());
identifiedOffers.add(possibleOffer);
LOGGER.info(
"Identified offer "
+
pair.getProduct1().getProductId()
+
" -> "
+
pair.getProduct2().getProductId());
// Following fields will be set if offer confirmed
// creationDate, offerId
// Following fields will be set if offer needs update
// upDate
} catch (InvalidCatalogueException e) {
// if no margin, getHighestMarginProducts will throw
System.out.print("product has no margin");
}
}
List<Offer> newOffers = new ArrayList<>();
for (Offer identifiedOffer : identifiedOffers) {
boolean isNew = true;
if (isNew) {
newOffers.add(identifiedOffer);
}
}
return newOffers;
}
}

View File

@@ -1,81 +0,0 @@
package de.rwu.easydrop.core;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import java.util.List;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
public class OfferReviewer {
/**
* Check all Offers and compare them with the API.
* @return list of all items that need to be changed
*/
public List<Offer> checkOffer(/*OfferReader/retriever for database? */) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
List<Offer> changedOffers = new ArrayList<>();
try {
// Establish the database connection
connection = DriverManager.getConnection("jdbc:sqlite:persistence.db");
// Create a SQL statement
statement = connection.createStatement();
// Execute the query to retrieve the entries
String query = "SELECT sourceProduct, saleProduct, creationDate, upDate, "
+ "checkDate, offerId FROM table";
resultSet = statement.executeQuery(query);
// Process the retrieved entries
while (resultSet.next()) {
Product sourceProduct = (Product) resultSet.getObject("sourceProduct");
Product saleProduct = (Product) resultSet.getObject("saleProduct");
java.sql.Date creationDate = resultSet.getDate("creationDate");
Date updateDate = resultSet.getDate("upDate");
Date checkDate = resultSet.getDate("checkDate");
String offerId = resultSet.getString("offerId");
// Call the API to get the current price
double apiPrice = getPriceFromAPI(sourceProduct);
// Compare the prices
if (saleProduct.getCurrentPrice() != apiPrice) {
// Price has changed, create a Product object and add it to the changedProducts list
Offer offer = new Offer();
changedOffers.add(offer);
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return new ArrayList<Offer>();
}
}

View File

@@ -1,110 +0,0 @@
package de.rwu.easydrop.core;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
public class OfferUpdater {
/**
* a.
*/
public OfferUpdater() {
OfferReviewer offerReviewer = new OfferReviewer();
List<Offer> changedOffers = offerReviewer.checkOffer();
}
/**
* A.
* @param offersToUpdate
*/
public void runUpdater(final List<Offer> offersToUpdate) {
Connection connection = null;
PreparedStatement deleteStatement = null;
PreparedStatement insertStatement = null;
try {
// Establish the database connection
connection = DriverManager.getConnection("jdbc:sqlite:persistence.db");
// Disable auto-commit to perform a transaction
connection.setAutoCommit(false);
// Prepare the DELETE statement to remove the existing entries
String deleteQuery = "DELETE FROM your_table WHERE product_id = ?";
deleteStatement = connection.prepareStatement(deleteQuery);
// Prepare the INSERT statement to add the new entries
String insertQuery = "INSERT INTO your_table (product_id, product_name, price)"
+ "VALUES (?, ?, ?)";
insertStatement = connection.prepareStatement(insertQuery);
// Retrieve the existing entries from the database
List<Product> existingProducts = retrieveExistingProducts(connection);
// Delete the existing entries that are not present in the changedProducts list
for (Product existingProduct : existingProducts) {
if (!changedOffers.(existingProduct)) {
deleteStatement.setString(1, existingProduct.getProductId());
deleteStatement.executeUpdate();
}
}
// Insert the new entries or update the existing entries
for (Product changedOffers : offersToUpdate) {
if (existingProducts.contains(changedOffers)) {
// Update the existing entry with the new data
// You need to modify the update query and statement based on your requirements
// Here's an example of updating the price of an existing entry
String updateQuery = "UPDATE table SET currentPrice = ? WHERE offerId = ?";
PreparedStatement updateStatement = connection.prepareStatement(updateQuery);
updateStatement.setDouble(1, changedOffers.getCurrentPrice());
updateStatement.setString(2, changedOffers.getProductId());
updateStatement.executeUpdate();
updateStatement.close();
} else {
// Insert the new entry
insertStatement.setString(1, changedOffers.getProductId());
insertStatement.setString(2, changedOffers.getMerchant());
insertStatement.setDouble(3, changedOffers.getCurrentPrice());
insertStatement.executeUpdate();
}
}
// Commit the transaction
connection.commit();
} catch (SQLException e) {
// Rollback the transaction in case of an exception
if (connection != null) {
try {
connection.rollback();
} catch (SQLException rollbackException) {
rollbackException.printStackTrace();
}
}
e.printStackTrace();
} finally {
// Close the resources (deleteStatement, insertStatement, connection) in a finally block
try {
if (deleteStatement != null) {
deleteStatement.close();
}
if (insertStatement != null) {
insertStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -1,34 +0,0 @@
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

@@ -2,13 +2,13 @@ package de.rwu.easydrop.data.connector;
import de.rwu.easydrop.api.dto.OfferDTO;
public abstract class AbstractOfferPersistence {
public interface OfferPersistenceInterface {
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
public abstract void saveOffer(OfferDTO dto);
void writeOffer(OfferDTO dto);
/**
* Gets a OfferDTO from persistence.
@@ -16,10 +16,10 @@ public abstract class AbstractOfferPersistence {
* @param offerId
* @return Offer data transfer object
*/
public abstract OfferDTO getOfferDTOById(String offerId);
OfferDTO getOfferDTOById(String offerId);
/**
* Deletes all data from persistence.
*/
public abstract void clearData();
void clearData();
}

View File

@@ -0,0 +1,30 @@
package de.rwu.easydrop.data.connector;
import de.rwu.easydrop.api.dto.ProductDTO;
/**
* Allows connecting to a persistent product data store.
*
* @since 0.2.0
*/
public interface ProductPersistenceInterface {
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
void writeProduct(ProductDTO dto);
/**
* Gets a ProductDTO from persistence.
*
* @param productId Product identifier
* @return Product data transfer object
*/
ProductDTO getProductDTOById(String productId);
/**
* Deletes all data from persistence.
*/
void clearData();
}

View File

@@ -1,6 +1,5 @@
package de.rwu.easydrop.data.connector;
import java.net.URL;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -9,6 +8,7 @@ import java.sql.Statement;
import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.model.Webshop;
@@ -18,7 +18,8 @@ import de.rwu.easydrop.model.Webshop;
*
* @since 0.2.0
*/
public final class SQLiteConnector extends AbstractProductPersistence {
public final class SQLiteConnector implements
ProductPersistenceInterface, OfferPersistenceInterface {
/**
* SQLite Database.
*/
@@ -53,8 +54,8 @@ public final class SQLiteConnector extends AbstractProductPersistence {
Connection connection = db.getConnection();
// Execute SQL statements to create tables
Statement statement = connection.createStatement();
statement.execute(
Statement createProducts = connection.createStatement();
createProducts.execute(
"CREATE TABLE IF NOT EXISTS products ("
+ "dataOrigin TEXT, "
+ "productId TEXT, "
@@ -62,12 +63,29 @@ public final class SQLiteConnector extends AbstractProductPersistence {
+ "merchant TEXT, "
+ "deliveryPrice REAL, "
+ "available INT, "
+ "lastupdate TEXT, "
+ "lastUpdate TEXT, "
+ "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE"
+ ")");
// Close the statement and connection
statement.close();
// Close the statement
createProducts.close();
Statement createOffers = connection.createStatement();
createOffers.execute(
"CREATE TABLE IF NOT EXISTS offers ("
+ "offerId TEXT, "
+ "sourceWebshop TEXT, "
+ "sourceId TEXT,"
+ "sourcePrice REAL, "
+ "targetWebshop TEXT, "
+ "targetId TEXT, "
+ "targetPrice REAL, "
+ "lastUpdate TEXT, "
+ "UNIQUE(offerId) ON CONFLICT REPLACE"
+ ")");
createOffers.close();
// Close the connection
connection.close();
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while initializing SQLite DB", e);
@@ -79,12 +97,12 @@ public final class SQLiteConnector extends AbstractProductPersistence {
*
* @param dto
*/
public void saveProduct(final ProductDTO dto) {
public void writeProduct(final ProductDTO dto) {
String query = "INSERT INTO products ("
+ "dataOrigin, productId, currentPrice, merchant, "
+ "deliveryPrice, available, lastupdate"
+ "deliveryPrice, available, lastUpdate"
+ ") VALUES ("
+ "?, ?, ?, ?, ?, ?, datetime('now', 'localtime')"
+ "?, ?, ?, ?, ?, ?, ?"
+ ")";
try (Connection connection = db.getConnection();
@@ -97,6 +115,7 @@ public final class SQLiteConnector extends AbstractProductPersistence {
statement.setString(++index, dto.getMerchant());
statement.setDouble(++index, dto.getDeliveryPrice());
statement.setBoolean(++index, dto.isAvailable());
statement.setString(++index, dto.getLastUpdate());
statement.executeUpdate();
} catch (SQLException e) {
@@ -138,7 +157,7 @@ public final class SQLiteConnector extends AbstractProductPersistence {
public void clearData() {
try (Connection connection = db.getConnection();
Statement statement = connection.createStatement()) {
String query = "DELETE FROM products";
String query = "DELETE FROM products; DELETE FROM offers;";
statement.executeUpdate(query);
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while clearing the database", e);
@@ -146,26 +165,68 @@ public final class SQLiteConnector extends AbstractProductPersistence {
}
@Override
protected Webshop getDataOrigin() {
throw new UnsupportedOperationException(
this.getClass().getName() + " doesn't support getDataOrigin");
public void writeOffer(final OfferDTO dto) {
String query = "INSERT INTO offers ("
+ "offerId, "
+ "sourceWebshop, sourceId, sourcePrice, "
+ "targetWebshop, targetId, targetPrice, "
+ "lastUpdate"
+ ") VALUES ("
+ "?, ?, ?, ?, ?, ?, ?, ?"
+ ")";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
int index = 0;
ProductDTO sourceProduct = dto.getSourceProduct();
ProductDTO targetProduct = dto.getTargetProduct();
statement.setString(++index, dto.getOfferId());
statement.setString(++index, sourceProduct.getProductId());
statement.setString(++index, sourceProduct.getDataOrigin().toString());
statement.setDouble(++index, sourceProduct.getCurrentPrice());
statement.setString(++index, targetProduct.getProductId());
statement.setString(++index, targetProduct.getDataOrigin().toString());
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);
}
}
@Override
protected String getApiKey() {
throw new UnsupportedOperationException(
this.getClass().getName() + " doesn't support getApiKey");
public OfferDTO getOfferDTOById(final String offerId) {
String query = "SELECT * FROM offers WHERE offerId = ?";
OfferDTO dto = null;
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, offerId);
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"));
dto.setSourceProduct(srcProduct);
dto.setTargetProduct(targetProduct);
dto.setLastUpdate(resultSet.getString("lastUpdate"));
}
}
} catch (SQLException e) {
throw new PersistenceException("Something went wrong while reading from SQLite", e);
}
@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");
return dto;
}
}

View File

@@ -0,0 +1,22 @@
package de.rwu.easydrop.exception;
public class InvalidOfferException extends RuntimeException {
/**
* Throws an exception that signifies the data of an Offer are invalid.
*
* @param message
*/
public InvalidOfferException(final String message) {
super(message);
}
/**
* Throws an exception that signifies the data of an Offer are invalid.
*
* @param message
* @param cause
*/
public InvalidOfferException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,8 +1,5 @@
package de.rwu.easydrop.model;
import java.util.Date;
import lombok.Data;
/**
@@ -10,9 +7,13 @@ import lombok.Data;
*
* @since 0.3.0
*/
@Data
public class Offer {
/**
* ID of the offer, built from identifiers of the source platforms.
*/
private String offerId;
/**
* The product that our software buys.
*/
@@ -21,27 +22,10 @@ public class Offer {
/**
* The product that our software sells.
*/
private Product saleProduct;
private Product targetProduct;
/**
* Date of the creation of the offer.
* NOTE: Use Timestamp? https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html
* Date of last update of the offer.
*/
private Date creationDate;
/**
* Date of last update of the offer on the destination website (API).
*/
private Date upDate;
/**
* Date of last check if offer is still valid.
*/
private Date checkDate;
/**
* ID of the offer.
*/
private String offerId;
private String lastUpdate;
}

View File

@@ -40,6 +40,11 @@ public class Product {
*/
private boolean available;
/**
* Last update from API.
*/
private String lastUpdate;
@Override
public final String toString() {
return "Product: ["

View File

@@ -68,7 +68,7 @@ public class ProductCatalogue {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Product Catalogue: ").append(productName).append("\n");
sb.append("Catalogue Name: ").append(productName).append("\n");
sb.append("Description: ").append(description).append("\n");
sb.append("Products:\n");
for (Product product : products) {

View File

@@ -1,7 +1,6 @@
package de.rwu.easydrop.service.mapping;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.model.Offer;
/**
@@ -31,6 +30,10 @@ public final class OfferMapper {
*/
public static Offer mapOfferFromDTO(final OfferDTO dto) {
Offer offer = new Offer();
offer.setOfferId(dto.getOfferId());
offer.setSourceProduct(ProductMapper.mapProductFromDTO(dto.getSourceProduct()));
offer.setTargetProduct(ProductMapper.mapProductFromDTO(dto.getTargetProduct()));
offer.setLastUpdate(dto.getLastUpdate());
return offer;
}
@@ -42,7 +45,11 @@ public final class OfferMapper {
* @return OfferDTO
*/
public static OfferDTO mapOfferToDTO(final Offer offer) {
OfferDTO dto = new OfferDTO(offer.getOfferId());
OfferDTO dto = new OfferDTO();
dto.setOfferId(offer.getOfferId());
dto.setSourceProduct(ProductMapper.mapProductToDTO(offer.getSourceProduct()));
dto.setTargetProduct(ProductMapper.mapProductToDTO(offer.getTargetProduct()));
dto.setLastUpdate(offer.getLastUpdate());
return dto;
}

View File

@@ -39,6 +39,7 @@ public final class ProductMapper {
product.setDeliveryPrice(dto.getDeliveryPrice());
product.setMerchant(dto.getMerchant());
product.setProductId(dto.getProductId());
product.setLastUpdate(dto.getLastUpdate());
return product;
}
@@ -56,6 +57,7 @@ public final class ProductMapper {
dto.setCurrentPrice(product.getCurrentPrice());
dto.setDeliveryPrice(product.getDeliveryPrice());
dto.setMerchant(product.getMerchant());
dto.setLastUpdate(product.getLastUpdate());
return dto;
}

View File

@@ -1,56 +1,56 @@
package de.rwu.easydrop.service.processing;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.exception.InvalidCatalogueException;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair;
import de.rwu.easydrop.util.FormattingUtil;
import de.rwu.easydrop.util.Timestamp;
public class OfferIdentifier {
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OfferIdentifier.class);
/**
* Creates dropshipping orders based on price margin.
* runIdentifier calls the price function that decides
* if it is feasible to dropship products.
*
* @since 0.3.0
* @param pCats Product catalogues
* @return Identified offers
*/
public final class OrderManager {
/**
* Temporary logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OrderManager.class);
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private OrderManager() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a stateless class, don't instantiate it.");
}
/**
* Creates orders for products with sufficient margin.
*
* @param pCats Product Catalogues
*/
public static void createOrders(final List<ProductCatalogue> pCats) {
public List<Offer> runIdentifier(final List<ProductCatalogue> pCats) {
List<Offer> identifiedOffers = new ArrayList<>();
for (ProductCatalogue pCat : pCats) {
ProductPair pair = getHighestMarginProducts(pCat);
// #12: Create actual orders/transactions, remove logger
// Call price finder for all catalogue
ProductPair pair = getHighestMarginProducts(pCat);
Offer possibleOffer = new Offer();
possibleOffer.setLastUpdate(Timestamp.now());
possibleOffer.setSourceProduct(pair.getProduct1());
possibleOffer.setTargetProduct(pair.getProduct2());
identifiedOffers.add(possibleOffer);
double margin = pair.getProduct2().getCurrentPrice()
- pair.getProduct1().getCurrentPrice();
String marginFormatted = FormattingUtil.formatEuro(margin);
LOGGER.info("{}: Margin {} ({} to {})",
LOGGER.info("\n Identified Offer: {} ({} to {}) with margin {} ",
pCat.getProductName(),
marginFormatted,
pair.getProduct1().getDataOrigin(),
pair.getProduct2().getDataOrigin());
pair.getProduct2().getDataOrigin(),
marginFormatted);
}
return identifiedOffers;
}
/**
@@ -79,7 +79,7 @@ public final class OrderManager {
}
if (cheapestProduct.getCurrentPrice() == mostExpensiveProduct.getCurrentPrice()) {
throw new InvalidCatalogueException("Price margin is zero!");
throw new InvalidOfferException("Price margin is zero!");
}
return new ProductPair(cheapestProduct, mostExpensiveProduct);

View File

@@ -1,15 +1,16 @@
package de.rwu.easydrop.core;
package de.rwu.easydrop.service.processing;
import java.util.List;
import de.rwu.easydrop.api.client.AmazonSeller;
import de.rwu.easydrop.api.client.EbaySeller;
import de.rwu.easydrop.exception.DataWriterException;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.writer.OfferWriter;
import de.rwu.easydrop.util.Config;
import de.rwu.easydrop.util.FormattingUtil;
public class OfferProvisioner {
/**
@@ -31,11 +32,11 @@ public class OfferProvisioner {
private EbaySeller ebaySeller;
private void toSeller(final Offer offer) throws IllegalArgumentException {
if (offer.getSaleProduct().getDataOrigin() == Webshop.eBay) {
this.ebaySeller.sellProduct(ProductMapper.mapProductToDTO(offer.getSaleProduct()));
if (offer.getTargetProduct().getDataOrigin() == Webshop.eBay) {
this.ebaySeller.sellProduct(ProductMapper.mapProductToDTO(offer.getTargetProduct()));
} else if (offer.getSaleProduct().getDataOrigin().equals(Webshop.Amazon)) {
this.amazonSeller.sellProduct(ProductMapper.mapProductToDTO(offer.getSaleProduct()));
} else if (offer.getTargetProduct().getDataOrigin().equals(Webshop.Amazon)) {
this.amazonSeller.sellProduct(ProductMapper.mapProductToDTO(offer.getTargetProduct()));
} else {
throw new IllegalArgumentException("Unsupported target plattform");
}
@@ -44,10 +45,11 @@ public class OfferProvisioner {
/**
* Is the class for placing orders on a target platform.
*
* @param db Persistence Interface
*/
public OfferProvisioner(/* OfferWriter for database? */) {
this.offerWriter = new OfferWriter();
public OfferProvisioner(final OfferPersistenceInterface db) {
this.offerWriter = new OfferWriter(db);
this.config = Config.getInstance();
this.amazonSeller = new AmazonSeller(
config.getProperty("AMAZON_API_URL"),
@@ -63,27 +65,17 @@ public class OfferProvisioner {
* @param offersToProvision
*/
public final void runProvisioner(final List<Offer> offersToProvision) {
for (Offer newOffer : offersToProvision) {
String newOfferId = FormattingUtil.removeSpaces(
newOffer.getSourceProduct().getDataOrigin().toString()
+ newOffer.getTargetProduct().getDataOrigin().toString()
+ "_"
+ newOffer.getSourceProduct().getProductId()
+ newOffer.getTargetProduct().getProductId());
try {
this.toSeller(newOffer);
// if successfully transmitted
// add to persistence
// "duplicate" the product with dataOrigin new platform and merchant = "me"
try {
newOffer.setOfferId(newOfferId);
offerWriter.writeOfferToPersistence(newOffer);
} catch (Exception e) {
System.out.println("Could not write to persistence");
}
} catch (IllegalArgumentException e) {
System.out.println(
"Offer could not be placed, "
+ newOffer.getSaleProduct().getDataOrigin()
+ " is not supported");
} catch (DataWriterException e) {
System.out.println("could not transmit offer");
}
}
}
}

View File

@@ -5,6 +5,9 @@ import java.util.List;
import javax.naming.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.util.ProductsConfig;
@@ -17,6 +20,11 @@ import lombok.Data;
*/
@Data
public class CatalogueRetriever {
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CatalogueRetriever.class);
/**
* User-configured products.
*/
@@ -56,15 +64,14 @@ public class CatalogueRetriever {
pCat.getProductName(), pCat.getDescription());
for (Product product : pCat.getProducts()) {
Product newProduct = new Product();
newProduct = productRetriever.getProductFromWebshop(product.getDataOrigin(),
Product newProduct = productRetriever.getProductFromWebshop(product.getDataOrigin(),
product.getProductId());
newProductCatalogue.addProduct(newProduct);
}
productCatalogues.add(newProductCatalogue);
LOGGER.info("\nLoaded Catalogue: \n" + newProductCatalogue.toString());
}
}
}

View File

@@ -1,5 +1,40 @@
package de.rwu.easydrop.service.retriever;
public class OfferRetriever {
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.service.mapping.OfferMapper;
/**
* Retrieves offer information from different sources.
*
* @since 0.3.0
*/
public class OfferRetriever {
/**
* Persistence interface.
*/
private OfferPersistenceInterface persistence;
/**
* Creates an Offer Retriever.
*
* @param db Persistence Interface
*/
public OfferRetriever(final OfferPersistenceInterface db) {
this.persistence = db;
}
/**
* Retrieves an offer from persistence.
*
* @param offerId
* @return Offer from persistence
*/
public Offer getOfferFromPersistence(final String offerId) {
OfferPersistenceInterface src = persistence;
OfferDTO dto = src.getOfferDTOById(offerId);
return OfferMapper.mapOfferFromDTO(dto);
}
}

View File

@@ -4,7 +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.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.service.mapping.ProductMapper;
@@ -20,19 +20,20 @@ public class ProductRetriever {
* Data source factory.
*/
private DataSourceFactory dataSourceFactory;
/**
* @param newDataSourceFactory the WebshopFactory to set
* Persistence interface.
*/
public void setWebshopFactory(final DataSourceFactory newDataSourceFactory) {
this.dataSourceFactory = newDataSourceFactory;
}
private ProductPersistenceInterface persistence;
/**
* @param newDataSourceFactory
* @param newPersistence
*/
public ProductRetriever(final DataSourceFactory newDataSourceFactory) {
this.setWebshopFactory(newDataSourceFactory);
public ProductRetriever(
final DataSourceFactory newDataSourceFactory,
final ProductPersistenceInterface newPersistence) {
this.dataSourceFactory = newDataSourceFactory;
this.persistence = newPersistence;
}
/**
@@ -74,9 +75,7 @@ public class ProductRetriever {
* @return Product from persistence
*/
public Product getProductFromPersistence(final String productId) {
AbstractProductPersistence src = dataSourceFactory.createProductPersistenceDataSource();
ProductDTO dto = src.getProductDTOById(productId);
ProductDTO dto = persistence.getProductDTOById(productId);
Product product = ProductMapper.mapProductFromDTO(dto);
ProductValidator.validate(product);

View File

@@ -1,5 +1,6 @@
package de.rwu.easydrop.service.validation;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
/**
@@ -16,13 +17,19 @@ public final class OfferValidator {
private OfferValidator() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a validator class, don't instantiate it.");
}
/**
* Makes sure an Offer does not contain invalid information.
*
* @param offer the Offer
*/
public static void validate(final Offer offer) { }
public static void validate(final Offer offer) {
try {
if (offer.getOfferId().equals("")) {
throw new InvalidOfferException("Offer ID cannot be empty");
}
} catch (NullPointerException e) {
throw new InvalidOfferException("Required information is missing in the offer", e);
}
}
}

View File

@@ -3,7 +3,7 @@ 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.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.mapping.ProductMapper;
@@ -18,14 +18,14 @@ public final class CatalogueWriter {
/**
* Holds a persistence reference.
*/
private AbstractProductPersistence persistence;
private ProductPersistenceInterface persistence;
/**
* Creates new instance.
*
* @param newPersistence
*/
public CatalogueWriter(final AbstractProductPersistence newPersistence) {
public CatalogueWriter(final ProductPersistenceInterface newPersistence) {
persistence = newPersistence;
}
@@ -40,7 +40,7 @@ public final class CatalogueWriter {
ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto);
persistence.writeProduct(dto);
}
}
}

View File

@@ -1,21 +1,20 @@
package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.data.connector.AbstractOfferPersistence;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.service.mapping.OfferMapper;
import de.rwu.easydrop.service.validation.OfferValidator;
public class OfferWriter {
/**
* Persistence.
*/
private AbstractOfferPersistence persistence;
private OfferPersistenceInterface persistence;
/**
* @param newPersistence the persistence to set
*/
public void setPersistence(final AbstractOfferPersistence newPersistence) {
public OfferWriter(final OfferPersistenceInterface newPersistence) {
this.persistence = newPersistence;
}
@@ -25,9 +24,8 @@ public class OfferWriter {
* @param offer
*/
public void writeOfferToPersistence(final Offer offer) {
OfferValidator.validate(offer);
OfferDTO dto = OfferMapper.mapOfferToDTO(offer);
persistence.saveOffer(dto);
persistence.writeOffer(dto);
}
}

View File

@@ -1,7 +1,7 @@
package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator;
@@ -15,12 +15,12 @@ public class ProductWriter {
/**
* Persistence.
*/
private AbstractProductPersistence persistence;
private ProductPersistenceInterface persistence;
/**
* @param newPersistence the persistence to set
*/
public void setPersistence(final AbstractProductPersistence newPersistence) {
public ProductWriter(final ProductPersistenceInterface newPersistence) {
this.persistence = newPersistence;
}
@@ -33,6 +33,6 @@ public class ProductWriter {
ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto);
persistence.writeProduct(dto);
}
}

View File

@@ -37,4 +37,14 @@ public final class FormattingUtil {
public static String urlEncode(final String str) {
return str.replace(" ", "+");
}
/**
* Removes spaces from target string.
*
* @param str String
* @return Space-less string
*/
public static String removeSpaces(final String str) {
return str.replace(" ", "");
}
}

View File

@@ -0,0 +1,29 @@
package de.rwu.easydrop.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* Serves as a timestamp source.
*
* @since 0.3.0
*/
public abstract class Timestamp {
/**
* Hidden constructor.
*/
private Timestamp() {
// Don't instantiate me!
}
/**
* Returns a formatted time string.
*
* @return String such as "2023-01-01 00:00:00"
*/
public static String now() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return now.format(formatter);
}
}