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