Example and description for #requestpart - java

The requirement is a user will upload files to spring boot (ReST request) using angular. The ReST interface store the file into gitlab for further processing. Using #RequestPart annotation, content with a different name will be retrieved from a request and placed into Gitlab. Implementation will look like as follows:
#ApiOperation(value = "Upload java files into Gitlab")
#RequestMapping(method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = "application/json")
#ApiResponses(value = { #ApiResponse(code = 200, response = CustomResponse.class, message = "Success"),
#ApiResponse(code = 400, response = ErrorResponse.class, message = "Bad Request"),
#ApiResponse(code = 500, response = ErrorResponse.class, message = "Internal Server Error") })
public CustomResponse uploadJava(
#ApiParam(value = "Interface", required = true)
#RequestPart(name = "interface", required = true) final MultipartFile interfaceClass,
#ApiParam(value = "Java Class", required = false)
#RequestPart(name = "javaClass", required = false) final MultipartFile javaClass) {
return null;
}
My question is,
How to add description and example for the above. (Similar to
RequestParam)
How to set content type for each file.
Note:
I tried modifying #RequestParam instead of #RequestPart. In this
case, the second file ("javaclass") alone pushed to spring boot.
My current POM
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
Regards

Related

How can I propagate request headers to response headers in SpringBoot 2

I have interfaces generated by Swagger Codegen. It looks like this:
#PostMapping(value = "/ipc/conf", produces = {"application/json", "application/problem+json"}, consumes = {
"application/json"})
default ResponseEntity<CustomResponseEntity> ipcConfPost(
#ApiParam(value = "ID", required = true) #RequestHeader(value = "X-Request-ID", required = true) String xRequestID,
#ApiParam(value = "Value for identifying a single transaction across multiple services up to the backend.", required = true) #RequestHeader(value = "X-Correlation-ID", required = true) String xCorrelationID,
#ApiParam(value = "The payload to transmit", required = true) #Valid #RequestBody IPcData ipcConfData,
#ApiParam(value = "The business context is a general classification for a larger number of requests.") #RequestHeader(value = "X-Business-Context", required = false) String xBusinessContext) {
getRequest().ifPresent(request -> {
for (MediaType mediaType : MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"id\" : \"id\", \"error\" : \"error\" }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
In the implementation I want to have a full list of request headers (I need some of them in the response) or to be able to get a value of a header that is not listed in the API. The thing is I cannot change the signature of the endpoint since it will cause a major headache in further releases.
So is there any way to achieve this?
You have the request object in your code already so you can get the headers from it. i.e. request.getHeaderNames() then loop through them.
After that, you can add them to the response with
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("key", "value");
ResponseEntity.ok().headers(responseHeaders).body("some body");

Spring Rest Controller with HttpServletRequest for upload resulting in no input file upload in Swagger

I have spring rest controller with some parameters and HttpServletRequest in input. I use it to stream input file. Simplifying:
#PostMapping(path = "...", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#Operation(summary = "Create document", description = "upload file for creating a document")
#ApiResponse(content = #Content(mediaType = MediaType.TEXT_PLAIN_VALUE, schema = #Schema(implementation = MyDTO.class)))
#ApiResponses(value = { #ApiResponse(responseCode = "200", description = "Data found"),
#ApiResponse(responseCode = "400", description = "Request not compliant with the defined schema")
...
public ResponseEntity<MyDTO> myUpl(
#PathVariable("myParam1") Long param1,
#RequestParam(name = "myParam2", required = true) Integer myParam2,
HttpServletRequest request ) {
So when I invoke swagger-ui and produce swagger yaml there is no file input.
The code works perfectly.
I need to clarify in swagger the input file for frontend team.
How can I do that?
EDIT:
Following other posts, temporanely added to controller:
#RequestBody(description = "Input file",
content = #Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE,
schema = #Schema(implementation = MultipartRequest.class),
encoding = #Encoding(name = "file", contentType = "application/pdf")))
At least I have in swagger yaml:
requestBody:
description: Input file
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/MultipartRequest'
encoding:
file:
contentType: application/pdf
and un swagger-ui:
Request body multipart/form-data
Input file
fileMap
object
fileNames
object
multiFileMap
object
It's the right way? There's something better? Is it possible to show Input File button in swagger-ui?

File Upload on Swagger Controller Interface

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);

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.

swagger-ui duplicating endpoints with interface and implementation of controller

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.

Categories