microservices versioning with header - java

issue is when i am looking swagger for v1 there i can see one endpoint which is valid, but for v2 i have given two endpoints inside controller, but /allusers endpoint i am not able to see. below are the controller.
controller v1:
package com.springboot.rest.controller.v1;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rest.dto.UserDto;
import com.springboot.rest.service.UserService;
#RestController(value = "userControllerV1")
#RequestMapping(value = "/userinfo", produces = "application/json")
public class UserController {
public static final String X_ACCEPT_VERSION_V1 = "X-Accept-Version" + "=" + "v1";
#Autowired
private UserService userService;
#GetMapping(value = "/allusers", headers = X_ACCEPT_VERSION_V1)
public List<UserDto> getUserinfo() {
List<UserDto> finalResults = userService.getAllUserInfo();
return finalResults;
}
}
controller v2:
package com.springboot.rest.controller.v2;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springboot.rest.dto.UserDto;
import com.springboot.rest.service.UserService;
#RestController(value = "userControllerV2")
#RequestMapping(value = "/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
public static final String X_ACCEPT_VERSION_V2 = "X-Accept-Version" + "=" + "v2";
#Autowired
private UserService userService;
#GetMapping(value = "/allusers", headers = X_ACCEPT_VERSION_V2)
public List<UserDto> getUserinfo() {
List<UserDto> finalResults = userService.getAllUserInfo();
return finalResults;
}
#GetMapping(value = "/message", headers = X_ACCEPT_VERSION_V2)
public String greetMessage() {
return userService.getGreetMessage();
}
}
and i don't want to change my getUserinfo() method, could anyone help?

URI paths for /allusers end point are same in both the controllers where as api endpoints should be unique through out the application. You can add version in the uri which will make it unique. For eg.
#RequestMapping(value = "/v2/userinfo", produces = MediaType.APPLICATION_JSON_VALUE)

I did many ways, but finally OpenApi and adding filter did it for me. below is the OpenApiConfig file and link for those who wants to achieve this.
#Configuration
public class OpenApiConfig {
#Bean
public OpenAPI customOpenApi() {
return new OpenAPI()
.components(new Components())
.info(new Info().title("User-Management Microservice")
.description("demo-microservice for user-management")
.termsOfService("www.abc.com")
.contact(new io.swagger.v3.oas.models.info.Contact()
.email("abc.com")
.name("user-management"))
.version("1.0"));
}
#Bean
public GroupedOpenApi v1OpenApi() {
String[] packagesToScan = {"com.springboot.rest.controller.v1"};
return GroupedOpenApi.builder().setGroup("v1 version").packagesToScan(packagesToScan).build();
}
#Bean
public GroupedOpenApi v2OpenApi() {
String[] packagesToScan = {"com.springboot.rest.controller.v2"};
return GroupedOpenApi.builder().setGroup("v2 version").packagesToScan(packagesToScan).build();
}
}
use below link for step by step explanation:
https://www.youtube.com/watch?v=Z4FwdCgik5M

Related

All injected member bean of Rest Controller are NULL when invoking a rest API with #RequestPart annotated method

Below is my Rest Controller Code:
package com.tripura.fileserver.controller;
import com.magic.fileserver.annotation.TrackTime;
import com.magic.fileserver.model.ResponseMessage;
import com.magic.fileserver.service.S3Factory;
import com.magic.fileserver.service.SequenceGeneratorService;
import com.magic.fileserver.service.StorageService;
import org.springframework.http.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.security.Principal;
import java.util.List;
#RestController
#RequestMapping(FileServerController.ROOT_MAPPING)
public class FileServerController {
public static final String ROOT_MAPPING = "/api/fileserver";
private final SequenceGeneratorService sequenceGeneratorService;
private final StorageService storageService;
private final S3Factory s3Factory;
public FileServerController(SequenceGeneratorService sequenceGeneratorService, StorageService storageService, S3Factory s3Factory) {
this.sequenceGeneratorService = sequenceGeneratorService;
this.storageService = storageService;
this.s3Factory = s3Factory;
}
#GetMapping(value = "/health/admin")
#PreAuthorize("hasRole('ADMIN')")
#TrackTime
public ResponseEntity<?> checkRequestAdmin() {
return new ResponseEntity<>("Hello admin", HttpStatus.OK);
}
#PostMapping(value = "/add/file", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
#PreAuthorize("hasRole('USER') or hasRole('CONSULTANT') or hasRole('ADMIN')")
#TrackTime
private ResponseEntity<ResponseMessage<String>> uploadFile(
#RequestPart("files") List<MultipartFile> files) {
// this.storageService.storeFileOnS3Bucket(files.get(0), image, fileName,
// userName, category);
ResponseMessage<String> responseMessage = new ResponseMessage<>();
responseMessage.setMessage("File added: " + files.get(0).getOriginalFilename());
return new ResponseEntity<>(responseMessage, HttpStatus.OK);
}
Problem is when i am invoking it via POSTMAN as below:
curl --location --request POST 'http://localhost:8096/api/fileserver/add/file' --form 'files=#"/G:/My Drive/Money_receipt.jpg"'
I can see valid file is coming inside the method but all the injected beans are NULL
Debug at the errornous method
My Environment:
Java 17 (zulu17.28.13-ca-jdk17.0.0-win_x64)
Spring Boot: 2.6.7
Finally i found the solution. It was a silly mistake as i declared access modifier for the API method uploadFile as private as below:
private ResponseEntity<ResponseMessage<String>> uploadFile
After changing the access modifier to public, it has resolved the issue:
public ResponseEntity<ResponseMessage<String>> uploadFile

Spring boot: Expected json responses, getting XML responses

I'm running a simple Spring boot application that retrieves details of countries from a MySQL database. The initial responses I got while running the application were in json. However, after a few edits in the application.properties file, I get my reponses in XML now. Any way to revert back to json reponses? This application is a part of a microservice application I'm trying to build with Spring cloud gateway and Eureka server.
application.properties
spring.jpa.hibernate.ddl-auto = update
spring.datasource.url= jdbc:mysql://localhost:3306/countries-microservice
spring.datasource.username= root
spring.datasource.password=
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.application.name=countries-service
server.port=3001
eureka.client.serviceUrl.defaultZone=http://localhost:3000/eureka/
CountryRepository.java
package com.example.countriesservice.repository;
import com.example.countriesservice.model.Country;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CountryRepository extends JpaRepository<Country, String> {
Country findByCountry(String country);
}
CountryService.java
package com.example.countriesservice.service;
import java.util.List;
import com.example.countriesservice.model.Country;
import com.example.countriesservice.repository.CountryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class CountryService {
private final CountryRepository countryRepository;
#Autowired
public CountryService(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
public List<Country> getAllCountries() {
return countryRepository.findAll();
}
public Country getCountry(String country) {
return countryRepository.findByCountry(country);
}
}
CountryController.java
package com.example.countriesservice.controller;
import com.example.countriesservice.service.CountryService;
import java.util.List;
import com.example.countriesservice.model.Country;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
#RequestMapping("/countries")
#RestController
public class CountryController {
private final CountryService countryService;
#Autowired
public CountryController(CountryService countryService) {
this.countryService = countryService;
}
#GetMapping("/getAll")
public List<Country> getAll() {
return countryService.getAllCountries();
}
#GetMapping("/{country}")
public Country getCountry(#PathVariable String country) {
return countryService.getCountry(country);
}
}
Output
Since I am still learning Spring Boot it would be great if you could explain what am I doing wrong and how to correct it in a bit detail.
Explicitly mention that a json response is required.
In CountryController.java
import org.springframework.http.MediaType;
#GetMapping(value = "/getAll", produces = { MediaType.APPLICATION_JSON_VALUE })
public List<Country> getAll() {
return countryService.getAllCountries();
}
#GetMapping(value = "/{country}", produces = { MediaType.APPLICATION_JSON_VALUE })
public Country getCountry(#PathVariable String country) {
return countryService.getCountry(country);
}

Spring Boot #ApiController annotation not working

I am working on my first Spring-Boot app. Got a working UI Controller implemented below:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.ArrayList;
#Controller
public class UiController {
private ProductService productService;
private LocationService locationService;
private InventoryService inventoryService;
private CartService cartService;
public UiController(
ProductService productService,
LocationService locationService,
InventoryService inventoryService,
CartService cartService) {
this.productService = productService;
this.locationService = locationService;
this.inventoryService = inventoryService;
this.cartService = cartService;
}
#GetMapping("/")
public String home(Model model) {
model.addAttribute("products", productService.getAllProducts());
return "index";
}
#GetMapping("/brand/{brand}")
public String brand(Model model, #PathVariable String brand) {
List prods = productService.getProductByBrand(brand);
if (prods.size() == 0) throw new ItemNotFoundException();
model.addAttribute("products", prods);
return "index";
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such item") // 404
public class ItemNotFoundException extends RuntimeException {
// ...
}
#GetMapping("/product/{productId}")
public String product(Model model, #PathVariable String productId) {
Product prod = productService.getProduct(productId);
if (prod == null) throw new ItemNotFoundException();
ArrayList<Product> ps = new ArrayList<Product>();
ps.add(prod);
model.addAttribute("products", ps);
return "index";
}
}
I want to add a REST controller returning the same thing as HTML only I want the responses to be in JSON. When there is no data, I want an error return. Added the below:
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.HttpStatus;
import java.util.List;
import java.util.ArrayList;
import com.google.gson.Gson;
#ApiController
public class ApiController {
private ProductService productService;
private LocationService locationService;
private InventoryService inventoryService;
private CartService cartService;
public ApiController(
ProductService productService,
LocationService locationService,
InventoryService inventoryService,
CartService cartService) {
this.productService = productService;
this.locationService = locationService;
this.inventoryService = inventoryService;
this.cartService = cartService;
}
#GetMapping("/rest")
public String home() {
List prods = productService.getAllProducts();
if (prods.size() == 0) throw new ItemNotFoundException();
return new Gson().toJson(prods);
}
#GetMapping("/rest/brand/{brand}")
public String brand(#PathVariable String brand) {
List prods = productService.getProductByBrand(brand);
if (prods.size() == 0) throw new ItemNotFoundException();
return new Gson().toJson(prods);
}
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such item") // 404
public class ItemNotFoundException extends RuntimeException {
// ...
}
#GetMapping("/rest/product/{productId}")
public String product(#PathVariable String productId) {
Product prod = productService.getProduct(productId);
if (prod == null) throw new ItemNotFoundException();
return new Gson().toJson(prod);
}
}
Apparently, autoconfig is working and my controller gets picked by the compiler. Only, I get the below error:
Compilation failure
ApiController.java:[21,2] incompatible types: com.rei.interview.ui.ApiController cannot be converted to java.lang.annotation.Annotation
What am I doing wrong and what should I do?
You did a simple mistake at the beginning of the controller.
The class must be annotated #RestController... not #ApiController
Change your code from
#ApiController
public class ApiController {
...
}
to
#RestController // <- Change annotation here
public class ApiController {
...
}
The error
ApiController.java:[21,2] incompatible types:
com.rei.interview.ui.ApiController cannot be converted to java.lang.annotation.Annotation
informs you that the annotation #ApiController is not a of type java.lang.annotation.Annotation

Making attribute in json request object not null

I'm building a Spring application which has #RestController, like:
#RestController
#RequestMapping(value = "/master")
public class MyController {
#Autowired
private MyService service;
#PostMapping("/call")
public ResponseEntity<Boolean> apiCall(#RequestBody MyDTO myDto) { ;
return new ResponseEntity<Boolean>(service.apiCall(myDto), OK);
}
}
And a request object:
public class MyDTO {
#JsonProperty("emp_number")
private long empNumber;
#JsonProperty("office_id")
private long officeId;
// ....constructors, etc.
}
In request json I want officeId to be not null.
So far I've tried marking the officeId field as:
#com.fasterxml.jackson.databind.annotation.JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
#JsonProperty(required = true)
#javax.validation.constraints.NotNull
But in the request json, even if I miss office_id, it is not throwing any error.
What am I missing?
It could be that in your case you are deserializing request, and missing primitive properties which are referenced by constructor are assigned a default value
java defaults
You could try to use corresponding Long wrapper object for MyDTO instead of primitives or maybe deserialization feature FAIL_ON_NULL_FOR_PRIMITIVES
You have to change the type from primitive to wrapper otherwise the default value of 0 will be considered and validation will pass.
Annotate MyDTO with #Valid annotation.
When Spring Boot finds an argument annotated with #Valid, it automatically bootstraps the default JSR 380 implementation — Hibernate Validator — and validates the argument.
from here
Please add following dependency to pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
PS: i have made minor adjustments to response structure to see the error in response
Entire code is as follows:
package com.example.spring.java.springjavasamples;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.HashMap;
import java.util.Map;
#SpringBootApplication
public class SpringJavaSamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJavaSamplesApplication.class, args);
}
}
#RestController
#RequestMapping(value = "/master")
class MyController {
private final MyService service;
public MyController(MyService service) {
this.service = service;
}
#PostMapping("/call")
public ResponseEntity<Map<String, Object>> apiCall(#Valid #RequestBody MyDTO myDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
bindingResult.getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return prepareResponse("error", errors, HttpStatus.BAD_REQUEST);
} else {
return prepareResponse("data", service.apiCall(myDto), HttpStatus.OK);
}
}
private ResponseEntity<Map<String, Object>> prepareResponse(String key, Object data, HttpStatus status) {
Map<String, Object> map = new HashMap<>();
map.put(key, data);
return new ResponseEntity<>(map, status);
}
}
class MyDTO {
#NotNull(message = "Employee number cannot be null")
#JsonProperty("emp_number")
private Long empNumber;
#NotNull(message = "Office Id cannot be null")
#JsonProperty("office_id")
private Long officeId;
#Override
public String toString() {
return "MyDTO{" +
"empNumber=" + empNumber +
", officeId=" + officeId +
'}';
}
}
#Service
class MyService {
public String apiCall(MyDTO myDto) {
System.out.println("all valid: " + myDto);
return myDto.toString();
}
}

Add query string value when html page is launched from Java Controller

I have a HTML page that is launched from a java controller after a post and I want to attach a query string value in the url ex: (localhost:8000/gdata?id=11). Can this be done? Here is my controller code:
package com.sa.example;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
//import org.springframework.web.bind.annotation.RequestParam;
import com.sentinel.advisor.GData;
import com.sentinel.advisor.GDataJdbcRepository;
#Controller
public class GDataController {
#Autowired
GDataJdbcRepository repository;
#GetMapping("/gdata")
public String gDataForm(Model model) {
return "gData";
}
#PostMapping("/gdata")
public String gDataSubmit(#ModelAttribute GData gData) {
String returnString = repository.insert(gData);
//returnString should be returned in the url as a query string
return "result";
}
}
You can use a redirect (it is best practice to redirect after post regardless see - https://en.wikipedia.org/wiki/Post/Redirect/Get.
Spring's redirect view:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/view/RedirectView.html
Something like:
#Controller
public class GDataController {
#Autowired
GDataJdbcRepository repository;
#GetMapping("/gdata")
public String gDataForm(Model model) {
return "gData";
}
#PostMapping("/gdata")
public RedirectView gDataSubmit(#ModelAttribute GData gData) {
String returnString = repository.insert(gData);
return new RedirectView("/sucess?returnString=" + returnString, true);
}
#GetMapping("/success")
public String getResultPage(#RequestParam("returnString")String returnString){
return "result";
}
}

Categories

Resources