Sprinboot backend and reactjs frontend FormData requst error - java

My springboot model is this:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Document(collection = "products")
public class Product {
#Id
private String id;
#NotEmpty(message = "name is mandatory")
private String name;
#NotEmpty(message = "price is mandatory")
private int price;
private MultipartFile file;
}
#AllArgsConstructor
#Data
#Builder
public class AddProductCommand implements Serializable {
#TargetAggregateIdentifier
private String id;
#NotNull(message = "no product details were supplied")
#Valid
private Product product;
public AddProductCommand(){
}
}
Since I have to send a MultipartFile from the reactjs, I must use FormData. I have tried the following:
async handleSubmit(event) {
event.preventDefault();
try {
let product = new FormData();
product.append("name", this.state.name);
product.append("price", this.state.price);
product.append("file", this.state.file);
let addProductCommand = new FormData();
addProductCommand.append("product", product);
const response = await axios.post(SELL_URL, addProductCommand , {
headers: {
'Content-Type': 'multipart/form-data'
}
});
this.clearState();
event.target.reset();
this.props.history.push("/buy");
} catch (err) {
console.log(JSON.stringify(err));
}
}
However, I got following error in the springboot:
DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'addProductCommand' on field 'product': rejected value [[object FormData]]; codes [typeMismatch.addProductCommand.product,typeMismatch.product,typeMismatch.com.cognizant.user.core.models.Product,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addProductCommand.product,product]; arguments []; default message [product]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.cognizant.user.core.models.Product' for property 'product': no matching editors or conversion strategy found]]
What have I missed and how should I fix this error?
Add controller code. I am trying to implement it as CQRS design. Here is the controller. I nedd to write more description not much code. Otherwise stackoverflow doesn't allow me do add more code :(.
#RestController
#RequestMapping(path = "/api/v1/addProduct")
public class AddProductController {
private final CommandGateway commandGateway;
#Autowired
public AddProductController(CommandGateway commandGateway) {
this.commandGateway = commandGateway;
}
#PostMapping
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute AddProductCommand command) {
var id = UUID.randomUUID().toString();
command.setId(id);
try {
commandGateway.sendAndWait(command);
return new ResponseEntity<>(new AddProductResponse(id, "Product added successfully!"), HttpStatus.CREATED);
} catch (Exception e) {
var safeErrorMessage = "Error while processing add product request for id - " + id;
System.out.println(e);
return new ResponseEntity<>(new AddProductResponse(id, safeErrorMessage), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}

for the above scenario, you are appending form data to the product key addProductCommand.append("product", product);
so in your controller, you should get the product entity. add this to your controller; #ModelAttribute("product")
public ResponseEntity<AddProductResponse> registerUser(#Valid #ModelAttribute("product") AddProductCommand command)

Related

How to return a custom message with #UniqueConstraint and #Column(unique = true)

I would be capable to return a custom message when a user send a data that already existis in database.
There is my class:
#Entity
#Table(name="unity")
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class Unity {
\\...
#CNPJ(message = "O CNPJ é inválido")
#NotNull(message = "Preencha o campo CNPJ")
#Column(name="cnpj", unique = true )
private String cnpj;
\\...
I would like to sent a constraint error as the #NotNull anottation does, so i can grab it in the validator within the controller.
#POST
#Override
#Transactional
public Response create(UnityDTO unityDTO){
Set<ConstraintViolation<UnityDTO>> constraintViolations = validator.validate(unityDTO);
if (constraintViolations.isEmpty()) {
UnityDTO unityDTOPersisted = unityMapper.unityToUnityDTO(unityService.save(unityDTO));
return Response
.fromResponse(
createdResponse(unityDTOPersisted.getId(), UnityEndpoint.class)
).entity(unityDTOPersisted).build();
} else {
Set<String> errors = constraintViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toSet());
return Response.status(Response.Status.BAD_REQUEST).entity(errors).build();
}
}
How can i do that?

Receive an urlenconded in Spring Boot API

I need to receive a request from a webhook from a third party API.
Post content is an urlenconded in following format:
event=invoice.created&data%5Bid%5D=value1&data%5Bstatus%5D=pending&data%5Baccount_id%5D=value2
The problem is serialize this params data[id] with these square brackets. I'm getting an error in spring boot:
Invalid property 'data[account_id]' of bean class [br.com.bettha.domain.dto.IuguWebhookDto]: Property referenced in indexed property path 'data[account_id]' is neither an array nor a List nor a Map; returned value was [IuguDataDto(id=null, account_id=null, status=null, subscription_id=null)]
My controller:
#PostMapping(value = "/subscription-invoice", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
#ApiOperation(
value="Create a subscription invoice from Iugu's webhook",
response= Invoice.class,
notes="This Operation creates a subscription invoice from Iugu's webhook")
#PreAuthorize("#oauth2.hasScope('read')")
public ResponseEntity<Invoice> createSubscriptionInvoice(IuguWebhookDto iuguWebhookDto) {
try {
Invoice invoice = paymentService.createSubscriptionInvoiceFromIugusWebhook(iuguWebhookDto);
return new ResponseEntity<>(invoice, HttpStatus.CREATED);
} catch (EntityNotFoundException e) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, e.getMessage(), e);
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage(), e);
}
}
IuguWebhookDto.java:
#Getter
#Setter
#NoArgsConstructor
#ToString
public class IuguWebhookDto implements Serializable {
private static final long serialVersionUID = -5557936429069206933L;
private String event;
private IuguDataDto data;
IuguDataDto.java:
#Getter
#Setter
#NoArgsConstructor
#ToString
public class IuguDataDto implements Serializable {
private static final long serialVersionUID = -5557936429069206933L;
private String id;
private String account_id;
private String status;
private String subscription_id;
How can I receive these request params as an object in Spring Boot?
I have the same problem using Iugu Webhooks API. To solve, i just stringify the raw data sent by Iugu, remove the unwanted characters, and then parse again the object to get the variables that i want.
var dataReceived = JSON.stringify(req.body).replace('data[id]','id').replace('data[status]','status').replace('data[account_id]','account_id');
var finalData = JSON.parse(dataReceived);
return res.status(200).send(finalData.id);

How to define parameter list dynamically based on request type for request model in spring boot for swagger

I am using spring boot's Rest Controller for creating rest end points. Along with swagger 2 for api documentation.
#RestController
#RequestMapping("/api")
public class BatchController extends ControllerConfig {
#PostMapping("/batch")
public GeneralResponse<Boolean> createBatch(#RequestBody Batch batch) throws Exception{
try{
batchService.createBatch(batch);
return new GeneralResponse<>(true,"batch created successfully", true, System.currentTimeMillis(), HttpStatus.OK);
} catch (Exception e){
return new GeneralResponse<>(false,e.getMessage(), false, System.currentTimeMillis(), HttpStatus.BAD_REQUEST);
}
}
#PutMapping("/batch")
public GeneralResponse<Boolean> updateBatch(#RequestBody Batch batch) {
try {
batchService.updateBatch(batch);
return new GeneralResponse<>(true, "batch updated successfully", true, System.currentTimeMillis(), HttpStatus.OK);
} catch (Exception e) {
return new GeneralResponse<>(false, e.getMessage(), false, System.currentTimeMillis(), HttpStatus.BAD_REQUEST);
}
}
}
And Batch Model :
#Entity
#Table
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class Batch {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long qualityId;
private Date date;
private String remark;
}
I am using JPA repository.
Now, For both the rest end points Swagger will show the request model as :
{
id: 0,
qualityId: 0,
date: "2020-10-04T21:18:00.656Z",
remark: "string"
}
but I want to hide "id" field for create batch request as that is autogenerated, but its required for update as that is based on id.
how can that be done?
Entities are not supposed to be exposed in the API layer,
You should create a dedicated DTO classes instead.
For example-
#Data
public class PutBatchDTO {
private Long id;
private Long qualityId;
private Date date;
private String remark;
}
#Data
public class PostBatchDTO {
private Long qualityId;
private Date date;
private String remark;
}

Validating multipart/form-data in Spring REST api

I recently came up to an issue related to validation. Typically, I am building a REST api that allow users to create their account including avatars. All of the information should be submitted when user clicks to Register button. So, my server will then receive a request that includes some fields like name (string), birthday (datetime), ... and avatar (multipart file). So, the question is how to validate the received file is a truly image and has an allowed size and simultaneously validate that the others (email, password) are also valid.
For the case that all fields is text, we can easily validate them using the combination of annotations like this
Controller
#PostMapping(path = "")
public ResponseEntity<?> createNewAccount(#RequestBody #Valid RegisterRequest registerRequest) {
Long resourceId = service.createNewCoderAccount(registerRequest);
return ResponseEntity.created(location(resourceId)).build();
}
Request DTO
#ConfirmedPassword
public class RegisterRequest extends BaseRequest implements ShouldConfirmPassword {
#NotBlank(message = "Field 'email' is required but not be given")
#Email
#Unique(message = "Email has been already in use", service = UserValidatorService.class, column = "email")
private String email;
#NotBlank(message = "Field 'password' is required but not be given")
#Size(min = 6, message = "Password should contain at least 6 characters")
private String password;
#NotBlank(message = "Field 'confirmPassword' is required but not be given")
private String confirmPassword;
#NotBlank(message = "Field 'firstName' is required but not be given")
private String firstName;
#NotBlank(message = "Field 'lastName' is required but not be given")
private String lastName;
}
Or in case that the request containing only file(s), we can absolutely do like this
Controller
#PostMapping(path = "/{id}")
public ResponseEntity<?> editChallengeMetadata(
#ModelAttribute ChallengeMetadataRequest request,
BindingResult bindingResult,
#PathVariable("id") Long id,
#CurrentUser User user
) throws BindException {
challengeMetadataRequestValidator.validate(request, bindingResult);
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
Long challengeId = service.updateChallengeMetadata(id, request, user);
return ResponseEntity.ok(RestResponse.build(challengeId, HttpStatus.OK));
}
Validator
public class ChallengeMetadataRequestValidator implements Validator {
#Override
public boolean supports(#NonNull Class<?> aClass) {
return ChallengeMetadataRequest.class.isAssignableFrom(aClass);
}
#Override
public void validate(#NonNull Object o, #NonNull Errors errors) {
ChallengeMetadataRequest request = (ChallengeMetadataRequest) o;
if (request.getBanner() != null && !request.getBanner().isEmpty()) {
if (!List.of("image/jpeg", "image/png").contains(request.getBanner().getContentType())) {
errors.rejectValue("banner", "challenge.mime-type.not-supported", new String[]{request.getBanner().getContentType()}, "Mime-type is not supported");
}
}
}
}
As you seen above, if I wrap all data (including avatar) in a DTO class, I definitely write its own validator. But what will happen if then I have to write manually hundreds validators like that.
So, do anyone have any idea about it, typically, make the multipart/form-data request becomes simalar with application/json request ?
Thanks and regards,

Java: compile conflict: return type is incompatible with HATEOAS ResourceSupport.getId

So I have this HATEOAS entity.
#Entity
#Table(name="users")
public class User extends ResourceSupport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long id;
public User() {
}
public Long getId() {
return new Long(id);
}
public void setId(Long id) {
this.id = id.longValue();
}
}
My entity has an id of type long, but HATEOAS's ResourceSupport requires that getId return a Link.
The entity has a Long id because the db has a long id, and it is a persisted entity. How can I implement this entity with HATEOAS?
Check out the "Link Builder" section of the documentation:
http://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.obtaining-links.builder
There, it describes how to use a ControllerLinkBuilder to create the Link using a separate controller class. Your User Object would implement Identifiable<Long>, as the example in the page above shows.
You can create one BeanResource bean which extends ResourceSupport bean like.
#JsonIgnoreProperties({ "id" })
public class BeanResource extends ResourceSupport {
#JsonUnwrapped
private Object resorce;
public Resource(Object resorce) {
this.resorce = resorce;
}
public Object getResorce() {
return resorce;
}
}
just Unwrap resource instance property so that BeanResource bean will render json like user bean along with ResourceSupport bean will render link json object,
after that you can create assembler like this.
public class UserAssembler extends ResourceAssemblerSupport<User, BeanResource> {
public UserAssembler() {
super(User.class, BeanResource.class);
}
#Override
public Resource toResource(User user) {
Resource userResource = new Resource(user);
try {
Link selfLink = linkTo(
methodOn(UserController.class).getUser(user.getId()))
.withSelfRel();
userResource.add(selfLink);
} catch (EntityDoseNotExistException e) {
e.printStackTrace();
}
return userResource;
}
}
and in controller just attach Resource bean which contains user bean like
#RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
public ResponseEntity<Resource> getUser(#PathVariable String userId)
throws EntityDoseNotExistException {
User user = userService.getUser(userId);
Resource userResource = userAssembler.toResource(user);
return new ResponseEntity<Resource>(userResource, HttpStatus.OK);
}

Categories

Resources