initial commit

This commit is contained in:
shazforiot 2022-06-14 21:53:05 +05:30
parent 119344f0e2
commit ea807acdba
41 changed files with 1296 additions and 0 deletions

View File

@ -0,0 +1,42 @@
---
apiVersion: v1
kind: Service
metadata:
name: productcatalogue
labels:
app: productcatalogue
spec:
type: NodePort
selector:
app: productcatalogue
ports:
- protocol: TCP
port: 8020
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: productcatalogue
spec:
selector:
matchLabels:
app: productcatalogue
replicas: 1
template:
metadata:
labels:
app: productcatalogue
spec:
containers:
- name: productcatalogue
image: danielbryantuk/djproductcatalogue:latest
ports:
- containerPort: 8020
livenessProbe:
httpGet:
path: /healthcheck
port: 8025
initialDelaySeconds: 30
timeoutSeconds: 1

View File

@ -0,0 +1,42 @@
---
apiVersion: v1
kind: Service
metadata:
name: shopfront
labels:
app: shopfront
spec:
type: NodePort
selector:
app: shopfront
ports:
- protocol: TCP
port: 8010
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: shopfront
spec:
selector:
matchLabels:
app: shopfront
replicas: 1
template:
metadata:
labels:
app: shopfront
spec:
containers:
- name: djshopfront
image: danielbryantuk/djshopfront:latest
ports:
- containerPort: 8010
livenessProbe:
httpGet:
path: /health
port: 8010
initialDelaySeconds: 30
timeoutSeconds: 1

View File

@ -0,0 +1,43 @@
---
apiVersion: v1
kind: Service
metadata:
name: stockmanager
labels:
app: stockmanager
spec:
type: NodePort
selector:
app: stockmanager
ports:
- protocol: TCP
port: 8030
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: stockmanager
spec:
spec:
selector:
matchLabels:
app: stockmanager
replicas: 1
template:
metadata:
labels:
app: stockmanager
spec:
containers:
- name: stockmanager
image: danielbryantuk/djstockmanager:latest
ports:
- containerPort: 8030
livenessProbe:
httpGet:
path: /health
port: 8030
initialDelaySeconds: 30
timeoutSeconds: 1

View File

@ -0,0 +1,5 @@
FROM openjdk:8-jre
ADD target/productcatalogue-0.0.1-SNAPSHOT.jar app.jar
ADD product-catalogue.yml app-config.yml
EXPOSE 8020
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar", "server", "app-config.yml"]

View File

@ -0,0 +1,9 @@
product-catalogue
=================
java -jar target/product-1.0-SNAPSHOT.jar server product-catalogue.yml
docker build -t danielbryantuk/product .
docker run -p 9010:9010 -p 9011:9011 -d danielbryantuk/product
Update

79
productcatalogue/pom.xml Normal file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.co.danielbryant.djshopping</groupId>
<artifactId>productcatalogue</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dropwizard.version>1.3.27</dropwizard.version>
<guice.version>4.2.3</guice.version>
</properties>
<dependencies>
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-core</artifactId>
<version>${dropwizard.version}</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.6</version>
<configuration>
<createDependencyReducedPom>true</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>uk.co.danielbryant.djshopping.productcatalogue.ProductServiceApplication</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,10 @@
version: 1.0-SNAPSHOT
server:
applicationConnectors:
- type: http
port: 8020
adminConnectors:
- type: http
port: 8025

View File

@ -0,0 +1,36 @@
package uk.co.danielbryant.djshopping.productcatalogue;
import com.google.inject.Guice;
import com.google.inject.Injector;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import uk.co.danielbryant.djshopping.productcatalogue.healthchecks.BasicHealthCheck;
import uk.co.danielbryant.djshopping.productcatalogue.configuration.ProductServiceConfiguration;
import uk.co.danielbryant.djshopping.productcatalogue.resources.ProductResource;
public class ProductServiceApplication extends Application<ProductServiceConfiguration> {
public static void main(String[] args) throws Exception {
new ProductServiceApplication().run(args);
}
@Override
public String getName() {
return "product-list-service";
}
@Override
public void initialize(Bootstrap<ProductServiceConfiguration> bootstrap) {
// nothing to do yet
}
@Override
public void run(ProductServiceConfiguration config,
Environment environment) {
final BasicHealthCheck healthCheck = new BasicHealthCheck(config.getVersion());
environment.healthChecks().register("template", healthCheck);
Injector injector = Guice.createInjector();
environment.jersey().register(injector.getInstance(ProductResource.class));
}
}

View File

@ -0,0 +1,21 @@
package uk.co.danielbryant.djshopping.productcatalogue.configuration;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import org.hibernate.validator.constraints.NotEmpty;
public class ProductServiceConfiguration extends Configuration {
@NotEmpty
private String version;
@JsonProperty
public String getVersion() {
return version;
}
@JsonProperty
public void setVersion(String version) {
this.version = version;
}
}

View File

@ -0,0 +1,17 @@
package uk.co.danielbryant.djshopping.productcatalogue.healthchecks;
import com.codahale.metrics.health.HealthCheck;
public class BasicHealthCheck extends HealthCheck {
private final String version;
public BasicHealthCheck(String version) {
this.version = version;
}
@Override
protected Result check() throws Exception {
return Result.healthy("Ok with version: " + version);
}
}

View File

@ -0,0 +1,43 @@
package uk.co.danielbryant.djshopping.productcatalogue.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.math.BigDecimal;
public class Product {
private String id;
private String name;
private String description;
private BigDecimal price;
public Product() {
// Needed for Jackson deserialization
}
public Product(String id, String name, String description, BigDecimal price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
@JsonProperty
public String getId() {
return id;
}
@JsonProperty
public String getName() {
return name;
}
@JsonProperty
public String getDescription() {
return description;
}
@JsonProperty
public BigDecimal getPrice() {
return price;
}
}

View File

@ -0,0 +1,50 @@
package uk.co.danielbryant.djshopping.productcatalogue.resources;
import com.codahale.metrics.annotation.Timed;
import com.google.inject.Inject;
import uk.co.danielbryant.djshopping.productcatalogue.services.ProductService;
import uk.co.danielbryant.djshopping.productcatalogue.model.Product;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Optional;
@Path("/products")
@Produces(MediaType.APPLICATION_JSON)
public class ProductResource {
private ProductService productService;
@Inject
public ProductResource(ProductService productService) {
this.productService = productService;
}
@GET
@Timed
public Response getAllProducts() {
return Response.status(200)
.entity(productService.getAllProducts())
.build();
}
@GET
@Timed
@Path("{id}")
public Response getProduct(@PathParam("id") String id) {
Optional<Product> result = productService.getProduct(id);
if (result.isPresent()) {
return Response.status(Response.Status.OK)
.entity(result.get())
.build();
} else {
return Response.status(Response.Status.NOT_FOUND)
.build();
}
}
}

View File

@ -0,0 +1,28 @@
package uk.co.danielbryant.djshopping.productcatalogue.services;
import uk.co.danielbryant.djshopping.productcatalogue.model.Product;
import java.math.BigDecimal;
import java.util.*;
public class ProductService {
//{productId, Product}
private Map<String, Product> fakeProductDAO = new HashMap<>();
public ProductService() {
fakeProductDAO.put("1", new Product("1", "Widget", "Premium ACME Widgets", new BigDecimal(1.20)));
fakeProductDAO.put("2", new Product("2", "Sprocket", "Grade B sprockets", new BigDecimal(4.10)));
fakeProductDAO.put("3", new Product("3", "Anvil", "Large Anvils", new BigDecimal(45.50)));
fakeProductDAO.put("4", new Product("4", "Cogs", "Grade Y cogs", new BigDecimal(1.80)));
fakeProductDAO.put("5", new Product("5", "Multitool", "Multitools", new BigDecimal(154.10)));
}
public List<Product> getAllProducts() {
return new ArrayList<>(fakeProductDAO.values());
}
public Optional<Product> getProduct(String id) {
return Optional.ofNullable(fakeProductDAO.get(id));
}
}

4
shopfront/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM openjdk:8-jre
ADD target/shopfront-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8010
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

76
shopfront/pom.xml Normal file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.co.danielbryant.djshopping</groupId>
<artifactId>shopfront</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>shopfront</name>
<description>Docker Java application Shopfront</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.22.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package uk.co.danielbryant.djshopping.shopfront;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableHystrix
public class ShopfrontApplication {
public static void main(String[] args) {
SpringApplication.run(ShopfrontApplication.class, args);
}
@Bean(name = "stdRestTemplate")
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

View File

@ -0,0 +1,20 @@
package uk.co.danielbryant.djshopping.shopfront.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import uk.co.danielbryant.djshopping.shopfront.services.ProductService;
@Controller
public class HomeController {
@Autowired
private ProductService productService;
@RequestMapping("/")
public String index(Model model) {
model.addAttribute("products", productService.getProducts());
return "index";
}
}

View File

@ -0,0 +1,52 @@
package uk.co.danielbryant.djshopping.shopfront.model;
import java.math.BigDecimal;
public class Product {
private String id;
private String sku;
private String name;
private String description;
private BigDecimal price;
private int amountAvailable;
public Product(String id, String name, String description, BigDecimal price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
public Product(String id, String sku, String name, String description, BigDecimal price, int amountAvailable) {
this.id = id;
this.sku = sku;
this.name = name;
this.description = description;
this.price = price;
this.amountAvailable = amountAvailable;
}
public String getId() {
return id;
}
public String getSku() {
return sku;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public BigDecimal getPrice() {
return price;
}
public int getAmountAvailable() {
return amountAvailable;
}
}

View File

@ -0,0 +1,39 @@
package uk.co.danielbryant.djshopping.shopfront.repo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import uk.co.danielbryant.djshopping.shopfront.services.dto.ProductDTO;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
public class ProductRepo {
@Value("${productCatalogueUri}")
private String productCatalogueUri;
@Autowired
@Qualifier(value = "stdRestTemplate")
private RestTemplate restTemplate;
public Map<String, ProductDTO> getProductDTOs() {
ResponseEntity<List<ProductDTO>> productCatalogueResponse =
restTemplate.exchange(productCatalogueUri + "/products",
HttpMethod.GET, null, new ParameterizedTypeReference<List<ProductDTO>>() {
});
List<ProductDTO> productDTOs = productCatalogueResponse.getBody();
return productDTOs.stream()
.collect(Collectors.toMap(ProductDTO::getId, Function.identity()));
}
}

View File

@ -0,0 +1,51 @@
package uk.co.danielbryant.djshopping.shopfront.repo;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import uk.co.danielbryant.djshopping.shopfront.services.dto.StockDTO;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
@Component
public class StockRepo {
private static final Logger LOGGER = LoggerFactory.getLogger(StockRepo.class);
@Value("${stockManagerUri}")
private String stockManagerUri;
@Autowired
@Qualifier(value = "stdRestTemplate")
private RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "stocksNotFound") // Hystrix circuit breaker for fault-tolernace demo
public Map<String, StockDTO> getStockDTOs() {
LOGGER.info("getStocksDTOs");
ResponseEntity<List<StockDTO>> stockManagerResponse =
restTemplate.exchange(stockManagerUri + "/stocks",
HttpMethod.GET, null, new ParameterizedTypeReference<List<StockDTO>>() {
});
List<StockDTO> stockDTOs = stockManagerResponse.getBody();
return stockDTOs.stream()
.collect(Collectors.toMap(StockDTO::getProductId, Function.identity()));
}
public Map<String, StockDTO> stocksNotFound() {
LOGGER.info("stocksNotFound *** FALLBACK ***");
return Collections.EMPTY_MAP;
}
}

View File

@ -0,0 +1,22 @@
package uk.co.danielbryant.djshopping.shopfront.resources;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import uk.co.danielbryant.djshopping.shopfront.model.Product;
import uk.co.danielbryant.djshopping.shopfront.services.ProductService;
import java.util.List;
@RestController
@RequestMapping("/products")
public class ProductResource {
@Autowired
private ProductService productService;
@RequestMapping()
public List<Product> getProducts() {
return productService.getProducts();
}
}

View File

@ -0,0 +1,45 @@
package uk.co.danielbryant.djshopping.shopfront.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.co.danielbryant.djshopping.shopfront.model.Product;
import uk.co.danielbryant.djshopping.shopfront.repo.StockRepo;
import uk.co.danielbryant.djshopping.shopfront.repo.ProductRepo;
import uk.co.danielbryant.djshopping.shopfront.services.dto.ProductDTO;
import uk.co.danielbryant.djshopping.shopfront.services.dto.StockDTO;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class ProductService {
@Autowired
private StockRepo stockRepo;
@Autowired
private ProductRepo productRepo;
public List<Product> getProducts() {
Map<String, ProductDTO> productDTOs = productRepo.getProductDTOs();
Map<String, StockDTO> stockDTOMap = stockRepo.getStockDTOs();
// Merge productDTOs and stockDTOs to a List of Products
return productDTOs.values().stream()
.map(productDTO -> {
StockDTO stockDTO = stockDTOMap.get(productDTO.getId());
if (stockDTO == null) {
stockDTO = StockDTO.DEFAULT_STOCK_DTO;
}
return new Product(productDTO.getId(), stockDTO.getSku(), productDTO.getName(), productDTO.getDescription(), productDTO.getPrice(), stockDTO.getAmountAvailable());
})
.collect(Collectors.toList());
}
public List<Product> productsNotFound() {
return Collections.EMPTY_LIST;
}
}

View File

@ -0,0 +1,36 @@
package uk.co.danielbryant.djshopping.shopfront.services.dto;
import java.math.BigDecimal;
public class ProductDTO {
private String id;
private String name;
private String description;
private BigDecimal price;
public ProductDTO() {
}
public ProductDTO(String id, String name, String description, BigDecimal price) {
this.id = id;
this.name = name;
this.description = description;
this.price = price;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public BigDecimal getPrice() {
return price;
}
}

View File

@ -0,0 +1,30 @@
package uk.co.danielbryant.djshopping.shopfront.services.dto;
public class StockDTO {
private String productId;
private String sku;
private int amountAvailable;
public static final StockDTO DEFAULT_STOCK_DTO = new StockDTO("", "default", 999);
public StockDTO() {
}
public StockDTO(String productId, String sku, int amountAvailable) {
this.productId = productId;
this.sku = sku;
this.amountAvailable = amountAvailable;
}
public String getProductId() {
return productId;
}
public String getSku() {
return sku;
}
public int getAmountAvailable() {
return amountAvailable;
}
}

View File

@ -0,0 +1,3 @@
server.port = 8010
productCatalogueUri = http://productcatalogue:8020
stockManagerUri = http://stockmanager:8030

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Welcome to the Docker Java Shopfront!</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"/>
<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"></script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid" style="background-color: #ff203b">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar"
aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Docker Java Shopfront</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Help</a></li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="active"><a href="#">Overview <span class="sr-only">(current)</span></a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1 class="page-header">Welcome to the Docker Java Shopfront!</h1>
<h2 class="sub-header">Please select a product!</h2>
<div class="table-responsive">
<table class="table table-striped" id="product-table">
<thead>
<tr>
<th>Product Num</th>
<th>SKU</th>
<th>Name</th>
<th>Description</th>
<th>Price £</th>
<th>Qty Available</th>
</tr>
</thead>
<tbody>
<tr th:each="product : ${products}">
<td th:text="${product.id}">1</td>
<td th:text="${product.sku}">12345678</td>
<td th:text="${product.name}">Widget</td>
<td th:text="${product.description}">Widget</td>
<td th:text="${#numbers.formatDecimal(product.price, 0, 'COMMA', 2, 'POINT')}">1.19</td>
<td th:text="${product.amountAvailable}">2</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="js/bootstrap.min.js"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
package uk.co.danielbryant.djshopping.shopfront;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ShopfrontApplication.class)
public class ShopfrontApplicationTests {
@Test
public void contextLoads() {
}
}

4
stockmanager/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM openjdk:8-jre
ADD target/stockmanager-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8030
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

85
stockmanager/pom.xml Normal file
View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.co.danielbryant.djshopping</groupId>
<artifactId>stockmanager</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>stockmanager</name>
<description>Docker Java application stock manager</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<cucumber.version>1.2.6</cucumber.version>
<hamcrest-core.version>2.2</hamcrest-core.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<version>${hamcrest-core.version}</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>${cucumber.version}</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>${cucumber.version}</version>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>${cucumber.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.7</version>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,12 @@
package uk.co.danielbryant.djshopping.stockmanager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StockManagerApplication {
public static void main(String[] args) {
SpringApplication.run(StockManagerApplication.class, args);
}
}

View File

@ -0,0 +1,38 @@
package uk.co.danielbryant.djshopping.stockmanager.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
import javax.annotation.PostConstruct;
@Component
public class DataGenerator {
private static final Logger LOGGER = LoggerFactory.getLogger(DataGenerator.class);
private StockRepository stockRepository;
@Autowired
protected DataGenerator(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
@PostConstruct
@Transactional
public void init() {
LOGGER.info("Generating synthetic data for demonstration purposes...");
stockRepository.save(new Stock("1", "12345678", 5));
stockRepository.save(new Stock("2", "34567890", 2));
stockRepository.save(new Stock("3", "54326745", 999));
stockRepository.save(new Stock("4", "93847614", 0));
stockRepository.save(new Stock("5", "11856388", 1));
LOGGER.info("... data generation complete");
}
}

View File

@ -0,0 +1,15 @@
package uk.co.danielbryant.djshopping.stockmanager.exceptions;
public class StockNotFoundException extends Exception {
public StockNotFoundException() {
}
public StockNotFoundException(String message) {
super(message);
}
public StockNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,34 @@
package uk.co.danielbryant.djshopping.stockmanager.model;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Stock {
@Id
private String productId;
private String sku;
private int amountAvailable;
protected Stock() {
}
public Stock(String productId, String sku, int amountAvailable) {
this.productId = productId;
this.sku = sku;
this.amountAvailable = amountAvailable;
}
public String getProductId() {
return productId;
}
public String getSku() {
return sku;
}
public int getAmountAvailable() {
return amountAvailable;
}
}

View File

@ -0,0 +1,7 @@
package uk.co.danielbryant.djshopping.stockmanager.repositories;
import org.springframework.data.repository.CrudRepository;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
public interface StockRepository extends CrudRepository<Stock, String> {
}

View File

@ -0,0 +1,39 @@
package uk.co.danielbryant.djshopping.stockmanager.resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.services.StockService;
import java.util.List;
@RestController
@RequestMapping("/stocks")
public class StockResource {
private static final Logger LOGGER = LoggerFactory.getLogger(StockResource.class);
@Autowired
private StockService stockService;
@RequestMapping()
public List<Stock> getStocks() {
LOGGER.info("getStocks (All stocks)");
return stockService.getStocks();
}
@RequestMapping("{productId}")
public Stock getStock(@PathVariable("productId") String productId) throws StockNotFoundException {
LOGGER.info("getStock with productId: {}", productId);
return stockService.getStock(productId);
}
@ExceptionHandler
@ResponseStatus(HttpStatus.NOT_FOUND)
public void handleStockNotFound(StockNotFoundException snfe) {
}
}

View File

@ -0,0 +1,33 @@
package uk.co.danielbryant.djshopping.stockmanager.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import uk.co.danielbryant.djshopping.stockmanager.exceptions.StockNotFoundException;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import uk.co.danielbryant.djshopping.stockmanager.repositories.StockRepository;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@Service
public class StockService {
private StockRepository stockRepository;
@Autowired
public StockService(StockRepository stockRepository) {
this.stockRepository = stockRepository;
}
public List<Stock> getStocks() {
return StreamSupport.stream(stockRepository.findAll().spliterator(), false)
.collect(Collectors.toList());
}
public Stock getStock(String productId) throws StockNotFoundException {
return stockRepository.findById(productId)
.orElseThrow(() -> new StockNotFoundException("Stock not found with productId: " + productId));
}
}

View File

@ -0,0 +1 @@
server.port = 8030

View File

@ -0,0 +1,15 @@
package functional;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
@RunWith(Cucumber.class)
@CucumberOptions(monochrome = true,
features = "classpath:features/",
plugin = "html:build/reports/cucumber",
glue = "functional",
strict = true)
public class FunctionalTests {
}

View File

@ -0,0 +1,52 @@
package functional;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import uk.co.danielbryant.djshopping.stockmanager.StockManagerApplication;
import uk.co.danielbryant.djshopping.stockmanager.model.Stock;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Is.is;
@ContextConfiguration
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = StockManagerApplication.class)
public class RestStepDefs {
@Autowired
private TestRestTemplate restTemplate;
private List<Stock> stocks;
@Given("^the application has been initialised with test data$")
public void init() {
//the default profile loads synthetic stocks
}
@When("^the client gets all stocks$")
public void getAllStocks() {
Stock[] stockArray = restTemplate.getForObject("/stocks", Stock[].class);
stocks = Arrays.asList(stockArray);
}
@Then("^a list of (.*) stocks will be returned$")
public void assertListOfStocksLength(int length) {
assertThat(stocks, hasSize(length));
}
@Then("^the stock at index (.*) will have the sku (.*)$")
public void assertStockHasSku(int stockIndex, String sku) {
assertThat(stocks.get(stockIndex).getSku(), is(sku));
}
}

View File

@ -0,0 +1,20 @@
package uk.co.danielbryant.djshopping.stockmanager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = StockManagerApplication.class)
public class ShopfrontApplicationTests {
@Autowired
private TestRestTemplate testRestTemplate;
@Test
public void contextLoads() {
}
}

View File

@ -0,0 +1,11 @@
Feature: Retrieving Stocks
Scenario: Should be able to get a list of all stocks
Given the application has been initialised with test data
When the client gets all stocks
Then a list of 5 stocks will be returned
Scenario: Should be able to get the correct SKU for the first stock
Given the application has been initialised with test data
When the client gets all stocks
Then the stock at index 0 will have the sku 12345678