I am just starting with swagger to generate a java Spring API.
The generation itself poses no problem. I used the PetStore yaml to generate the SpringBoot serverside code without a problem.
After i generate the code however I cannot find a good tutorial/way explaining to me where to best put the custom code i want to write. Writing it directly into the generated code does not seem like a good idea, since if i ever regenerate because of changes in the definitions you don't want to override the code used.
You get stubs like:
#Controller
public class PetApiController implements PetApi {
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
// do some magic!
return new ResponseEntity<ModelApiResponse>(HttpStatus.OK);
}
...
}
Now is there a way to fill the "do some magic!" part during/after generation with i.e. a call to a service, so that i can use this as sort of entry point. e.g. autowiring a service which has the same methods but can be provided by me, so that i can keep my implementations separate from the generated code.
#Controller
public class PetApiController implements PetApi {
...
#Autowired
PetApiService petApiService;
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
return petApiService.uploadFile(file);
}
...
}
Thanks
There is no easy way of regenerating new code using Swagger and stop it from overwriting any code you want to make. Although there is a way of mitigate all the copy-pasting if you want to change the api. Instead of writing all the code on the "do some magic" comment try to put in on a method in an outside class, so in the case you need to regenerate the code with swagger, you would only need to copy a single line, I'll give you an example:
#Controller
public class PetApiController implements PetApi {
#Autowired
GenerateResponse generateResponse;
...
public ResponseEntity<ModelApiResponse> uploadFile(#ApiParam(value = "ID of pet to update",required=true ) #PathVariable("petId") Long petId,
#ApiParam(value = "Additional data to pass to server") #RequestPart(value="additionalMetadata", required=false) String additionalMetadata,
#ApiParam(value = "file detail") #RequestPart("file") MultipartFile file) {
return generateResponse.uploadFile(petId, additionalMetadata, file);
}
...
}
And in your outside class that I called "GenerateResponse":
#Component
public class GenerateResponse{
#Autowired
PetApiService petApiService;
public ResponseEntity<ModelApiResponse> uploadFile(Long petId, String additionalMetadata, MultipartFile file){
return petApiService.uploadFile(file);
}
}
So you just have to copy the "return GenerateResponse.uploadFile(petId, additionalMetadata, file);" line and only create the autowired GenerateResponse once every time you change it.
Related
I've seen many sources and also few questions on SO but didn't find solution.
I want to send to my Spring app POST/PUT-requests that contain JSON-object Car and attached file.
For the moment I have a CarController which correctly works with JSON-objects
#PutMapping("/{id}/update")
public void updateCar(#PathVariable(value = "id") Long carId, #Validated #RequestBody Car car) throws ResourceNotFoundException {
// I can work with received car
}
I also have a FileController which correctly works with file
#PostMapping("/upload")
public void uploadFiles(#RequestParam("file") MultipartFile file) throws IOException {
// I can work with received file
}
But how should my method look like to be able to work with both car and file? This code doesn't provide me any of car or file.
#PutMapping("/{id}/update")
public void updateCar(#PathVariable(value = "id") Long carId, #Validated #RequestBody Car car, #RequestParam("file") MultipartFile file) throws ResourceNotFoundException, IOException {
// can not work neither with car nor with file
}
Separate controllers work well during test from Postman. But when I try third code I got these results:
You can use consumes = { MediaType.MULTIPART_FORM_DATA_VALUE } field of #RequestMapping annotation and #RequestPart annotation for method parameters:
ResponseEntity<> foo(#RequestPart ParType value, #RequestPart MultipartFile anotherChoice) {
...
Yes, I agree with Vladimir; multipart/form-data, #RequestParts instead of body & param:
#PutMapping(value = "/{id}/update", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public void updateCar(#PathVariable(value = "id") Long carId,
#RequestPart("car") Car car,
#RequestPart("file") MultipartFile file) {
...
Then in Postman:
use Body>form-data
when issues:
display Content-Type column.
set Content-Type per part.
There is nothing wrong with your code and it could work as it is.
You could eventually improve its readability by using #RequestPart instead of #RequestParam and #RequestBody when it's a multipart request.
You can find more details about multipart requests in this article https://www.baeldung.com/sprint-boot-multipart-requests
Most importantly, to make it work/ or to test in the correct way:
When using postman for multipart requests, you have to define the content type of each RequestPart.
It's a hidden column in the form-data screen, that you can show as follows:
Check the box "Content-Type" and the new column will appear:
And finally, define the content type of each part.
what I am trying to do is,
If I take one pojo class like
#Entity
#Table(name = "property_table")
public class Property {
#Id
#Column(name = "property_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int propertyId;
#Column(name = "property_name")
private String propertyName;
#Column(name = "property_type")
private String propertyType;
}
In RestController I wrote Two Methods like
#GetMapping(value = "/getProperties", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public List<Property> getProperties() {
//some code
}
#GetMapping(value = "/getPropertyById", produces = { "application/json",
"application/xml" }, consumes = { "application/xml", "application/json" })
#ResponseBody
public Property getPropertyById() {
//some code
}
So, hear what I am trying to do is
for first api method I want return json like some parameters from Property pojo class i.e., like
for getProperties api method
{
"property":[
{
"propertyId":001,
"propertyName":"PROPERTY 1"
},
{
"propertyId":002,
"propertyName":"PROPERTY 2"
}
],
In the Above json I want to return only two parameters i.e propertyId,propertyName and remaining parameter i.e propertyType I dont want to retun in json.
How to return like that?
and for the second api method I want to return all three parameters. i.e., like below
for getPropertyById api method
{
"propertyId":001,
"propertyName":"PROPERTY 1",
"propertyType:"PROPERTY_TYPE 1"
},
how to maintain different json response using same pojo class with different parameters for different api methods.
please help me to solve this isuue.
Thanks.
REST API under/over-fetching is a well-known problem. There's only two (classical ways) to handle that.
The first one is to build one model per each attribute visibility state. So, in your case, you'll need to create two different models (this kind of models are called DTO - Data Transfert Object). One model will have a propertyType attribute, the other will not. The model Property you've shared shows that you use the same class as entity and as transfert object. This solution will add some complexity to your app because you will have to implement some mappers to convert your entity to a corresponding DTO.
The second one is to accept that you send an attribute that will not be useful (be aware of the over-fetching). This solution is often the most adopted one. The cons of this solution is when you don't want to send something to your client (imagine a User model, you want to get the password from your client but you don't want to sent it back to it). Another obvious negative point is that the transactions will be larger but it is negligible in most cases
I would strongly advice you to keep your #Entity isolated in the 'db' layer. So that changes on the database side don't affect your API and vice versa. Also, you will have much better control over what data is exposed in your API. For your needs you can create 2 true DTOs, like PropertyDto and PropertyDetailsDto (or using private fields and getters/setters).
public class PropertyDto {
public String propertyId;
public String propertyName;
}
public class PropertyDetailsDto extends PropertyDto {
public String propertyType;
}
Map your #Entity to a specific dto corresponding to your needs.
EDIT
public List<PropertyDto> getProperties() {
return toPropertyDtos(repository.findAll());
}
public PropertyDetailsDto getPropertyById(Long id) {
return toPropertyDetailsDto(repository.findBy(id));
}
in some Mapper.java
...
public static List<PropertyDto> toPropertyDtos(List<Property> properties) {
return properties.stream()
.map(Mapper::toPropertyDto)
.collect(toList());
}
private static PropertyDto toPropertyDto(Property property) {
PropertyDto dto = new PropertyDto();
dto.propertyId = property.propertyId;
dto.propertyName = property.propertyName;
return dto;
}
// same stuff for `toPropertyDetailsDto`, you could extract common mapping parts in a separate private method inside `Mapper`
...
Hello Friends,
I am looking for a solution where I can upload multiple MultipartFile along with input JSON body. I knew there are many related things available but none of the thing were I find my solution. which provoke me to ask a question to community.
My requirement is something likewise,
I have developed(In fact developing & am in last stage) open source library where schools/colleges/universities would willingly upload study materials for students based upon subject wise. Along with material User(authenticated) must has to provide few text info like, title, description, remarks.
so My expected entity is something likewise,
#Entity
class Material {
private MultipartFile[] content;
private String title;
private String description;
private String remarks;
}
I would like to use spring MVC feature something likewise where am getting failed & need your help here,
public String uploadMaterial(#RequestBody Material material) {
... business logic ...
return "successfully material saved.";
}
Any help will be really Appreciate!!
#RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
public #ResponseBody Material createMaterial(#RequestPart("addtionalData") String addtionalData, #RequestPart("fileList") List<MultipartFile> fileList) throws IOException {
Material material = new ObjectMapper().readValue(addtionalData, Material.class);
material.setContent(fileList);
// do what ever you want to do
}
I had a similar requirement for one of my projects and i used below technique to upload the images
public Job uploadImageForAJo(#PathVariable Long jobId, #RequestParam("file") MultipartFile[] files)
and here if you want you can pass a json payload with your other required information as the #RequestBody
you can have a look at below question to find how to test your api with postman
Tool for sending multipart/form-data request
#CrossOrigin(origins = "*")
#PostMapping("/uploadDocuments")
public Object uploadFilesOnAwsS3(
#RequestHeader(name = "docId") String docId,
#RequestHeader(name = "referenceId") String referenceId,
#RequestHeader(name = "docTypeId") String docTypeId,
#RequestParam(value = "data") Object data,
#RequestParam(value = "files", required = true) MultipartFile[] files) throws IOException, MsuException {
}
I want to send an object to the controller that has several lists with files and several fields with plain text.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I am sending data using postman. However, he throws me away
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundarywY7ByvgonAjDoaCT;charset=UTF-8' not supported
What should I set the Content-type for this controller so that I can send an object that has fields of plain text and lists with files?
If I set the header in the header
Content-type: multipart/form-data; charset=utf-8
it throws me in the console
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
As said dknight #RequestBody means use of JSON or XML data with maps your DTO bean.
In case of MultipartFile you can't use JSON data so you can't use #RequestBody.
Try with #ModelAttribute annotation.
Working sample :
#PostMapping("/promoters")
#Timed
public ResponseEntity<PromoterDTO> createPromoter(#ModelAttribute PromoterDTO promoterDTO) throws URISyntaxException { ... }
With PromoterDTO like this :
public class PromoterDTO implements Serializable {
private Long id;
private String name;
private String address;
private MultipartFile logo;
}
In Postman, you need to set the body to be of type raw, and from the drop down you can select JSON, I had a similar issue, this fixed my issue.
Instead of #RequestBody, use #RequestParam!
use #ModelAttribute instead of #ResponseBody as this takes up data in key value pairs and the later is used for an object like, json.
While hitting the api simply pass the multipart type and json key value pairs of the object. It works fine!
stack overflow question on this
Instead of #RequestBody, use #ModelAttribute like,
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#ModelAttribute #Valid final ContributionNew<Photo> contribution
) {
import org.springframework.web.bind.annotation.ModelAttribute;
Use #ModelAttribute instead of #RequestBody. It worked for me.
produces = { "application/json" } has to written in the controller instead of consumes = { "application/json" }
Hi Folks simply change : #RequestBody to #ModelAttribute
public ResponseEntity<DTO> exemple(#ModelAttribute Dto dto) throws TechnicalException
Happy coding.
Here's a full code sample written in Kotlin using Spring Boot 2.1.7
Example uses a ProfileRepository that would be something you implement of course.
Kotlin is nice, because the data class implements serializable already.
Take note, that you have to use var not val for the model objects properties otherwise the field values will be null in the log message.
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
#RestController
class ExampleController(private val myProfileRepository: ProfileRepository) {
#PostMapping("/api/uploadFile")
fun createProvider(#ModelAttribute request: CreateProfileRequest): Provider {
println("received create request with photo: ${request.photo} for the following person ${request.name}")
return myProfileRepository.save(Provider(name = request.name!!))
}
}
data class CreateProfileRequest(
var name: String? = null,
var photo: MultipartFile? = null
)
I have the following controller. I am using Spring to create Restful APIs.
#RestController
public class UserController extends RestControlValidator {
#RequestMapping(value = "/user/", method = RequestMethod.POST, headers = "Accept=application/json", consumes = "application/json", produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody List newUser(#RequestBody #Valid UserInput input,BindingResult result)
{Some code}
}
The UserInput class looks like this:
public class UserInput{
#NotEmpty
private String emailId;
#NotEmpty
private String fName;
private String lName;
private int sex;
//getters and setters
Now when I try and access /user/ with data {"sex":"Male"}, I get the following response:
I want the response in case of such a request to be:
{"errors":{"sex":"The value must be an integer"}}
Is there any way of customising BAD REQUEST responses in Spring?
Considering the current scenario the most ideal solution would be to alter the behavior of HandlerMethodArgumentResolve as the json to pojo constructed by #RequestBody fails because we dont get a chance to check the wrong data and this check can very well be done in the custom message converter
A. first we would need to create LanguageMessageConverter as follows
public class LanguageMessageConverter extends
AbstractHttpMessageConverter<Language> {
private Gson gson = new Gson();
public LanguageMessageConverter() {
super(new MediaType("application", "json", Charset.forName("UTF-8")));
}
#Override
protected boolean supports(Class<?> clazz) {
return Language.class.equals(clazz);
}
Map<String, String> mp = new HashMap<>();
#Override
protected Language readInternal(Class<? extends Language> clazz,
HttpInputMessage httpInputMessage) throws IOException,
HttpMessageNotReadableException {
Map langmp = gson.fromJson(
convertStreamToString(httpInputMessage.getBody()), Map.class);
for (Field field : clazz.getDeclaredFields()) {
if (!langmp.get(field.getName()).getClass().getCanonicalName().equals(field.getType().getCanonicalName())) {
if (field.getType().getCanonicalName().equals("java.lang.Integer")||field.getType().getCanonicalName().toString().equals("int")) {
langmp.put(field.getName(), "0");
} else if (field.getType().equals("java.lang.String")) {
//TODO COde needs to be improved here because this check is not efficient
langmp.put(field.getName(), "wrong");
}
}
}
Language lang = gson.fromJson(gson.toJson(langmp), clazz);
return lang;
}
we need to set the media type new MediaType("application", "json", Charset.forName("UTF-8")) which will make sure this class intervenes the mentioned MIME type
Considering we need to manipulate the result I found it best to convert it to map langmp (There are better JSON Parsers which can be used)
Since we need to to understand the existing type I used reflection api to get the fields via getDeclaredFields()
Using the above made the logical check using the datatype to understand if the type is incorrect for eg if the field datatype is int and if it is found as String then corresponding map value will be substituted
once that is done the map will hold the updated values where in if the data was wrong a default value would be set eg if the int var is set to 0 since the originating json had a String in it.
Once that is done the updated map is converted to the concerned class.
B. Secondly we need to register the custom MessageConverter in the dispatcher xml i.e. LanguageMessageConverter
<mvc:annotation-driven >
<mvc:message-converters register-defaults="true">
<bean class="com.comp.org.controller.LanguageMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
register-defaults="true" is very important since we are adding Custom MessageConverter but we also need the other existing converters working along with the one we have added
LanguageMessageConverter needs to be registered here.
C. Considering the concerned pojo is populated with the necessary details it would reach our controller post processing in the custom converter now we would add the manual validation eg. if the int variable has 0 the necessary error json should be returned
As per your request even if the json consists of the wrong data the custom message converter should process it and accordingly in the controller we can validate the condition mentioned.
The code definitely can be improved further. Kindly let me know if this solution fulfilled your requirement or any part of the code requires further elaboration and hopefully addressed your concern.
I had the same issue, than I solved that way:
Create an Object called Error, like that (don't forget to implement Serializable...):
private String fieldName;
private String errorCode;
private String defaultMessage;
public Error() {
}
public Error(String fieldName, String errorCode, String defaultMessage) {
this.fieldName = fieldName;
this.errorCode = errorCode;
this.defaultMessage = defaultMessage;
}
/* getters, setters */
Inside the #RestController method you ave to call inputValidator.validate() method (if you didn't create an Object Validator for your UserInput then we're really don't speaking the same language...)
// validating the userInput
userInputValidator.validate(userInput, bindingResult);
if (bindingResult.hasErrors()) {
List<Error> errors = new ArrayList<>(bindingResult.getErrorCount());
for (FieldError fieldWithError : bindingResult.getFieldErrors()) {
errors.add(new Error(fieldWithError.getField(), fieldWithError.getCode(), fieldWithError.getDefaultMessage()));
}
return errors;
}
// in case of success:
return null;
Finally you'll have to translate the JSON object to your client side. You'll have two kind of objects:
3.1. null (undefined depending on the language you're using)
3.2. A JSON object like that:
[
{
"fieldName": "name",
"errorCode": "user.input.name.in.blank",
"defaultMessage": "Insert a valid name!"
},
{
"fieldName": "firstPhone",
"errorCode": "user.input.first.phone.blank",
"defaultMessage": "Insert a valid first phone!"
}
]