From 555e0a54d58a803a9e2083ac965ce5d4d6b01694 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Fri, 19 May 2023 19:37:54 +0200 Subject: [PATCH 01/31] #48 Added Amazon API model + sample response --- SampleResponses/AmazonProductAPIModel.json | 544 +++++++++++++++++++++ 1 file changed, 544 insertions(+) create mode 100644 SampleResponses/AmazonProductAPIModel.json diff --git a/SampleResponses/AmazonProductAPIModel.json b/SampleResponses/AmazonProductAPIModel.json new file mode 100644 index 0000000..0d0cd3a --- /dev/null +++ b/SampleResponses/AmazonProductAPIModel.json @@ -0,0 +1,544 @@ +{ + "/products/2020-08-26/products/{productId}/offers": { + "get": { + "produces": ["application/json"], + "parameters": [ + { + "description": "The Amazon Standard Item Identifier (ASIN) for the product.", + "in": "path", + "name": "productId", + "required": true, + "type": "string" + }, + { + "description": "The region where the customer wants to purchase the product.", + "enum": ["DE", "FR", "UK", "IT", "ES", "US", "CA", "JP"], + "x-docgen-enum-table-extension": [ + { + "value": "DE", + "description": "Germany" + }, + { + "value": "FR", + "description": "France" + }, + { + "value": "UK", + "description": "United Kingdom" + }, + { + "value": "IT", + "description": "Italy" + }, + { + "value": "ES", + "description": "Spain" + }, + { + "value": "US", + "description": "United States of America" + }, + { + "value": "CA", + "description": "Canada" + }, + { + "value": "JP", + "description": "Japan" + } + ], + "in": "query", + "name": "productRegion", + "required": true, + "type": "string" + }, + { + "description": "This field determines the region where to ship the product based on the value in the shippingPostalCode.", + "in": "query", + "name": "shippingRegion", + "required": false, + "type": "string" + }, + { + "description": "The locale of the request, in the form of an IETF language tag. Each ProductRegion supports one or more locales. This value should comply with java.util.Locale.", + "pattern": "^[-_]+$", + "in": "query", + "name": "locale", + "required": true, + "type": "string" + }, + { + "description": "The shipping postal or zip code for customer's request. This parameter doesn't hold geocode.", + "in": "query", + "name": "shippingPostalCode", + "required": false, + "type": "string" + }, + { + "description": "The zero-based number of the page being requested. If not specified, a default value of 0 will be used. When passed, the value must be equal or greater than zero, and less than the number of pages returned in the response.", + "in": "query", + "name": "pageNumber", + "required": false, + "type": "integer" + }, + { + "description": "The number of items desired for each page in the response. If not specified, a default value of 24 will be used. Maximum items that can be fetched in single request is 24.", + "in": "query", + "name": "pageSize", + "required": false, + "type": "integer" + }, + { + "description": "A value that identifies the group within the business account that a customer belongs to. The customer can set this in Amazon Business account information. GroupTag is necessary only if the customer account belongs to more than one group.", + "in": "query", + "name": "groupTag", + "required": false, + "type": "string" + }, + { + "collectionFormat": "csv", + "description": "A list of filter ids for use to query results.", + "in": "query", + "name": "filterIds", + "required": false, + "type": "array", + "items": { + "type": "string" + } + }, + { + "description": "The number of units the customer intends to purchase. This helps Amazon Business determines quantity-based discounts if an eligible offer is present. Defaults to 1.", + "in": "query", + "name": "quantity", + "required": false, + "type": "integer", + "default": 1 + }, + { + "description": "The email address of the customer requesting this resource", + "in": "header", + "name": "x-amz-user-email", + "required": true, + "type": "string" + }, + { + "collectionFormat": "csv", + "description": "A list specifying the offer fields for inclusion in the response object. Excluding this query parameter results in the response including all inclusions.", + "enum": [ + "ALL", + "availability", + "buyingGuidance", + "buyingRestrictions", + "condition", + "fulfillmentType", + "merchant", + "price", + "productCondition", + "productConditionNote", + "quantityLimits", + "quantityPrice", + "taxExclusivePrice", + "deliveryInformation", + "listPrice", + "shippingOptions" + ], + "in": "query", + "name": "inclusionsForOffers", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "x-example": "/path?inclusionsForOffers=FOO,BAR" + } + ], + "responses": { + "200": { + "description": "Search success", + "schema": { + "$ref": "#/definitions/OffersResult" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + }, + "x-amazon-spds-sandbox-behaviors": [ + { + "request": { + "parameters": { + "productRegion": { + "value": "DE" + }, + "locale": { + "value": "es_US" + }, + "productId": { + "value": "B07VDJ5RVF" + }, + "pageNumber": { + "value": "1" + }, + "pageSize": { + "value": "12" + }, + "filterIds": { + "value": ["NEW", "PRIME"] + } + } + }, + "response": { + "offerCount": 2, + "numberOfPages": 1, + "featuredOffer": { + "availability": "[availability]", + "buyingGuidance": "NONE", + "deliveryInformation": "[deliveryInformation]", + "fulfillmentType": "AMAZON_FULFILLMENT", + "offerId": "[offerId]", + "merchant": { + "name": "[merchantName]", + "merchantId": "[merchantId]", + "meanFeedbackRating": 5.0, + "totalFeedbackCount": 1 + }, + "price": { + "value": { + "amount": 1.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "listPrice": { + "value": { + "amount": 2.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "taxExclusivePrice": { + "taxExclusiveAmount": { + "amount": 1.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "displayString": "[taxExclusivePriceDisplayString]", + "label": "[taxExclusivePriceLabel]" + }, + "quantityLimits": { + "minQuantity": 1, + "maxQuantity": 6 + }, + "quantityPrice": { + "quantityPriceTiers": [ + { + "quantityDisplay": "string", + "unitPrice": { + "amount": 0, + "currencyCode": "USD" + }, + "minQuantity": 0, + "price": { + "value": { + "amount": 0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "savingMessage": "string", + "taxExclusivePrice": { + "taxExclusiveAmount": { + "amount": 0, + "currencyCode": "USD" + }, + "displayString": "[taxExclusivePriceDisplayString]", + "formattedPrice": "[formattedPrice]", + "label": "[taxExclusivePriceLabel]" + } + } + ] + }, + "productCondition": "NEW", + "productConditionNote": "[productConditionNote]", + "condition": { + "conditionValue": "NEW", + "conditionNote": "string", + "subCondition": "ACCEPTABLE" + }, + "buyingRestrictions": [], + "shippingOptions": [ + { + "shippingCost": { + "value": { + "amount": 9.0, + "currencyCode": "USD" + } + }, + "deliveryRange": { + "max": "2022-01-01T00:00:01.000Z", + "min": "2022-01-01T00:00:01.000Z" + } + } + ] + }, + "offers": [ + { + "offerId": "[offerId]", + "merchant": { + "name": "[merchantName]", + "merchantId": "[merchantId]", + "meanFeedbackRating": 5.0, + "totalFeedbackCount": 1 + }, + "deliveryInformation": "[deliveryInformation]", + "price": { + "value": { + "amount": 1.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "listPrice": { + "value": { + "amount": 2.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "taxExclusivePrice": { + "taxExclusiveAmount": { + "amount": 1.0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "displayString": "[taxExclusivePriceDisplayString]", + "label": "[taxExclusivePriceLabel]" + }, + "quantityLimits": { + "minQuantity": 1, + "maxQuantity": 6 + }, + "quantityPrice": { + "quantityPriceTiers": [ + { + "quantityDisplay": "string", + "unitPrice": { + "amount": 0, + "currencyCode": "USD" + }, + "minQuantity": 0, + "price": { + "value": { + "amount": 0, + "currencyCode": "USD" + }, + "formattedPrice": "[formattedPrice]", + "priceType": "NEW" + }, + "savingMessage": "string", + "taxExclusivePrice": { + "taxExclusiveAmount": { + "amount": 0, + "currencyCode": "USD" + }, + "displayString": "[taxExclusivePriceDisplayString]", + "formattedPrice": "[formattedPrice]", + "label": "[taxExclusivePriceLabel]" + } + } + ] + }, + "productCondition": "NEW", + "productConditionNote": "[productConditionNote]", + "condition": { + "conditionValue": "NEW", + "conditionNote": "string", + "subCondition": "ACCEPTABLE" + }, + "availability": "[availability]", + "fulfillmentType": "THIRD_PARTY", + "buyingGuidance": "NONE", + "buyingRestrictions": [], + "shippingOptions": [ + { + "shippingCost": { + "value": { + "amount": 9.0, + "currencyCode": "USD" + } + }, + "deliveryRange": { + "max": "2022-01-01T00:00:01.000Z", + "min": "2022-01-01T00:00:01.000Z" + } + } + ] + } + ], + "filterGroups": [ + { + "displayName": "[displayName]", + "items": [ + { + "displayName": "[itemDisplayName]", + "id": "[itemId]" + } + ] + } + ] + } + } + ] + }, + "400": { + "description": "The service was unable to process the request. Reasons for the error are described in an error response object.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + }, + "x-amazon-spds-sandbox-behaviors": [ + { + "request": { + "parameters": { + "productRegion": { + "value": "US" + }, + "productId": { + "value": "B07VDJ5RVF" + } + } + }, + "response": { + "errors": [ + { + "code": "INVALID_REQUEST_PARAMETER", + "message": "Missing required request parameters: [locale]" + } + ] + } + } + ] + }, + "401": { + "description": "The request's authorization header isn't formatted correctly or doesn't contain a valid authorization input.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + }, + "404": { + "description": "The resource specified doesn't exist.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + }, + "500": { + "description": "Encountered an unexpected condition which prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorResponse" + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "description": "The rate limit (requests per second) for this operation.", + "type": "string" + }, + "x-amzn-requestid": { + "description": "A unique request reference identifier.", + "type": "string" + } + } + } + }, + "tags": ["search"], + "description": "Search for offers of a specific product.", + "operationId": "searchOffersRequest", + "summary": "Paginated and filtered search for offers of a specific product." + } + } +} From 1143097aeeb7567add9ed05b6672535a1180f7cc Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Sat, 20 May 2023 05:51:51 +0200 Subject: [PATCH 02/31] Added basic outline --- pom.xml | 2 +- src/main/java/de/rwu/easydrop/Main.java | 24 +++++++++++++++++++ .../rwu/easydrop/api/client/DataSource.java | 23 ++++++++++++++++++ .../rwu/easydrop/api/client/package-info.java | 6 +++++ .../de/rwu/easydrop/api/dto/ProductDTO.java | 8 +++++++ .../de/rwu/easydrop/api/dto/package-info.java | 6 +++++ .../de/rwu/easydrop/api/package-info.java | 6 +++++ .../data/connector/DatabaseConnector.java | 5 ++++ .../easydrop/data/connector/package-info.java | 6 +++++ .../de/rwu/easydrop/data/dao/ProductDAO.java | 5 ++++ .../rwu/easydrop/data/dao/package-info.java | 6 +++++ .../de/rwu/easydrop/data/model/Product.java | 5 ++++ .../rwu/easydrop/data/model/package-info.java | 6 +++++ .../de/rwu/easydrop/data/package-info.java | 6 +++++ .../de/rwu/easydrop/demo/package-info.java | 4 ++-- .../java/de/rwu/easydrop/package-info.java | 6 +++++ .../service/mapping/ProductMapper.java | 5 ++++ .../service/mapping/package-info.java | 6 +++++ .../de/rwu/easydrop/service/package-info.java | 6 +++++ .../service/processing/OrderManager.java | 5 ++++ .../service/processing/package-info.java | 6 +++++ .../service/validation/ProductValidator.java | 5 ++++ .../service/validation/package-info.java | 6 +++++ .../de/rwu/easydrop/util/FormattingUtil.java | 5 ++++ .../de/rwu/easydrop/util/package-info.java | 6 +++++ 25 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/main/java/de/rwu/easydrop/Main.java create mode 100644 src/main/java/de/rwu/easydrop/api/client/DataSource.java create mode 100644 src/main/java/de/rwu/easydrop/api/client/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java create mode 100644 src/main/java/de/rwu/easydrop/api/dto/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/api/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java create mode 100644 src/main/java/de/rwu/easydrop/data/connector/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java create mode 100644 src/main/java/de/rwu/easydrop/data/dao/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/data/model/Product.java create mode 100644 src/main/java/de/rwu/easydrop/data/model/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/data/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java create mode 100644 src/main/java/de/rwu/easydrop/service/mapping/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/service/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/service/processing/OrderManager.java create mode 100644 src/main/java/de/rwu/easydrop/service/processing/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java create mode 100644 src/main/java/de/rwu/easydrop/service/validation/package-info.java create mode 100644 src/main/java/de/rwu/easydrop/util/FormattingUtil.java create mode 100644 src/main/java/de/rwu/easydrop/util/package-info.java diff --git a/pom.xml b/pom.xml index 880badf..a9a4243 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ de.rwu easydrop jar - 0.1-SNAPSHOT + 0.1.0-SNAPSHOT EasyDrop http://maven.apache.org diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java new file mode 100644 index 0000000..608bcd0 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -0,0 +1,24 @@ +package de.rwu.easydrop; + +/** + * Kickoff point for the service. + * + * @since 0.1.0 + */ +public final class Main { + /** + * Prevents unwanted instantiation. + */ + private Main() { + throw new UnsupportedOperationException("Don't instantiate me! >:("); + } + + /** + * Application entrypoint. + * + * @param args + */ + public static void main(final String[] args) { + System.out.println("I'm alive!"); + } +} diff --git a/src/main/java/de/rwu/easydrop/api/client/DataSource.java b/src/main/java/de/rwu/easydrop/api/client/DataSource.java new file mode 100644 index 0000000..740c173 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/client/DataSource.java @@ -0,0 +1,23 @@ +package de.rwu.easydrop.api.client; + +import de.rwu.easydrop.api.dto.ProductDTO; + +/** + * Universal interface to implement concrete APIs on. + */ +public interface DataSource { + /** + * Retrieves product info from the data source. + * + * @param productId Identifier + * @return ProductDTO + */ + ProductDTO getProductById(String productId); + + /** + * Breaks the connection after specified time. + * + * @param timeoutSeconds Seconds to time out after + */ + void setConnectionTimeout(int timeoutSeconds); +} diff --git a/src/main/java/de/rwu/easydrop/api/client/package-info.java b/src/main/java/de/rwu/easydrop/api/client/package-info.java new file mode 100644 index 0000000..b4040d6 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/client/package-info.java @@ -0,0 +1,6 @@ +/** + * API client connectors for interaction with external data sources. + * + * @since 0.1.0 + */ +package de.rwu.easydrop.api.client; diff --git a/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java new file mode 100644 index 0000000..a8d8dfc --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java @@ -0,0 +1,8 @@ +package de.rwu.easydrop.api.dto; + +/** + * Product data transfer object. + */ +public class ProductDTO { + +} diff --git a/src/main/java/de/rwu/easydrop/api/dto/package-info.java b/src/main/java/de/rwu/easydrop/api/dto/package-info.java new file mode 100644 index 0000000..7d4f621 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/dto/package-info.java @@ -0,0 +1,6 @@ +/** + * Data transfer objects for data derived from external sources. + * + * @since 0.1.0 + */ +package de.rwu.easydrop.api.dto; diff --git a/src/main/java/de/rwu/easydrop/api/package-info.java b/src/main/java/de/rwu/easydrop/api/package-info.java new file mode 100644 index 0000000..5c0f1cf --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/package-info.java @@ -0,0 +1,6 @@ +/** + * Interaction with external APIs. + * + * @since 0.1.0 + */ +package de.rwu.easydrop.api; diff --git a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java new file mode 100644 index 0000000..cd933d8 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.data.connector; + +public class DatabaseConnector { + +} diff --git a/src/main/java/de/rwu/easydrop/data/connector/package-info.java b/src/main/java/de/rwu/easydrop/data/connector/package-info.java new file mode 100644 index 0000000..fe11513 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/connector/package-info.java @@ -0,0 +1,6 @@ +/** + * Connectors for databases. + * + * @todo implement + */ +package de.rwu.easydrop.data.connector; diff --git a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java new file mode 100644 index 0000000..e981caa --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.data.dao; + +public class ProductDAO { + +} diff --git a/src/main/java/de/rwu/easydrop/data/dao/package-info.java b/src/main/java/de/rwu/easydrop/data/dao/package-info.java new file mode 100644 index 0000000..8ca4719 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/dao/package-info.java @@ -0,0 +1,6 @@ +/** + * Data access objects for business objects created from persistence. + * + * @todo implement + */ +package de.rwu.easydrop.data.dao; diff --git a/src/main/java/de/rwu/easydrop/data/model/Product.java b/src/main/java/de/rwu/easydrop/data/model/Product.java new file mode 100644 index 0000000..e637c33 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/model/Product.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.data.model; + +public class Product { + +} diff --git a/src/main/java/de/rwu/easydrop/data/model/package-info.java b/src/main/java/de/rwu/easydrop/data/model/package-info.java new file mode 100644 index 0000000..ed9879b --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/model/package-info.java @@ -0,0 +1,6 @@ +/** + * Business objects. + * + * @todo implement + */ +package de.rwu.easydrop.data.model; diff --git a/src/main/java/de/rwu/easydrop/data/package-info.java b/src/main/java/de/rwu/easydrop/data/package-info.java new file mode 100644 index 0000000..8d4e6b7 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/data/package-info.java @@ -0,0 +1,6 @@ +/** + * Structure for business objects and persisting their info. + * + * @todo implement + */ +package de.rwu.easydrop.data; diff --git a/src/main/java/de/rwu/easydrop/demo/package-info.java b/src/main/java/de/rwu/easydrop/demo/package-info.java index 01c1a1b..d4537c6 100644 --- a/src/main/java/de/rwu/easydrop/demo/package-info.java +++ b/src/main/java/de/rwu/easydrop/demo/package-info.java @@ -1,6 +1,6 @@ /** - * Dieses Paket beinhaltet ist zu Demo-Zwecken. + * Package for demo purposes. * - * @since 0.1-SNAPSHOT + * @todo implement */ package de.rwu.easydrop.demo; diff --git a/src/main/java/de/rwu/easydrop/package-info.java b/src/main/java/de/rwu/easydrop/package-info.java new file mode 100644 index 0000000..9d9a0d1 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/package-info.java @@ -0,0 +1,6 @@ +/** + * One of the Top 2 dropshipping platforms around. + * + * @since 0.1.0 + */ +package de.rwu.easydrop; diff --git a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java new file mode 100644 index 0000000..7f006d8 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.service.mapping; + +public class ProductMapper { + +} diff --git a/src/main/java/de/rwu/easydrop/service/mapping/package-info.java b/src/main/java/de/rwu/easydrop/service/mapping/package-info.java new file mode 100644 index 0000000..c7ef687 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/mapping/package-info.java @@ -0,0 +1,6 @@ +/** + * Maps different formats of corresponding objects. + * + * @todo implement + */ +package de.rwu.easydrop.service.mapping; diff --git a/src/main/java/de/rwu/easydrop/service/package-info.java b/src/main/java/de/rwu/easydrop/service/package-info.java new file mode 100644 index 0000000..5c1e2fc --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/package-info.java @@ -0,0 +1,6 @@ +/** + * Packages for supporting business logic. + * + * @todo implement + */ +package de.rwu.easydrop.service; diff --git a/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java new file mode 100644 index 0000000..36d391f --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.service.processing; + +public class OrderManager { + +} diff --git a/src/main/java/de/rwu/easydrop/service/processing/package-info.java b/src/main/java/de/rwu/easydrop/service/processing/package-info.java new file mode 100644 index 0000000..1348cce --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/processing/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports diverse business processes and enforces business rules. + * + * @todo implement + */ +package de.rwu.easydrop.service.processing; diff --git a/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java new file mode 100644 index 0000000..292aeda --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.service.validation; + +public class ProductValidator { + +} diff --git a/src/main/java/de/rwu/easydrop/service/validation/package-info.java b/src/main/java/de/rwu/easydrop/service/validation/package-info.java new file mode 100644 index 0000000..7333014 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/service/validation/package-info.java @@ -0,0 +1,6 @@ +/** + * Supports validation processes. + * + * @todo implement + */ +package de.rwu.easydrop.service.validation; diff --git a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java new file mode 100644 index 0000000..abf0551 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java @@ -0,0 +1,5 @@ +package de.rwu.easydrop.util; + +public class FormattingUtil { + +} diff --git a/src/main/java/de/rwu/easydrop/util/package-info.java b/src/main/java/de/rwu/easydrop/util/package-info.java new file mode 100644 index 0000000..c5417ed --- /dev/null +++ b/src/main/java/de/rwu/easydrop/util/package-info.java @@ -0,0 +1,6 @@ +/** + * General utility such as formatting helpers. + * + * @todo implement + */ +package de.rwu.easydrop.util; From 89efc24ca95bd4603bc33eaf4fa80b353a58257b Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Sat, 20 May 2023 05:58:31 +0200 Subject: [PATCH 03/31] Set more feasible exception type --- src/main/java/de/rwu/easydrop/Main.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 608bcd0..34edee6 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -9,8 +9,8 @@ public final class Main { /** * Prevents unwanted instantiation. */ - private Main() { - throw new UnsupportedOperationException("Don't instantiate me! >:("); + private Main() throws IllegalAccessException { + throw new IllegalAccessException("Don't instantiate me! >:("); } /** From 08dc4e92457b1520e012887572718fc115287cc2 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 01:33:23 +0200 Subject: [PATCH 04/31] Added JSON dependency --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index a9a4243..19759b6 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,11 @@ 5.3.1 test + + org.json + json + 20230227 + From 39115d9f0e42e5c289a72cc4f8f76979498d8196 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 01:34:07 +0200 Subject: [PATCH 05/31] Explicitly linked checkstyle ruleset --- config/custom-checkstyle.xml | 200 +++++++++++++++++++++++++++++++++++ pom.xml | 1 + 2 files changed, 201 insertions(+) create mode 100644 config/custom-checkstyle.xml diff --git a/config/custom-checkstyle.xml b/config/custom-checkstyle.xml new file mode 100644 index 0000000..71d9e97 --- /dev/null +++ b/config/custom-checkstyle.xml @@ -0,0 +1,200 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 19759b6..c6d84c8 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ checkstyle-check verify + config/custom-checkstyle.xml true true true From cadbe04a5df4fb7bbb0a2d4f0c890575a9973ee7 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 01:34:21 +0200 Subject: [PATCH 06/31] Added config structure --- .gitignore | 6 ++++++ config/demo.config.properties | 3 +++ 2 files changed, 9 insertions(+) create mode 100644 config/demo.config.properties diff --git a/.gitignore b/.gitignore index 3d4bc4d..24affe0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +################################################################################################### +## Project Internal ############################################################################### +################################################################################################### + +config.properties + ################################################################################################### ## Visual Studio Code ############################################################################# ################################################################################################### diff --git a/config/demo.config.properties b/config/demo.config.properties new file mode 100644 index 0000000..fbb6e44 --- /dev/null +++ b/config/demo.config.properties @@ -0,0 +1,3 @@ +# Amazon Credentials +AMAZON_API_URL= +AMAZON_API_KEY= \ No newline at end of file From 175140451de8d18747dabfb3d4571a0b72edcf6f Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 02:57:33 +0200 Subject: [PATCH 07/31] Added config utility --- .../java/de/rwu/easydrop/util/ConfigUtil.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/de/rwu/easydrop/util/ConfigUtil.java diff --git a/src/main/java/de/rwu/easydrop/util/ConfigUtil.java b/src/main/java/de/rwu/easydrop/util/ConfigUtil.java new file mode 100644 index 0000000..45eae1b --- /dev/null +++ b/src/main/java/de/rwu/easydrop/util/ConfigUtil.java @@ -0,0 +1,49 @@ +package de.rwu.easydrop.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.Properties; + +/** + * Allows access to the config. + * + * @since 0.1.0 + */ +public final class ConfigUtil { + /** + * Config file location. + */ + private static final String CONFIG_LOCATION = "config/config.properties"; + + /** + * Private constructor to prevent unwanted instantiation. + * + * @throws UnsupportedOperationException always + */ + private ConfigUtil() throws UnsupportedOperationException { + throw new UnsupportedOperationException("This is a utility class, don't instantiate it."); + } + + /** + * Returns a config value by specified key. + * + * @param key Config Key, like "API_KEY" + * @return Config value + */ + public static String getConfig(final String key) { + Properties config = new Properties(); + try (FileInputStream input = new FileInputStream(CONFIG_LOCATION)) { + config.load(input); + } catch (IOException e) { + e.printStackTrace(); + } + + String value = config.getProperty(key); + if (value == null) { + throw new NoSuchElementException("Requested config value does not exist"); + } + + return value; + } +} From 70221d26a68ae2732e17ff859fd922dde864df86 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 02:58:10 +0200 Subject: [PATCH 08/31] #39 Added basic implementation --- src/main/java/de/rwu/easydrop/Main.java | 10 +- .../api/client/AmazonProductDataSource.java | 121 +++++++++++++++ .../rwu/easydrop/api/client/DataSource.java | 9 +- .../de/rwu/easydrop/api/dto/ProductDTO.java | 138 ++++++++++++++++++ .../de/rwu/easydrop/util/FormattingUtil.java | 36 ++++- 5 files changed, 304 insertions(+), 10 deletions(-) create mode 100644 src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 34edee6..83ce1ef 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -1,5 +1,8 @@ package de.rwu.easydrop; +import de.rwu.easydrop.api.client.AmazonProductDataSource; +import de.rwu.easydrop.util.ConfigUtil; + /** * Kickoff point for the service. * @@ -19,6 +22,11 @@ public final class Main { * @param args */ public static void main(final String[] args) { - System.out.println("I'm alive!"); + String amznBaseUrl = ConfigUtil.getConfig("AMAZON_API_URL"); + String amznApiKey = ConfigUtil.getConfig("AMAZON_API_KEY"); + + AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey); + System.out.print(amznSrc.getProductDTOById("B096Y2TYKV").toString()); + } } diff --git a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java new file mode 100644 index 0000000..762c929 --- /dev/null +++ b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java @@ -0,0 +1,121 @@ +package de.rwu.easydrop.api.client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +import org.json.JSONException; +import org.json.JSONObject; + +import de.rwu.easydrop.api.dto.ProductDTO; + +/** + * Interface to an Amazon data source. + * + * @since 0.1.0 + */ +public final class AmazonProductDataSource implements DataSource { + /** + * Name of this data source. + */ + private static final String DATA_ORIGIN = "Amazon"; + /** + * Base URL to the Amazon data source. + */ + private String baseUrl; + /** + * Credential key to authorize acccess. + */ + private String apiKey; + /** + * Product region parameter required for data access. + */ + private static final String PRODUCT_REGION = "DE"; + /** + * Locale parameter required for data access. + */ + private static final String LOCALE = "de_DE"; + + /** + * Sets up instance with Base URL and API Key. + * + * @param newBaseUrl + * @param newApiKey + */ + public AmazonProductDataSource(final String newBaseUrl, final String newApiKey) { + this.baseUrl = newBaseUrl; + this.apiKey = newApiKey; + } + + @Override + public ProductDTO getProductDTOById(final String productId) { + JSONObject offer = null; + ProductDTO product = new ProductDTO(productId, DATA_ORIGIN); + + try { + URL apiUrl = new URL( + baseUrl + + "/products/2020-08-26/products/" + + productId + + "/offers?productRegion=" + + PRODUCT_REGION + + "&locale=" + + LOCALE); + + HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection(); + connection.setRequestMethod("GET"); + connection.setRequestProperty("Credential", apiKey); + + int responseCode = connection.getResponseCode(); + BufferedReader reader; + if (responseCode == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } else { + return product; + } + + String line; + StringBuilder response = new StringBuilder(); + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + + offer = new JSONObject(response.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + + if (offer == null) { + return product; + } + + try { + offer = offer.getJSONObject("featuredOffer"); + + product.setDataOrigin(DATA_ORIGIN); + product.setAvailable(offer + .getString("availability") + .equals("available")); + product.setCurrentPrice(offer + .getJSONObject("price") + .getJSONObject("value") + .getDouble("amount")); + product.setDeliveryPrice(offer + .getJSONArray("shippingOptions") + .getJSONObject(0) + .getJSONObject("shippingCost") + .getJSONObject("value") + .getDouble("amount")); + product.setMerchant(offer + .getJSONObject("merchant") + .getString("name")); + } catch (JSONException e) { + return product; + } + + return product; + } +} diff --git a/src/main/java/de/rwu/easydrop/api/client/DataSource.java b/src/main/java/de/rwu/easydrop/api/client/DataSource.java index 740c173..7df7178 100644 --- a/src/main/java/de/rwu/easydrop/api/client/DataSource.java +++ b/src/main/java/de/rwu/easydrop/api/client/DataSource.java @@ -12,12 +12,5 @@ public interface DataSource { * @param productId Identifier * @return ProductDTO */ - ProductDTO getProductById(String productId); - - /** - * Breaks the connection after specified time. - * - * @param timeoutSeconds Seconds to time out after - */ - void setConnectionTimeout(int timeoutSeconds); + ProductDTO getProductDTOById(String productId); } diff --git a/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java index a8d8dfc..801437a 100644 --- a/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java +++ b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java @@ -1,8 +1,146 @@ package de.rwu.easydrop.api.dto; +import de.rwu.easydrop.util.FormattingUtil; + /** * Product data transfer object. + * + * @since 0.1.0 */ public class ProductDTO { + /** + * Data source platform, like "Amazon". + */ + private String dataOrigin; + /** + * @return the dataOrigin + */ + public String getDataOrigin() { + return dataOrigin; + } + + /** + * @param newDataOrigin the dataOrigin to set + */ + public void setDataOrigin(final String newDataOrigin) { + this.dataOrigin = newDataOrigin; + } + + /** + * Platform internal product identifier. + */ + private String productId; + + /** + * @return the productId + */ + public String getProductId() { + return productId; + } + + /** + * @param newProductId the productId to set + */ + public void setProductId(final String newProductId) { + this.productId = newProductId; + } + + /** + * Current product price per piece in Euro. + */ + private double currentPrice; + + /** + * @return the currentPrice + */ + public double getCurrentPrice() { + return currentPrice; + } + + /** + * @param newCurrentPrice the currentPrice to set + */ + public void setCurrentPrice(final double newCurrentPrice) { + this.currentPrice = newCurrentPrice; + } + + /** + * Name of mercant offering the product on the platform. + */ + private String merchant; + + /** + * @return the merchant + */ + public String getMerchant() { + return merchant; + } + + /** + * @param newMerchant the merchant to set + */ + public void setMerchant(final String newMerchant) { + this.merchant = newMerchant; + } + + /** + * Additional Cost for delivery in Euro. + */ + private double deliveryPrice; + + /** + * @return the deliveryPrice + */ + public double getDeliveryPrice() { + return deliveryPrice; + } + + /** + * @param newDeliveryPrice the deliveryPrice to set + */ + public void setDeliveryPrice(final double newDeliveryPrice) { + this.deliveryPrice = newDeliveryPrice; + } + + /** + * Whether the product can be purchased at this point. + */ + private boolean available; + + /** + * @return the available + */ + public boolean isAvailable() { + return available; + } + + /** + * @param newAvailable the available to set + */ + public void setAvailable(final boolean newAvailable) { + this.available = newAvailable; + } + + /** + * Creates ProductDTO instance. + * + * @param newProductId Interal Product indetifier + * @param newDataOrigin Data Origin + */ + public ProductDTO(final String newProductId, final String newDataOrigin) { + this.productId = newProductId; + this.dataOrigin = newDataOrigin; + } + + @Override + public final String toString() { + return "ProductDTO{" + + productId + " from " + + merchant + " (" + + dataOrigin + ")" + + " at " + + FormattingUtil.formatEuro(currentPrice) + " (available: " + + (available ? "yes" : "no") + ")}"; + } } diff --git a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java index abf0551..f7bb95f 100644 --- a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java +++ b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java @@ -1,5 +1,39 @@ package de.rwu.easydrop.util; -public class FormattingUtil { +import java.text.NumberFormat; +import java.util.Currency; +import java.util.Locale; +/** + * Helps format a bunch of things. + * + * @since 0.1.0 + */ +public final class FormattingUtil { + + /** + * Private constructor to prevent unwanted instantiation. + * + * @throws UnsupportedOperationException always + */ + private FormattingUtil() throws UnsupportedOperationException { + throw new UnsupportedOperationException("This is a utility class, don't instantiate it."); + } + + /** + * Formats a price to the German Euro format. + * + * @param amount price + * @return formatted price + */ + public static String formatEuro(final double amount) { + // Set up environment + Locale locale = Locale.GERMANY; + Currency currency = Currency.getInstance("EUR"); + NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale); + + numberFormat.setCurrency(currency); + + return numberFormat.format(amount); + } } From 748c65b1bebec78d8ab97b87bbe1be42941ce6a5 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 07:11:52 +0200 Subject: [PATCH 09/31] Removed demo code --- .../java/de/rwu/easydrop/demo/DemoClass.java | 16 ---------------- .../de/rwu/easydrop/demo/package-info.java | 6 ------ .../de/rwu/easydrop/demo/DemoClassTest.java | 19 ------------------- 3 files changed, 41 deletions(-) delete mode 100644 src/main/java/de/rwu/easydrop/demo/DemoClass.java delete mode 100644 src/main/java/de/rwu/easydrop/demo/package-info.java delete mode 100644 src/test/java/de/rwu/easydrop/demo/DemoClassTest.java diff --git a/src/main/java/de/rwu/easydrop/demo/DemoClass.java b/src/main/java/de/rwu/easydrop/demo/DemoClass.java deleted file mode 100644 index ff91a4f..0000000 --- a/src/main/java/de/rwu/easydrop/demo/DemoClass.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.rwu.easydrop.demo; - -/** - * Demo-Class. - */ -public final class DemoClass { - /** - * Demo-Method inverting a boolean. - * - * @param bool Input Boolean - * @return Inverted Boolean - */ - public boolean invertBool(final boolean bool) { - return !bool; - } -} diff --git a/src/main/java/de/rwu/easydrop/demo/package-info.java b/src/main/java/de/rwu/easydrop/demo/package-info.java deleted file mode 100644 index d4537c6..0000000 --- a/src/main/java/de/rwu/easydrop/demo/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Package for demo purposes. - * - * @todo implement - */ -package de.rwu.easydrop.demo; diff --git a/src/test/java/de/rwu/easydrop/demo/DemoClassTest.java b/src/test/java/de/rwu/easydrop/demo/DemoClassTest.java deleted file mode 100644 index fb4aa0e..0000000 --- a/src/test/java/de/rwu/easydrop/demo/DemoClassTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.rwu.easydrop.demo; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -public class DemoClassTest { - private final DemoClass demoClass = new DemoClass(); - - @Test - public void testInvertBoolTrue() { - assertEquals("Inverting true should return false", false, demoClass.invertBool(true)); - } - - @Test - public void testInvertBoolFalse() { - assertEquals("Inverting false should return true", true, demoClass.invertBool(false)); - } -} From 88d2ad34c32f81abcc55ebbc604848e8ae4074a2 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 07:26:26 +0200 Subject: [PATCH 10/31] Added SLF4J+Logback as Logging libraries --- config/logback.xml | 12 ++++++++++++ pom.xml | 13 +++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 config/logback.xml diff --git a/config/logback.xml b/config/logback.xml new file mode 100644 index 0000000..6b610a9 --- /dev/null +++ b/config/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + UTF-8 + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c6d84c8..083cd77 100644 --- a/pom.xml +++ b/pom.xml @@ -20,17 +20,30 @@ 4.13.2 test + org.mockito mockito-core 5.3.1 test + org.json json 20230227 + + + org.slf4j + slf4j-api + 2.0.7 + + + ch.qos.logback + logback-classic + 1.4.7 + From d1d15f79eca06f0f7a461983f3e43f79fdb88915 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 07:26:53 +0200 Subject: [PATCH 11/31] Rewrote System.out.print to Logger --- src/main/java/de/rwu/easydrop/Main.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 83ce1ef..13b3ad6 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -1,5 +1,8 @@ package de.rwu.easydrop; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import de.rwu.easydrop.api.client.AmazonProductDataSource; import de.rwu.easydrop.util.ConfigUtil; @@ -9,6 +12,8 @@ import de.rwu.easydrop.util.ConfigUtil; * @since 0.1.0 */ public final class Main { + private static final Logger logger = LoggerFactory.getLogger(Main.class); + /** * Prevents unwanted instantiation. */ @@ -24,9 +29,14 @@ public final class Main { public static void main(final String[] args) { String amznBaseUrl = ConfigUtil.getConfig("AMAZON_API_URL"); String amznApiKey = ConfigUtil.getConfig("AMAZON_API_KEY"); + String testProduct = null; AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey); - System.out.print(amznSrc.getProductDTOById("B096Y2TYKV").toString()); - + try { + testProduct = amznSrc.getProductDTOById("B096Y2TYKV").toString(); + logger.info(testProduct); + } catch (IllegalArgumentException e) { + logger.error("Something went wrong :(", e); + } } } From 4649b58859d83e0b57fa0e994e6eca88216e029e Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 07:27:16 +0200 Subject: [PATCH 12/31] Provided descriptions + todo for empty classes --- .../api/client/AmazonProductDataSource.java | 20 +++++++------------ .../data/connector/DatabaseConnector.java | 5 +++++ .../de/rwu/easydrop/data/dao/ProductDAO.java | 5 +++++ .../de/rwu/easydrop/data/model/Product.java | 5 +++++ .../service/mapping/ProductMapper.java | 8 ++++++++ .../service/processing/OrderManager.java | 5 +++++ .../service/validation/ProductValidator.java | 5 +++++ 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java index 762c929..c3378ea 100644 --- a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java +++ b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java @@ -50,8 +50,8 @@ public final class AmazonProductDataSource implements DataSource { } @Override - public ProductDTO getProductDTOById(final String productId) { - JSONObject offer = null; + public ProductDTO getProductDTOById(final String productId) throws IllegalArgumentException { + StringBuilder response = new StringBuilder(); ProductDTO product = new ProductDTO(productId, DATA_ORIGIN); try { @@ -73,27 +73,21 @@ public final class AmazonProductDataSource implements DataSource { if (responseCode == HttpURLConnection.HTTP_OK) { reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); } else { - return product; + throw new IllegalArgumentException( + "Amazon API responded with error code " + responseCode); } String line; - StringBuilder response = new StringBuilder(); while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); - - offer = new JSONObject(response.toString()); } catch (IOException e) { - e.printStackTrace(); - } - - if (offer == null) { - return product; + throw new IllegalArgumentException("Couldn't fulfill Amazon API request"); } try { - offer = offer.getJSONObject("featuredOffer"); + JSONObject offer = new JSONObject(response.toString()).getJSONObject("featuredOffer"); product.setDataOrigin(DATA_ORIGIN); product.setAvailable(offer @@ -113,7 +107,7 @@ public final class AmazonProductDataSource implements DataSource { .getJSONObject("merchant") .getString("name")); } catch (JSONException e) { - return product; + // Pass, allow incomplete ProductDTO to pass for later validation } return product; diff --git a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java index cd933d8..53a398e 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java +++ b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.data.connector; +/** + * Allows connecting to a SQLite Database. + * + * @todo implement + */ public class DatabaseConnector { } diff --git a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java index e981caa..ccd1b3a 100644 --- a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java +++ b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.data.dao; +/** + * Product data access object. + * + * @todo implement + */ public class ProductDAO { } diff --git a/src/main/java/de/rwu/easydrop/data/model/Product.java b/src/main/java/de/rwu/easydrop/data/model/Product.java index e637c33..33ec55e 100644 --- a/src/main/java/de/rwu/easydrop/data/model/Product.java +++ b/src/main/java/de/rwu/easydrop/data/model/Product.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.data.model; +/** + * A Product + * + * @since 0.1.0 + */ public class Product { } diff --git a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java index 7f006d8..ed66ef6 100644 --- a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java +++ b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java @@ -1,5 +1,13 @@ package de.rwu.easydrop.service.mapping; +/** + * Maps between Product, ProductDAO and ProductDTO. + * + * @since 0.1.0 + * @see Product + * @see ProductDTO + * @see ProductDAO + */ public class ProductMapper { } diff --git a/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java index 36d391f..a8f1405 100644 --- a/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java +++ b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.service.processing; +/** + * Processes dropshipping orders + * + * @todo implement + */ public class OrderManager { } diff --git a/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java index 292aeda..6f6a941 100644 --- a/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java +++ b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java @@ -1,5 +1,10 @@ package de.rwu.easydrop.service.validation; +/** + * Confirms validity of Product data + * + * @since 0.1.0 + */ public class ProductValidator { } From 4a163f6c7e9b50703685846b2ec38c6518f928e8 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 07:29:55 +0200 Subject: [PATCH 13/31] Checkstyle fixes --- src/main/java/de/rwu/easydrop/Main.java | 9 ++++++--- .../rwu/easydrop/data/connector/DatabaseConnector.java | 2 +- .../de/rwu/easydrop/data/connector/package-info.java | 2 +- src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java | 2 +- src/main/java/de/rwu/easydrop/data/dao/package-info.java | 2 +- src/main/java/de/rwu/easydrop/data/model/Product.java | 2 +- .../java/de/rwu/easydrop/data/model/package-info.java | 2 +- src/main/java/de/rwu/easydrop/data/package-info.java | 2 +- .../de/rwu/easydrop/service/mapping/package-info.java | 2 +- src/main/java/de/rwu/easydrop/service/package-info.java | 2 +- .../de/rwu/easydrop/service/processing/OrderManager.java | 4 ++-- .../de/rwu/easydrop/service/processing/package-info.java | 2 +- .../easydrop/service/validation/ProductValidator.java | 2 +- .../de/rwu/easydrop/service/validation/package-info.java | 2 +- src/main/java/de/rwu/easydrop/util/package-info.java | 2 +- 15 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 13b3ad6..1b7726a 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -12,7 +12,10 @@ import de.rwu.easydrop.util.ConfigUtil; * @since 0.1.0 */ public final class Main { - private static final Logger logger = LoggerFactory.getLogger(Main.class); + /** + * Logger for main process. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); /** * Prevents unwanted instantiation. @@ -34,9 +37,9 @@ public final class Main { AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey); try { testProduct = amznSrc.getProductDTOById("B096Y2TYKV").toString(); - logger.info(testProduct); + LOGGER.info(testProduct); } catch (IllegalArgumentException e) { - logger.error("Something went wrong :(", e); + LOGGER.error("Something went wrong :(", e); } } } diff --git a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java index 53a398e..ce6360a 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java +++ b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java @@ -3,7 +3,7 @@ package de.rwu.easydrop.data.connector; /** * Allows connecting to a SQLite Database. * - * @todo implement + * TODO implement */ public class DatabaseConnector { diff --git a/src/main/java/de/rwu/easydrop/data/connector/package-info.java b/src/main/java/de/rwu/easydrop/data/connector/package-info.java index fe11513..f5b2dc7 100644 --- a/src/main/java/de/rwu/easydrop/data/connector/package-info.java +++ b/src/main/java/de/rwu/easydrop/data/connector/package-info.java @@ -1,6 +1,6 @@ /** * Connectors for databases. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.data.connector; diff --git a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java index ccd1b3a..f787da9 100644 --- a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java +++ b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java @@ -3,7 +3,7 @@ package de.rwu.easydrop.data.dao; /** * Product data access object. * - * @todo implement + * TODO implement */ public class ProductDAO { diff --git a/src/main/java/de/rwu/easydrop/data/dao/package-info.java b/src/main/java/de/rwu/easydrop/data/dao/package-info.java index 8ca4719..2affd23 100644 --- a/src/main/java/de/rwu/easydrop/data/dao/package-info.java +++ b/src/main/java/de/rwu/easydrop/data/dao/package-info.java @@ -1,6 +1,6 @@ /** * Data access objects for business objects created from persistence. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.data.dao; diff --git a/src/main/java/de/rwu/easydrop/data/model/Product.java b/src/main/java/de/rwu/easydrop/data/model/Product.java index 33ec55e..6b976d6 100644 --- a/src/main/java/de/rwu/easydrop/data/model/Product.java +++ b/src/main/java/de/rwu/easydrop/data/model/Product.java @@ -1,7 +1,7 @@ package de.rwu.easydrop.data.model; /** - * A Product + * A Product. * * @since 0.1.0 */ diff --git a/src/main/java/de/rwu/easydrop/data/model/package-info.java b/src/main/java/de/rwu/easydrop/data/model/package-info.java index ed9879b..f33ce31 100644 --- a/src/main/java/de/rwu/easydrop/data/model/package-info.java +++ b/src/main/java/de/rwu/easydrop/data/model/package-info.java @@ -1,6 +1,6 @@ /** * Business objects. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.data.model; diff --git a/src/main/java/de/rwu/easydrop/data/package-info.java b/src/main/java/de/rwu/easydrop/data/package-info.java index 8d4e6b7..a161322 100644 --- a/src/main/java/de/rwu/easydrop/data/package-info.java +++ b/src/main/java/de/rwu/easydrop/data/package-info.java @@ -1,6 +1,6 @@ /** * Structure for business objects and persisting their info. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.data; diff --git a/src/main/java/de/rwu/easydrop/service/mapping/package-info.java b/src/main/java/de/rwu/easydrop/service/mapping/package-info.java index c7ef687..ab86051 100644 --- a/src/main/java/de/rwu/easydrop/service/mapping/package-info.java +++ b/src/main/java/de/rwu/easydrop/service/mapping/package-info.java @@ -1,6 +1,6 @@ /** * Maps different formats of corresponding objects. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.service.mapping; diff --git a/src/main/java/de/rwu/easydrop/service/package-info.java b/src/main/java/de/rwu/easydrop/service/package-info.java index 5c1e2fc..ff59e0c 100644 --- a/src/main/java/de/rwu/easydrop/service/package-info.java +++ b/src/main/java/de/rwu/easydrop/service/package-info.java @@ -1,6 +1,6 @@ /** * Packages for supporting business logic. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.service; diff --git a/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java index a8f1405..4d373b2 100644 --- a/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java +++ b/src/main/java/de/rwu/easydrop/service/processing/OrderManager.java @@ -1,9 +1,9 @@ package de.rwu.easydrop.service.processing; /** - * Processes dropshipping orders + * Processes dropshipping orders. * - * @todo implement + * TODO implement */ public class OrderManager { diff --git a/src/main/java/de/rwu/easydrop/service/processing/package-info.java b/src/main/java/de/rwu/easydrop/service/processing/package-info.java index 1348cce..db7cb9e 100644 --- a/src/main/java/de/rwu/easydrop/service/processing/package-info.java +++ b/src/main/java/de/rwu/easydrop/service/processing/package-info.java @@ -1,6 +1,6 @@ /** * Supports diverse business processes and enforces business rules. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.service.processing; diff --git a/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java index 6f6a941..3c93108 100644 --- a/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java +++ b/src/main/java/de/rwu/easydrop/service/validation/ProductValidator.java @@ -1,7 +1,7 @@ package de.rwu.easydrop.service.validation; /** - * Confirms validity of Product data + * Confirms validity of Product data. * * @since 0.1.0 */ diff --git a/src/main/java/de/rwu/easydrop/service/validation/package-info.java b/src/main/java/de/rwu/easydrop/service/validation/package-info.java index 7333014..6b45561 100644 --- a/src/main/java/de/rwu/easydrop/service/validation/package-info.java +++ b/src/main/java/de/rwu/easydrop/service/validation/package-info.java @@ -1,6 +1,6 @@ /** * Supports validation processes. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.service.validation; diff --git a/src/main/java/de/rwu/easydrop/util/package-info.java b/src/main/java/de/rwu/easydrop/util/package-info.java index c5417ed..40789f2 100644 --- a/src/main/java/de/rwu/easydrop/util/package-info.java +++ b/src/main/java/de/rwu/easydrop/util/package-info.java @@ -1,6 +1,6 @@ /** * General utility such as formatting helpers. * - * @todo implement + * TODO implement */ package de.rwu.easydrop.util; From 120f4a0ee45480ed3c5624fc4a0b7b020b73bed7 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 08:35:33 +0200 Subject: [PATCH 14/31] Switched from JUnit 4 to JUnit 5 --- pom.xml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 083cd77..ddb88a0 100644 --- a/pom.xml +++ b/pom.xml @@ -11,13 +11,21 @@ UTF-8 + 17 + 17 - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter-api + 5.8.0 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.8.0 test From 4674f4e8267ed7b3987bbf1d0ea6ca1d375c88f9 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 08:35:51 +0200 Subject: [PATCH 15/31] Split monolithic function --- .../api/client/AmazonProductDataSource.java | 39 +++++++++++++------ .../rwu/easydrop/api/client/DataSource.java | 14 ++++++- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java index c3378ea..04613eb 100644 --- a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java +++ b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java @@ -4,6 +4,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; import org.json.JSONException; @@ -55,14 +56,7 @@ public final class AmazonProductDataSource implements DataSource { ProductDTO product = new ProductDTO(productId, DATA_ORIGIN); try { - URL apiUrl = new URL( - baseUrl - + "/products/2020-08-26/products/" - + productId - + "/offers?productRegion=" - + PRODUCT_REGION - + "&locale=" - + LOCALE); + URL apiUrl = createApiUrl(productId); HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection(); connection.setRequestMethod("GET"); @@ -74,7 +68,7 @@ public final class AmazonProductDataSource implements DataSource { reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); } else { throw new IllegalArgumentException( - "Amazon API responded with error code " + responseCode); + "Nothing found: Amazon API responded with error code " + responseCode); } String line; @@ -82,13 +76,25 @@ public final class AmazonProductDataSource implements DataSource { response.append(line); } reader.close(); + + JSONObject offer = new JSONObject(response.toString()).getJSONObject("featuredOffer"); + buildProductDTO(product, offer); } catch (IOException e) { throw new IllegalArgumentException("Couldn't fulfill Amazon API request"); } - try { - JSONObject offer = new JSONObject(response.toString()).getJSONObject("featuredOffer"); + return product; + } + /** + * Enriches a ProductDTO with API-gathered data. + * + * @param product Unfinished ProductDTO + * @param offer Product data + * @return Finished ProductDTO + */ + private ProductDTO buildProductDTO(final ProductDTO product, final JSONObject offer) { + try { product.setDataOrigin(DATA_ORIGIN); product.setAvailable(offer .getString("availability") @@ -112,4 +118,15 @@ public final class AmazonProductDataSource implements DataSource { return product; } + + @Override + public URL createApiUrl(final String productId) throws MalformedURLException { + return new URL(baseUrl + + "/products/2020-08-26/products/" + + productId + + "/offers?productRegion=" + + PRODUCT_REGION + + "&locale=" + + LOCALE); + } } diff --git a/src/main/java/de/rwu/easydrop/api/client/DataSource.java b/src/main/java/de/rwu/easydrop/api/client/DataSource.java index 7df7178..7b1c4e7 100644 --- a/src/main/java/de/rwu/easydrop/api/client/DataSource.java +++ b/src/main/java/de/rwu/easydrop/api/client/DataSource.java @@ -1,5 +1,8 @@ package de.rwu.easydrop.api.client; +import java.net.MalformedURLException; +import java.net.URL; + import de.rwu.easydrop.api.dto.ProductDTO; /** @@ -9,8 +12,17 @@ public interface DataSource { /** * Retrieves product info from the data source. * - * @param productId Identifier + * @param productId ASIN * @return ProductDTO */ ProductDTO getProductDTOById(String productId); + + /** + * Creates an URL object to connect to the API with. + * + * @param productId ASIN + * @return URL object + * @throws MalformedURLException + */ + URL createApiUrl(String productId) throws MalformedURLException; } From 849ce7825d28b206518c9e6c66691bc7e66a53eb Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 08:36:05 +0200 Subject: [PATCH 16/31] #39 Added some tests --- .../api/AmazonProductDataSourceTest.java | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java diff --git a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java new file mode 100644 index 0000000..ff472fe --- /dev/null +++ b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java @@ -0,0 +1,64 @@ +package de.rwu.easydrop.api; + +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.net.URL; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +import de.rwu.easydrop.api.client.AmazonProductDataSource; + +public class AmazonProductDataSourceTest { + + private AmazonProductDataSource dataSource; + + @BeforeEach + public void setup() { + dataSource = new AmazonProductDataSource( + "https://www.example.com/api", + "my-api-key"); + MockitoAnnotations.openMocks(this); + } + + @Test + public void testConstructor() { + // Arrange + String baseUrl = "https://www.example.com/api"; + String apiKey = "my-api-key"; + + // Assert + try { + Field baseUrlField = AmazonProductDataSource.class.getDeclaredField("baseUrl"); + baseUrlField.setAccessible(true); + Assertions.assertEquals(baseUrl, baseUrlField.get(dataSource)); + + Field apiKeyField = AmazonProductDataSource.class.getDeclaredField("apiKey"); + apiKeyField.setAccessible(true); + Assertions.assertEquals(apiKey, apiKeyField.get(dataSource)); + } catch (NoSuchFieldException e) { + Assertions.fail(); + } catch (IllegalAccessException e) { + Assertions.fail(); + } + } + + @Test + public void testCreateApiUrl() throws MalformedURLException { + // Test case 1 + String productId1 = "12345"; + URL expectedUrl1 = new URL( + "https://www.example.com/api/products/2020-08-26/products/12345/offers?productRegion=DE&locale=de_DE"); + URL createdUrl1 = dataSource.createApiUrl(productId1); + Assertions.assertEquals(expectedUrl1, createdUrl1); + + // Test case 2 + String productId2 = "67890"; + URL expectedUrl2 = new URL( + "https://www.example.com/api/products/2020-08-26/products/67890/offers?productRegion=DE&locale=de_DE"); + URL createdUrl2 = dataSource.createApiUrl(productId2); + Assertions.assertEquals(expectedUrl2, createdUrl2); + } +} From 82ff5720e3874b0c3e8f6d555b6d1d46ddba104e Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 21:28:47 +0200 Subject: [PATCH 17/31] Rewrote function for better reliability --- src/main/java/de/rwu/easydrop/util/FormattingUtil.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java index f7bb95f..50639e3 100644 --- a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java +++ b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java @@ -27,13 +27,6 @@ public final class FormattingUtil { * @return formatted price */ public static String formatEuro(final double amount) { - // Set up environment - Locale locale = Locale.GERMANY; - Currency currency = Currency.getInstance("EUR"); - NumberFormat numberFormat = NumberFormat.getCurrencyInstance(locale); - - numberFormat.setCurrency(currency); - - return numberFormat.format(amount); + return String.format(Locale.GERMAN, "%,.2f", amount) + " €"; } } From cb57e1c19053fec048fd3a8fe8102c65a4f5223b Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Tue, 23 May 2023 21:29:20 +0200 Subject: [PATCH 18/31] Added json-path library --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index ddb88a0..f87a132 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ json 20230227 + + com.jayway.jsonpath + json-path + 2.8.0 + org.slf4j From 69bde92a70ac56e4a814ea37c15c030c7dda68d0 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 00:22:00 +0200 Subject: [PATCH 19/31] Rewrote Config handling --- src/main/java/de/rwu/easydrop/Main.java | 11 ++- .../java/de/rwu/easydrop/util/Config.java | 89 +++++++++++++++++++ .../java/de/rwu/easydrop/util/ConfigUtil.java | 49 ---------- 3 files changed, 96 insertions(+), 53 deletions(-) create mode 100644 src/main/java/de/rwu/easydrop/util/Config.java delete mode 100644 src/main/java/de/rwu/easydrop/util/ConfigUtil.java diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 1b7726a..45299f2 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -1,10 +1,12 @@ package de.rwu.easydrop; +import javax.naming.ConfigurationException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.rwu.easydrop.api.client.AmazonProductDataSource; -import de.rwu.easydrop.util.ConfigUtil; +import de.rwu.easydrop.util.Config; /** * Kickoff point for the service. @@ -29,9 +31,10 @@ public final class Main { * * @param args */ - public static void main(final String[] args) { - String amznBaseUrl = ConfigUtil.getConfig("AMAZON_API_URL"); - String amznApiKey = ConfigUtil.getConfig("AMAZON_API_KEY"); + public static void main(final String[] args) throws ConfigurationException { + Config config = Config.getInstance(); + String amznBaseUrl = config.getProperty("AMAZON_API_URL"); + String amznApiKey = config.getProperty("AMAZON_API_KEY"); String testProduct = null; AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey); diff --git a/src/main/java/de/rwu/easydrop/util/Config.java b/src/main/java/de/rwu/easydrop/util/Config.java new file mode 100644 index 0000000..6b0e89d --- /dev/null +++ b/src/main/java/de/rwu/easydrop/util/Config.java @@ -0,0 +1,89 @@ +package de.rwu.easydrop.util; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.Properties; + +import javax.naming.ConfigurationException; + +/** + * Allows access to the config. + * + * @since 0.1.0 + */ +public final class Config { + /** + * Config file location. + */ + private static final String CONFIG_LOCATION = "config/config.properties"; + /** + * Holds the config values. + */ + private Properties properties = null; + /** + * Singleton instance. + */ + private static Config instance = null; + + /** + * Private constructor to prevent unwanted instantiation. + */ + private Config() throws ConfigurationException { + loadConfig(); + } + + /** + * Returns current config instance. + * + * @return Config instance + * @throws ConfigurationException + */ + public static Config getInstance() throws ConfigurationException { + if (instance == null) { + return new Config(); + } + + return instance; + } + + /** + * Loads config file values into the instance. + * + * @throws ConfigurationException + */ + public void loadConfig() throws ConfigurationException { + Properties newProps = new Properties(); + try (FileInputStream input = new FileInputStream(CONFIG_LOCATION)) { + newProps.load(input); + properties = newProps; + } catch (IOException e) { + throw new ConfigurationException("Couldn't load required config file"); + } + } + + /** + * Returns a config property by specified key. + * + * @param key Config Key, like "API_KEY" + * @return Config value + * @throws NoSuchElementException Required key missing + */ + public String getProperty(final String key) throws NoSuchElementException { + String value = properties.getProperty(key); + if (value == null) { + throw new NoSuchElementException("Requested config value does not exist"); + } + return value; + } + + /** + * Overrides a config property loaded from file for current instance. + * + * @param key Config Key + * @param value Property Value + */ + public void setProperty(final String key, final String value) { + properties.setProperty(key, value); + } +} diff --git a/src/main/java/de/rwu/easydrop/util/ConfigUtil.java b/src/main/java/de/rwu/easydrop/util/ConfigUtil.java deleted file mode 100644 index 45eae1b..0000000 --- a/src/main/java/de/rwu/easydrop/util/ConfigUtil.java +++ /dev/null @@ -1,49 +0,0 @@ -package de.rwu.easydrop.util; - -import java.io.FileInputStream; -import java.io.IOException; -import java.util.NoSuchElementException; -import java.util.Properties; - -/** - * Allows access to the config. - * - * @since 0.1.0 - */ -public final class ConfigUtil { - /** - * Config file location. - */ - private static final String CONFIG_LOCATION = "config/config.properties"; - - /** - * Private constructor to prevent unwanted instantiation. - * - * @throws UnsupportedOperationException always - */ - private ConfigUtil() throws UnsupportedOperationException { - throw new UnsupportedOperationException("This is a utility class, don't instantiate it."); - } - - /** - * Returns a config value by specified key. - * - * @param key Config Key, like "API_KEY" - * @return Config value - */ - public static String getConfig(final String key) { - Properties config = new Properties(); - try (FileInputStream input = new FileInputStream(CONFIG_LOCATION)) { - config.load(input); - } catch (IOException e) { - e.printStackTrace(); - } - - String value = config.getProperty(key); - if (value == null) { - throw new NoSuchElementException("Requested config value does not exist"); - } - - return value; - } -} From c09127200bcedbc4a23a47a76bdff25bf8677552 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 00:23:24 +0200 Subject: [PATCH 20/31] Removed unused imports --- src/main/java/de/rwu/easydrop/util/FormattingUtil.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java index 50639e3..283e506 100644 --- a/src/main/java/de/rwu/easydrop/util/FormattingUtil.java +++ b/src/main/java/de/rwu/easydrop/util/FormattingUtil.java @@ -1,7 +1,5 @@ package de.rwu.easydrop.util; -import java.text.NumberFormat; -import java.util.Currency; import java.util.Locale; /** From 17bd6ab5872f974165b069a4f8f444e4a46e037c Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 00:24:01 +0200 Subject: [PATCH 21/31] #39 Rewrote JSON accessing --- .../api/client/AmazonProductDataSource.java | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java index 04613eb..dbbdc6d 100644 --- a/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java +++ b/src/main/java/de/rwu/easydrop/api/client/AmazonProductDataSource.java @@ -7,8 +7,9 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; -import org.json.JSONException; -import org.json.JSONObject; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; +import com.jayway.jsonpath.ReadContext; import de.rwu.easydrop.api.dto.ProductDTO; @@ -77,8 +78,7 @@ public final class AmazonProductDataSource implements DataSource { } reader.close(); - JSONObject offer = new JSONObject(response.toString()).getJSONObject("featuredOffer"); - buildProductDTO(product, offer); + buildProductDTO(product, response.toString()); } catch (IOException e) { throw new IllegalArgumentException("Couldn't fulfill Amazon API request"); } @@ -90,29 +90,22 @@ public final class AmazonProductDataSource implements DataSource { * Enriches a ProductDTO with API-gathered data. * * @param product Unfinished ProductDTO - * @param offer Product data + * @param json Product data * @return Finished ProductDTO */ - private ProductDTO buildProductDTO(final ProductDTO product, final JSONObject offer) { + public ProductDTO buildProductDTO(final ProductDTO product, final String json) { + String root = "$.featuredOffer."; + ReadContext ctx = JsonPath.parse(json); + try { product.setDataOrigin(DATA_ORIGIN); - product.setAvailable(offer - .getString("availability") - .equals("available")); - product.setCurrentPrice(offer - .getJSONObject("price") - .getJSONObject("value") - .getDouble("amount")); - product.setDeliveryPrice(offer - .getJSONArray("shippingOptions") - .getJSONObject(0) - .getJSONObject("shippingCost") - .getJSONObject("value") - .getDouble("amount")); - product.setMerchant(offer - .getJSONObject("merchant") - .getString("name")); - } catch (JSONException e) { + product.setAvailable( + ctx.read(root + "availability", String.class).equals("available")); + product.setCurrentPrice(ctx.read(root + "price.value.amount", double.class)); + product.setDeliveryPrice( + ctx.read(root + "shippingOptions[0].shippingCost.value.amount", double.class)); + product.setMerchant(ctx.read(root + "merchant.name", String.class)); + } catch (PathNotFoundException e) { // Pass, allow incomplete ProductDTO to pass for later validation } From da1cad1a59e8b8f48c04c123a30180905a46e8db Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 00:24:30 +0200 Subject: [PATCH 22/31] #39 Added some unit tests --- .../api/AmazonProductDataSourceTest.java | 152 ++++++++++++++++-- .../java/de/rwu/easydrop/util/ConfigTest.java | 86 ++++++++++ .../rwu/easydrop/util/FormattingUtilTest.java | 58 +++++++ 3 files changed, 283 insertions(+), 13 deletions(-) create mode 100644 src/test/java/de/rwu/easydrop/util/ConfigTest.java create mode 100644 src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java diff --git a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java index ff472fe..d643d91 100644 --- a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java +++ b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java @@ -1,6 +1,16 @@ package de.rwu.easydrop.api; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.lang.reflect.Field; +import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; @@ -10,34 +20,36 @@ import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import de.rwu.easydrop.api.client.AmazonProductDataSource; +import de.rwu.easydrop.api.dto.ProductDTO; public class AmazonProductDataSourceTest { - private AmazonProductDataSource dataSource; + private AmazonProductDataSource demoDataSource; + + private static String demoApiKey = "my-api-key"; + private static String demoApiUrl = "https://www.example.com/api"; + private static String demoDataOrigin = "Amazon"; + private static String demoProductId = "whateverId"; @BeforeEach public void setup() { - dataSource = new AmazonProductDataSource( - "https://www.example.com/api", - "my-api-key"); + demoDataSource = new AmazonProductDataSource( + demoApiUrl, + demoApiKey); MockitoAnnotations.openMocks(this); } @Test public void testConstructor() { - // Arrange - String baseUrl = "https://www.example.com/api"; - String apiKey = "my-api-key"; - // Assert try { Field baseUrlField = AmazonProductDataSource.class.getDeclaredField("baseUrl"); baseUrlField.setAccessible(true); - Assertions.assertEquals(baseUrl, baseUrlField.get(dataSource)); + Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoDataSource)); Field apiKeyField = AmazonProductDataSource.class.getDeclaredField("apiKey"); apiKeyField.setAccessible(true); - Assertions.assertEquals(apiKey, apiKeyField.get(dataSource)); + Assertions.assertEquals(demoApiKey, apiKeyField.get(demoDataSource)); } catch (NoSuchFieldException e) { Assertions.fail(); } catch (IllegalAccessException e) { @@ -51,14 +63,128 @@ public class AmazonProductDataSourceTest { String productId1 = "12345"; URL expectedUrl1 = new URL( "https://www.example.com/api/products/2020-08-26/products/12345/offers?productRegion=DE&locale=de_DE"); - URL createdUrl1 = dataSource.createApiUrl(productId1); + URL createdUrl1 = demoDataSource.createApiUrl(productId1); Assertions.assertEquals(expectedUrl1, createdUrl1); // Test case 2 String productId2 = "67890"; URL expectedUrl2 = new URL( - "https://www.example.com/api/products/2020-08-26/products/67890/offers?productRegion=DE&locale=de_DE"); - URL createdUrl2 = dataSource.createApiUrl(productId2); + "https://www.example.com/api/products/2020-08-26/" + + "products/67890/offers?productRegion=DE&locale=de_DE"); + URL createdUrl2 = demoDataSource.createApiUrl(productId2); Assertions.assertEquals(expectedUrl2, createdUrl2); } + + @Test + public void testBuildProductDTO_completeData() { + // Set up the test environment + String json = "{\"featuredOffer\": {\"availability\": \"available\"," + + "\"price\": {\"value\": {\"amount\": 10.0}}," + + "\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " + + "\"merchant\": {\"name\": \"Merchant A\"}}}"; + ProductDTO product = new ProductDTO(demoProductId, demoDataOrigin); + + // Invoke the method + ProductDTO result = demoDataSource.buildProductDTO(product, json); + + // Verify the product DTO properties + assertEquals(demoDataOrigin, result.getDataOrigin()); + assertEquals(true, result.isAvailable()); + assertEquals(10.0, result.getCurrentPrice()); + assertEquals(2.5, result.getDeliveryPrice()); + assertEquals("Merchant A", result.getMerchant()); + } + + @Test + public void testBuildProductDTO_incompleteData() { + // Set up the test environment + String json = "{\"otherOffer\": {\"availability\": \"available\"," + + "\"price\": {\"value\": {\"amount\": 10.0}}," + + "\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " + + "\"merchant\": {\"name\": \"Merchant A\"}}}"; + ProductDTO product = new ProductDTO(demoProductId, demoDataOrigin); + + // Invoke the method + ProductDTO result = demoDataSource.buildProductDTO(product, json); + + // Verify that the product DTO remains unchanged + assertEquals(demoDataOrigin, result.getDataOrigin()); + assertEquals(false, result.isAvailable()); + assertEquals(0.0, result.getCurrentPrice()); + assertEquals(0.0, result.getDeliveryPrice()); + assertEquals(null, result.getMerchant()); + } + + @Test + public void testGetProductDTOById_successfulRequest() throws IOException { + // Set up the test environment + String mockResponse = "{\"featuredOffer\": {\"availability\": \"available\"," + + "\"price\": {\"value\": {\"amount\": 10.0}}," + + "\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " + + "\"merchant\": {\"name\": \"Merchant A\"}}}"; + + AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); + URL mockURL = mock(URL.class); + when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL); + when(dataSource.buildProductDTO(any(), anyString())).thenCallRealMethod(); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockURL.openConnection()).thenReturn(mockConnection); + when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + when(mockConnection.getInputStream()).thenReturn(new ByteArrayInputStream(mockResponse.getBytes())); + when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod(); + + // Invoke the method + ProductDTO result = dataSource.getProductDTOById(demoProductId); + + // Verify the product DTO properties + assertEquals(demoProductId, result.getProductId()); + assertEquals("Amazon", result.getDataOrigin()); + assertEquals(true, result.isAvailable()); + assertEquals(10.0, result.getCurrentPrice()); + assertEquals(2.5, result.getDeliveryPrice()); + assertEquals("Merchant A", result.getMerchant()); + } + + @Test + public void testGetProductDTOById_failedRequest() throws IOException { + // Set up the test environment + + AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); + URL mockURL = mock(URL.class); + when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL); + when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod(); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockURL.openConnection()).thenReturn(mockConnection); + when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND); + + // Invoke the method and verify the exception + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + dataSource.getProductDTOById(demoProductId); + }); + + // Verify the exception message + assertEquals("Nothing found: Amazon API responded with error code 404", exception.getMessage()); + } + + @Test + public void testGetProductDTOById_ioException() throws IOException { + // Set up the test environment + AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); + URL mockURL = mock(URL.class); + when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL); + when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod(); + when(dataSource.buildProductDTO(any(), anyString())).thenCallRealMethod(); + HttpURLConnection mockConnection = mock(HttpURLConnection.class); + when(mockURL.openConnection()).thenReturn(mockConnection); + when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + when(mockConnection.getInputStream()).thenThrow(new IOException()); + + // Invoke the method and verify the exception + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + dataSource.getProductDTOById(demoProductId); + }); + + // Verify the exception message + assertEquals("Couldn't fulfill Amazon API request", exception.getMessage()); + } } diff --git a/src/test/java/de/rwu/easydrop/util/ConfigTest.java b/src/test/java/de/rwu/easydrop/util/ConfigTest.java new file mode 100644 index 0000000..9aa2d09 --- /dev/null +++ b/src/test/java/de/rwu/easydrop/util/ConfigTest.java @@ -0,0 +1,86 @@ +package de.rwu.easydrop.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.spy; + +import java.io.FileInputStream; +import java.util.NoSuchElementException; + +import javax.naming.ConfigurationException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class ConfigTest { + @Mock + private FileInputStream mockFileInputStream; + + private Config config; + + @BeforeEach + void setUp() throws ConfigurationException { + MockitoAnnotations.openMocks(this); + config = spy(Config.getInstance()); + } + + @Test + void testGetProperty_ExistingKey() throws NoSuchElementException { + config.setProperty("API_KEY", "12345"); + + String value = config.getProperty("API_KEY"); + + assertEquals("12345", value); + } + + @Test + void testGetProperty_NonExistingKey() { + assertThrows(NoSuchElementException.class, () -> config.getProperty("NON_EXISTING_KEY")); + } + + @Test + void testSetProperty() { + config.setProperty("API_KEY", "12345"); + + assertEquals("12345", config.getProperty("API_KEY")); + } + + @Test + void testGetInstanceNull() { + config = null; + + try { + Config newConfig = Config.getInstance(); + // Check if the returned instance is not null + assertNotNull(newConfig); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown."); + } + } + + @Test + void testGetInstanceNotNull() { + try { + Config newConfig = Config.getInstance(); + // Check if the returned instance is not null + assertNotNull(newConfig); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown."); + } + } + + @Test + void testLoadConfigSuccessfully() { + try { + config.loadConfig(); + config.setProperty("WHATEVER", "SUCCESS"); + assertNotNull(config.getProperty("WHATEVER")); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown"); + } + } +} diff --git a/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java b/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java new file mode 100644 index 0000000..3fc600d --- /dev/null +++ b/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java @@ -0,0 +1,58 @@ +package de.rwu.easydrop.util; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class FormattingUtilTest { + + @Test + public void testConstructorIsPrivate() + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + // Check for private constructor + Constructor constructor = FormattingUtil.class.getDeclaredConstructor(); + assertTrue(Modifier.isPrivate(constructor.getModifiers())); + + // Make sure exception is thrown when instantiating + constructor.setAccessible(true); + assertThrows(InvocationTargetException.class, () -> { + constructor.newInstance(); + }); + } + + @Test + public void testFormatEuro_positiveAmount() { + double amount = 1234.56; + String expectedFormattedAmount = "1.234,56 €"; + + String formattedAmount = FormattingUtil.formatEuro(amount); + + Assertions.assertEquals(expectedFormattedAmount, formattedAmount); + } + + @Test + public void testFormatEuro_zeroAmount() { + double amount = 0.0; + String expectedFormattedAmount = "0,00 €"; + + String formattedAmount = FormattingUtil.formatEuro(amount); + + Assertions.assertEquals(expectedFormattedAmount, formattedAmount); + } + + @Test + public void testFormatEuro_negativeAmount() { + double amount = -789.12; + String expectedFormattedAmount = "-789,12 €"; + + String formattedAmount = FormattingUtil.formatEuro(amount); + + Assertions.assertEquals(expectedFormattedAmount, formattedAmount); + } +} From ffc804aba10ea757a754f9d8399b3f98c0a1056b Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 01:04:13 +0200 Subject: [PATCH 23/31] #39 Fixed tests and added some more --- config/notconfig.properties | 3 + src/main/java/de/rwu/easydrop/Main.java | 1 + .../java/de/rwu/easydrop/util/Config.java | 37 ++++++-- .../de/rwu/easydrop/util/ConfigImplTest.java | 95 +++++++++++++++++++ .../java/de/rwu/easydrop/util/ConfigTest.java | 60 ++---------- testResources/empty.properties | 0 testResources/testdata.properties | 1 + 7 files changed, 138 insertions(+), 59 deletions(-) create mode 100644 config/notconfig.properties create mode 100644 src/test/java/de/rwu/easydrop/util/ConfigImplTest.java create mode 100644 testResources/empty.properties create mode 100644 testResources/testdata.properties diff --git a/config/notconfig.properties b/config/notconfig.properties new file mode 100644 index 0000000..1f5ee7e --- /dev/null +++ b/config/notconfig.properties @@ -0,0 +1,3 @@ +# Amazon Credentials +AMAZON_API_URL=https://checksch.de/api/amazon +AMAZON_API_KEY=lassMichRein \ No newline at end of file diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java index 45299f2..af1aeea 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -33,6 +33,7 @@ public final class Main { */ public static void main(final String[] args) throws ConfigurationException { Config config = Config.getInstance(); + config.loadConfig(); String amznBaseUrl = config.getProperty("AMAZON_API_URL"); String amznApiKey = config.getProperty("AMAZON_API_KEY"); String testProduct = null; diff --git a/src/main/java/de/rwu/easydrop/util/Config.java b/src/main/java/de/rwu/easydrop/util/Config.java index 6b0e89d..7a7f1e3 100644 --- a/src/main/java/de/rwu/easydrop/util/Config.java +++ b/src/main/java/de/rwu/easydrop/util/Config.java @@ -16,7 +16,22 @@ public final class Config { /** * Config file location. */ - private static final String CONFIG_LOCATION = "config/config.properties"; + private String configLocation = "config/config.properties"; + + /** + * @return the configLocation + */ + public String getConfigLocation() { + return configLocation; + } + + /** + * @param newConfigLocation the configLocation to set + */ + public void setConfigLocation(final String newConfigLocation) { + configLocation = newConfigLocation; + } + /** * Holds the config values. */ @@ -27,10 +42,10 @@ public final class Config { private static Config instance = null; /** - * Private constructor to prevent unwanted instantiation. + * Private constructor to prevent external instantiation. */ - private Config() throws ConfigurationException { - loadConfig(); + private Config() { + // Do Nothing } /** @@ -39,7 +54,7 @@ public final class Config { * @return Config instance * @throws ConfigurationException */ - public static Config getInstance() throws ConfigurationException { + public static Config getInstance() { if (instance == null) { return new Config(); } @@ -54,7 +69,7 @@ public final class Config { */ public void loadConfig() throws ConfigurationException { Properties newProps = new Properties(); - try (FileInputStream input = new FileInputStream(CONFIG_LOCATION)) { + try (FileInputStream input = new FileInputStream(configLocation)) { newProps.load(input); properties = newProps; } catch (IOException e) { @@ -70,10 +85,18 @@ public final class Config { * @throws NoSuchElementException Required key missing */ public String getProperty(final String key) throws NoSuchElementException { - String value = properties.getProperty(key); + String value = null; + + try { + value = properties.getProperty(key); + } catch (NullPointerException e) { + throw new NoSuchElementException("Config has not been loaded"); + } + if (value == null) { throw new NoSuchElementException("Requested config value does not exist"); } + return value; } diff --git a/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java b/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java new file mode 100644 index 0000000..9dbc58d --- /dev/null +++ b/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java @@ -0,0 +1,95 @@ +package de.rwu.easydrop.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.NoSuchElementException; + +import javax.naming.ConfigurationException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class ConfigImplTest { + private Config config; + private final static String TESTDATA_PATH = "testResources/testdata.properties"; + private final static String TESTDATA_KEY = "API_KEY"; + private final static String TESTDATA_VAL = "keyIsHere"; + + @BeforeEach + void setUp() { + config = Config.getInstance(); + } + + @Test + void testGetProperty_ExistingKey() throws ConfigurationException { + config.setConfigLocation(TESTDATA_PATH); + config.loadConfig(); + config.setProperty(TESTDATA_KEY, TESTDATA_VAL); + + String value = config.getProperty(TESTDATA_KEY); + + assertEquals(TESTDATA_VAL, value); + } + + @Test + void testGetProperty_ConfigNotLoaded() { + NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> { + config.getProperty(TESTDATA_KEY); + }); + + assertEquals("Config has not been loaded", exception.getMessage()); + } + + @Test + void testGetProperty_NonExistingKey() { + try { + config.setConfigLocation(TESTDATA_PATH); + config.loadConfig(); + + NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> { + config.getProperty("I_DONT_EXIST"); + }); + + assertEquals("Requested config value does not exist", exception.getMessage()); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown"); + } + } + + @Test + void testSetProperty() { + try { + config.setConfigLocation(TESTDATA_PATH); + config.loadConfig(); + config.setProperty(TESTDATA_KEY, "12345"); + + assertEquals("12345", config.getProperty(TESTDATA_KEY)); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown"); + } + } + + @Test + void testLoadConfigSuccessfully() { + try { + config.setConfigLocation("testResources/testdata.properties"); + config.loadConfig(); + assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY)); + } catch (ConfigurationException e) { + fail("ConfigurationException should not be thrown"); + } + } + + @Test + void testLoadConfigMissingFile() { + config.setConfigLocation("path/that/doesnt/exist/config.properties"); + + ConfigurationException exception = assertThrows(ConfigurationException.class, () -> { + config.loadConfig(); + }); + + assertEquals("Couldn't load required config file", exception.getMessage()); + } +} diff --git a/src/test/java/de/rwu/easydrop/util/ConfigTest.java b/src/test/java/de/rwu/easydrop/util/ConfigTest.java index 9aa2d09..8cf0c2a 100644 --- a/src/test/java/de/rwu/easydrop/util/ConfigTest.java +++ b/src/test/java/de/rwu/easydrop/util/ConfigTest.java @@ -2,24 +2,15 @@ package de.rwu.easydrop.util; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.spy; -import java.io.FileInputStream; -import java.util.NoSuchElementException; - import javax.naming.ConfigurationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; class ConfigTest { - @Mock - private FileInputStream mockFileInputStream; - private Config config; @BeforeEach @@ -28,59 +19,24 @@ class ConfigTest { config = spy(Config.getInstance()); } - @Test - void testGetProperty_ExistingKey() throws NoSuchElementException { - config.setProperty("API_KEY", "12345"); - - String value = config.getProperty("API_KEY"); - - assertEquals("12345", value); - } - - @Test - void testGetProperty_NonExistingKey() { - assertThrows(NoSuchElementException.class, () -> config.getProperty("NON_EXISTING_KEY")); - } - - @Test - void testSetProperty() { - config.setProperty("API_KEY", "12345"); - - assertEquals("12345", config.getProperty("API_KEY")); - } - @Test void testGetInstanceNull() { config = null; - try { - Config newConfig = Config.getInstance(); - // Check if the returned instance is not null - assertNotNull(newConfig); - } catch (ConfigurationException e) { - fail("ConfigurationException should not be thrown."); - } + Config newConfig = Config.getInstance(); + assertNotNull(newConfig); } @Test void testGetInstanceNotNull() { - try { - Config newConfig = Config.getInstance(); - // Check if the returned instance is not null - assertNotNull(newConfig); - } catch (ConfigurationException e) { - fail("ConfigurationException should not be thrown."); - } + Config newConfig = Config.getInstance(); + assertNotNull(newConfig); } @Test - void testLoadConfigSuccessfully() { - try { - config.loadConfig(); - config.setProperty("WHATEVER", "SUCCESS"); - assertNotNull(config.getProperty("WHATEVER")); - } catch (ConfigurationException e) { - fail("ConfigurationException should not be thrown"); - } + void testSetConfigLocation() { + String newPath = "new/location/config.properties"; + config.setConfigLocation(newPath); + assertEquals(newPath, config.getConfigLocation()); } } diff --git a/testResources/empty.properties b/testResources/empty.properties new file mode 100644 index 0000000..e69de29 diff --git a/testResources/testdata.properties b/testResources/testdata.properties new file mode 100644 index 0000000..cbf5875 --- /dev/null +++ b/testResources/testdata.properties @@ -0,0 +1 @@ +API_KEY=keyIsHere \ No newline at end of file From 50eb408b2f373f5174fe947939ea95c743814ed8 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 01:05:37 +0200 Subject: [PATCH 24/31] Removed needless conf file --- config/notconfig.properties | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 config/notconfig.properties diff --git a/config/notconfig.properties b/config/notconfig.properties deleted file mode 100644 index 1f5ee7e..0000000 --- a/config/notconfig.properties +++ /dev/null @@ -1,3 +0,0 @@ -# Amazon Credentials -AMAZON_API_URL=https://checksch.de/api/amazon -AMAZON_API_KEY=lassMichRein \ No newline at end of file From a41d3c473c9d0b49efdb7f4cd55610c088705b08 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 01:08:53 +0200 Subject: [PATCH 25/31] Removed visibility modifier code smells --- .../api/AmazonProductDataSourceTest.java | 18 +++++++++--------- .../de/rwu/easydrop/util/ConfigImplTest.java | 2 +- .../rwu/easydrop/util/FormattingUtilTest.java | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java index d643d91..8aff951 100644 --- a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java +++ b/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java @@ -22,7 +22,7 @@ import org.mockito.MockitoAnnotations; import de.rwu.easydrop.api.client.AmazonProductDataSource; import de.rwu.easydrop.api.dto.ProductDTO; -public class AmazonProductDataSourceTest { +class AmazonProductDataSourceTest { private AmazonProductDataSource demoDataSource; @@ -32,7 +32,7 @@ public class AmazonProductDataSourceTest { private static String demoProductId = "whateverId"; @BeforeEach - public void setup() { + void setup() { demoDataSource = new AmazonProductDataSource( demoApiUrl, demoApiKey); @@ -40,7 +40,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testConstructor() { + void testConstructor() { // Assert try { Field baseUrlField = AmazonProductDataSource.class.getDeclaredField("baseUrl"); @@ -58,7 +58,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testCreateApiUrl() throws MalformedURLException { + void testCreateApiUrl() throws MalformedURLException { // Test case 1 String productId1 = "12345"; URL expectedUrl1 = new URL( @@ -76,7 +76,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testBuildProductDTO_completeData() { + void testBuildProductDTO_completeData() { // Set up the test environment String json = "{\"featuredOffer\": {\"availability\": \"available\"," + "\"price\": {\"value\": {\"amount\": 10.0}}," + @@ -96,7 +96,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testBuildProductDTO_incompleteData() { + void testBuildProductDTO_incompleteData() { // Set up the test environment String json = "{\"otherOffer\": {\"availability\": \"available\"," + "\"price\": {\"value\": {\"amount\": 10.0}}," + @@ -116,7 +116,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testGetProductDTOById_successfulRequest() throws IOException { + void testGetProductDTOById_successfulRequest() throws IOException { // Set up the test environment String mockResponse = "{\"featuredOffer\": {\"availability\": \"available\"," + "\"price\": {\"value\": {\"amount\": 10.0}}," + @@ -146,7 +146,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testGetProductDTOById_failedRequest() throws IOException { + void testGetProductDTOById_failedRequest() throws IOException { // Set up the test environment AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); @@ -167,7 +167,7 @@ public class AmazonProductDataSourceTest { } @Test - public void testGetProductDTOById_ioException() throws IOException { + void testGetProductDTOById_ioException() throws IOException { // Set up the test environment AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); URL mockURL = mock(URL.class); diff --git a/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java b/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java index 9dbc58d..db7de79 100644 --- a/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java +++ b/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java @@ -11,7 +11,7 @@ import javax.naming.ConfigurationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class ConfigImplTest { +class ConfigImplTest { private Config config; private final static String TESTDATA_PATH = "testResources/testdata.properties"; private final static String TESTDATA_KEY = "API_KEY"; diff --git a/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java b/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java index 3fc600d..1c7d4f9 100644 --- a/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java +++ b/src/test/java/de/rwu/easydrop/util/FormattingUtilTest.java @@ -10,10 +10,10 @@ import java.lang.reflect.Modifier; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class FormattingUtilTest { +class FormattingUtilTest { @Test - public void testConstructorIsPrivate() + void testConstructorIsPrivate() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // Check for private constructor Constructor constructor = FormattingUtil.class.getDeclaredConstructor(); @@ -27,7 +27,7 @@ public class FormattingUtilTest { } @Test - public void testFormatEuro_positiveAmount() { + void testFormatEuro_positiveAmount() { double amount = 1234.56; String expectedFormattedAmount = "1.234,56 €"; @@ -37,7 +37,7 @@ public class FormattingUtilTest { } @Test - public void testFormatEuro_zeroAmount() { + void testFormatEuro_zeroAmount() { double amount = 0.0; String expectedFormattedAmount = "0,00 €"; @@ -47,7 +47,7 @@ public class FormattingUtilTest { } @Test - public void testFormatEuro_negativeAmount() { + void testFormatEuro_negativeAmount() { double amount = -789.12; String expectedFormattedAmount = "-789,12 €"; From a6ccbe8b3c89d67bcc8b1b8275e59e649ab1828c Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 01:15:56 +0200 Subject: [PATCH 26/31] Removed needless setter --- src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java index 801437a..e3a706c 100644 --- a/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java +++ b/src/main/java/de/rwu/easydrop/api/dto/ProductDTO.java @@ -39,13 +39,6 @@ public class ProductDTO { return productId; } - /** - * @param newProductId the productId to set - */ - public void setProductId(final String newProductId) { - this.productId = newProductId; - } - /** * Current product price per piece in Euro. */ From 43ae67e197ebc1f0bec66871845c1e6d3339181f Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 01:16:09 +0200 Subject: [PATCH 27/31] Moved to proper package --- .../easydrop/api/{ => client}/AmazonProductDataSourceTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/test/java/de/rwu/easydrop/api/{ => client}/AmazonProductDataSourceTest.java (98%) diff --git a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java b/src/test/java/de/rwu/easydrop/api/client/AmazonProductDataSourceTest.java similarity index 98% rename from src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java rename to src/test/java/de/rwu/easydrop/api/client/AmazonProductDataSourceTest.java index 8aff951..032d682 100644 --- a/src/test/java/de/rwu/easydrop/api/AmazonProductDataSourceTest.java +++ b/src/test/java/de/rwu/easydrop/api/client/AmazonProductDataSourceTest.java @@ -1,4 +1,4 @@ -package de.rwu.easydrop.api; +package de.rwu.easydrop.api.client; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -19,7 +19,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; -import de.rwu.easydrop.api.client.AmazonProductDataSource; import de.rwu.easydrop.api.dto.ProductDTO; class AmazonProductDataSourceTest { From 9751e633ad5e24dd9522b78390d067deb3808db7 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 03:07:48 +0200 Subject: [PATCH 28/31] Merge branch '#39-Amazon-Product-API' --- .../rwu/easydrop/api/dto/ProductDTOTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/de/rwu/easydrop/api/dto/ProductDTOTest.java diff --git a/src/test/java/de/rwu/easydrop/api/dto/ProductDTOTest.java b/src/test/java/de/rwu/easydrop/api/dto/ProductDTOTest.java new file mode 100644 index 0000000..9d5917a --- /dev/null +++ b/src/test/java/de/rwu/easydrop/api/dto/ProductDTOTest.java @@ -0,0 +1,33 @@ +package de.rwu.easydrop.api.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +class ProductDTOTest { + @Test + void testToString1() { + ProductDTO product1 = new ProductDTO("12345", "Amazon"); + product1.setMerchant("Merchant A"); + product1.setCurrentPrice(19.99); + product1.setAvailable(true); + + String expectedString1 = "ProductDTO{12345 from Merchant A (Amazon) at 19,99 € (available: yes)}"; + String result1 = product1.toString(); + + assertEquals(expectedString1, result1); + } + + @Test + void testToString2() { + ProductDTO product2 = new ProductDTO("67890", "eBay"); + product2.setMerchant("Merchant B"); + product2.setCurrentPrice(9.99); + product2.setAvailable(false); + + String expectedString2 = "ProductDTO{67890 from Merchant B (eBay) at 9,99 € (available: no)}"; + String result2 = product2.toString(); + + assertEquals(expectedString2, result2); + } +} From 652d5aa1999a6ee9fa3fb412b7aee7bf756055e6 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 03:10:49 +0200 Subject: [PATCH 29/31] Finalized versioning --- pom.xml | 2 +- src/main/java/de/rwu/easydrop/data/model/Product.java | 2 +- .../java/de/rwu/easydrop/service/mapping/ProductMapper.java | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index f87a132..88f5f8e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ de.rwu easydrop jar - 0.1.0-SNAPSHOT + 0.1.0 EasyDrop http://maven.apache.org diff --git a/src/main/java/de/rwu/easydrop/data/model/Product.java b/src/main/java/de/rwu/easydrop/data/model/Product.java index 6b976d6..cfee872 100644 --- a/src/main/java/de/rwu/easydrop/data/model/Product.java +++ b/src/main/java/de/rwu/easydrop/data/model/Product.java @@ -3,7 +3,7 @@ package de.rwu.easydrop.data.model; /** * A Product. * - * @since 0.1.0 + * TODO implement */ public class Product { diff --git a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java index ed66ef6..5d4a6d3 100644 --- a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java +++ b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java @@ -3,7 +3,8 @@ package de.rwu.easydrop.service.mapping; /** * Maps between Product, ProductDAO and ProductDTO. * - * @since 0.1.0 + * TODO implement + * * @see Product * @see ProductDTO * @see ProductDAO From e81397b9d9a85cc891c2f75bc7a7a9b94137c651 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 03:13:18 +0200 Subject: [PATCH 30/31] Updated changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5b7a3..dd98e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ # Changelog -## Use me! \ No newline at end of file +## 0.1.0 + +### Added + +- (DataSource) → AmazonProductDataSource as first external data source +- ProductDTO as data holder structure for data from external sources +- Config for credential, base URLs etc. +- FormattingUtil for formatting price strings From e67c30ea29cb55ca15e608adb9ee9db3168aacd1 Mon Sep 17 00:00:00 2001 From: Marvin Scham Date: Wed, 24 May 2023 12:48:38 +0200 Subject: [PATCH 31/31] Added references by issue number --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd98e5c..010c0fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Added -- (DataSource) → AmazonProductDataSource as first external data source -- ProductDTO as data holder structure for data from external sources -- Config for credential, base URLs etc. -- FormattingUtil for formatting price strings +- (DataSource) → AmazonProductDataSource as first external data source (#39) +- ProductDTO as data holder structure for data from external sources (~ #39) +- Config for credential, base URLs etc. (~ #39) +- FormattingUtil for formatting price strings (~ #39)