I am using Swagger version 2.4.0 and Spring Boot 2.0.4.RELEASE and have an application with several API endpoints and with default Swagger configuration having default produces header value set to application/json.
SwaggerConfig.java
#Configuration
#EnableSwagger2
public class SwaggerConfig {
private static final Set<String> DEFAULT_PRODUCES_AND_CONSUMES = ImmutableSet.of(
"application/json"
);
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.produces(DEFAULT_PRODUCES_AND_CONSUMES)
.consumes(DEFAULT_PRODUCES_AND_CONSUMES);
}
}
And with API endpoint set up being.
ApiEndpoint.java
#Consumes(MediaType.APPLICATION_JSON)
#Api(value = "/transform", protocols = "http, https")
#RequestMapping(value = "/transform")
public interface ApiEndpoint {
#POST
#RequestMapping(value = "/text", method = RequestMethod.POST)
#ApiOperation(value = "Transform to plain text", produces = MediaType.TEXT_PLAIN)
#CrossOrigin
String transformToText(#RequestBody TransformRequest transformRequest) throws Exception;
}
Now I want this endpoint to produce response with content type being only plain text, but SwaggerConfig adds application/json option as default one. So in order to correctly use this endpoint I would need to change Response Content Type from application/json to text/plain every time, which would get annoying pretty quick considering that this endpoint is used for testing. Is there a way to override SwaggerConfig or to add a parameter so text/plain is the only option or at least to set the text/plain as the default option only for this one endpoint?
you just have to define the response content type in your requestMapping annotation.
That is,
#RequestMapping(value = "/text", method = RequestMethod.POST)
will be replaced by,
#RequestMapping(value = "/text", method = RequestMethod.POST, produces="text/plain")
Means, you have to define in requestMapping that what type of content this mapping will going to return.
Note : Will be good practice if you use
#PostMapping(value = "/text", produces="text/plain")
Hope, the solution will work fine.
just specifying type in order in the endpoint annotation, example:
atribute produces receive an array, so you can put more than one type
#PostMapping(value = "/text", produces = { MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_VALUE })
I advise removing produces in #Bean, as its API contains endpoint that did not always follow the standard idea of "application/json"
Related
Here is my controller request
#PostMapping("/requestApproval")
#PreAuthorize("hasRole('USER')")
public ResponseEntity<MessageResponse> requestApproval(#DTO(TripIdDTO.class) Trip requestingApprovalTrip) {
this.tripService.requestApproval(requestingApprovalTrip);
return ResponseEntity.ok().body(new MessageResponse("Trip status has been changed to WAITING_FOR_APPROVAL!"));
}
The annotation takes the request body from JSON format, converts it into DTO and than into the Trip entity type.
Swagger generates the parameters using the fields of the Trip entity. Is there a way to customize swagger to use the TripIdDTO class to create the parameters for the documentation isntead of Trip?
Since the project doesn't obey the usual contract between Swagger and Spring Boot, some additional settings should be done to make it work as wish.
Step 1 Register the real API model
#Configuration
// Only need for swagger 2.9.2
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket api() {
TypeResolver resolver = new TypeResolver();
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build().additionalModels(resolver.resolve(MessageDto.class));
}
}
Step 2 Tell Swagger the real API model used
#RestController
public class TestController {
#RequestMapping(value = "/message",
produces = {"application/json;charset=utf-8"},
consumes = {"application/json;charset=utf-8"},
method = RequestMethod.POST)
#ApiImplicitParams({
#ApiImplicitParam(name = "request", required = true,
dataType = "MessageDto", paramType = "body")
})
ResponseEntity<?> createMessage(Message message) {
return null;
}
}
By doing this, we declare a param with the type MessageDto and should be fetched from the HTTP request body.
Step 3 Tell Swagger to ignore the existed params
#Data
public class Message {
#ApiModelProperty(hidden = true)
private Integer code = null;
#ApiModelProperty(hidden = true)
private String message = null;
}
The Message class has two filed with public get methods(I think it's also your case). Since there is no #RequestBody besides Message, Swagger will treat all fields of Message as query params. To make these useless parts invisible in the API doc, we should mark them as hidden.
P.S.
This function works properly in Swagger 2.9.2, and not work in 3.0.0. It is a bug and this needs some time to get fixed I guess. You can find more information in springfox issue #3435.
All the codes can be found at this repo - swagger demo.
I have the API:
#GetMapping(path = "/users/{userId}")
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
//logic here
}
it returns JSON response, as it should.
And there's another app that I don't have access to, and it calls my API as, for example, GET /users/123.xml in order to receive XML response.
But in this case, my API fails with 400 error, because it cannot parse 123.xml into Long.
Option #GetMapping(value = {"/users/{userId}", "/users/{userId}.xml"}) fails with the same error.
What can I do to respond with XML syntax when calling /{userId}.xml and in the mean time, respond with JSON syntax when calling /{userId}?
EDIT:
I want it to do without specifically adding 'Accept' headers, and without writing any additional logic, that'll parse {userId}.xml and then set the appropriate response type.
That's can be done by using a ContentNegotiationConfigurer, you can configure it as follow :
#Configuration
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
}
It should work fine with your endpoint :
#GetMapping(path = "/users/{userId}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
return new ResponseEntity<>(userService.get(userId), HttpStatus.OK);
}
The easiest solution that I can think right off will be to have an optional Request parameter "responseType" with default value as json, and if someone wants XML response, they can call the url like :GET /users/123?responseType=xml
Since the default value of the parameter will be 'json' and it will have property "required= false", you wouldn't need to worry in use cases where json response is desired, and if someone wants XML response, they can add the optional RequestParam. Also, I guess you will need to specify produces with json and xml return types for the controller to let spring-boot know that it can produce different kinds of responses, something like -
#RequestMapping(value = "/users/{userid}", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE }, consumes = MediaType.ALL_VALUE)
public ResponseEntity<User> getuserById(#PathVariable String
userid,#RequestParam(required=
false,defaultValue="json",name="responseType"),#RequestHeader ("content-type") String
contentType)
)
EDIT : You can use either the request param or the Request header, I provided both in the example for your reference
As an owner of the API you should declare what kind of a responses you are able to produce - in your case it is either JSON or XML:
#GetMapping(path = "/users/{userId}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
return new ResponseEntity<>(userService.get(userId), HttpStatus.OK);
}
Any client of the API can now choose which response format is preferred using Accept header - for example Accept: application/xml. Spring will respect that and return response in requested format.
To make it work you need to add additional dependency that will be used by Spring to produce XML responses:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
If you really need to go in /users/123.xml direction you'll have to change userId type to String and parse it yourself like this:
#GetMapping(path = "/users/{userId}")
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") String userId) {
if (hasXMLExtension(userId)) {
return ResponseEntity
.ok()
.contentType(MediaType.XML)
.body(requestdUser);
} else {
return ResponseEntity
.ok()
.contentType(MediaType.JSON)
.body(requestdUser);
}
}
I am using Spring Boot 1.5.2.RELEASE and not able to incorporate JSR - 349 ( bean validation 1.1 ) for #RequestParam & #PathVariable at method itself.
For POST requests, if method parameter is a Java POJO then annotating that parameter with #Valid is working fine but annotating #RequestParam & #PathVariable with something like #NotEmpty, #Email not working.
I have annotated controller class with Spring's #Validated
There are lots of questions on SO and I have commented on this answer that its not working for me.
Spring Boot includes - validation-api-1.1.0.Final.jar and hibernate-validator-5.3.4.Final.jar .
Am I missing anything?
Example code ,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(#Email #NotEmpty #RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
response.setResponse(Constants.SUCCESS);
response.setMessage("testValidated");
logger.error("Validator Not called");
return response;
}
Below handler is never called when I send empty values or not well formed email address for email & control always goes to with in testValidated method.
#ExceptionHandler(ConstraintViolationException.class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBean handle(ConstraintViolationException exception) {
StringBuilder messages = new StringBuilder();
ResponseBean response = new ResponseBean();
exception.getConstraintViolations().forEach(entry -> messages.append(entry.getMessage() + "\n"));
response.setResponse(Constants.FAILURE);
response.setErrorcode(Constants.ERROR_CODE_BAD_REQUEST);
response.setMessage(messages.toString());
return response;
}
ResponseBean<T> is my application specific class.
I had asked the question after more than two days of unsuccessful hit & trial. Lots of confusing answers are out there because of confusions around Spring Validations and JSR validations, how Spring invokes JSR validators, changes in JSR standards & types of validations supported.
Finally, this article helped a lot.
I solved problem in two steps,
1.Added following beans to my Configuration - without these beans , nothing works.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor mvProcessor = new MethodValidationPostProcessor();
mvProcessor.setValidator(validator());
return mvProcessor;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
return validator;
}
2.Placed Spring's #Validated annotation on my controller like below,
#RestController
#RequestMapping("/...")
#Validated
public class MyRestController {
}
Validated is - org.springframework.validation.annotation.Validated
This set up doesn't affected #Valid annotations for #RequestBody validations in same controller and those continued to work as those were.
So now, I can trigger validations like below for methods in MyRestController class,
#RequestMapping(method = RequestMethod.GET, value = "/testValidated" , consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseBean<String> testValidated(
#Email(message="email RequestParam is not a valid email address")
#NotEmpty(message="email RequestParam is empty")
#RequestParam("email") String email) {
ResponseBean<String> response = new ResponseBean<>();
....
return response;
}
I had to add another handler in exception handler for exception - ConstraintViolationException though since #Validated throws this exception while #Valid throws MethodArgumentNotValidException
Spring #Validated #Controller did not mapped when adding #Validated. Removal of any inheritance from controller itself did help. Otherwise Sabir Khan's answer worked and did help.
With multiple Spring controllers that consume and produce application/json, my code is littered with long annotations like:
#RequestMapping(value = "/foo", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
Is there a way to produce a "composite/inherited/aggregated" annotation with default values for consumes and produces, such that I could instead write something like:
#JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
How do we define something like #JSONRequestMapping above? Notice the value and method passed in just like in #RequestMapping, also good to be able to pass in consumes or produces if the default isn't suitable.
I need to control what I'm returning. I want the produces/consumes annotation-methods so that I get the appropriate Content-Type headers.
As of Spring 4.2.x, you can create custom mapping annotations, using #RequestMapping as a meta-annotation. So:
Is there a way to produce a "composite/inherited/aggregated"
annotation with default values for consumes and produces, such that I
could instead write something like:
#JSONRequestMapping(value = "/foo", method = RequestMethod.POST)
Yes, there is such a way. You can create a meta annotation like following:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#RequestMapping(consumes = "application/json", produces = "application/json")
public #interface JsonRequestMapping {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "method")
RequestMethod[] method() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "params")
String[] params() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "headers")
String[] headers() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "consumes")
String[] consumes() default {};
#AliasFor(annotation = RequestMapping.class, attribute = "produces")
String[] produces() default {};
}
Then you can use the default settings or even override them as you want:
#JsonRequestMapping(method = POST)
public String defaultSettings() {
return "Default settings";
}
#JsonRequestMapping(value = "/override", method = PUT, produces = "text/plain")
public String overrideSome(#RequestBody String json) {
return json;
}
You can read more about AliasFor in spring's javadoc and github wiki.
The simple answer to your question is that there is no Annotation-Inheritance in Java. However, there is a way to use the Spring annotations in a way that I think will help solve your problem.
#RequestMapping is supported at both the type level and at the method level.
When you put #RequestMapping at the type level, most of the attributes are 'inherited' for each method in that class. This is mentioned in the Spring reference documentation. Look at the api docs for details on how each attribute is handled when adding #RequestMapping to a type. I've summarized this for each attribute below:
name: Value at Type level is concatenated with value at method level using '#' as a separator.
value: Value at Type level is inherited by method.
path: Value at Type level is inherited by method.
method: Value at Type level is inherited by method.
params: Value at Type level is inherited by method.
headers: Value at Type level is inherited by method.
consumes: Value at Type level is overridden by method.
produces: Value at Type level is overridden by method.
Here is a brief example Controller that showcases how you could use this:
package com.example;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping(path = "/",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
method = {RequestMethod.GET, RequestMethod.POST})
public class JsonProducingEndpoint {
private FooService fooService;
#RequestMapping(path = "/foo", method = RequestMethod.POST)
public String postAFoo(#RequestBody ThisIsAFoo theFoo) {
fooService.saveTheFoo(theFoo);
return "http://myservice.com/foo/1";
}
#RequestMapping(path = "/foo/{id}", method = RequestMethod.GET)
public ThisIsAFoo getAFoo(#PathVariable String id) {
ThisIsAFoo foo = fooService.getAFoo(id);
return foo;
}
#RequestMapping(path = "/foo/{id}", produces = MediaType.APPLICATION_XML_VALUE, method = RequestMethod.GET)
public ThisIsAFooXML getAFooXml(#PathVariable String id) {
ThisIsAFooXML foo = fooService.getAFoo(id);
return foo;
}
}
You shouldn't need to configure the consumes or produces attribute at all. Spring will automatically serve JSON based on the following factors.
The accepts header of the request is application/json
#ResponseBody annotated method
Jackson library on classpath
You should also follow Wim's suggestion and define your controller with the #RestController annotation. This will save you from annotating each request method with #ResponseBody
Another benefit of this approach would be if a client wants XML instead of JSON, they would get it. They would just need to specify xml in the accepts header.
You can use the #RestController instead of #Controller annotation.
There are 2 annotations in Spring: #RequestBody and #ResponseBody. These annotations consumes, respectively produces JSONs. Some more info here.
I am new to Spring and Rest Endpoints.
I have a controller, which accepts #RequestParam and returns a JSON Response.
By default the #RequestParam required = "true", which is how I need it.
I am using Spring 3.1.3
This is my Get Method in the controller:
#Controller
#RequestMapping("/path")
public class MyController{
#RequestMapping(value = "/search/again.do", method = RequestMethod.GET, produces = {
"application/json"
})
public ResponseEntity<?> find(#RequestParam(value = "test", required = true) final String test) {
return new ResponseEntity<String>("Success ", HttpStatus.OK);
}
}
When I send a get with the request param it hits the endpoint , which is how I expect.
Example : path/search/again.do?test=yes
Everything is perfect.
This is where I am having issue:
When I send a Get with that value missing:
Example: path/search/again.do
I get a 400 Bad Request. May be this is correct.
But what I want to achieve is. When the required value is missing in the GET request.
I can send a JSON response as that #RequestParam Value test is missing.
Can anybody guide me how to achieve this.
I am not sure what I am missing.
Thanks in advance.
If you look closely at your code, you'll see that the answer is staring right at you. Just change required to false and you should be good to go. When the user doesn't provide a value for GET parameter test, then you can return a special message.
#Controller
#RequestMapping("/path")
public class MyController {
#RequestMapping(value = "/search/again.do", method = RequestMethod.GET, produces = {
"application/json"
})
public ResponseEntity<?> find(#RequestParam(value = "test", required = false) final String test) {
if (test == null) {
return new ResponseEntity<String>("test parameter is missing", HttpStatus.OK);
}
else {
return new ResponseEntity<String>("Success ", HttpStatus.OK);
}
}
}
Solution 1: You can use custom #ExceptionHandler in your controller, e.g
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<?> paramterValidationHandler(HttpServletResquest request){
//validate the request here and return an ResponseEntity Object
}
Solution 2: Would be custom spring ErrorController which I never have tried myself but it possible to override it.
Solution 3: You can write an ControllerAdvice for a global controller exception handling.
Well if you set the parameter test is required. U just can't send the request without that param. Try to change the param required= false and handle the missing param in the method. You can us something likeif(test==null) throw new Exception("Param test missing")