I'm facing with a very strange behaviour in my application.
#RestController
#RequestMapping("users")
#Validated
#Api(tags = "User controller v2")
public class UserController {
#ApiOperation(value = "Get details of a user by id")
#ApiResponses(value = {#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 401, message = "Authorization information is missing or invalid."),
#ApiResponse(code = 403, message = "Requested resource is forbidden for current user"),
#ApiResponse(code = 200, message = "OK")})
#GetMapping("/{userId}")
public ResponseEntity<User> getUserDetails(#PathVariable #NotNull String userId) {
// calling service methods
}
}
This works good and it's how I see it on /swagger-ui.html
But if I add any swagger description to my request params like a default value, I can't see this method in the swagger anymore:
#ApiOperation(value = "Get details of a user by id")
#ApiResponses(value = {#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 401, message = "Authorization information is missing or invalid."),
#ApiResponse(code = 403, message = "Requested resource is forbidden for current user"),
#ApiResponse(code = 200, message = "OK")})
#GetMapping("/{userId}")
public ResponseEntity<User> getUserDetails(
#ApiParam(defaultValue = "12345") #PathVariable #NotNull String userId) {
// calling service methods
}
This is how my swagger configured
public class WebConfiguration {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.securitySchemes(Collections.singletonList(apiKey()))
.securityContexts(Collections.singletonList(securityContext()))
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.test"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My test service")
.version("2.0")
.build();
}
private ApiKey apiKey() {
return new ApiKey("JWT", "Authorization", "header");
}
private SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
AuthorizationScope[] authorizationScopes = new AuthorizationScope[] {authorizationScope};
return Collections.singletonList(new SecurityReference("JWT", authorizationScopes));
}
}
Also, in my app logs I see such message:
ERROR [,,,] 36927 --- [ restartedMain] s.d.s.web.scanners.ApiDescriptionReader : Skipping process path[/api/v2/users/{userId}], method[getUserDetails] as it has an error.
Any ideas why it may happen and how to solve it?
While debugging Spring context I found such error in the ApiDescriptionReader class:
java.lang.NoSuchMethodError: io.swagger.annotations.ApiParam.allowEmptyValue()Z
I have solved this issue by adding this dependency:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.2</version>
</dependency>
Related
#Path("/avManageThemesService")
#Api(description = "the avManageThemesService API")
#javax.annotation.Generated(value =
"org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2022-09-
02T19:11:34.805+05:30[Asia/Calcutta]")public interface AvManageThemesServiceApi {
#PUT
#Consumes({ "application/json" })
#Produces({ "application/json" })
#ApiOperation(value = "Update Theme", notes = "", tags={ "Update Theme" })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = SaveThemeResponse.class),
#ApiResponse(code = 400, message = "Bad Request", response = ErrorDetails.class),
#ApiResponse(code = 401, message = "Unauthorized", response = ErrorDetails.class) })
Response updateTheme(#Valid SaveThemeJSONRequest saveThemeJSONRequest);
}
#Path("/avListThemes")
#Api(description = "the avListThemes API")
#javax.annotation.Generated(value =
"org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2022-09-
02T19:11:34.805+05:30[Asia/Calcutta]")public interface AvListThemesApi {
#GET
#Produces({ "application/json" })
#ApiOperation(value = "Get all themes", notes = "", tags={ "getAllThemes" })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = GetThemesResponse.class),
#ApiResponse(code = 400, message = "Bad Request", response = ErrorDetails.class),
#ApiResponse(code = 401, message = "Unauthorized", response = ErrorDetails.class) })
Response getAllThemes();
}
public class Example implements AvManageThemesServiceApi, AvListThemesApi{
#Override
public Response updateTheme(SaveThemeJSONRequest aSaveThemeJSONRequest){
return Response.ok(200).build();
}
#Override
public Response getAllThemes(){
return Response.ok(200).build();
}
}
when i hit url from postman, it only gives response for the 1st implemented interface(here:AvManageThemesServiceApi) and gives response RESTEASY003210: Could not find resource for full path: for the other one(here:AvListThemesApi). Reverse happens when interfaces positions are exhanged.
Though when implemented with separate controllers, both works fine.
I'm creating an API micro-service that provides file storage with AWS. I'm putting together the Swagger and Controller, and we need to be able to allow users to upload a file on the Swagger. The problem is our controller is set up as an interface instead of a class, and solutions from Google/SO aren't making the cut to be able to work with interfaces. To clarify, I don't need to manipulate the file at all, just take it in. Our internal implementation methods will take then send it off to S3.
This is using Java 11, AWS S3, Spring Boot, and Swagger 2. I've tried using #ApiParam and #FormDataParam inside the method createFile, but I've gotten two different errors:
method must be abstract
annotations are not allowed here.
#RequestMapping(value = {"v3/registration/documents", "v4/registration/documents"})
#RestController
#Api(
value = "file-storage",
description = "File storage service",
tags = {"file-storage"})
public interface FileController {
#PostMapping(
value = "/{salesPlanAff}",
produces = {MediaType.APPLICATION_JSON_VALUE},
consumes = {MediaType.APPLICATION_JSON_VALUE})
#ApiOperation(value = "Upload a file")
#ApiResponses(
value = {
#ApiResponse(code = 200, message = "Success", response = FileResponseDTO.class),
#ApiResponse(code = 201, message = "Created"),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Internal Server Error")
})
void createFile(
#PathVariable(required = true, name = "webSessionId") String webSessionId,
#PathVariable(required = false, name = "salesPlanAff") String salesPlanAff);
What I expected is to have a button on my swagger page allowing file upload, didn't quite expect this much difficulty in adding file upload.
I have a FileUpload in my swagger page and it works like a charm. The only difference from your is that I am not doing it on an interface...
import org.springframework.web.multipart.MultipartFile;
...
#ApiOperation(value = "Analyse the identifiers in the file")
#RequestMapping(value = "/form", method = RequestMethod.POST, produces = "application/json")
#ResponseBody
public AnalysisResult getPostFile( #ApiParam(name = "file", value = "The file")
#RequestPart MultipartFile file,
HttpServletRequest r) {
UserData ud = controller.getUserData(file);
return controller.analyse(ud, r, file.getOriginalFilename());
}
I trimmed off a little bit this code, but you can find a the original one in our repository
Also, working version of this code can be executed tested here
Thanks
I figured out how to get the annotations in, part of it stemmed from typos. For anyone interested, here's the solution:
public interface FileController {
#PostMapping(
value = "/{salesPlanAff}",
produces = {MediaType.APPLICATION_JSON_VALUE},
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
#ApiOperation(value = "Upload a file")
#ApiResponses(
value = {
#ApiResponse(code = 200, message = "Success", response = FileResponseDTO.class),
#ApiResponse(code = 201, message = "Created"),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 403, message = "Forbidden"),
#ApiResponse(code = 404, message = "Not Found"),
#ApiResponse(code = 500, message = "Internal Server Error")
})
void createFile(
#PathVariable(required = true, name = "webSessionId") String webSessionId,
#PathVariable(required = false, name = "salesPlanAff") String salesPlanAff,
#ApiParam(required = true, value = "Document to be uploaded")
#RequestPart MultipartFile multipartFile,
#ApiParam(required = true, value = "File Type")
#QueryParam("documentType") String documentType);
I have a spring boot application. I have chosen to implement my controllers as an interface defining the endpoint and its respective implementation (i.e EndpointX, EndpointXController w/ EndpointXController being the implementation). I have all of my annotations for swagger in the interface files to prevent cluttering of the implementation class; however, I see duplicate endpoints on swagger UI as shown below:
This is my docket setup:
#Bean
public Docket customImplementation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.withMethodAnnotation(RequestMapping.class))
.paths(PathSelectors.ant("/consent/*"))
.build()
.directModelSubstitute(java.time.LocalDate.class, java.sql.Date.class)
.directModelSubstitute(java.time.OffsetDateTime.class, java.util.Date.class)
.apiInfo(apiInfo());
}
How can I tell swagger/swagger-ui to only show one endpoint for the rest service? I.e consent-api-controller wouldn't be shown or picked up by swagger.
Edit: Posted Controller and interface code
#Controller
public class ConsentApiController implements ConsentApi {
#Autowired
private IConsentApiService consentApiService;
private final ObjectMapper objectMapper;
private final HttpServletRequest request;
#Autowired
public ConsentApiController(ObjectMapper objectMapper, HttpServletRequest request) {
this.objectMapper = objectMapper;
this.request = request;
}
#Override
public Optional<ObjectMapper> getObjectMapper() {
return Optional.ofNullable(objectMapper);
}
#Override
public Optional<HttpServletRequest> getRequest() {
return Optional.ofNullable(request);
}
public ResponseEntity getConsent(#ApiParam(value = "Identifier for the consent object to be retrieved", required = true) #Valid #RequestBody ConsentReadRequestParent consentReadRequestParent) {
return consentApiService.getConsent(consentReadRequestParent);
}
public ResponseEntity postConsent(#ApiParam(value = "Populated consent object") #Valid #RequestBody ConsentParentRequest consentObj) {
// Pass request to service where it will be split into DTOs and passed to DAOs and get response
return consentApiService.postConsent(consentObj);
}
public ResponseEntity searchConsent(#Valid #RequestBody SearchParentRequest spr){
return consentApiService.searchConsent(spr);
}
}
#Api(value = "consent")
public interface ConsentApi {
default Optional<ObjectMapper> getObjectMapper() {
return Optional.empty();
}
default Optional<HttpServletRequest> getRequest() {
return Optional.empty();
}
default Optional<String> getAcceptHeader() {
return getRequest().map(r -> r.getHeader("Accept"));
}
#ApiOperation(value = "The Read Consent API is a resource that conforms to a RESTful syntax to retrieve the details of a single consent record.", nickname = "getConsent", notes = "Cannot read without parameters. Minimum of 2 characters in each field. Maximum of 50 characters in each field. Should be able to handle special characters.", response = Consent.class, tags = {"consent",})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = Consent.class),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 405, message = "Method Not Allowed"),
#ApiResponse(code = 500, message = "Internal Server Error"),
#ApiResponse(code = 604, message = "Could Not Retrieve Data for Consent"),
#ApiResponse(code = 714, message = "Consent Not Found Matching Input Values")})
#RequestMapping(value = "/consent/read",
method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<?> getConsent(#ApiParam(value = "Identifier for the consent object to be retrieved.", required = true) #Valid #RequestBody ConsentReadRequestParent consentReadRequestParent);
#ApiOperation(value = "The Create Consent API is a resource that conforms to a RESTful syntax to persist a single consent record.", nickname = "postConsent", notes = "<business and backend logic/requirements>", response = ConsentResponseParent.class, tags = {"consent",})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = ConsentResponseParent.class),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 405, message = "Method Not Allowed"),
#ApiResponse(code = 500, message = "Internal Server Error")})
#RequestMapping(value = "/consent/create",
method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<?> postConsent(#ApiParam(value = "filled-out consent object") #Valid #RequestBody ConsentParentRequest consentObj);
#ApiOperation(value = "The Search Consent API is a resource that conforms to a RESTful syntax to query consent records.", response = SearchParentResponse.class, tags = {"consent",})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK", response = SearchParentResponse.class),
#ApiResponse(code = 400, message = "Bad Request"),
#ApiResponse(code = 405, message = "Method Not Allowed"),
#ApiResponse(code = 500, message = "Internal Server Error")})
#RequestMapping(value = "/consent/search",
method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
ResponseEntity<?> searchConsent(#Valid #RequestBody SearchParentRequest spr);
}
Remove attribute "tags" from #ApiOperation annotation. Adding tags will make the api appear under the context menu as well as under its own separate menu.
Another approach is jus the #Api for the controller class:
#Controller
#Api(tags = "consent")
public class ConsentApiController implements ConsentApi {
Or into the interface:
#Api(value = "consent", tags = "consent")
public interface ConsentApi {
You could explicitly exclude the controllers that should not be shown by moving the interfaces to a different package and using
.apis(RequestHandlerSelectors.basePackage("my.impl.package"))
or by writing your own, custom set of predicates and passing them into .apis().
However this would me more like a workaround. Are you certain there are no #RequestMapping annotations used in the implementing classes? I could not replicate this behaviour locally.
I want to avoid Mass Assignment: Insecure Binder Configuration issue for our application which is written in Jersey framework. I was thinking is there any other way we can use #InitBinder from spring and for each request to this service only allow to set the allowed properties and set all other properties to null.
#Controller
#Path("/ar")
#Api(tags = { "Request" })
public class RequestService extends AbstractService {
static final Logger logger = Logger
.getLogger("RequestServiceLogger");
#InitBinder
public void customizeBinding (WebDataBinder binder) {
System.out.println("Inside init binder ============== ");
//I want to allow the allowed field only for AccountRequest object
binder.setAllowedFields(allowedFields);
}
#Path("/submitrequest")
#POST
#Consumes({ "application/json" })
#Produces({ "application/json" })
#ApiOperation(value = "Validates a request", notes = "Validates a request", response = RequestResponse.class)
#ApiImplicitParams({ #io.swagger.annotations.ApiImplicitParam(name = "Auth", value = "value", required = true, dataType = "string", paramType = "header") })
#ApiResponses({
#io.swagger.annotations.ApiResponse(code = 200, message = "OK", responseHeaders = { #io.swagger.annotations.ResponseHeader(name = "X-ResponseTime", description = "Total Time Taken", response = String.class) }, response = RequestResponse.class),
#io.swagger.annotations.ApiResponse(code = 400, message = "Bad Request", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 401, message = "Unauthorized", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 403, message = "Forbidden", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 404, message = "Not Found", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 405, message = "Method Not Allowed", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 415, message = "Unsupported Media Type", response = com.model.ErrorDetail.class),
#io.swagger.annotations.ApiResponse(code = 500, message = "Internal Server error", response = com.ErrorDetail.class) })
public Response submitRequest(#ApiParam(value = "AccountRequest JSON input data.", required = true) AccountRequest accountRequest,
#Context HttpServletRequest request) throws Exception {
System.out.println("Inside submitRequest ============== ");
}
}
*** If there are any other alternative ways to filter request object properties please let me know.
I have an API like this
#GET
#Path("/tasks")
#Consumes({ "application/json" })
#Produces({ "application/json" })
#io.swagger.annotations.ApiOperation(value = "List all tasks", notes = "", response = Tasks.class, authorizations = {
#io.swagger.annotations.Authorization(value = "api_key")
}, tags={ "tasks", })
#io.swagger.annotations.ApiResponses(value = {
#io.swagger.annotations.ApiResponse(code = 200, message = "An array of tasks", response = Tasks.class),
#io.swagger.annotations.ApiResponse(code = 200, message = "unexpected error", response = Tasks.class) })
public Response getTasks(#Context SecurityContext securityContext) throws NotFoundException {
return delegate.getTasks(securityContext);
}
A parameter is Jersey SecurityContext. My question is,
What can i do with this securitycontext?
And how has a request to this api look like?
I have read this https://jersey.java.net/documentation/latest/security.html but i didn't get a full understanding of the SecurityContext at all.
Wished Behaviour would be send two informations as strings like a user and a key if it is possible.