How to rethrown an exception in Java - java

I am using maven codegen plugin to generate the controller interface with a schema like the following
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/MyResponse'
description: OK
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/MyError'
The interface like the following
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Authentication succeeded", content = #Content(mediaType = "application/json", schema = #Schema(implementation = MyResponse.class))),
#ApiResponse(responseCode = "401", description = "Authentication failed", content = #Content(mediaType = "application/json", schema = #Schema(implementation = MyError.class))) })
#RequestMapping(value = "/login", method = RequestMethod.POST)
default ResponseEntity<MyResponse> LoginMethod(//some parameters...) { //something}
In my controller, I would like to call an external API which throws an API exception
public ResponseEntity<MyResponse> LoginMethod(//some parameters...) {
try {
//call external API which throw an exception
} catch(ApiException e){
e.getResponseBody; // This is a string type of MyError class in JSON format returned
// throw e;
}
I would like to redirect the response body but the interface defines the return type to be ResponseEntity so I can't simply rethrow the exception or return ResponseEntity.
#ApiResponse seems not correcting the response type as well.
As stated in this question,
How to handle multiple response/return types (empty for 204, non-empty for 400 etc) in swagger codegen?
I can throw as this way
throw new ResponseStatusException(HttpStatus.valueOf(e.getCode()), e.getResponseBody());
But is there a better way to do that? I just want to return the e.getResponseBody() as an object instead of a string.
Many thanks.

You can add the ApiException in throws declaration like that :
public ResponseEntity<MyResponse> LoginMethod(//some parameters...) throws ApiException {
// here your code that can create teh ApiException
}
Now the method which call this will ask for the throw exception too. You will be able to manage exception in it.
You can also create a new object which contains all informations that you need. It will also format informations to be always the same, not depending of the throwed error.

Related

Spring Boot Rest Controller endpoint exception: HttpMediaTypeNotAcceptableException

I am experiencing an issue while a mandatory field is not filled, the following exception is displayed in the logs:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
Lets say I have an object CodeRequest that contains an attribute as follows:
#NotBlank(message = "payloadFormatIndicator.required")
#Size(max = 2, message = "payloadFormatIndicator.size")
private String payloadFormatIndicator;
My controller have the object CodeRequest as parameter as shown below:
#PostMapping(value = "/dummy", produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<BufferedImage> generateQRCode(#Valid #RequestBody CodeRequest paymentRequest) throws Exception {
log.debug("generateQRCode with the following request {}", paymentRequest);
return ResponseEntity.status(HttpStatus.OK).body(ipsPaymentService.generateQRCode(paymentRequest));
}
When I leave the mandatory field payloadFormatIndicator empty I expect to get an error message that payloadFormatIndicator.required is required in my response.
However, I get the following error message in the log:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
My exception handler is shown below:
#Slf4j
#ControllerAdvice
public class RestControllerExceptionHandler extends ResponseEntityExceptionHandler {
#Override
public ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
HttpStatus status, WebRequest request) {
log.error(exception.getMessage(), exception);
ExceptionResponse exceptionResponse = new ExceptionResponse(HttpStatus.BAD_REQUEST,
exception.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return new ResponseEntity<>(exceptionResponse, new HttpHeaders(), exceptionResponse.getHttpStatus());
}
It looks like because the method generateQRCode is returning ResponseEntity<BufferedImage> it is causing this issue because for the other methods on my controller, the exception handling is working fine.
I am using swagger to test the rest API and the content type is shown below:
Any idea how I can fix it pls?
The issue is because of producer media-type. The response only accept image/png, yet when there is an error media-type is application/json.
change your code like this,
#PostMapping(value = "/dummy", produces = "application/json, image/png")

How to return different objects for different response codes in Swagger-generated REST API?

I want to respond with different result objects in a Swagger generated API. The type of object is dependent on the result code.
But it seems that the Swagger codegen generates only code that allows the first defined/used type to be returned.
An example Swagger definition that returns different objects in the OK and error case is like:
swagger: "2.0"
info:
description: "API"
version: 1.0.0
title: Example
host: localhost:8080
schemes:
- http
paths:
/exampleCall:
get:
operationId: exampleCall
produces:
- application/json
responses:
200:
description: OK
schema:
$ref: '#/definitions/exampleResponse'
400:
description: Error
schema:
$ref: '#/definitions/exampleError'
definitions:
exampleResponse:
type: object
properties:
result:
type: string
exampleError:
type: object
properties:
code:
type: string
This then gets generated by the SwaggerCodeGen into following API interface
#Validated
#Api(value = "exampleCall", description = "the exampleCall API")
#RequestMapping(value = "")
public interface ExampleCallApi {
ExampleCallApiDelegate getDelegate();
#ApiOperation(value = "", nickname = "exampleCall", notes = "", response = ExampleResponse.class, tags={ })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = ExampleResponse.class),
#ApiResponse(code = 400, message = "Error", response = ExampleError.class) })
#RequestMapping(value = "/exampleCall",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<ExampleResponse> exampleCall() {
return getDelegate().exampleCall();
}
}
But when I try to implement the delegate like this
public class ExampleCallApiDelegateImpl implements ExampleCallApiDelegate {
#Override
public ResponseEntity<ExampleResponse> exampleCall() {
ExampleError error = new ExampleError();
error.setCode("123");
return new ResponseEntity<ExampleError>(error, HttpStatus.BAD_REQUEST);
}
}
it of course fails to compile because of incorrect return types.
What would be the proper way to implement different return objects per response code with that Swagger generated API?
Is there even a proper way?

Spring MVC test throwing 415 HttpMediaTypeNotSupportedException

this may or may not be a somewhat long post, but I'm going to be pasting every single piece of information relating to this issue and the method I am testing, from Controller class with method to the a.jax snippet. I have asked about 4-6 developers and no one can find out the reason why its giving me a 415 error instead of a 200, because it just seems like I am doing everything correct. I just need some fresh eyes and new perspectives, hopefully someone could help me solve this. I will be pasting the classes and the relevant pieces now, and then a couple comments after the snippets.
Controller class
#Controller
#RequestMapping(value = "/user")
public class Controller
{
#Autowired
private Service service;
public Controller() {
}
#RequestMapping(value = "/landing/{age}/{name}/{title}/{family}/{gender}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public #ResponseBody String update(#RequestBody HouseModel model, #PathVariable int age, #PathVariable String name, #PathVariable String title, #PathVariable String family, #PathVariable String gender)
{
String result = service.update(model, age, name, title, family, gender);
// this will just return the string "Success" if update works or "Failed" if query is
wrong or not found
return result;
}
Service Class
#Service
public class Service
{
#Autowired
Dao dao;
public Service() {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
return dao.update(HouseModel model, int age, String name, String title, String family);
}
}
Dao class
#Repository
public class Dao
{
public Dao () {
}
public String update(HouseModel model, int age, String name, String title, String family)
{
String result = "";
//some long query that updates the table and will populate result as "Success" or "Failed"
return result
}
}
Controller test class
#EnableWebMvc
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:applicationContext-testing.xml",
"classpath:applicationContext-EIPW.xml"})
public class ControllerTest {
#Autowired
private Controller controller;
#Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
#Before
public void setup() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void test_update() throws Exception {
String URI = "/user/landing/22/Test/Mr/Yes/Male";
String content = "{\n \"HouseId\": 5,\n \"DateOfPurchase\": \"2019-01-01\",\n \"Price\": 100,\n \"Floors\": 5,\n \"Style\": \"Victorian\",\n}";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.put(URI).contentType(MediaType.APPLICATION_JSON).content(content).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andDo(MockMvcResultHandlers.print()).andReturn();
}
j.ajax
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "text"
printed error message
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /user/landing/22/Test/Mr/Yes/Male
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = controller.Controller
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 415
Error message = null
Headers = {Accept=[application/octet-stream, */*, text/plain;charset=ISO-8859-1, */*, application/xml, text/xml, application/*+xml, application/x-www-form-urlencoded, multipart/form-data]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Some Comments:
So I've had over 50 stack overflow tabs open relating to the same problem, and a lot of them had similar solutions that seemed so simple. Here are some, if not all of the things I did in attempts to try and solve this problem:
Switched around the content-type and accepts headers of requestBuilder to be MediaTypes of APPLICATION_JSON or APPLICATION_JSON_VALUE or ALL_VALUE, etc
Added produces or consumes = "application/json" or MediaType.APPLICATION_JSON/APPLICATION_JSON_VALUE/ALL_VALUE into the requestMapping().
Played around with a.jax to change content-type or accepts around
A couple of other things that I don't remember, but alas the 415 status is still here
I also do have setters and a default constructor in the HouseModel, and default constructors in every layer. I am 99.9% sure I have tried almost everything, if not everything, unless I am just missing something and am being stupid then yeah. I sent the request with the body as JSON raw an as:
{
"HouseId": 5,
"DateOfPurchase": "2019-01-01",
"Price": 100,
"Floors": 5,
"Style": "Victorian",
}
and it returned back success, I will attach its headers here:
[![Picture Link][1]][1]
[1]: https://i.stack.imgur.com/AqKnY.png
There is something interesting though, I did get one method to work but it required no arguments in its parameters, it was just a get method (dao calls database to store stuff in a list):
**Controller method**
#RequestMapping(value = "/levels", method = RequestMethod.POST, produces = "application/json")
public #ResponseBody String getLevels() throws Exception
{
ObjectMapper mapper = new ObjectMapper();
// there is a long logic after this map to populate the map
Map<LevelObject, List<LevelObject>> map = new HashMap<LevelObject, List<LevelObject>>();
return mapper.writeValueAsString(map);
}
This is such a simple test and it worked perfectly fine, giving me a status 200 and my expected result.
**Test class method**
#Test
public void test_getLevels() throws Exception {
String URI = "/user/levels";
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.post(URI).accept(MediaType.APPLICATION_JSON);
MvcResult mvcResult = mockMvc.perform(requestBuilder).andExpect(status().isOk()).andReturn();
}
**j.ajax for the getLevels method**
$j.ajax({
type: "POST",
url: "user/levels",
async: false,
dataType: "json",
Thank you so much!
Though I am not a front-end developer, I am sure that problem is in below line
data: JSON.stringify(
buildEdit(editRecords[i], ecRecord)
)
Reason - I tested your code locally. Code works fine from postman, if I select request body as raw and type as JSON
But if select request body as raw and type as "TXT". I am getting same error as you.
[![enter image description here][2]][2]
So, I am sure that your request body is not being built as JSON. rather it is being sent as some other format. Hence, the error.
[2]: https://i.stack.imgur.com/cqSCC.png
Also, you can try to change dataType: "text" to dataType: "json"
Please try the below,
$j.ajax({
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
type: "PUT",
async: false,
data:
buildEdit(editRecords[i], ecRecord),
url:
"/user/landing/22/Test/Mr/Yes/Male",
dataType: "json"
Remove converting the json to String
Change the dataType to json

Why is object query parameter null?

I describe a path in openapi file in my spring boot application.
Openapi generates by the file api class which handles http requests.
Also I use swagger which hepls construct a valid url, where I can put query parameters as well.
I'm wondering, why having all this generated staff I receive null object instead of expected.
part of api.yaml
/films:
get:
summary: Отфильтрованные фильмы
operationId: findFilms
tags: [selections]
parameters:
- in: query
name: filter
schema:
type: object
properties:
genreId:
type: integer
year:
type: integer
countryId:
type: integer
style: deepObject
explode: false
responses:
200:
description: successfull response
content:
application/json:
schema:
$ref: 'list-schemas.yaml#/components/schemas/SelectionTo'
generated java class
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
#Validated
#Api(value = "Selections")
public interface SelectionsApi {
default Optional<NativeWebRequest> getRequest() {
return Optional.empty();
}
#ApiOperation(value = "Отфильтрованные фильмы", nickname = "findFilms", , response = SelectionTo.class, tags={ "selections", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful response", response = SelectionTo.class) })
#RequestMapping(value = "/films",
produces = { "application/json" },
method = RequestMethod.GET)
default ResponseEntity<SelectionTo> _findFilms(#ApiParam() #Valid #RequestParam(value = "filter", required = false) Filter filter) {
return findFilms(filter);
}
// Override this method
default ResponseEntity<SelectionTo> findFilms(Filter filter) {
getRequest().ifPresent(request -> {
...
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
generated query parameter class
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
public class Filter {
#JsonProperty("genreId")
private Integer genreId = null;
#JsonProperty("year")
private Integer year = null;
#JsonProperty("countryId")
private Integer countryId = null;
public Filter genreId(Integer genreId) {
this.genreId = genreId;
return this;
}
implementing interface
#Override
public ResponseEntity<SelectionTo> findFilms(Filter filterType) {
//here filter is null !
return ResponseEntity.ok(transformer.transform(service.getItemsInfo()));
}
request
http://localhost/films?filter[genreId]=13&filter[year]=2021
How openapi file could be improved? Because this is the only thing I've defined. Or what else could the reason?
As far as I can see, Spring MVC does not support decoding nested object query parameters in the OpenAPI deepObject style, like filter[genreId]=13, at least out of the box.
Try to remove #RequestParam() from filter object.
Like this:
default ResponseEntity<SelectionTo> _findFilms(#ApiParam() #Valid Filter filter) {
return findFilms(filter);
}
Also the request should be http://localhost/films?genreId=13&year=2021

Can not upload file when using a swagger generated spring server

I want to implement a file readout function on my REST Service. Since I do not know how to use spring myself, I use swagger to generate the server code for me. Normally this works perfectly fine, but when I try to upload files I get the following error:
{
"timestamp": "2018-11-07T12:27:43.119Z",
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.multipart.support.MissingServletRequestPartException",
"message": "Required request part 'file' is not present",
"path": "/requirements/import"
}
My yaml uses the following lines for the import function:
/requirements/import:
post:
consumes:
- multipart/form-data
description:
Returns all requirements contained in the submitted reqIf file.
parameters:
- name: reqIfFile
in: formData
type: file
description: The reqIf file that contains the requirements.
responses:
200:
description: An array of requirements.
schema:
type: array
items:
$ref: 'requirement'
The generated interface (with some added exceptions):
#javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-04-05T07:19:00.887Z")
#Api(value = "requirements", description = "the requirements API")
public interface RequirementsApi {
#ApiOperation(value = "", nickname = "requirementsImportPost", notes = "Returns all requirements contained in the submitted reqIf file.", response = Requirement.class, responseContainer = "List", tags = {})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "An array of requirements.", response = Requirement.class, responseContainer = "List") })
#CrossOrigin(origins = "*")
#RequestMapping(value = "/requirements/import", produces = { "application/json" }, consumes = {
"multipart/form-data" }, method = RequestMethod.POST)
ResponseEntity<List<Requirement>> requirementsImportPost(
#ApiParam(value = "file detail") #Valid #RequestPart("file") MultipartFile reqIfFile)
throws IOException, ContinuumException;
}
The code that actually does the readout:
#javax.annotation.Generated(value = "io.swagger.codegen.languages.SpringCodegen", date = "2018-04-05T07:19:00.887Z")
#Controller
public class RequirementsApiController implements RequirementsApi {
#Override
public ResponseEntity<List<Requirement>> requirementsImportPost(
#ApiParam(value = "file detail") #Valid #RequestPart("file") final MultipartFile reqIfFile)
throws IOException, ContinuumException {
InputStream fileStream = new BufferedInputStream(reqIfFile.getInputStream());
List<Requirement> list = ReadReqIF.readReqIfFile(fileStream);
return new ResponseEntity<List<Requirement>>(list, HttpStatus.OK);
}
}
Can someone tell me where a possible error is?
I encountered the same problem with my swagger generated spring server.
I was able to workaround the problem by modifying the generated server code to change the name "file" in #RequestPart("file") to the name specified in the swagger spec. In your case, it should be #RequestPart("reqIfFile"). It'd have to be modified in both the interface and controller code.
There is likely a bug in the Spring server generator code in Swagger editor. I can't think of any other reason for the RequestPart annotation to be named "file" which is essentially the "type" and not name of the parameter.

Categories