WebFlux Swagger (Open API) integraton - Post Request sample - java

I have integrated Swagger (OpenAPI) with Spring Webflux as mentioned here: https://springdoc.org/#spring-weblfuxwebmvcfn-with-functional-endpoints using RouterOperation. The integration works fine and is accessible at /swagger-ui.html
However, for POST APIs, I am not seeing the "Request" sample when I click on "Try it out" button. My Post API accepts a Json as Request Body.
How do I configure this ? Can that be done via Annotations along with RouterOperation or something else ?
Edit: Below is my Router class code:
#Configuration
public class MyRouter {
#RouterOperations({
#RouterOperation(path = "/data", beanClass = MyHandler.class, beanMethod = "getData"),
#RouterOperation(path = "/allData", beanClass = MyHandler.class, beanMethod = "getAllData") })
#Bean
public RouterFunction<ServerResponse> route(MyHandler MyHandler) {
return RouterFunctions
.route(RequestPredicates.POST("/data").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), MyHandler::getData)
.andRoute(RequestPredicates.GET("/allData").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), MyHandler::getAllData);
}
}
Upon adding RouterOperations annotation I can see the swagger-ui showing both the GET and POST APIs correctly but not the request schema sample.
I also came across yaml / json file to describe this. But I am not getting where to put this file in my application so that swagger-ui uses it.

Finally found it
Using #Operation and #Schema, can define the class that is required as input in request body. This will be shown as sample json structure in Swagger-ui. No other configuration required.
#RouterOperations({
#RouterOperation(
path = "/data", beanClass = MyHandler.class, beanMethod = "getData",
operation = #Operation(
operationId = "opGetData",
requestBody = #RequestBody(required = true, description = "Enter Request body as Json Object",
content = #Content(
schema = #Schema(implementation = ApiRequestBody.class))))),
#RouterOperation(path = "/allData", beanClass = MyHandler.class, beanMethod = "getAllData")})
#Bean
public RouterFunction<ServerResponse> route(MyHandler myHandler) {
return RouterFunctions
.route(RequestPredicates.POST("/data").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), myHandler::getData)
.andRoute(RequestPredicates.GET("/allData").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)), myHandler::getAllData);
}

Related

How to set Additional Properties to boolean

I am trying to set Additional Properties element into the Open API Schema 3.X but unfortunatel I was not able to find anything in the documentation that help me on it.
I have a Application in Spring boot and it is using Spring doc OAS that relies on Swagger OAS as transitive dependency.
Let me pick some code snippet here:
#GetMapping("/{accountId}")
#Operation(summary = "Get account by account id", tags = TAG)
#ApiResponses(value = {
#ApiResponse(responseCode = "200", description = "Return a specific account queried by path",
content = { #Content(mediaType = "application/json",
schema = #Schema(implementation = AccountDetailsDTO.class)) }),
#ApiResponse(responseCode = "404", description = "No accounts found",
content = #Content) })
public ResponseEntity<AccountDetailsDTO> getAccountDetailsByClientId(#PathVariable("accountId") Integer accountId) { }
This attribute is default to true and What I would like to see is as false like that below:
If you want explicitly set the attribute to false you can a TransformationFilter (annoted #Component for Spring) to set additionalProperties to false for each component of you specification if you are using Springfox.
If you are using Springdoc, you can add a OpenApiCustomiser bean, see examples
Example with Springdoc OpenAPI
#Bean
public OpenApiCustomiser openApiCustomiser() {
return openApi -> openApi.getComponents().getSchemas().values().forEach( s -> s.setAdditionalProperties(false));
}
Example with Springfox framework
#Component
#Order(Ordered.HIGHEST_PRECEDENCE + 1)
public class OpenApiTransformationFilter implements WebMvcOpenApiTransformationFilter
{
public boolean supports(#NotNull DocumentationType delimiter)
{
return SwaggerPluginSupport.pluginDoesApply(delimiter);
}
#Override
public OpenAPI transform(OpenApiTransformationContext<HttpServletRequest> context)
{
OpenAPI openApi = context.getSpecification();
openApi.getComponents().getSchemas().values().forEach(schema -> schema.setAdditionalProperties(false));
return openApi;
}
}
One workaround might be define a dummy class that contains the type information, then use that as the #Schema#implementation class in your #APIResponse.
static class YourTypeMap extends java.util.HashMap<String, YourType> {};
Then:
#APIResponse(
responseCode = "200",
content = #Content(
mediaType = "application/json",
schema = #Schema(implementation = YourTypeMap.class)))
Credits: MikeEdgar

SpringBoot – add Cache Control Headers in Rest methods

I have a basic SpringBoot 2.0.5.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR
I have created this Rest method:
#GetMapping(path = "/users/notifications", consumes = "application/json", produces = "application/json")
public ResponseEntity<List<UserNotification>> userNotifications(
#RequestHeader(value = "Authorization") String authHeader) {
User user = authUserOnPath("/users/notifications", authHeader);
List<UserNotification> menuAlertNotifications = menuService
.getLast365DaysNotificationsByUser(user);
return ResponseEntity.ok(menuAlertNotifications)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS));;
}
and I want to add a Cache Control Headers, but I don't know how...
I got a compilation error:
Multiple markers at this line
- The method cacheControl(CacheControl) is undefined for the type
ResponseEntity<List<UserNotification>>
- CacheControl
- cacheControl
I also add this property in application.properties
security.headers.cache=false
When you use ResponseEntity.ok(T body) the return type is ResponseEntity<T> as it is a shortcut method to add data to the body part of the ResponseEntity.
You need the builder object that is created via ResponseEntity.ok() with no param which returns a Builder object. You then add your data yourself on via the body method.
So your code should be like this
#GetMapping(path = "/users/notifications", consumes = "application/json", produces = "application/json")
public ResponseEntity<List<UserNotification>> userNotifications(
#RequestHeader(value = "Authorization") String authHeader) {
User user = authUserOnPath("/users/notifications", authHeader);
List<UserNotification> menuAlertNotifications = menuService
.getLast365DaysNotificationsByUser(user);
return ResponseEntity.ok().cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS)).body(menuAlertNotifications);
}

Internationalization Support for backend Messages in Spring Application

I am trying to implement internationalization in my application. I already went through many blogs & tutorials which explain how we can implement it using different libraries.
The one I am planning to use is I18N with spring.
My application's structure is something like this :-
My application's front end (based on Angular2) consumes Rest APIs that are exposed from the backend.
I am using Spring Rest for implementing the Rest APIs. For every API call I am preparing & sending appropriate messages to UI.
Now by default messages are in English but now I want to add internationalization support to it. How can I do it ?
Below is the example of one of the Rest API that I am exposing and the way I'm sending the messages :-
#CrossOrigin(methods = RequestMethod.POST)
#PostMapping(value = "/user/resetUserAccount", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<String> resetUserAccount(#RequestBody InputObj inputObj) {
boolean isUserAccountReset = userService.resetUserAccount(inputObj);
if (isUserAccountReset) {
return new ResponseEntity<String>(successResponse("User Account Reset Successful").toString(), HttpStatus.OK);
}
return new ResponseEntity<String>(failureResponse("Failed to Reset User Account").toString(), HttpStatus.CONFLICT);
}
I have written 2 helper methods given below that prepare the response messages :-
private JSONObject successResponse(String apiMessage) {
JSONObject success = new JSONObject();
success.put("reponse", "success");
success.put("message", apiMessage);
return success;
}
private JSONObject failureResponse(String apiMessage) {
JSONObject failure= new JSONObject();
success.put("reponse", "failure");
success.put("message", apiMessage);
return failure;
}
Add the following to the configuration class
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.US); // Set default Locale as US
return slr;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("i18n/messages"); // name of the resource bundle
source.setUseCodeAsDefaultMessage(true);
return source;
}
Create a new directory named i18n inside resources directory and put your messages.properties and the other internationalized property files like messages_ru.properties, messages_fr.properties etc inside it. Create message key and values like below:
messages.properties
msg.success=User Account Reset Successful
msg.failure=Failed to Reset User Account
Now inject the MessageSource Bean where you want to internationalize the message, i.e. your controller and then accept the Locale from headers in controller method and get messages from properties files like below:
#Autowired
private MessageSource messageSource;
#CrossOrigin(methods = RequestMethod.POST)
#PostMapping(value = "/user/resetUserAccount", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ResponseEntity<String> resetUserAccount(#RequestHeader("Accept-Language") Locale locale, #RequestBody InputObj inputObj) {
boolean isUserAccountReset = userService.resetUserAccount(inputObj);
if (isUserAccountReset) {
return new ResponseEntity<String>(successResponse(messageSource.getMessage("msg.success",null,locale)).toString(), HttpStatus.OK);
}
return new ResponseEntity<String>(failureResponse(messageSource.getMessage("msg.failure",null,locale)).toString(), HttpStatus.CONFLICT);
}

Uploading a file via Spring

I have a dev server which revolves angular 2 at localhost: 4200, and tomcat with Spring on localhost: 8080.
I try to upload a file to the server in the following manner:
angular code:
uploadAvatar(file:File){
let xhr = new XMLHttpRequest()
xhr.open("POST",`http://localhost:8080/api/upload/avatar`)
xhr.setRequestHeader("Content-Type","multipart/form-data")
xhr.send(file)
}
Controller code Spring:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST)
public String uploadFile(MultipartFile file){
log.info(file);
return file.getName();
}
But after trying to download a file error appears in the java-console:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request;
nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
How do I fix this error?
Thank you.
UPDATE 1
The "duplicate" is used with Spring MVC + JSP, I'm trying to download a file via Ajax. And the version of the decision does not help me given there.
UPDATE 2
Spring Boot(v1.4.3.RELEASE)
I use the java configuration, if you want I will give an example of a full configuration.
I found the solution to my problem, I postorabs below describe in detail what I did for this.
As the presentation I use Angular 2, sending the file takes place in the following manner.
uploadFile(file:File){
let form = new FormData();
form.append("file",file)
let xhr = new XMLHttpRequest()
xhr.open("POST",`${URL}/api/upload/avatar`)
xhr.send(form)
}
Content-Type and the boundary in this case, are automatically linked.
The following manipulations need to be done on the server Stronie:
Add two bean:
#Bean(name = "commonsMultipartResolver")
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
#Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("10MB");
factory.setMaxRequestSize("10MB");
return factory.createMultipartConfig();
}
The controller looks like this:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(#RequestPart MultipartFile file){
log.info(file);
return file.getOriginalFilename();
}

Springfox Swagger adding response status 200 to POST and PUT

I am using springfox-swagger2 version 2.6.1, and it is inserting HTTP 200 response messages for PUT and POST operations automatically, despite my attempts to configure it not to do so (I do not use response status 200 for POST or PUT, but 201 and 204, respectively); see below screenshot:
I have seen answers to similar questions where the authors suggest adding a #ResponseStatus annotation to your controller to "fix" it, but this becomes inflexible and goes against Spring's own documentation regarding the use of ResponseEntity vs #ResponseStatus for rest APIs. Examples:
How to change the response status code for successful operation in Swagger?
and
https://github.com/springfox/springfox/issues/908
Is there any other way to force Springfox Swagger not to add this 200 OK status code?
My Docket configuration:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.select().
apis(RequestHandlerSelectors.any()).
paths(paths()).
build()
.pathMapping("/")
.apiInfo(apiInfo())
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(newRule(
typeResolver.resolve(DeferredResult.class, typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)
));
...and the actual API endpoint declaration:
#RequestMapping(method = RequestMethod.POST, produces = "application/json")
#ApiOperation(value = "Create a new enrolment", code = 201)
#ApiResponses(value = {
#ApiResponse(code = 201, message = "New enrolment created",
responseHeaders = #ResponseHeader(name = "Location", description = "The resulting URI of the newly-created enrolment", response = String.class))})
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Void> saveNewEnrolment(#ApiParam(value = "Enrolment to save", required = true) #RequestBody final Enrolment enrolment) {
// implementation code removed; "location" header is created and returned
return ResponseEntity.created(location).build();
}
Try adding #ResponseStatus(HttpStatus.CREATED) or #ResponseStatus(HttpStatus.NO_CONTENT) annotation. Taken from here
remove the
produces = "application/json"
part from the #RequestMapping annotation since your response is of type Void.class

Categories