Commit 73d1aaa6 authored by Ashok Kumar K's avatar Ashok Kumar K

Added customer-address validator, modified save and update customer

parent 155d35e2
...@@ -2,14 +2,12 @@ package com.nisum.ecomcustomer; ...@@ -2,14 +2,12 @@ package com.nisum.ecomcustomer;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@EnableMongoRepositories
@SpringBootApplication @SpringBootApplication
public class EcomCustomerApplication { public class EcomCustomerApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(EcomCustomerApplication.class, args); SpringApplication.run(EcomCustomerApplication.class, args);
} }
} }
...@@ -8,6 +8,9 @@ import org.springframework.data.convert.WritingConverter; ...@@ -8,6 +8,9 @@ import org.springframework.data.convert.WritingConverter;
public class CustomMongoConverters { public class CustomMongoConverters {
private CustomMongoConverters() {
}
@WritingConverter @WritingConverter
static class CustomerTypeToIntegerConverter implements Converter<CustomerType, Integer> { static class CustomerTypeToIntegerConverter implements Converter<CustomerType, Integer> {
......
...@@ -3,14 +3,16 @@ package com.nisum.ecomcustomer.config.mongodb; ...@@ -3,14 +3,16 @@ package com.nisum.ecomcustomer.config.mongodb;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration; import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import static com.nisum.ecomcustomer.config.mongodb.CustomMongoConverters.*; import static com.nisum.ecomcustomer.config.mongodb.CustomMongoConverters.*;
import static com.nisum.ecomcustomer.config.mongodb.CustomMongoConverters.CustomerTypeToIntegerConverter;
import static com.nisum.ecomcustomer.config.mongodb.CustomMongoConverters.IntegerToCustomerTypeConverter;
import static org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter; import static org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
@EnableMongoRepositories("com.nisum.ecomcustomer.repository")
@Configuration @Configuration
@EnableMongoAuditing
public class MongoConfig extends AbstractMongoClientConfiguration { public class MongoConfig extends AbstractMongoClientConfiguration {
@Value("${spring.data.mongodb.database}") @Value("${spring.data.mongodb.database}")
......
...@@ -8,6 +8,8 @@ import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventLis ...@@ -8,6 +8,8 @@ import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventLis
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDate;
@Component @Component
public class CustomerDbOpsEventListener extends AbstractMongoEventListener<Customer> { public class CustomerDbOpsEventListener extends AbstractMongoEventListener<Customer> {
...@@ -21,6 +23,7 @@ public class CustomerDbOpsEventListener extends AbstractMongoEventListener<Custo ...@@ -21,6 +23,7 @@ public class CustomerDbOpsEventListener extends AbstractMongoEventListener<Custo
if (source.getId() == null) { if (source.getId() == null) {
DbSequence dbSequence = dbSequenceRepository.findBySequenceName(Customer.SEQUENCE_NAME).orElse(new DbSequence(Customer.SEQUENCE_NAME)); DbSequence dbSequence = dbSequenceRepository.findBySequenceName(Customer.SEQUENCE_NAME).orElse(new DbSequence(Customer.SEQUENCE_NAME));
source.setId(dbSequence.incrementLastId()); source.setId(dbSequence.incrementLastId());
source.setCreatedDate(LocalDate.now());
dbSequenceRepository.save(dbSequence); dbSequenceRepository.save(dbSequence);
} }
} }
......
...@@ -27,10 +27,15 @@ public class CustomerController { ...@@ -27,10 +27,15 @@ public class CustomerController {
} }
@PostMapping("/registerAll") @PostMapping("/registerAll")
public ResponseEntity<List<Customer>> registerCustomers(@RequestBody @NotNull List<Customer> customer) { public ResponseEntity<List<Customer>> registerCustomers(@RequestBody @NotNull @Valid List<Customer> customer) {
return new ResponseEntity<>(customerService.registerAll(customer), HttpStatus.CREATED); return new ResponseEntity<>(customerService.registerAll(customer), HttpStatus.CREATED);
} }
@PutMapping("/update")
public ResponseEntity<Customer> updateCustomer(@RequestBody @NotNull(message = "body can't be empty") @Valid Customer customer) {
return new ResponseEntity<>(customerService.update(customer), HttpStatus.CREATED);
}
@GetMapping("/") @GetMapping("/")
public ResponseEntity<List<Customer>> getAllCustomers() { public ResponseEntity<List<Customer>> getAllCustomers() {
return ResponseEntity.ok(customerService.getAllCustomers()); return ResponseEntity.ok(customerService.getAllCustomers());
...@@ -41,7 +46,9 @@ public class CustomerController { ...@@ -41,7 +46,9 @@ public class CustomerController {
return ResponseEntity.ok(customerService.getCustomerById(id)); return ResponseEntity.ok(customerService.getCustomerById(id));
} }
@GetMapping("/email/{email}") //TODO - make email field unique
// @GetMapping("/email/{email}")
public ResponseEntity<Customer> getCustomerByEmail(@PathVariable @Email String email) { public ResponseEntity<Customer> getCustomerByEmail(@PathVariable @Email String email) {
return ResponseEntity.ok(customerService.getCustomerByEmail(email)); return ResponseEntity.ok(customerService.getCustomerByEmail(email));
} }
...@@ -57,7 +64,8 @@ public class CustomerController { ...@@ -57,7 +64,8 @@ public class CustomerController {
} }
@DeleteMapping("/id/{id}") @DeleteMapping("/id/{id}")
public void deleteById(@PathVariable Long id) { public ResponseEntity<Void> deleteById(@PathVariable Long id) {
customerService.deleteById(id); customerService.deleteById(id);
return ResponseEntity.noContent().build();
} }
} }
...@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice; ...@@ -10,6 +10,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
...@@ -26,8 +27,8 @@ public class CustomerServiceExceptionHandler { ...@@ -26,8 +27,8 @@ public class CustomerServiceExceptionHandler {
} }
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpServletRequest httpServletRequest) { public ResponseEntity<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest httpServletRequest) {
ErrorResponse errorResponse = buildErrorResponse("Invalid payload/request-params/path-params", HttpStatus.BAD_REQUEST, httpServletRequest); ErrorResponse errorResponse = buildErrorResponse("Invalid payload or params", HttpStatus.BAD_REQUEST, httpServletRequest);
List<SubError> subErrors = ex.getBindingResult().getFieldErrors() List<SubError> subErrors = ex.getBindingResult().getFieldErrors()
.stream() .stream()
.map(fieldError -> new ValidationError(fieldError.getObjectName(), fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage())) .map(fieldError -> new ValidationError(fieldError.getObjectName(), fieldError.getField(), fieldError.getRejectedValue(), fieldError.getDefaultMessage()))
...@@ -36,7 +37,16 @@ public class CustomerServiceExceptionHandler { ...@@ -36,7 +37,16 @@ public class CustomerServiceExceptionHandler {
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
} }
// TODO - build default error object @ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException ex, HttpServletRequest httpServletRequest) {
ErrorResponse errorResponse = buildErrorResponse("Invalid payload or params", HttpStatus.BAD_REQUEST, httpServletRequest);
List<SubError> subErrors = ex.getConstraintViolations().stream()
.map(violation -> new ValidationError(violation.getPropertyPath().toString(), violation.getPropertyPath().toString(), violation.getInvalidValue(), violation.getMessage()))
.collect(Collectors.toList());
errorResponse.setSubErrors(subErrors);
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class) @ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest httpServletRequest) { public ResponseEntity<ErrorResponse> handleException(Exception ex, HttpServletRequest httpServletRequest) {
return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest); return buildErrorResponseEntity(ex.getLocalizedMessage(), HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest);
......
package com.nisum.ecomcustomer.exceptions; package com.nisum.ecomcustomer.exceptions;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
......
package com.nisum.ecomcustomer.exceptions; package com.nisum.ecomcustomer.exceptions;
public class SubError { public interface SubError {
} }
...@@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode; ...@@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode;
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
@Data @Data
public class ValidationError extends SubError{ public class ValidationError implements SubError{
private String object; private String object;
private String field; private String field;
private Object rejectedValue; private Object rejectedValue;
......
...@@ -9,8 +9,6 @@ import javax.validation.constraints.NotBlank; ...@@ -9,8 +9,6 @@ import javax.validation.constraints.NotBlank;
@Data @Data
public class Address { public class Address {
// @Field("id")
// private Long addressId;
@Field("type") @Field("type")
private AddressType addressType; private AddressType addressType;
@Field("line1") @Field("line1")
......
package com.nisum.ecomcustomer.model; package com.nisum.ecomcustomer.model;
import com.nisum.ecomcustomer.validators.CustomerAddressesConstraint;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.*;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.Field;
...@@ -12,6 +11,8 @@ import org.springframework.data.mongodb.core.mapping.Field; ...@@ -12,6 +11,8 @@ import org.springframework.data.mongodb.core.mapping.Field;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.Email; import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@NoArgsConstructor @NoArgsConstructor
...@@ -30,22 +31,19 @@ public class Customer { ...@@ -30,22 +31,19 @@ public class Customer {
private String firstName; private String firstName;
@Field("lName") @Field("lName")
private String lastName; private String lastName;
@Indexed(unique = true)
@NotBlank(message = "email is mandatory") @NotBlank(message = "email is mandatory")
@Email @Email
@Indexed(unique = true, name = "email_index")
private String email; private String email;
@NotBlank(message = "dayPhone can't be blank") @NotBlank(message = "dayPhone can't be blank")
private String dayPhone; private String dayPhone;
@Field("type") @Field("type")
private CustomerType customerType; private CustomerType customerType;
@CustomerAddressesConstraint
@Valid @Valid
private List<Address> addresses; private List<Address> addresses;
@CreatedDate
// TODO - add audit fields private LocalDate createdDate;
@LastModifiedDate
// @CreatedDate private LocalDateTime modifiedDate;
// private LocalDateTime createdDate;
//
// @LastModifiedDate
// private LocalDateTime modifiedDate;
} }
...@@ -11,6 +11,8 @@ public interface CustomerService { ...@@ -11,6 +11,8 @@ public interface CustomerService {
List<Customer> registerAll(List<Customer> customers); List<Customer> registerAll(List<Customer> customers);
Customer update(Customer customer);
List<Customer> getAllCustomers(); List<Customer> getAllCustomers();
Customer getCustomerById(Long id); Customer getCustomerById(Long id);
......
...@@ -5,6 +5,7 @@ import com.nisum.ecomcustomer.model.Customer; ...@@ -5,6 +5,7 @@ import com.nisum.ecomcustomer.model.Customer;
import com.nisum.ecomcustomer.repository.CustomerRepository; import com.nisum.ecomcustomer.repository.CustomerRepository;
import com.nisum.ecomcustomer.service.CustomerService; import com.nisum.ecomcustomer.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
...@@ -15,8 +16,12 @@ public class CustomerServiceImpl implements CustomerService { ...@@ -15,8 +16,12 @@ public class CustomerServiceImpl implements CustomerService {
@Autowired @Autowired
CustomerRepository customerRepository; CustomerRepository customerRepository;
@Autowired
MongoTemplate mongoTemplate;
@Override @Override
public Customer register(Customer customer) { public Customer register(Customer customer) {
customer.setId(null); // auto-generated
return customerRepository.save(customer); return customerRepository.save(customer);
} }
...@@ -25,6 +30,18 @@ public class CustomerServiceImpl implements CustomerService { ...@@ -25,6 +30,18 @@ public class CustomerServiceImpl implements CustomerService {
return customerRepository.saveAll(customers); return customerRepository.saveAll(customers);
} }
@Override
public Customer update(Customer customer) {
Customer existingCustomer;
if (customer.getId() != null) {
existingCustomer = customerRepository.findById(customer.getId()).orElseThrow(CustomerNotFoundException::new);
//createdDate is prone to be changed by client
customer.setCreatedDate(existingCustomer.getCreatedDate());
} else
throw new CustomerNotFoundException();
return customerRepository.save(customer);
}
@Override @Override
public List<Customer> getAllCustomers() { public List<Customer> getAllCustomers() {
return customerRepository.findAll(); return customerRepository.findAll();
......
package com.nisum.ecomcustomer.validators;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {CustomerAddressesValidator.class})
@Documented
public @interface CustomerAddressesConstraint {
String message() default "Must contain only one shipping address and one billing address";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.nisum.ecomcustomer.validators;
import com.nisum.ecomcustomer.model.Address;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.List;
public class CustomerAddressesValidator implements ConstraintValidator<CustomerAddressesConstraint, List<Address>> {
private ConstraintValidatorContext validatorContext;
@Override
public boolean isValid(List<Address> addresses, ConstraintValidatorContext context) {
validatorContext = context;
return hasValidSize(addresses) && hasAtLeastAndOnlyOneBillingAddress(addresses) && hasAtLeastOneAndOnlyOneDefaultShippingAddress(addresses);
}
private boolean hasValidSize(List<Address> addresses) {
boolean hasValidSize = addresses.size() <= 5 && addresses.size() > 1;
if (!hasValidSize) {
addValidationMessage("Must contain only one billing address and at least one shipping address. At most 5 addresses are allowed");
}
return hasValidSize;
}
private boolean hasAtLeastAndOnlyOneBillingAddress(List<Address> addresses) {
boolean hasAtLeastAndOnlyOneBillingAddress = addresses.stream().filter(address -> address.getAddressType().getValue().equals("bill")).count() == 1;
if (!hasAtLeastAndOnlyOneBillingAddress) {
addValidationMessage("Must contain only one billing address and at least one shipping address.");
}
return hasAtLeastAndOnlyOneBillingAddress;
}
private boolean hasAtLeastOneAndOnlyOneDefaultShippingAddress(List<Address> addresses) {
boolean hasAtLeastOneShippingAddress = addresses.stream().anyMatch(address -> address.getAddressType().getValue().equals("ship"));
boolean hasOnlyOneDefaultShippingAddress = false;
if (hasAtLeastOneShippingAddress) {
hasOnlyOneDefaultShippingAddress = addresses.stream().filter(address -> address.getAddressType().getValue().equals("ship") && address.isDefault()).count() == 1;
if (!hasOnlyOneDefaultShippingAddress) {
addValidationMessage("there must be only one default shipping address");
}
} else {
addValidationMessage("Must contain only one billing address and at least one shipping address.");
}
return hasAtLeastOneShippingAddress && hasOnlyOneDefaultShippingAddress;
}
private void addValidationMessage(String message) {
validatorContext.disableDefaultConstraintViolation(); // disable default violation message
validatorContext.buildConstraintViolationWithTemplate(message).addConstraintViolation();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment