Merge branch 'master' into synthetics
This commit is contained in:
2
.env
2
.env
@@ -1,3 +1,3 @@
|
|||||||
# environment file for docker-compose
|
# environment file for docker-compose
|
||||||
REPO=robotshop
|
REPO=robotshop
|
||||||
TAG=0.5.0
|
TAG=0.5.1
|
||||||
|
@@ -6,7 +6,7 @@ You can get more detailed information from my [blog post](https://www.instana.co
|
|||||||
|
|
||||||
This sample microservice application has been built using these technologies:
|
This sample microservice application has been built using these technologies:
|
||||||
- NodeJS ([Express](http://expressjs.com/))
|
- NodeJS ([Express](http://expressjs.com/))
|
||||||
- Java ([Spark Java](http://sparkjava.com/))
|
- Java ([Spring Boot](https://spring.io/))
|
||||||
- Python ([Flask](http://flask.pocoo.org))
|
- Python ([Flask](http://flask.pocoo.org))
|
||||||
- Golang
|
- Golang
|
||||||
- PHP (Apache)
|
- PHP (Apache)
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
FROM mysql:5.7.30
|
FROM mysql:5.7.30
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
|
||||||
ENV MYSQL_ALLOW_EMPTY_PASSWORD=yes \
|
ENV MYSQL_ALLOW_EMPTY_PASSWORD=yes \
|
||||||
MYSQL_DATABASE=cities \
|
MYSQL_DATABASE=cities \
|
||||||
MYSQL_USER=shipping \
|
MYSQL_USER=shipping \
|
||||||
@@ -11,6 +13,6 @@ RUN /root/config.sh
|
|||||||
|
|
||||||
COPY scripts/* /docker-entrypoint-initdb.d/
|
COPY scripts/* /docker-entrypoint-initdb.d/
|
||||||
|
|
||||||
RUN /entrypoint.sh mysqld & while [ ! -f /tmp/finished ]; do sleep 10; done
|
#RUN /entrypoint.sh mysqld & while [ ! -f /tmp/finished ]; do sleep 10; done
|
||||||
RUN rm /docker-entrypoint-initdb.d/*
|
#RUN rm /docker-entrypoint-initdb.d/*
|
||||||
|
|
||||||
|
4
shipping/.gitignore
vendored
Normal file
4
shipping/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.settings
|
@@ -1,15 +1,14 @@
|
|||||||
#
|
#
|
||||||
# Build
|
# Build
|
||||||
#
|
#
|
||||||
FROM openjdk:8-jdk AS build
|
FROM debian:10 AS build
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install maven
|
RUN apt-get update && apt-get -y install maven
|
||||||
|
|
||||||
WORKDIR /opt/shipping
|
WORKDIR /opt/shipping
|
||||||
|
|
||||||
COPY pom.xml /opt/shipping/
|
COPY pom.xml /opt/shipping/
|
||||||
RUN mvn install
|
RUN mvn dependency:resolve
|
||||||
|
|
||||||
COPY src /opt/shipping/src/
|
COPY src /opt/shipping/src/
|
||||||
RUN mvn package
|
RUN mvn package
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ WORKDIR /opt/shipping
|
|||||||
ENV CART_ENDPOINT=cart:8080
|
ENV CART_ENDPOINT=cart:8080
|
||||||
ENV DB_HOST=mysql
|
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" ]
|
CMD [ "java", "-Xmn256m", "-Xmx768m", "-jar", "shipping.jar" ]
|
||||||
|
|
||||||
|
126
shipping/pom.xml
126
shipping/pom.xml
@@ -1,78 +1,72 @@
|
|||||||
<project>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<groupId>steveww</groupId>
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<artifactId>shipping</artifactId>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<version>1.0</version>
|
<parent>
|
||||||
<packaging>jar</packaging>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<name>Spark Java Sample</name>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.3.3.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<groupId>com.instana</groupId>
|
||||||
|
<artifactId>shipping</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<name>shipping service</name>
|
||||||
|
<description>Shipping calculations</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>1.8</maven.compiler.source>
|
<java.version>1.8</java.version>
|
||||||
<maven.compiler.target>1.8</maven.compiler.target>
|
</properties>
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.sparkjava</groupId>
|
<groupId>org.springframework.retry</groupId>
|
||||||
<artifactId>spark-core</artifactId>
|
<artifactId>spring-retry</artifactId>
|
||||||
<version>2.7.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>slf4j-simple</artifactId>
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
<version>1.7.25</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>c3p0</groupId>
|
|
||||||
<artifactId>c3p0</artifactId>
|
|
||||||
<version>0.9.1.2</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>mysql</groupId>
|
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
|
||||||
<version>8.0.19</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-dbutils</groupId>
|
|
||||||
<artifactId>commons-dbutils</artifactId>
|
|
||||||
<version>1.7</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
<version>2.8.2</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<version>4.5.5</version>
|
<version>4.5.12</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifest>
|
|
||||||
<mainClass>org.steveww.spark.Main</mainClass>
|
|
||||||
</manifest>
|
|
||||||
</archive>
|
|
||||||
<descriptorRefs>
|
|
||||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
|
||||||
</descriptorRefs>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</project>
|
</project>
|
||||||
|
@@ -1,23 +1,20 @@
|
|||||||
package org.steveww.spark;
|
package com.instana.robotshop.shipping;
|
||||||
|
|
||||||
public class Location {
|
|
||||||
private double latitude;
|
|
||||||
private double longitude;
|
|
||||||
|
|
||||||
public Location(double latitude, double longitude) {
|
public class Calculator {
|
||||||
|
private double latitude = 0;
|
||||||
|
private double longitude = 0;
|
||||||
|
|
||||||
|
Calculator(double latitdue, double longitude) {
|
||||||
this.latitude = latitude;
|
this.latitude = latitude;
|
||||||
this.longitude = longitude;
|
this.longitude = longitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getLatitude() {
|
Calculator(City city) {
|
||||||
return this.latitude;
|
this.latitude = city.getLatitude();
|
||||||
|
this.longitude = city.getLongitude();
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getLongitude() {
|
|
||||||
return this.longitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the distance between this location and the target location.
|
* Calculate the distance between this location and the target location.
|
||||||
* Use decimal lat/long degrees
|
* Use decimal lat/long degrees
|
||||||
@@ -34,10 +31,13 @@ public class Location {
|
|||||||
double diffLatR = Math.toRadians(targetLatitude - this.latitude);
|
double diffLatR = Math.toRadians(targetLatitude - this.latitude);
|
||||||
double diffLongR = Math.toRadians(targetLongitude - this.longitude);
|
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 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));
|
double c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1.0 - a));
|
||||||
|
|
||||||
return (long)Math.rint(earthRadius * c / 1000.0);
|
return (long)Math.rint(earthRadius * c / 1000.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,85 @@
|
|||||||
|
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 uuid;
|
||||||
|
|
||||||
|
@Column(name = "country_code")
|
||||||
|
private String code;
|
||||||
|
private String city;
|
||||||
|
private String name;
|
||||||
|
private String region;
|
||||||
|
private double latitude;
|
||||||
|
private double longitude;
|
||||||
|
|
||||||
|
public long getUuid() {
|
||||||
|
return this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
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<City, Long> {
|
||||||
|
List<City> findByCode(String code);
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
value = "select c from City c where c.code = ?1 and c.city like ?2%"
|
||||||
|
)
|
||||||
|
List<City> match(String code, String text);
|
||||||
|
|
||||||
|
City findById(long id);
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,12 @@
|
|||||||
|
package com.instana.robotshop.shipping;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||||
|
|
||||||
|
public interface CodeRepository extends PagingAndSortingRepository<Code, Long> {
|
||||||
|
|
||||||
|
Iterable<Code> findAll();
|
||||||
|
|
||||||
|
Code findById(long id);
|
||||||
|
}
|
@@ -0,0 +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.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);
|
||||||
|
|
||||||
|
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<Code> codes() {
|
||||||
|
logger.info("all codes");
|
||||||
|
|
||||||
|
Iterable<Code> codes = coderepo.findAll(Sort.by(Sort.Direction.ASC, "name"));
|
||||||
|
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/cities/{code}")
|
||||||
|
public List<City> cities(@PathVariable String code) {
|
||||||
|
logger.info("cities by code {}", code);
|
||||||
|
|
||||||
|
List<City> cities = cityrepo.findByCode(code);
|
||||||
|
|
||||||
|
return cities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/match/{code}/{text}")
|
||||||
|
public List<City> 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<City> 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
package com.instana.robotshop.shipping;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package org.steveww.spark;
|
package com.instana.robotshop.shipping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bean to hold shipping information
|
* Bean to hold shipping information
|
||||||
@@ -12,7 +12,7 @@ public class Ship {
|
|||||||
this.cost = 0.0;
|
this.cost = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Ship(long distnace, double cost) {
|
public Ship(long distance, double cost) {
|
||||||
this.distance = distance;
|
this.distance = distance;
|
||||||
this.cost = cost;
|
this.cost = cost;
|
||||||
}
|
}
|
||||||
@@ -32,5 +32,10 @@ public class Ship {
|
|||||||
public double getCost() {
|
public double getCost() {
|
||||||
return this.cost;
|
return this.cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Distance: %d Cost: %f", distance, cost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@@ -0,0 +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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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<Map<String, Object>> 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();
|
|
||||||
}
|
|
||||||
}
|
|
6
shipping/src/main/resources/application.properties
Normal file
6
shipping/src/main/resources/application.properties
Normal file
@@ -0,0 +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
|
Reference in New Issue
Block a user