How to send MultipartFile and Json in spring boot - java

Here is my controller:
#RestController
#RequestMapping("/api/product")
public class ProductController {
#Autowired
ProductService productService;
#PreAuthorize(value = "hasRole('ADD_PRODUCT')")
#PostMapping(value = "/add", consumes = {
MediaType.MULTIPART_FORM_DATA_VALUE,
MediaType.APPLICATION_JSON_VALUE
})
public HttpEntity<?> addProduct(#CurrentUser User user,
#RequestPart("files") MultipartFile multipartFile,
#Valid #RequestPart("info") ProductDto productDto) {
ApiResponse apiResponse = productService.addProduct(user, productDto, multipartFile);
return ResponseEntity.status(apiResponse.isSuccess() ? 201 : 409).body(apiResponse);
}
}
It should receive a MultipartFile and Json object and my ProductDto class is:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ProductDto {
#NotNull(message = "Name can't be empty")
#NotBlank(message = "Name can't be blank")
private String name;
#Length(max = 1000)
private String description;
#NotNull(message = "Price can't be empty")
private double price;//Evaluated in the $
#NotNull(message = "You should choose one of the categories")
private UUID categoryId;
}
I'm finding difficulties when I try to send request:
But it always giving me 403 Forbidden and I don't know the reason.
This request is not coming to addProduct() method at all and I have permission "ADD_PRODUCT".
#CurrentUser annotation class is:
#Documented
#Retention(value = RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
#AuthenticationPrincipal
public #interface CurrentUser { }
Not any error or exception is thrown as well.
I have tried #RequestParam and #RequestPart both annotations.
I want to learn how to deal with situations like this when sending MultipartFile and Json object simultaneously.
How can I do that in Spring boot?

The 403 in postman I usually get it when I use http instead of https in the URL. I think you can try editing the postman URL or if that doesn't work try to tell the spring boot app to use simple http.

Related

#Valid annotation is not working and giving NotReadablePropertyException

I have request class for a patch API in the below format
#Schema
#Data
#EqualsAndHashCode(callSuper = true)
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class PricingUsageTemplatePatchInput extends BaseRequest {
#Schema(description = "From node of the Template")
private JsonNullable<#Valid VertexNode> from;
#Schema(description = "To node of the Template")
private JsonNullable<#Valid VertexNode> to;
#NotNull
#Schema(description = "Current version of the template for which update is being made.")
private Long version;
}
VertexNode is as below
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class VertexNode {
#NotNull
#Valid
private Selectors selectors;
#NotNull
#Valid
private Cardinality cardinality;
}
And in the controller layer is as below
#PatchMapping("/{key}")
#Operation(description = "API to update the template")
public PricingUsageTemplateResponse update(#PathVariable #IsUUID final String key,
#Valid #RequestBody #NotNull #Parameter(description = "PricingUsageTemplatePatchInput", required = true)
PricingUsageTemplatePatchInput pricingUsagePatchInput) {
var request = PricingUsageTemplateUpdateRequest.builder()
.key(key)
.pricingUsageTemplatePatchInput(pricingUsageTemplatePatchInput)
.build();
return (PricingUsageTemplateResponse) actionRegistry.get(request).invoke(request);
}
When I am sending selector as null from the postman for the above api , the valid annotation is not able to send valid not null validation error instead I am getting 5xx error with below reason
org.springframework.beans.NotReadablePropertyException: Invalid property 'to.selectors' of bean class [domain.pricing_usage.dto.request.PricingUsageTemplatePatchInput]: Bean property 'to.selectors' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Can anyone help why #Valid is not working as expected

javax.validation not working when noargsconstructor is used

I am trying to validate a request object, whose class is as follows:
#Value
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#AllArgsConstructor
public class SomeCreateRequest {
#NotNull(message = "Name can not be null")
#NotBlank(message = "Name can not be empty")
String name;
}
Without NoArgsConstructor, validation works but POST operation fails with HttpMessageNotReadableException; with NoArgsConstructor, POST operation succeeds but validation doesn't work.
What am I doing wrong?
P.S. using Java 8, Spring 2.3.4.RELEASE, javax.validation 2.0.1.Final and my controller looks like below:
#RequiredArgsConstructor
#RestController
#RequestMapping("/api")
public class MyController {
private final MyService myService;
#PostMapping()
public MyResponse submit(#Valid #RequestBody SomeCreateRequest request){
return myService.create(request);
}
}

Using JPA Entity to persist HTTP Request

I expose an endpoint where my client invoke requests to fire spring batch jobs.
#RestController
#RequestMapping("/test")
public class TestController {
#Autowired
private MyProcessor processor;
#PostMapping(value = "/runJob", consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<MyResponse> runJob(#Valid #RequestBody MyRequest request) {
//persist the request using Spring JPA
String requestTrackingId = UUID.randomUUID().toString()
MyResponse response = processor.process(request, requestTrackingId); //run spring batch job and create response
return new ResponseEntity<>(response, CREATED);
}
}
Requests include name, and some other params:
public class MyRequest {
private String name;
private String runDate;
private boolean rerun;
}
I have a requirement to use Spring JPA to persist the http job run requests in a table. The table should persist the request data along with the unique id so it can be tracked and also should capture job status so it can be queried by clients using the tracking id.
I need help with the JPA implementation including creating the entity and persisting the request to the table when it hits the endpoint. What would the implementation look like? The id on the table should the tracking id.
Please give additional information if you want a more sophisticated answer.
One possible implementation:
The Model
#Entity
#Table
public class RequestData{
#Id
#Column(name = "id", nullable = false, unique = true, updatable = false, length = 32)
private String id; // = IdGenerator.generateId(); #use it if you want to get the id from the object, and not set it.
#Basic
private String name;
#Basic
private String runDate;
#Basic
private Boolean rerun;
//[getters/setters] here
}
Repository interface
public interface RequestDataRepository extends JpaRepository<RequestData, String>{}
Now you can inject the repostiory to your controller and save the request:
#Autowired
private RequestDataRepository repository;
#PostMapping(value = "/runJob", consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<MyResponse> runJob(#Valid #RequestBody MyRequest request) {
String uniqueId=IdGenerator.generateId()
RequestData requestData=new RequestData();
requestData.setId(uniqueId);
requestData.setName(request.getName())
.... //other fields setters (except id)
repository.save(requestData)
//alternativelly get the id from the object, then you do not need to create id here, but in the RequestData object
//String uniqueId=requestData.getId();
[...]
}
UUID generator
public final class IdGenerator {
private static org.springframework.util.IdGenerator idGenerator = new AlternativeJdkIdGenerator();
private IdGenerator() {
throw new UnsupportedOperationException("IdGenerator is an utility class");
}
public static String generateId() {
return idGenerator.generateId().toString().replaceAll("-", "");
}
}

Send a rest response instead of a HTML Page when javax.validation.constraints.* is not met

I am building a Spring REST service, and i have a bunch of endpoints which take a request payload via the POST method. I have included JSR 303 spec in my project and it works fine for validations. Now how do i make my application send an JSON response back along with a different status code. At present the Application gives a 400 with a tomcat error page.
Update:
I figured out that i need to include BindingResult in my method and hence i can extract the errors from there.
#PostMapping(value = "/validateBankInformation", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
protected ResponseEntity<BusinessSolutionVO> validateBankInformation(#Valid #RequestBody BankInformation bankInformation, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<ObjectError> errors = bindingResult.getAllErrors();
for (ObjectError error : errors) {
System.out.println(error.getDefaultMessage());
}
}
}
Is your entity class annotated correctly?
You must define the UniqueConstraints inside the #Table annotation.
#Table(uniqueConstraints = { #UniqueConstraint(columnNames = { "username" }),
#UniqueConstraint(columnNames = { "email" }), #UniqueConstraint(columnNames = { "field1", "field2" }) })
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Email
#NotNull
private String email;
#NotNull
#Size(min = 4, max = 24)
private String username;
#NotNull
private String password;
#NotNull
private String field1;
#NotNull
private String field2;
// Getters
// Setters
// Other Methods
}
There are different ways to solve this. But the simplest one for this case is to return the http error response like below:
#PostMapping(value = "/validateBankInformation", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
protected ResponseEntity<BusinessSolutionVO> validateBankInformation(#Valid #RequestBody BankInformation bankInformation, BindingResult bindingResult) {
if (bindingResult.hasFieldErrors()) {
String errors = bindingResult.getFieldErrors().stream()
.map(p -> p.getDefaultMessage()).collect(Collectors.joining("\n"));
//throw new InvalidRequestParameterException("Bad Request", errors);
return ResponseEntity.badRequest().body(errors);
}
return ResponseEntity.ok("Successful");
}
Or you can create custom exception messages and throw them.

Spring Data REST: "no String-argument constructor/factory method to deserialize from String value"

When I use Lombok in my Spring Data REST application to define complex types like:
#NoArgsConstructor
#AllArgsConstructor
#Data
#Entity
#Table(name = "BOOK")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Long id;
private String title;
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
private Person author;
// ...
}
with a Spring Data REST controllers like:
#RepositoryRestController
public class BookRepositoryRestController {
private final BookRepository repository;
#Autowired
public BookRepositoryRestController(BookRepository repository) {
this.repository = repository;
}
#RequestMapping(method = RequestMethod.POST,value = "/books")
public #ResponseBody PersistentEntityResource post(
#RequestBody Book book,
PersistentEntityResourceAssembler assembler) {
Book entity = processPost(book);
return assembler.toResource(entity);
}
private Book processPost(Book book) {
// ...
return this.repository.save(book);
}
}
I get an ugly error:
no String-argument constructor/factory method to deserialize from String value
from Spring Data REST's use of Jackson with a Book POST like:
curl -X POST
-H 'content-type: application/json'
-d '{"title":"Skip Like a Pro", "author": "/people/123"}'
http://localhost:8080/api/books/
The de-serialization error happens when Jackson tries to resolve the /people/123 local URI which should resolve to a single, unique Person. If I remove my #RepositoryRestController, everything works fine. Any idea what's wrong with my REST controller definition?
In the #RepositoryRestController, change the type of the #RequestBody argument from Book to Resource<Book>:
import org.springframework.hateoas.Resource;
// ...
#RequestMapping(method = RequestMethod.POST,value = "/books")
public #ResponseBody PersistentEntityResource post(
#RequestBody Resource<Book> bookResource, // Resource<Book>
PersistentEntityResourceAssembler assembler) {
Book book = bookResource.getContent()
// ...
}
and in the Book entity definition modify the AllArgsConstructor annotation to be: #AllArgsConstructor(suppressConstructorProperties = true).
See Spring Data REST #687 for more information.

Categories