diff --git a/.env b/.env index d555315..00fdcd7 100644 --- a/.env +++ b/.env @@ -1,3 +1,3 @@ # environment file for docker-compose REPO=robotshop -TAG=0.5.0 +TAG=0.5.1 diff --git a/README.md b/README.md index ba802a9..a8c6a2f 100644 --- a/README.md +++ b/README.md @@ -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: - NodeJS ([Express](http://expressjs.com/)) -- Java ([Spark Java](http://sparkjava.com/)) +- Java ([Spring Boot](https://spring.io/)) - Python ([Flask](http://flask.pocoo.org)) - Golang - PHP (Apache) diff --git a/mysql/Dockerfile b/mysql/Dockerfile index dcd2bf8..6dbd9d3 100644 --- a/mysql/Dockerfile +++ b/mysql/Dockerfile @@ -1,5 +1,7 @@ FROM mysql:5.7.30 +VOLUME /data + ENV MYSQL_ALLOW_EMPTY_PASSWORD=yes \ MYSQL_DATABASE=cities \ MYSQL_USER=shipping \ @@ -11,6 +13,6 @@ RUN /root/config.sh COPY scripts/* /docker-entrypoint-initdb.d/ -RUN /entrypoint.sh mysqld & while [ ! -f /tmp/finished ]; do sleep 10; done -RUN rm /docker-entrypoint-initdb.d/* +#RUN /entrypoint.sh mysqld & while [ ! -f /tmp/finished ]; do sleep 10; done +#RUN rm /docker-entrypoint-initdb.d/* diff --git a/shipping/.gitignore b/shipping/.gitignore new file mode 100644 index 0000000..7075a2f --- /dev/null +++ b/shipping/.gitignore @@ -0,0 +1,4 @@ +/target +/.classpath +/.project +/.settings diff --git a/shipping/Dockerfile b/shipping/Dockerfile index 720e938..afd9d32 100644 --- a/shipping/Dockerfile +++ b/shipping/Dockerfile @@ -1,15 +1,14 @@ # # 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 - +RUN mvn dependency:resolve COPY src /opt/shipping/src/ 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/pom.xml b/shipping/pom.xml index e185c4a..008e7c4 100644 --- a/shipping/pom.xml +++ b/shipping/pom.xml @@ -1,78 +1,72 @@ - - 4.0.0 - steveww - shipping - 1.0 - jar - Spark Java Sample + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.3.RELEASE + + + com.instana + shipping + 1.0 + shipping service + Shipping calculations - - 1.8 - 1.8 - + + 1.8 + - + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + - com.sparkjava - spark-core - 2.7.2 + org.springframework.retry + spring-retry - org.slf4j - slf4j-simple - 1.7.25 - - - c3p0 - c3p0 - 0.9.1.2 - - - mysql - mysql-connector-java - 8.0.19 - - - commons-dbutils - commons-dbutils - 1.7 - - - com.google.code.gson - gson - 2.8.2 + org.springframework.boot + spring-boot-starter-actuator + + + mysql + mysql-connector-java + runtime + org.apache.httpcomponents httpclient - 4.5.5 + 4.5.12 - + + + 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/org/steveww/spark/Location.java b/shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java similarity index 69% rename from shipping/src/main/java/org/steveww/spark/Location.java rename to shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java index 643a4e6..85c591c 100644 --- a/shipping/src/main/java/org/steveww/spark/Location.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Calculator.java @@ -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.longitude = longitude; } - public double getLatitude() { - return this.latitude; + Calculator(City city) { + this.latitude = city.getLatitude(); + this.longitude = city.getLongitude(); } - public double getLongitude() { - return this.longitude; - } - - /** * Calculate the distance between this location and the target location. * Use decimal lat/long degrees @@ -34,10 +31,13 @@ public class Location { 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 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 new file mode 100644 index 0000000..d1fb033 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/City.java @@ -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); + } +} 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..1602e1a --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/CityRepository.java @@ -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 { + 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 new file mode 100644 index 0000000..03f020a --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Controller.java @@ -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 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); + + 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/JpaConfig.java b/shipping/src/main/java/com/instana/robotshop/shipping/JpaConfig.java new file mode 100644 index 0000000..3cd995a --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/JpaConfig.java @@ -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(); + } +} 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/org/steveww/spark/Ship.java b/shipping/src/main/java/com/instana/robotshop/shipping/Ship.java similarity index 72% rename from shipping/src/main/java/org/steveww/spark/Ship.java rename to shipping/src/main/java/com/instana/robotshop/shipping/Ship.java index 665f925..3f4b003 100644 --- a/shipping/src/main/java/org/steveww/spark/Ship.java +++ b/shipping/src/main/java/com/instana/robotshop/shipping/Ship.java @@ -1,4 +1,4 @@ -package org.steveww.spark; +package com.instana.robotshop.shipping; /** * Bean to hold shipping information @@ -12,7 +12,7 @@ public class Ship { this.cost = 0.0; } - public Ship(long distnace, double cost) { + public Ship(long distance, double cost) { this.distance = distance; this.cost = cost; } @@ -32,5 +32,10 @@ public class Ship { 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 new file mode 100644 index 0000000..0dcfc93 --- /dev/null +++ b/shipping/src/main/java/com/instana/robotshop/shipping/ShippingServiceApplication.java @@ -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; + } + } +} 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/resources/application.properties b/shipping/src/main/resources/application.properties new file mode 100644 index 0000000..0749323 --- /dev/null +++ b/shipping/src/main/resources/application.properties @@ -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