From caf9f7b9dd225d25914d1e739e5aae385aa6e4ec Mon Sep 17 00:00:00 2001 From: Steve Waterworth Date: Tue, 25 Aug 2020 17:39:53 +0100 Subject: [PATCH 1/4] first failed attempt --- shipping/Dockerfile | 7 +- shipping/docker-compose.yaml | 23 ++ shipping/pom.xml | 112 ++++---- .../main/java/com/instana/robotshop/City.java | 72 +++++ .../com/instana/robotshop/CityRepository.java | 12 + .../java/com/instana/robotshop/JpaConfig.java | 29 ++ .../robotshop/ShippingApplication.java | 11 + .../main/java/org/steveww/spark/Location.java | 43 --- .../src/main/java/org/steveww/spark/Main.java | 252 ------------------ .../src/main/java/org/steveww/spark/Ship.java | 36 --- 10 files changed, 197 insertions(+), 400 deletions(-) create mode 100644 shipping/docker-compose.yaml create mode 100644 shipping/src/main/java/com/instana/robotshop/City.java create mode 100644 shipping/src/main/java/com/instana/robotshop/CityRepository.java create mode 100644 shipping/src/main/java/com/instana/robotshop/JpaConfig.java create mode 100644 shipping/src/main/java/com/instana/robotshop/ShippingApplication.java delete mode 100644 shipping/src/main/java/org/steveww/spark/Location.java delete mode 100644 shipping/src/main/java/org/steveww/spark/Main.java delete mode 100644 shipping/src/main/java/org/steveww/spark/Ship.java diff --git a/shipping/Dockerfile b/shipping/Dockerfile index 720e938..a999680 100644 --- a/shipping/Dockerfile +++ b/shipping/Dockerfile @@ -1,16 +1,15 @@ # # Build # -FROM openjdk:8-jdk AS build +FROM debian:10 AS build RUN apt-get update && apt-get -y install maven WORKDIR /opt/shipping COPY pom.xml /opt/shipping/ -RUN mvn install - COPY src /opt/shipping/src/ +RUN mvn compile RUN mvn package # @@ -25,7 +24,7 @@ WORKDIR /opt/shipping ENV CART_ENDPOINT=cart:8080 ENV DB_HOST=mysql -COPY --from=build /opt/shipping/target/shipping-1.0-jar-with-dependencies.jar shipping.jar +COPY --from=build /opt/shipping/target/shipping-1.0.jar shipping.jar CMD [ "java", "-Xmn256m", "-Xmx768m", "-jar", "shipping.jar" ] diff --git a/shipping/docker-compose.yaml b/shipping/docker-compose.yaml new file mode 100644 index 0000000..9cf946b --- /dev/null +++ b/shipping/docker-compose.yaml @@ -0,0 +1,23 @@ +version: '3' +services: + shipping: + build: + context: . + image: foo + ports: + - "8080:8080" + depends_on: + - mysql + networks: + - test + + mysql: + image: robotshop/rs-mysql-db + cap_add: + - NET_ADMIN + networks: + - test + + +networks: + test: diff --git a/shipping/pom.xml b/shipping/pom.xml index e185c4a..2e54b80 100644 --- a/shipping/pom.xml +++ b/shipping/pom.xml @@ -1,78 +1,60 @@ - + + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.2.RELEASE + + + 4.0.0 - steveww + robotshop shipping 1.0 - jar - Spark Java Sample + Shipping Service - 1.8 - 1.8 + 1.8 + com.instana.robotshop.ShippingApplication - - com.sparkjava - spark-core - 2.7.2 - - - org.slf4j - slf4j-simple - 1.7.25 - - - c3p0 - c3p0 - 0.9.1.2 - - + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-data-rest + + + mysql mysql-connector-java 8.0.19 - - - commons-dbutils - commons-dbutils - 1.7 - - - com.google.code.gson - gson - 2.8.2 - - - org.apache.httpcomponents - httpclient - 4.5.5 - - + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + - - - - org.apache.maven.plugins - maven-assembly-plugin - - - package - - single - - - - - org.steveww.spark.Main - - - - jar-with-dependencies - - - - - - - diff --git a/shipping/src/main/java/com/instana/robotshop/City.java b/shipping/src/main/java/com/instana/robotshop/City.java new file mode 100644 index 0000000..dcfcd75 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/City.java @@ -0,0 +1,72 @@ +package com.instana.robotshop; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +/* +* Bean for City +*/ +@Entity +public class City { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id; + + private String code; + private String city; + private String name; + private String region; + private double latitude; + private double longitude; + + public String getCode() { + return this.code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getCity() { + return this.city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRegion() { + return this.region; + } + + public void setRegion(String code) { + this.region = region; + } + + public double getLatitude() { + return this.latitude; + } + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + public double getLongitude() { + return this.longitude; + } + + public void setLongitude(double longitude) { + this.longitude = longitude; + } +} diff --git a/shipping/src/main/java/com/instana/robotshop/CityRepository.java b/shipping/src/main/java/com/instana/robotshop/CityRepository.java new file mode 100644 index 0000000..4eda518 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/CityRepository.java @@ -0,0 +1,12 @@ +package com.instana.robotshop; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(collectionResourceRel = "cities", path = "city") +public interface CityRepository extends PagingAndSortingRepository { + List findByCode(@Param("code") String code); +} diff --git a/shipping/src/main/java/com/instana/robotshop/JpaConfig.java b/shipping/src/main/java/com/instana/robotshop/JpaConfig.java new file mode 100644 index 0000000..e63d064 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/JpaConfig.java @@ -0,0 +1,29 @@ +package com.instana.robotshop; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.sql.DataSource; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Bean; + +@Configuration +public class JpaConfig { + private static final Logger logger = LoggerFactory.getLogger(JpaConfig.class); + + @Bean + public DataSource getDataSource() { + String JDBC_URL = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") == null ? "mysql" : System.getenv("DB_HOST")); + + logger.info("jdbc url {}", JDBC_URL); + + DataSourceBuilder bob = DataSourceBuilder.create(); + + bob.driverClassName("com.mysql.jdbc.Driver"); + bob.url(JDBC_URL); + bob.username("shipping"); + bob.password("secret"); + + return bob.build(); + } +} diff --git a/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java b/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java new file mode 100644 index 0000000..7c0b221 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java @@ -0,0 +1,11 @@ +package com.instana.robotshop; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShippingApplication { + public static void main(String[] args) { + SpringApplication.run(ShippingApplication.class, args); + } +} diff --git a/shipping/src/main/java/org/steveww/spark/Location.java b/shipping/src/main/java/org/steveww/spark/Location.java deleted file mode 100644 index 643a4e6..0000000 --- a/shipping/src/main/java/org/steveww/spark/Location.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.steveww.spark; - -public class Location { - private double latitude; - private double longitude; - - public Location(double latitude, double longitude) { - this.latitude = latitude; - this.longitude = longitude; - } - - public double getLatitude() { - return this.latitude; - } - - public double getLongitude() { - return this.longitude; - } - - - /** - * Calculate the distance between this location and the target location. - * Use decimal lat/long degrees - * Formula is Haversine https://www.movable-type.co.uk/scripts/latlong.html - **/ - public long getDistance(double targetLatitude, double targetLongitude) { - double distance = 0.0; - double earthRadius = 6371e3; // meters - - // convert to radians - double latitudeR = Math.toRadians(this.latitude); - double targetLatitudeR = Math.toRadians(targetLatitude); - // difference in Radians - double diffLatR = Math.toRadians(targetLatitude - this.latitude); - double diffLongR = Math.toRadians(targetLongitude - this.longitude); - - double a = Math.sin(diffLatR / 2.0) * Math.sin(diffLatR / 2.0) + Math.cos(latitudeR) * Math.cos(targetLatitudeR) * Math.sin(diffLongR / 2.0) * Math.sin(diffLongR); - - double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a)); - - return (long)Math.rint(earthRadius * c / 1000.0); - } -} diff --git a/shipping/src/main/java/org/steveww/spark/Main.java b/shipping/src/main/java/org/steveww/spark/Main.java deleted file mode 100644 index 99a9a1b..0000000 --- a/shipping/src/main/java/org/steveww/spark/Main.java +++ /dev/null @@ -1,252 +0,0 @@ -package org.steveww.spark; - -import com.mchange.v2.c3p0.ComboPooledDataSource; -import spark.Spark; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.commons.dbutils.QueryRunner; -import org.apache.commons.dbutils.handlers.MapListHandler; -import org.apache.commons.dbutils.DbUtils; -import com.google.gson.Gson; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.Types; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -public class Main { - private static String CART_URL = null; - private static String JDBC_URL = null; - private static Logger logger = LoggerFactory.getLogger(Main.class); - private static ComboPooledDataSource cpds = null; - - public static void main(String[] args) { - // Get ENV configuration values - CART_URL = String.format("http://%s/shipping/", System.getenv("CART_ENDPOINT") != null ? System.getenv("CART_ENDPOINT") : "cart"); - JDBC_URL = String.format("jdbc:mysql://%s/cities?useSSL=false&autoReconnect=true", System.getenv("DB_HOST") != null ? System.getenv("DB_HOST") : "mysql"); - - // - // Create database connector - // TODO - might need a retry loop here - // - try { - cpds = new ComboPooledDataSource(); - cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver - cpds.setJdbcUrl( JDBC_URL ); - cpds.setUser("shipping"); - cpds.setPassword("secret"); - // some config - cpds.setMinPoolSize(5); - cpds.setAcquireIncrement(5); - cpds.setMaxPoolSize(20); - cpds.setMaxStatements(180); - } - catch(Exception e) { - logger.error("Database Exception", e); - } - - // Spark - Spark.port(8080); - - Spark.get("/health", (req, res) -> "OK"); - - Spark.get("/count", (req, res) -> { - String data; - try { - data = queryToJson("select count(*) as count from cities"); - res.header("Content-Type", "application/json"); - } catch(Exception e) { - logger.error("count", e); - res.status(500); - data = "ERROR"; - } - - return data; - }); - - Spark.get("/codes", (req, res) -> { - String data; - try { - String query = "select code, name from codes order by name asc"; - data = queryToJson(query); - res.header("Content-Type", "application/json"); - } catch(Exception e) { - logger.error("codes", e); - res.status(500); - data = "ERROR"; - } - - return data; - }); - - // needed for load gen script - Spark.get("/cities/:code", (req, res) -> { - String data; - try { - String query = "select uuid, name from cities where country_code = ?"; - logger.info("Query " + query); - data = queryToJson(query, req.params(":code")); - res.header("Content-Type", "application/json"); - } catch(Exception e) { - logger.error("cities", e); - res.status(500); - data = "ERROR"; - } - - return data; - }); - - Spark.get("/match/:code/:text", (req, res) -> { - String data; - try { - String query = "select uuid, name from cities where country_code = ? and city like ? order by name asc limit 10"; - logger.info("Query " + query); - data = queryToJson(query, req.params(":code"), req.params(":text") + "%"); - res.header("Content-Type", "application/json"); - } catch(Exception e) { - logger.error("match", e); - res.status(500); - data = "ERROR"; - } - - return data; - }); - - Spark.get("/calc/:uuid", (req, res) -> { - double homeLat = 51.164896; - double homeLong = 7.068792; - String data; - - Location location = getLocation(req.params(":uuid")); - Ship ship = new Ship(); - if(location != null) { - long distance = location.getDistance(homeLat, homeLong); - // charge 0.05 Euro per km - // try to avoid rounding errors - double cost = Math.rint(distance * 5) / 100.0; - ship.setDistance(distance); - ship.setCost(cost); - res.header("Content-Type", "application/json"); - data = new Gson().toJson(ship); - } else { - data = "no location"; - logger.warn(data); - res.status(400); - } - - return data; - }); - - Spark.post("/confirm/:id", (req, res) -> { - logger.info("confirm " + req.params(":id") + " - " + req.body()); - String cart = addToCart(req.params(":id"), req.body()); - logger.info("new cart " + cart); - - if(cart.equals("")) { - res.status(404); - } else { - res.header("Content-Type", "application/json"); - } - - return cart; - }); - - logger.info("Ready"); - } - - - - /** - * Query to Json - QED - **/ - private static String queryToJson(String query, Object ... args) { - List> listOfMaps = null; - try { - QueryRunner queryRunner = new QueryRunner(cpds); - listOfMaps = queryRunner.query(query, new MapListHandler(), args); - } catch (SQLException se) { - throw new RuntimeException("Couldn't query the database.", se); - } - - return new Gson().toJson(listOfMaps); - } - - /** - * Special case for location, dont want Json - **/ - private static Location getLocation(String uuid) { - Location location = null; - Connection conn = null; - PreparedStatement stmt = null; - ResultSet rs = null; - String query = "select latitude, longitude from cities where uuid = ?"; - - try { - conn = cpds.getConnection(); - stmt = conn.prepareStatement(query); - stmt.setInt(1, Integer.parseInt(uuid)); - rs = stmt.executeQuery(); - while(rs.next()) { - location = new Location(rs.getDouble(1), rs.getDouble(2)); - break; - } - } catch(Exception e) { - logger.error("Location exception", e); - } finally { - DbUtils.closeQuietly(conn, stmt, rs); - } - - return location; - } - - private static String addToCart(String id, String data) { - StringBuilder buffer = new StringBuilder(); - - DefaultHttpClient httpClient = null; - try { - // set timeout to 5 secs - HttpParams httpParams = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParams, 5000); - - httpClient = new DefaultHttpClient(httpParams); - HttpPost postRequest = new HttpPost(CART_URL + id); - StringEntity payload = new StringEntity(data); - payload.setContentType("application/json"); - postRequest.setEntity(payload); - HttpResponse res = httpClient.execute(postRequest); - - if(res.getStatusLine().getStatusCode() == 200) { - BufferedReader in = new BufferedReader(new InputStreamReader(res.getEntity().getContent())); - String line; - while((line = in.readLine()) != null) { - buffer.append(line); - } - } else { - logger.warn("Failed with code: " + res.getStatusLine().getStatusCode()); - } - } catch(Exception e) { - logger.error("http client exception", e); - } finally { - if(httpClient != null) { - httpClient.getConnectionManager().shutdown(); - } - } - - return buffer.toString(); - } -} diff --git a/shipping/src/main/java/org/steveww/spark/Ship.java b/shipping/src/main/java/org/steveww/spark/Ship.java deleted file mode 100644 index 665f925..0000000 --- a/shipping/src/main/java/org/steveww/spark/Ship.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.steveww.spark; - -/** - * Bean to hold shipping information - **/ -public class Ship { - private long distance; - private double cost; - - public Ship() { - this.distance = 0; - this.cost = 0.0; - } - - public Ship(long distnace, double cost) { - this.distance = distance; - this.cost = cost; - } - - public void setDistance(long distance) { - this.distance = distance; - } - - public void setCost(double cost) { - this.cost = cost; - } - - public long getDistance() { - return this.distance; - } - - public double getCost() { - return this.cost; - } -} - From f0043a520bd5432bfd8c05be6ad42432b93ac333 Mon Sep 17 00:00:00 2001 From: Steve Waterworth Date: Wed, 26 Aug 2020 12:48:37 +0100 Subject: [PATCH 2/4] different frameworks --- shipping/pom.xml | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/shipping/pom.xml b/shipping/pom.xml index 2e54b80..69553a5 100644 --- a/shipping/pom.xml +++ b/shipping/pom.xml @@ -1,39 +1,37 @@ - - + 4.0.0 + org.springframework.boot spring-boot-starter-parent - 2.3.2.RELEASE + 2.3.3.RELEASE + com.instana + shipping + 0.0.1-SNAPSHOT + shipping service + Shipping calculations - 4.0.0 - robotshop - shipping - 1.0 - Shipping Service + + 1.8 + - - 1.8 - com.instana.robotshop.ShippingApplication - - - + org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot - spring-boot-starter-data-rest + spring-boot-starter-web - mysql - mysql-connector-java - 8.0.19 + mysql + mysql-connector-java + runtime org.springframework.boot From 7c3ffda0cbe7df932b5c97072f5dee0ac983f9dd Mon Sep 17 00:00:00 2001 From: Steve Waterworth Date: Wed, 26 Aug 2020 12:49:20 +0100 Subject: [PATCH 3/4] change package name --- .../com/instana/robotshop/CityRepository.java | 12 ---------- .../robotshop/ShippingApplication.java | 11 ---------- .../robotshop/{ => shipping}/City.java | 7 +++++- .../robotshop/shipping/CityRepository.java | 11 ++++++++++ .../robotshop/shipping/Controller.java | 22 +++++++++++++++++++ .../robotshop/{ => shipping}/JpaConfig.java | 2 +- .../shipping/ShippingServiceApplication.java | 13 +++++++++++ .../src/main/resources/application.properties | 1 + 8 files changed, 54 insertions(+), 25 deletions(-) delete mode 100644 shipping/src/main/java/com/instana/robotshop/CityRepository.java delete mode 100644 shipping/src/main/java/com/instana/robotshop/ShippingApplication.java rename shipping/src/main/java/com/instana/robotshop/{ => shipping}/City.java (84%) create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/Controller.java rename shipping/src/main/java/com/instana/robotshop/{ => shipping}/JpaConfig.java (95%) create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java create mode 100644 shipping/src/main/resources/application.properties diff --git a/shipping/src/main/java/com/instana/robotshop/CityRepository.java b/shipping/src/main/java/com/instana/robotshop/CityRepository.java deleted file mode 100644 index 4eda518..0000000 --- a/shipping/src/main/java/com/instana/robotshop/CityRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.instana.robotshop; - -import java.util.List; - -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.core.annotation.RepositoryRestResource; - -@RepositoryRestResource(collectionResourceRel = "cities", path = "city") -public interface CityRepository extends PagingAndSortingRepository { - List findByCode(@Param("code") String code); -} diff --git a/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java b/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java deleted file mode 100644 index 7c0b221..0000000 --- a/shipping/src/main/java/com/instana/robotshop/ShippingApplication.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.instana.robotshop; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class ShippingApplication { - public static void main(String[] args) { - SpringApplication.run(ShippingApplication.class, args); - } -} diff --git a/shipping/src/main/java/com/instana/robotshop/City.java b/shipping/src/main/java/com/instana/robotshop/shipping/City.java similarity index 84% rename from shipping/src/main/java/com/instana/robotshop/City.java rename to shipping/src/main/java/com/instana/robotshop/shipping/City.java index dcfcd75..567ac54 100644 --- a/shipping/src/main/java/com/instana/robotshop/City.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/City.java @@ -1,4 +1,4 @@ -package com.instana.robotshop; +package com.instana.robotshop.shipping; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -69,4 +69,9 @@ public class City { public void setLongitude(double longitude) { this.longitude = longitude; } + + @Override + public String toString() { + return String.format("Country: %s City: %s Region: %s Coords: %f %f", this.code, this.city, this.region, this.latitude, this.longitude); + } } diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java b/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java new file mode 100644 index 0000000..5ee396f --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java @@ -0,0 +1,11 @@ +package com.instana.robotshop.shipping; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface CityRepository extends CrudRepository { + List findByCode(String code); + + City findById(long id); +} diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java b/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java new file mode 100644 index 0000000..dc74431 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java @@ -0,0 +1,22 @@ +package com.instana.robotshop.shipping; + +import java.util.List; +import java.util.ArrayList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Controller { + private static final Logger logger = LoggerFactory.getLogger(Controller.class); + + @GetMapping("/cities") + public List cities(@RequestParam(value = "code") String code) { + logger.info("cities by code {}", code); + + return new ArrayList(); + } +} diff --git a/shipping/src/main/java/com/instana/robotshop/JpaConfig.java b/shipping/src/main/java/com/instana/robotshop/shipping/JpaConfig.java similarity index 95% rename from shipping/src/main/java/com/instana/robotshop/JpaConfig.java rename to shipping/src/main/java/com/instana/robotshop/shipping/JpaConfig.java index e63d064..3cd995a 100644 --- a/shipping/src/main/java/com/instana/robotshop/JpaConfig.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/JpaConfig.java @@ -1,4 +1,4 @@ -package com.instana.robotshop; +package com.instana.robotshop.shipping; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java b/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java new file mode 100644 index 0000000..f368895 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java @@ -0,0 +1,13 @@ +package com.instana.robotshop.shipping; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ShippingServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(ShippingServiceApplication.class, args); + } + +} diff --git a/shipping/src/main/resources/application.properties b/shipping/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/shipping/src/main/resources/application.properties @@ -0,0 +1 @@ + From f0b49233a3d7bebb741d2512ba7155d4efa88dd4 Mon Sep 17 00:00:00 2001 From: Steve Waterworth Date: Tue, 1 Sep 2020 16:30:35 +0100 Subject: [PATCH 4/4] spring boot application --- shipping/Dockerfile | 2 +- shipping/docker-compose.yaml | 23 ---- shipping/pom.xml | 16 ++- .../robotshop/shipping/Calculator.java | 43 +++++++ .../robotshop/shipping/CartHelper.java | 75 ++++++++++++ .../com/instana/robotshop/shipping/City.java | 10 +- .../robotshop/shipping/CityRepository.java | 6 + .../com/instana/robotshop/shipping/Code.java | 47 ++++++++ .../robotshop/shipping/CodeRepository.java | 12 ++ .../robotshop/shipping/Controller.java | 112 +++++++++++++++++- .../shipping/RetryableDataSource.java | 30 +++++ .../com/instana/robotshop/shipping/Ship.java | 41 +++++++ .../shipping/ShippingServiceApplication.java | 30 +++++ .../src/main/resources/application.properties | 5 + 14 files changed, 422 insertions(+), 30 deletions(-) delete mode 100644 shipping/docker-compose.yaml create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/CartHelper.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/Code.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/CodeRepository.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/RetryableDataSource.java create mode 100644 shipping/src/main/java/com/instana/robotshop/shipping/Ship.java diff --git a/shipping/Dockerfile b/shipping/Dockerfile index a999680..afd9d32 100644 --- a/shipping/Dockerfile +++ b/shipping/Dockerfile @@ -8,8 +8,8 @@ RUN apt-get update && apt-get -y install maven WORKDIR /opt/shipping COPY pom.xml /opt/shipping/ +RUN mvn dependency:resolve COPY src /opt/shipping/src/ -RUN mvn compile RUN mvn package # diff --git a/shipping/docker-compose.yaml b/shipping/docker-compose.yaml deleted file mode 100644 index 9cf946b..0000000 --- a/shipping/docker-compose.yaml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3' -services: - shipping: - build: - context: . - image: foo - ports: - - "8080:8080" - depends_on: - - mysql - networks: - - test - - mysql: - image: robotshop/rs-mysql-db - cap_add: - - NET_ADMIN - networks: - - test - - -networks: - test: diff --git a/shipping/pom.xml b/shipping/pom.xml index 69553a5..008e7c4 100644 --- a/shipping/pom.xml +++ b/shipping/pom.xml @@ -10,7 +10,7 @@ com.instana shipping - 0.0.1-SNAPSHOT + 1.0 shipping service Shipping calculations @@ -27,12 +27,26 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.retry + spring-retry + + + org.springframework.boot + spring-boot-starter-actuator + mysql mysql-connector-java runtime + + org.apache.httpcomponents + httpclient + 4.5.12 + + org.springframework.boot spring-boot-starter-test diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java b/shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java new file mode 100644 index 0000000..85c591c --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java @@ -0,0 +1,43 @@ +package com.instana.robotshop.shipping; + + +public class Calculator { + private double latitude = 0; + private double longitude = 0; + + Calculator(double latitdue, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + Calculator(City city) { + this.latitude = city.getLatitude(); + this.longitude = city.getLongitude(); + } + + /** + * Calculate the distance between this location and the target location. + * Use decimal lat/long degrees + * Formula is Haversine https://www.movable-type.co.uk/scripts/latlong.html + **/ + public long getDistance(double targetLatitude, double targetLongitude) { + double distance = 0.0; + double earthRadius = 6371e3; // meters + + // convert to radians + double latitudeR = Math.toRadians(this.latitude); + double targetLatitudeR = Math.toRadians(targetLatitude); + // difference in Radians + double diffLatR = Math.toRadians(targetLatitude - this.latitude); + double diffLongR = Math.toRadians(targetLongitude - this.longitude); + + double a = Math.sin(diffLatR / 2.0) * Math.sin(diffLatR / 2.0) + + Math.cos(latitudeR) * Math.cos(targetLatitudeR) + * Math.sin(diffLongR / 2.0) * Math.sin(diffLongR); + + double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a)); + + return (long)Math.rint(earthRadius * c / 1000.0); + } +} + diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/CartHelper.java b/shipping/src/main/java/com/instana/robotshop/shipping/CartHelper.java new file mode 100644 index 0000000..e10af1a --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/CartHelper.java @@ -0,0 +1,75 @@ +package com.instana.robotshop.shipping; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; + +public class CartHelper { + private static final Logger logger = LoggerFactory.getLogger(CartHelper.class); + + private String baseUrl; + + public CartHelper(String baseUrl) { + this.baseUrl = baseUrl; + } + + // TODO - Remove deprecated calls + public String addToCart(String id, String data) { + logger.info("add shipping to cart {}", id); + StringBuilder buffer = new StringBuilder(); + + CloseableHttpClient httpClient = null; + try { + // set timeout to 5 secs + HttpParams httpParams = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParams, 5000); + + httpClient = HttpClients.createDefault(); + HttpPost postRequest = new HttpPost(baseUrl + id); + StringEntity payload = new StringEntity(data); + payload.setContentType("application/json"); + postRequest.setEntity(payload); + CloseableHttpResponse res = httpClient.execute(postRequest); + + if (res.getStatusLine().getStatusCode() == 200) { + BufferedReader in = new BufferedReader(new InputStreamReader(res.getEntity().getContent())); + String line; + while ((line = in.readLine()) != null) { + buffer.append(line); + } + } else { + logger.warn("Failed with code {}", res.getStatusLine().getStatusCode()); + } + try { + res.close(); + } catch(IOException e) { + logger.warn("httpresponse", e); + } + } catch(Exception e) { + logger.warn("http client exception", e); + } finally { + if (httpClient != null) { + try { + httpClient.close(); + } catch(IOException e) { + logger.warn("httpclient", e); + } + } + } + + // this will be empty on error + return buffer.toString(); + } +} diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/City.java b/shipping/src/main/java/com/instana/robotshop/shipping/City.java index 567ac54..d1fb033 100644 --- a/shipping/src/main/java/com/instana/robotshop/shipping/City.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/City.java @@ -1,20 +1,24 @@ package com.instana.robotshop.shipping; +import javax.persistence.Table; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Column; /* * Bean for City */ @Entity +@Table(name = "cities") public class City { @Id @GeneratedValue(strategy = GenerationType.AUTO) - private long id; + private long uuid; + @Column(name = "country_code") private String code; private String city; private String name; @@ -22,6 +26,10 @@ public class City { private double latitude; private double longitude; + public long getUuid() { + return this.uuid; + } + public String getCode() { return this.code; } diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java b/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java index 5ee396f..1602e1a 100644 --- a/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java @@ -3,9 +3,15 @@ package com.instana.robotshop.shipping; import java.util.List; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.Query; public interface CityRepository extends CrudRepository { List findByCode(String code); + @Query( + value = "select c from City c where c.code = ?1 and c.city like ?2%" + ) + List match(String code, String text); + City findById(long id); } diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/Code.java b/shipping/src/main/java/com/instana/robotshop/shipping/Code.java new file mode 100644 index 0000000..152d386 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Code.java @@ -0,0 +1,47 @@ +package com.instana.robotshop.shipping; + +import javax.persistence.Table; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +/* +* Bean for Code +*/ +@Entity +@Table(name = "codes") +public class Code { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long uuid; + + private String code; + private String name; + + public long getUuid() { + return this.uuid; + } + + public String getCode() { + return this.code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return String.format("Code: %s Name: %s", this.code, this.name); + } +} diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/CodeRepository.java b/shipping/src/main/java/com/instana/robotshop/shipping/CodeRepository.java new file mode 100644 index 0000000..70f5798 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/CodeRepository.java @@ -0,0 +1,12 @@ +package com.instana.robotshop.shipping; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; + +public interface CodeRepository extends PagingAndSortingRepository { + + Iterable findAll(); + + Code findById(long id); +} diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java b/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java index dc74431..03f020a 100644 --- a/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java @@ -1,22 +1,126 @@ package com.instana.robotshop.shipping; import java.util.List; +import java.util.Map; import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Sort; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.http.HttpStatus; @RestController public class Controller { private static final Logger logger = LoggerFactory.getLogger(Controller.class); - @GetMapping("/cities") - public List cities(@RequestParam(value = "code") String code) { + private String CART_URL = String.format("http://%s/shipping/", getenv("CART_ENDPOINT", "cart")); + + @Autowired + private CityRepository cityrepo; + + @Autowired + private CodeRepository coderepo; + + private String getenv(String key, String def) { + String val = System.getenv(key); + val = val == null ? def : val; + + return val; + } + + @GetMapping("/health") + public String health() { + return "OK"; + } + + @GetMapping("/count") + public String count() { + long count = cityrepo.count(); + + return String.valueOf(count); + } + + @GetMapping("/codes") + public Iterable codes() { + logger.info("all codes"); + + Iterable codes = coderepo.findAll(Sort.by(Sort.Direction.ASC, "name")); + + return codes; + } + + @GetMapping("/cities/{code}") + public List cities(@PathVariable String code) { logger.info("cities by code {}", code); - return new ArrayList(); + List cities = cityrepo.findByCode(code); + + return cities; + } + + @GetMapping("/match/{code}/{text}") + public List match(@PathVariable String code, @PathVariable String text) { + logger.info("match code {} text {}", code, text); + + if (text.length() < 3) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST); + } + + List cities = cityrepo.match(code, text); + /* + * This is a dirty hack to limit the result size + * I'm sure there is a more spring boot way to do this + * TODO - neater + */ + if (cities.size() > 10) { + cities = cities.subList(0, 9); + } + + return cities; + } + + @GetMapping("/calc/{id}") + public Ship caclc(@PathVariable long id) { + double homeLatitude = 51.164896; + double homeLongitude = 7.068792; + + logger.info("Calculation for {}", id); + + City city = cityrepo.findById(id); + if (city == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "city not found"); + } + + Calculator calc = new Calculator(city); + long distance = calc.getDistance(homeLatitude, homeLongitude); + // avoid rounding + double cost = Math.rint(distance * 5) / 100.0; + Ship ship = new Ship(distance, cost); + logger.info("shipping {}", ship); + + return ship; + } + + // enforce content type + @PostMapping(path = "/confirm/{id}", consumes = "application/json", produces = "application/json") + public String confirm(@PathVariable String id, @RequestBody String body) { + logger.info("confirm id: {}", id); + logger.info("body {}", body); + + CartHelper helper = new CartHelper(CART_URL); + String cart = helper.addToCart(id, body); + + if (cart.equals("")) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "cart not found"); + } + + return cart; } } diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/RetryableDataSource.java b/shipping/src/main/java/com/instana/robotshop/shipping/RetryableDataSource.java new file mode 100644 index 0000000..7dcfd06 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/RetryableDataSource.java @@ -0,0 +1,30 @@ +package com.instana.robotshop.shipping; + +import java.sql.Connection; +import java.sql.SQLException; +import javax.sql.DataSource; + +import org.springframework.jdbc.datasource.AbstractDataSource; +import org.springframework.retry.annotation.Retryable; +import org.springframework.retry.annotation.Backoff; + +class RetryableDataSource extends AbstractDataSource { + private DataSource delegate; + + public RetryableDataSource(DataSource delegate) { + this.delegate = delegate; + } + + @Override + @Retryable(maxAttempts = 10, backoff = @Backoff(multiplier = 2.3, maxDelay = 30000)) + public Connection getConnection() throws SQLException { + return delegate.getConnection(); + } + + @Override + @Retryable(maxAttempts = 10, backoff = @Backoff(multiplier = 2.3, maxDelay = 30000)) + public Connection getConnection(String username, String password) throws SQLException { + return delegate.getConnection(username, password); + } +} + diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/Ship.java b/shipping/src/main/java/com/instana/robotshop/shipping/Ship.java new file mode 100644 index 0000000..3f4b003 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Ship.java @@ -0,0 +1,41 @@ +package com.instana.robotshop.shipping; + +/** + * Bean to hold shipping information + **/ +public class Ship { + private long distance; + private double cost; + + public Ship() { + this.distance = 0; + this.cost = 0.0; + } + + public Ship(long distance, double cost) { + this.distance = distance; + this.cost = cost; + } + + public void setDistance(long distance) { + this.distance = distance; + } + + public void setCost(double cost) { + this.cost = cost; + } + + public long getDistance() { + return this.distance; + } + + public double getCost() { + return this.cost; + } + + @Override + public String toString() { + return String.format("Distance: %d Cost: %f", distance, cost); + } +} + diff --git a/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java b/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java index f368895..0dcfc93 100644 --- a/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java @@ -1,13 +1,43 @@ package com.instana.robotshop.shipping; +import javax.sql.DataSource; + import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.retry.annotation.EnableRetry; + @SpringBootApplication +@EnableRetry public class ShippingServiceApplication { public static void main(String[] args) { SpringApplication.run(ShippingServiceApplication.class, args); } + @Bean + public BeanPostProcessor dataSourceWrapper() { + return new DataSourcePostProcessor(); + } + + @Order(Ordered.HIGHEST_PRECEDENCE) + private class DataSourcePostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException { + if (bean instanceof DataSource) { + bean = new RetryableDataSource((DataSource)bean); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String name) throws BeansException { + return bean; + } + } } diff --git a/shipping/src/main/resources/application.properties b/shipping/src/main/resources/application.properties index 8b13789..0749323 100644 --- a/shipping/src/main/resources/application.properties +++ b/shipping/src/main/resources/application.properties @@ -1 +1,6 @@ +spring.jmx.enabled=true +management.endpoint.info.enabled=true +management.endpoint.health.enabled=true +management.endpoint.metrics.enabled=true +management.endpoint.env.enabled=true