Using kafka as messaging queue - java

I am trying to use Kafka as a messaging queue in my controller (so task):
#RequestMapping("users")
#RestController
public class UserController extends BaseController {
private static final String KAFKA_TOPIC = "virto_users";
#Autowired
private KafkaTemplate<String, String> mKafkaTemplate;
#PutMapping(value = "{id}")
public ResponseEntity<String> put(#PathVariable UUID id,
#RequestBody String profile) {
String url = ServerConfig.USERS_HOST + "/users/" + id;
ResponseEntity<String> entity = request.put(url, profile);
HttpStatus status = entity.getStatusCode();
if (status == HttpStatus.CREATED || status == HttpStatus.NO_CONTENT) {
return entity;
}
JSONObject json = new JSONObject();
json.put("id", id);
json.put("profile", profile);
sendMessage(json.toString());
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
public void sendMessage(String msg) {
mKafkaTemplate.send(KAFKA_TOPIC, msg);
}
#KafkaListener(topics = KAFKA_TOPIC, groupId = KafkaConfig.GROUP_ID)
public void listen(String message) {
JSONObject json = new JSONObject(message);
UUID id = UUID.fromString(json.getString("id"));
String profile = json.getString("profile");
put(id, profile);
}
}
If my inner service is available and returned CREATED or NO_CONTENT then all is right and I can return result else I need to return the NO_CONTENT status and put this message in my queue to try again until it will be processed. I made it like above but it doesn't look like a good solution, imho. I wanted to ask just for a some advice how can I improve this solution or that it's normal.

Related

How to get better coverage in Junit test with two arguments

I'm writing Junit tests and I'm getting only partial coverage. I'd like to get a better coverage percentage with the following method.
#Getter
#Setter
private String sessionId = "";
#Service
public class Service {
private final WebClient webClient;
public Flux<String> getIdsList(String sessionId, String query) throws RuntimeException {
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("q", query);
logger.info("Retrieved ID list.");
return webClient
.post()
.uri("/query")
.header("Authorization", sessionId)
.body(BodyInserters.fromMultipartData(map))
.retrieve()
.bodyToMono(ProductListResponse.class)
.doOnSuccess(this::checkVaultResponseErrors)
.flatMapIterable(res -> {
List<String> idList = new ArrayList<>();
res.data.forEach(id -> Objects.requireNonNull(idList).add(id.id));
if (idList.isEmpty())
logger.info("No new products for update or insert found.");
else
logger.info(String.format("Found %s new / updated products.", String.valueOf(idList.size())));
return idList;
})
.doOnError(throwable -> {
String warnMessage = "Unable to get products ids: " + throwable.getMessage();
logger.warn(warnMessage);
})
.delayElements(Duration.ofMillis(420));
}
Here's a ProductListResponse class
#JsonIgnoreProperties(ignoreUnknown = true)
public class ProductListResponse extends VaultResponse {
#JsonProperty("data")
public List<ProductIDs> data;
public static class ProductIDs {
#JsonProperty("id")
public String id;
}
}
VaultResponse class looks like this:
#Getter
#JsonInclude(JsonInclude.Include.NON_NULL)
public class VaultResponse {
#JsonProperty("responseStatus")
public ResponseStatus responseStatus;
#JsonProperty("errors")
public List<Error> errors;
}
Here's my test.
#Test
void should_get_ids_list() {
String body = "{ \"key\" : \"value\"}";
var uriMock = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
var headersSpecMock = Mockito.mock(WebClient.RequestHeadersSpec.class);
var responseSpecMock = Mockito.mock(WebClient.ResponseSpec.class);
webClient = WebClient.builder()
.exchangeFunction(clientRequest ->
Mono.just(ClientResponse.create(HttpStatus.OK)
.header("content-type", "application/json")
.body(body)
.build())
).build();
when(uriMock.uri(ArgumentMatchers.<String>notNull())).thenReturn(headersSpecMock);
when(headersSpecMock.header(notNull(), notNull())).thenReturn(headersSpecMock);
when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull()))
.thenReturn(Mono.just(body));
I'm getting only partial coverage, and I was unable to get into the .flatMapIterable part of the tested method. Does anyone have an idea how I can improve my test?

Using params in GetMapping in Spring results in ambiguous handler method for multiple parameters

I have the following REST endpoints in Spring boot
#GetMapping(value = "students", params = {"name"})
public ResponseEntity<?> getByName(#RequestParam final String name) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
#GetMapping(value = "students", params = {"tag"})
public ResponseEntity<?> getByTag(#RequestParam final String tag) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
The above handlers work fine for the following requests:
localhost:8080/test/students?name="Aron"
localhost:8080/test/students?tag="player"
However, whenever I try the following:
localhost:8060/test/students?name="Aron"&tag="player"
it throws java.lang.IllegalStateException: Ambiguous handler methods mapped and responds with an HTTP 500
How can I change this behavior? I want my app to respond only when I get either a tag query parameter or a name query parameter.
For anything else, I want it to ignore even if it's a combination of two parameters.
Why is it throwing the ambiguous error here and how can we handle that?
You can use #RequestParam(required = false):
#GetMapping(value = "students")
public ResponseEntity<?> get(
#RequestParam(required = false) final String name,
#RequestParam(required = false) final String tag) {
if ((name == null) == (tag == null)) {
return new ResponseEntity<>(false, HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(true, HttpStatus.OK);
}
it seems you can use negations in params. Something like:
#GetMapping(value = "students", params = {"name", "!tag"})
public ResponseEntity<?> getByName(#RequestParam final String name) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
#GetMapping(value = "students", params = {"tag", "!name"})
public ResponseEntity<?> getByTag(#RequestParam final String tag) {
return new ResponseEntity<>(true, HttpStatus.OK);
}
References: Advanced #RequestMapping options

Spring Boot: Json Response is not Mapping to Entity

I am trying to map this Json response I consume through a secured RESTful API; however, the mapping is for some reason not happening correctly as I keep getting a NULL response instead of populated object.
This is my entity:
#JsonTypeName("order")
#JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT ,use = JsonTypeInfo.Id.NAME)
public class Order {
#JsonProperty("id")
private long customerd;
#JsonProperty("email")
private String customeEmail;
public long getCustomerd() {
return customerd;
}
public void setCustomerd(long customerd) {
this.customerd = customerd;
}
public String getCustomeEmail() {
return customeEmail;
}
public void setCustomeEmail(String customeEmail) {
this.customeEmail = customeEmail;
}
}
This is my service method:
public Order orderDetails (#RequestBody Order order){
String username = "username";
String password = "password";
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
// request url
String url = "https://test.myshopify.com/admin/orders/2013413015655.json";
RestTemplate restTemplate = new RestTemplate();
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Order> response = restTemplate.exchange(
url, HttpMethod.GET, request, Order.class);
return order;
}
This is my Controller Method:
#GetMapping("/orderdetails")
#ResponseStatus(HttpStatus.FOUND)
public Order getBasicAut(Order order){
return basicAuth.orderDetails(order);
}
You should configure your REST to produce the JSON content
#GetMapping(path = "/orderdetails", produces = {MediaType.APPLICATION_JSON_VALUE})
Specifically, those codes below work well
#RestController
public class JsonController {
#GetMapping(path = "/orderdetails", produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseStatus(HttpStatus.FOUND)
public Order sendJsonBack() {
final Order order = new Order();
order.setCustomerd(123L);
order.setCustomeEmail("mail#gmail.com");
return order;
}
}
#Data
class Order implements Serializable {
private static final long serialVersionUID = -4258392267221190600L;
#JsonProperty("id")
private long customerd;
#JsonProperty("email")
private String customeEmail;
}
The issue was with what my methods were returning. They both have to return a ResponseEntity of type Order as in:
public ResponseEntity<Order> orderDetails (#RequestBody Order order){
String username = "username";
String password = "password";
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(username, password);
// request url
String url = "https://test.myshopify.com/admin/orders/2013413015655.json";
RestTemplate restTemplate = new RestTemplate();
HttpEntity request = new HttpEntity(headers);
ResponseEntity<Order> response = restTemplate.exchange(
url, HttpMethod.GET, request, Order.class);
return response;
}
And my Controller to:
#GetMapping("/orderdetails")
#ResponseStatus(HttpStatus.FOUND)
public ResponseEntity<Order> getBasicAut(Order order){
return basicAuth.orderDetails(order);
}

Spring REST API multiple RequestParams vs controller implementation

I'm wondering about proper way of implementating of controller in case of GET request with multiple request params given. In my understanding of REST it's much better to have one endpoint with additional parameters for filtering/sorting than several endpoints (one for each case). I'm just wondering about maintanance and extensibility of such endpoint. Please have a look on example below :
#RestController
#RequestMapping("/customers")
public class CustomerController {
#Autowired
private CustomerRepository customerRepo;
#GetMapping
public Page<Customer> findCustomersByFirstName(
#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName,
#RequestParam("status") Status status, Pageable pageable) {
if (firstName != null) {
if (lastName != null) {
if (status != null) {
return customerRepo.findByFirstNameAndLastNameAndStatus(
firstName, lastName, status, pageable);
} else {
return customerRepo.findByFirstNameAndLastName(
firstName, lastName, pageable);
}
} else {
// other combinations omitted for sanity
}
} else {
// other combinations omitted for sanity
}
}
}
Such endpoint seems to be very convenient to use (order of parameters doesn't matter, all of them are optional...), but maintaining something like this looks like a hell (number of combinations can be enormous).
My question is - what is the best way to deal with something like this? How is it designed in "professional" APIs?
What is the best way to deal with something like this?
The best way to deal with it is to use the tools already available. As you are using Spring Boot and, I assume therefore, Spring Data JPA then enable the QueryDsl suppport and web support extensions for Spring Data JPA.
You controller then simply becomes:
#GetMapping
public Page<Customer> searchCustomers(
#QuerydslPredicate(root = Customer.class) Predicate predicate, Pageable pageable) {
return customerRepo.findBy(predicate, pageable);
}
and your repository is simply extended to support QueryDsl:
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>,
QueryDslPredicateExecutor<Customer>{
}
You can now query by any combination of params without writing any further code.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.type-safe
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.extensions.querydsl
Good day. I can't call myself a professional, but here are some tips which can make this controller looks better.
Use DTO instead of using a group of parameters
public class CustomerDTO {
private String firstName;
private String lastName;
private String status;
}
With this class your method's signature will look like this:
#GetMapping
public Page<Customer> findCustomersByFirstName(CustomerDTO customerDTO, Pageable pageable) {
...
}
Use validation if you need one
For example, you can make some of these fields are required:
public class CustomerDTO {
#NotNull(message = "First name is required")
private String firstName;
private String lastName;
private String status;
}
Don't forget to add #Valid annotation before the DTO parameter in your controller.
Use specification instead of this block with if-else
Here is a great guide on how to do it - REST Query Language with Spring Data JPA Specifications
Use the service layer, don't need to call repository from the controller
#GetMapping
public Page<Customer> findCustomersByFirstName(#Valid CustomerDTO customerDTO, BindingResult bindingResult, Pageable pageable) {
if (bindingResult.hasErrors()) {
// error handling
}
return customerService.findAllBySpecification(new CustomerSpecification(customerDTO));
}
Your controller should not contain any logic about working with entities or some business stuff. It's only about handling request/errors, redirects, views, etc...
Its good to have a POST request with such validations instead of a GET request.You can use following method for the controller.
#PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(#Valid #RequestBody Customer customer){
return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
}
use the DTO as follows.
public class Customer {
private String firstName;
private String lastName;
private String status;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName= firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName= lastName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status= status;
}
public LivenessInputModel(String firstName, String lastName, String status) {
this.firstName= firstName;
this.lastName= lastName;
this.status= status;
}
public LivenessInputModel() {
}
}
And add a controller level exception advice to return the response in errors.
#ControllerAdvice
public class ControllerExceptionAdvice {
private static final String EXCEPTION_TRACE = "Exception Trace:";
private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);
public ControllerExceptionAdvice() {
super();
}
#ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}
#ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.INTERNAL_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
HttpServletRequest request, HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
ValidationException validationEx = new ValidationException(e);
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}
#ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.METHOD_NOT_ALLOWED);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
#ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
HttpServletResponse response) {
log.error(EXCEPTION_TRACE, e);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
ExceptionMessages.BAD_REQUEST_DEFAULT);
return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
}
Actually, you answered half of the answer yourself, Query Params are used for filtration purposes, and as you can see in your code this will be allowed via GET request. But your question regarding validations is something a trade-off.
For example; if you don't want to have this kind of check, you can depend on mandatory required = true which is the default in #RequestParam, and handle it in the response immediately.
Or you can use alternatively #RequestBody with support of #Valid for more clear info for what wrong had occurred; for example
#PostMapping(value = "/order")
public ResponseEntity<?> submitRequest(#RequestBody #Valid OrderRequest requestBody,
Errors errors) throws Exception {
if (errors.hasErrors())
throw new BusinessException("ERR-0000", "", HttpStatus.NOT_ACCEPTABLE);
return new ResponseEntity<>(sendOrder(requestBody), HttpStatus.CREATED);
}
// Your Pojo
public class OrderRequest {
#NotNull(message = "channel is required")
private String channel;
#NotNull(message = "Party ID is required")
private long partyId;
}
For more information check this #Valid usage in Spring
This way will decouple your validation mechanism from Controller layer to Business layer. which in turns will save lots of boilerplate code, but as you noticed with change to POST instead.
So in general, there is no direct answer to your question, and the short answer is it depends, so choose whatever easy for you with good capabilities and less maintainance will be the best practice
As an alternative solution besides other ones, you can use JpaSpecificationExecutor<T> in your repository and create a specification object based on your arguments and pass it to the findAll method.
So, your repository should extend from the JpaSpecificationExecutor<Customer> interface as follows:
#Repository
interface CustomerRepository extends JpaSpecificationExecutor<Customer> {
}
Your controller should get the required arguments as Map<String, String to gain dynamic behavior.
#RestController
#RequestMapping("/customers")
public class CustomerController {
private final CustomerRepository repository;
#Autowired
public CustomerController(CustomerRepository repository) {
this.repository = repository;
}
#GetMapping
public Page<Customer> findAll(#RequestBody HashMap<String, String> filters, Pageable pageable) {
return repository.findAll(QueryUtils.toSpecification(filters), pageable);
}
}
And, you should define a method to convert provided arguments to Specification<Customer>:
class QueryUtils {
public static Specification<Customer> toSpecification(Map<String, String> filters) {
Specification<Customer> conditions = null;
for (Map.Entry<String, String> entry : filters.entrySet()) {
Specification<Customer> condition = Specification.where((root, query, cb) -> cb.equal(root.get(entry.getKey()), entry.getValue()));
if (conditions == null) {
conditions = condition;
} else {
conditions = conditions.and(condition);
}
}
return conditions;
}
}
Also, You can use the Meta model to make better criteria query and combine it with the provided solution.

Creating Custom Exception for Resource Not Found 404 SPRING BOOT

I do have a class CustomResponseEntityExceptionHandler class extending ResponseEntityExceptionHandler with 2 methods one for handling wrong formats and the other one when a resource (url) is requested but does not exist. I would like to give the user a custom message instead of the default White Label Error page of Spring. I'm trying to understand why my handleResourceNotFoundException method from CustomResponseEntityExceptionHandler is not called when a non existing URI is requested but instead I keep having the White Label Error page. Thanks for your help!
curl localhost:8080/doesnotexist
This is my CustomResponseEntityExceptionHandler
#ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorDetails> handleAllWrongFormatExceptions(WrongFormatException ex,
WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(true));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(ResourceNotFoundException.class)
public final ResponseEntity<ErrorDetails> handleResourceNotFoundException(ResourceNotFoundException ex,
WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
Simple class for holding custom error details
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDetails() {
return details;
} }
And here is my controller
#Controller
public class StringManipController {
#Autowired
private StringProcessingService stringProcessingService;
#GetMapping("/")
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
public String home(#RequestParam(name = "value", required = false, defaultValue = "") String value) {
return "Welcome to the String Processing Application";
}
#GetMapping("/stringDedup")
#ResponseBody
public ProcessedString doManip(#RequestParam(name = "value", required = false, defaultValue = "") String value) {
String result = stringProcessingService.getStringManipulation(value);
return new ProcessedString(result);
}
#GetMapping("/writeNumber")
#ResponseBody
public ProcessedString getWriteNumber(
#RequestParam(name = "value", required = false, defaultValue = "") String value) {
String number = stringProcessingService.getNumberToWords(value);
return new ProcessedString(number);
} }
And here the ResourceNotFoundException class
#ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 266853955330077478L;
public ResourceNotFoundException(String exception) {
super(exception);
} }
If you used Spring Boot:
#ControllerAdvice
public class CustomResponseEntityExceptionHandler {
#ExceptionHandler(value = { NoHandlerFoundException.class })
public ResponseEntity<Object> noHandlerFoundException(Exception ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("test");
}
}
or
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(
NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, "Test", headers, status, request);
}
}
In the org.springframework.web.servlet.DispatcherServlet class there is a variable called throwExceptionIfNoHandlerFound. If this is set to true a method called noHandlerFound will throw a NoHandlerFoundException. Your exception handler will now catch it.
Add this property to your application.properties file: spring.mvc.throw-exception-if-no-handler-found=true

Categories

Resources