Skip to content

Commit 654c13a

Browse files
Merge pull request #3 from companieshouse/PCI-571-controller-method-and-dtos
Pci 318 add item to basket
2 parents 66678b9 + 2f37b65 commit 654c13a

27 files changed

+1100
-7
lines changed

pom.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@
1414

1515
<properties>
1616
<java.version>1.8</java.version>
17+
<structured-logging.version>1.9.0</structured-logging.version>
18+
<commons.lang.version>2.6</commons.lang.version>
19+
<commons.beanutils.version>1.9.4</commons.beanutils.version>
1720
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
21+
<gson.version>2.8.0</gson.version>
1822
<jacoco-maven-plugin.version>0.7.7.201606060606</jacoco-maven-plugin.version>
1923
<junit-jupiter-engine-version>5.2.0</junit-jupiter-engine-version>
2024
<mockito-junit-jupiter-version>2.18.0</mockito-junit-jupiter-version>
@@ -23,6 +27,10 @@
2327
</properties>
2428

2529
<dependencies>
30+
<dependency>
31+
<groupId>org.springframework.boot</groupId>
32+
<artifactId>spring-boot-starter-data-mongodb</artifactId>
33+
</dependency>
2634
<dependency>
2735
<groupId>org.springframework.boot</groupId>
2836
<artifactId>spring-boot-starter-data-rest</artifactId>
@@ -45,6 +53,23 @@
4553
<version>${junit-jupiter-engine-version}</version>
4654
<scope>test</scope>
4755
</dependency>
56+
<dependency>
57+
<groupId>uk.gov.companieshouse</groupId>
58+
<artifactId>structured-logging</artifactId>
59+
<version>${structured-logging.version}</version>
60+
<scope>compile</scope>
61+
</dependency>
62+
<dependency>
63+
<groupId>commons-lang</groupId>
64+
<artifactId>commons-lang</artifactId>
65+
<version>${commons.lang.version}</version>
66+
</dependency>
67+
<dependency>
68+
<groupId>commons-beanutils</groupId>
69+
<artifactId>commons-beanutils</artifactId>
70+
<version>${commons.beanutils.version}</version>
71+
</dependency>
72+
4873
<dependency>
4974
<groupId>org.mockito</groupId>
5075
<artifactId>mockito-junit-jupiter</artifactId>
@@ -57,6 +82,21 @@
5782
<version>${hamcrest-all-version}</version>
5883
<scope>test</scope>
5984
</dependency>
85+
<dependency>
86+
<groupId>org.mapstruct</groupId>
87+
<artifactId>mapstruct</artifactId>
88+
<version>${org.mapstruct.version}</version>
89+
</dependency>
90+
<dependency>
91+
<groupId>com.google.code.gson</groupId>
92+
<artifactId>gson</artifactId>
93+
<version>${gson.version}</version>
94+
</dependency>
95+
<dependency>
96+
<groupId>de.flapdoodle.embed</groupId>
97+
<artifactId>de.flapdoodle.embed.mongo</artifactId>
98+
<scope>test</scope>
99+
</dependency>
60100
</dependencies>
61101

62102
<build>

routes.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ app_name: orders.api.ch.gov.uk
22
group: api
33
weight: 991
44
routes:
5-
1: ^/orders/healthcheck
5+
1: ^/basket/items
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package uk.gov.companieshouse.orders.api.config;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.databind.DeserializationFeature;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
7+
import com.fasterxml.jackson.databind.SerializationFeature;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
11+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
12+
import uk.gov.companieshouse.orders.api.interceptor.LoggingInterceptor;
13+
14+
@Configuration
15+
public class ApplicationConfig implements WebMvcConfigurer {
16+
17+
@Override
18+
public void addInterceptors(final InterceptorRegistry registry) {
19+
registry.addInterceptor(new LoggingInterceptor());
20+
}
21+
22+
@Bean
23+
public ObjectMapper objectMapper() {
24+
return new ObjectMapper()
25+
.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL)
26+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
27+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
28+
.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
29+
.findAndRegisterModules();
30+
}
31+
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package uk.gov.companieshouse.orders.api.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.data.mongodb.MongoDbFactory;
6+
import org.springframework.data.mongodb.core.convert.DbRefResolver;
7+
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
8+
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
9+
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
10+
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
11+
12+
@Configuration
13+
public class MongoConfig {
14+
/**
15+
* _class maps to the model class in mongoDB (e.g. _class : uk.gov.companieshouse.items.orders.api.model.CertificateItem)
16+
* when using spring data mongo it by default adds a _class key to your collection to be able to
17+
* handle inheritance. But if your domain model is simple and flat, you can remove it by overriding
18+
* the default MappingMongoConverter.
19+
* Copied from items.orders.api.ch.gov.uk
20+
*/
21+
22+
@Bean
23+
public MappingMongoConverter mappingMongoConverter(final MongoDbFactory factory,
24+
final MongoMappingContext context) {
25+
final DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
26+
final MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);
27+
28+
// Don't save _class to mongo
29+
mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));
30+
31+
return mappingConverter;
32+
}
33+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package uk.gov.companieshouse.orders.api.controller;
2+
3+
import org.springframework.http.HttpStatus;
4+
import org.springframework.http.ResponseEntity;
5+
import org.springframework.web.bind.annotation.*;
6+
import uk.gov.companieshouse.orders.api.dto.AddToBasketRequestDTO;
7+
import uk.gov.companieshouse.orders.api.dto.AddToBasketResponseDTO;
8+
import uk.gov.companieshouse.orders.api.mapper.BasketMapper;
9+
import uk.gov.companieshouse.orders.api.model.Basket;
10+
11+
import uk.gov.companieshouse.logging.Logger;
12+
import uk.gov.companieshouse.logging.LoggerFactory;
13+
import uk.gov.companieshouse.orders.api.service.BasketService;
14+
import uk.gov.companieshouse.orders.api.util.EricHeaderHelper;
15+
16+
import javax.servlet.http.HttpServletRequest;
17+
import javax.validation.Valid;
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
import java.util.Optional;
21+
22+
import static uk.gov.companieshouse.orders.api.OrdersApiApplication.APPLICATION_NAMESPACE;
23+
24+
@RestController
25+
public class BasketController {
26+
private static final Logger LOGGER = LoggerFactory.getLogger(APPLICATION_NAMESPACE);
27+
28+
private static final String REQUEST_ID_HEADER_NAME = "X-Request-ID";
29+
private static final String LOG_MESSAGE_DATA_KEY = "message";
30+
31+
private final BasketMapper mapper;
32+
private final BasketService basketService;
33+
34+
public BasketController(final BasketMapper mapper, final BasketService basketService){
35+
this.mapper = mapper;
36+
this.basketService = basketService;
37+
}
38+
39+
@PostMapping("${uk.gov.companieshouse.orders.api.basket.items}")
40+
public ResponseEntity<AddToBasketResponseDTO> addItemToBasket(final @Valid @RequestBody AddToBasketRequestDTO addToBasketRequestDTO,
41+
HttpServletRequest request,
42+
final @RequestHeader(REQUEST_ID_HEADER_NAME) String requestId){
43+
trace("ENTERING addItemToBasket(" + addToBasketRequestDTO + ")", requestId);
44+
45+
final Optional<Basket> retrievedBasket = basketService.getBasketById(EricHeaderHelper.getIdentity(request));
46+
47+
Basket mappedBasket = mapper.addToBasketRequestDTOToBasket(addToBasketRequestDTO);
48+
49+
Basket returnedBasket;
50+
if(retrievedBasket.isPresent()) {
51+
retrievedBasket.get().getData().setItems(mappedBasket.getData().getItems());
52+
returnedBasket = basketService.saveBasket(retrievedBasket.get());
53+
} else {
54+
mappedBasket.setId(EricHeaderHelper.getIdentity(request));
55+
returnedBasket = basketService.saveBasket(mappedBasket);
56+
}
57+
58+
final AddToBasketResponseDTO addToBasketResponseDTO = mapper.basketToAddToBasketDTO(returnedBasket);
59+
trace("EXITING addItemToBasket() with " + addToBasketRequestDTO, requestId);
60+
return ResponseEntity.status(HttpStatus.OK).body(addToBasketResponseDTO);
61+
}
62+
63+
/**
64+
* Utility method that logs each message with the request ID for log tracing/analysis.
65+
* @param message the message to log
66+
* @param requestId the request ID
67+
*/
68+
private void trace(final String message, final String requestId) {
69+
final Map<String, Object> logData = new HashMap<>();
70+
logData.put(LOG_MESSAGE_DATA_KEY, message);
71+
LOGGER.traceContext(requestId, "X Request ID header", logData);
72+
}
73+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package uk.gov.companieshouse.orders.api.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.google.gson.Gson;
6+
7+
import javax.validation.constraints.NotBlank;
8+
import javax.validation.constraints.NotNull;
9+
10+
public class AddToBasketRequestDTO {
11+
12+
@NotBlank
13+
@JsonProperty("item_uri")
14+
private String itemUri;
15+
16+
public String getItemUri() {
17+
return itemUri;
18+
}
19+
20+
public void setItemUri(String itemUri) {
21+
this.itemUri = itemUri;
22+
}
23+
24+
@Override
25+
public String toString() { return new Gson().toJson(this); }
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package uk.gov.companieshouse.orders.api.dto;
2+
3+
import com.google.gson.Gson;
4+
5+
public class AddToBasketResponseDTO {
6+
7+
private String itemUri;
8+
9+
public String getItemUri() {
10+
return itemUri;
11+
}
12+
13+
public void setItemUri(String itemUri) {
14+
this.itemUri = itemUri;
15+
}
16+
17+
@Override
18+
public String toString() { return new Gson().toJson(this); }
19+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package uk.gov.companieshouse.orders.api.interceptor;
2+
3+
import org.springframework.lang.Nullable;
4+
import org.springframework.web.servlet.ModelAndView;
5+
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
6+
import uk.gov.companieshouse.logging.Logger;
7+
import uk.gov.companieshouse.logging.LoggerFactory;
8+
import uk.gov.companieshouse.logging.util.RequestLogger;
9+
10+
import javax.servlet.http.HttpServletRequest;
11+
import javax.servlet.http.HttpServletResponse;
12+
13+
import static uk.gov.companieshouse.orders.api.OrdersApiApplication.APPLICATION_NAMESPACE;
14+
15+
public class LoggingInterceptor extends HandlerInterceptorAdapter implements RequestLogger {
16+
17+
private static final Logger LOGGER = LoggerFactory.getLogger(APPLICATION_NAMESPACE);
18+
19+
@Override
20+
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
21+
22+
logStartRequestProcessing(request, LOGGER);
23+
return true;
24+
}
25+
26+
@Override
27+
public void postHandle(HttpServletRequest request,
28+
HttpServletResponse response,
29+
Object handler,
30+
@Nullable ModelAndView modelAndView) {
31+
32+
logEndRequestProcessing(request, response, LOGGER);
33+
}
34+
35+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package uk.gov.companieshouse.orders.api.mapper;
2+
3+
import org.mapstruct.AfterMapping;
4+
import org.mapstruct.Mapper;
5+
import org.mapstruct.Mapping;
6+
import org.mapstruct.MappingTarget;
7+
import uk.gov.companieshouse.orders.api.dto.AddToBasketRequestDTO;
8+
import uk.gov.companieshouse.orders.api.dto.AddToBasketResponseDTO;
9+
import uk.gov.companieshouse.orders.api.model.Basket;
10+
import uk.gov.companieshouse.orders.api.model.Item;
11+
12+
import java.util.Arrays;
13+
14+
@Mapper(componentModel = "spring")
15+
public interface BasketMapper {
16+
Basket addToBasketRequestDTOToBasket(AddToBasketRequestDTO addToBasketRequestDTO);
17+
18+
@Mapping(target = "itemUri", expression = "java(basket.getData().getItems().get(0).getItemUri())")
19+
AddToBasketResponseDTO basketToAddToBasketDTO(Basket basket);
20+
21+
@AfterMapping
22+
default void fillBasket(AddToBasketRequestDTO addToBasketRequestDTO, @MappingTarget Basket basket) {
23+
Item item = new Item();
24+
item.setItemUri(addToBasketRequestDTO.getItemUri());
25+
basket.getData().setItems(Arrays.asList(item));
26+
}
27+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package uk.gov.companieshouse.orders.api.model;
2+
3+
import com.google.gson.Gson;
4+
import org.springframework.data.annotation.Id;
5+
import org.springframework.data.mongodb.core.mapping.Document;
6+
7+
import java.time.LocalDateTime;
8+
import java.util.List;
9+
10+
@Document(collection = "basket")
11+
public class Basket {
12+
@Id
13+
private String id;
14+
15+
private LocalDateTime createdAt;
16+
17+
private LocalDateTime updatedAt;
18+
19+
private BasketData data = new BasketData();
20+
21+
22+
public String getId() {
23+
return id;
24+
}
25+
26+
public void setId(String id) {
27+
this.id = id;
28+
}
29+
30+
public LocalDateTime getCreatedAt() {
31+
return createdAt;
32+
}
33+
34+
public void setCreatedAt(LocalDateTime createdAt) {
35+
this.createdAt = createdAt;
36+
}
37+
38+
public LocalDateTime getUpdatedAt() {
39+
return updatedAt;
40+
}
41+
42+
public void setUpdatedAt(LocalDateTime updatedAt) {
43+
this.updatedAt = updatedAt;
44+
}
45+
46+
public BasketData getData() {
47+
return data;
48+
}
49+
50+
public void setData(BasketData data) {
51+
this.data = data;
52+
}
53+
54+
public void setItems(List<Item> items) {
55+
data.setItems(items);
56+
}
57+
58+
public List<Item> getItems() {
59+
return data.getItems();
60+
}
61+
62+
@Override
63+
public String toString() { return new Gson().toJson(this); }
64+
65+
}

0 commit comments

Comments
 (0)