#valid #requestBody kotlin with entity into entity - spring-boot

I have a problem with valid request in kotlin because I currently have an object composed of a list of integers and another entity called emailData, when I send incomplete or in error format emaildata, the validation does not happen and let me enter the controller. my code this and my request in postman these
fun sendMessage(#Valid #RequestBody notificationData: NotificationData) {
this.notificationManager.sendNotificationByType(notificationData)
}
data class NotificationData(
#get:NotEmpty
#get:NotNull
#field:NotNull
#Size(min = 2, max = 14)
#get:JsonProperty("notification_type")
var notificationType : List<Int> = listOf(),
#Valid
//# #field:NotEmpty(message = "SRTERST enter id")
#get:JsonProperty("email_data")
var emailData : EmailData = EmailData())
data class EmailData(
#NotNull
#get:NotEmpty
#get:JsonProperty("email_receiver")
var emailReceiver : List<String> = listOf(),
#NotNull
#get:NotEmpty
#get:JsonProperty("subject")
var subject : String = "",
#get:NotEmpty
#NotNull
#get:JsonProperty("template_id")
var templateId : String = "",
#get:NotEmpty
#NotNull
#get:JsonProperty("template_params")
var templateParams : HashMap<String, String> = HashMap())
when i send
{
"notification_type":["0"],
"email_data":{
"subject":"test",
"template_id":"d-1860fd6fa461449b88c578b124a0b331"
}
}
the validation for the emailData no work.

Related

How to create a #PostMaping for create an entity when i have a many to many relation ? Spring -> Postman

When i send a request in json format from Postman, i hava an error : java.lang.IllegalArgumentException: The given id must not be null!
I try to do that in different ways
#RestController
#PostMapping("create")
public AppointmentResponse createAppointment(#RequestBody #Valid AppointmentRequest appointmentRequest) {
return appointmentService.createAppointment(appointmentRequest);
}
public AppointmentResponse createAppointment(AppointmentRequest appointmentRequest) {
Appointment appointmentToSave = appointmentMapper.map(appointmentRequest);
Manicurist manicurist = manicuristRepository.findById(appointmentRequest.getManicuristId()).orElseThrow(
() -> new BusinessException("Manicurist not found")
);
appointmentToSave.setManicurist(manicurist);
Customer customer = customerRepository.findById(appointmentRequest.getCustomerId()).orElseThrow(
() -> new BusinessException("Customer not found")
);
appointmentToSave.setCustomer(customer);
List<NailsService> nailsServices = new ArrayList<>();
for (Integer id : appointmentRequest.getNailsServicesIds()) {
NailsService nailsService = nailsServiceRepository.findById(id).orElseThrow(
() -> new BusinessException("NailsService not found")
);
nailsServices.add(nailsService);
appointmentToSave.setNailsServices((List<NailsService>) nailsService);
}
Appointment appointmentForResponse = appointmentRepository.save(appointmentToSave);
return appointmentMapper.map(appointmentForResponse);
}
public class AppointmentRequest {
private Integer id;
#NotNull
#Future(message = "Please check the date")
private LocalDate appointmentDate;
#NotNull
private LocalTime appointmentTime;
private Integer manicuristId;
private Integer customerId;
private Integer[] nailsServicesIds;
class AppointmentResponnse same with Request

One to Many save (POST) results in Bad Request (400) using Fetch for List<Entity>

I am trying to save Parent (One) and Children (Many) entities at the same time.
I took help from here and here.
I have an User Entity like below:
#Entity
#Table(name = "app_user")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class AppUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "salutation")
private String salutation;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "preference")
private String preference;
public AppUser(String salutation, String name, String email, String preference, List<Address> addressList,
List<Expertise> expertise) {
super();
this.salutation = salutation;
this.name = name;
this.email = email;
this.preference = preference;
this.addressList = addressList;
this.expertise = expertise;
}
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "address_id")
private List<Address> addressList = new ArrayList<>();
#OneToMany(orphanRemoval = true, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "expertise_id")
private List<Expertise> expertise = new ArrayList<>();
My POST controller method.
#PostMapping("/appUsers")
public ResponseEntity<AppUser> createUser(#RequestBody AppUser appUser) {
try {
AppUser _appUser = appUserRepository.save(
new AppUser(appUser.getSalutation(), appUser.getName(), appUser.getEmail(),
appUser.getPreference(), appUser.getAddressList(),
appUser.getExpertise()));
return new ResponseEntity<>(_appUser, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
My pure JS (Fetch) snippet:
<script>
async function postDataToServer(postData) {
const baseURL = "http://localhost:8080/api";
try {
const res = await fetch(`${baseURL}/appUsers`, {
method: "post",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(postData),
});
if (!res.ok) {
const message = `An error has occured: ${res.status} - ${res.statusText}`;
throw new Error(message);
}
} catch (err) {
alert(err.message);
}
}
</script>
Using above, I can see the form data nicely forming up like below:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
"java",
"springboot",
"javascript"
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
During submit if I don't select expertise, it all works find. i.e. the user gets saved but if I select expertise checkboxes I get a 400 bad request message at the browser console and JSON parse erroSTS console like this:
2022-02-25 11:02:53.009 WARN 25007 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.spring.boot.rocks.model.Expertise (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('java') at [Source: (PushbackInputStream); line: 1, column: 234] (through reference chain: com.spring.boot.rocks.model.AppUser["expertise"]->java.util.ArrayList[0])]
I created a github project here if any more details are required.
Question: What I am missing? How do I convert expertise collection to List using pure JS only ? Or how do I handle expertise collection in controller?
Your form data is not in correct format. This should be like this:
{
"salutation": "Mr.",
"name": "Ajay Kumar",
"email": "ajay#kumar.com",
"address_main": "1234 StreetName State 12345",
"address_1": "2345 StreetName State 23456",
"address_2": "3456 StreetName State 34567",
"preference": "Vegeterian",
"expertise": [
{
"java",
"springboot",
"javascript"
}
],
"secret": "1abc1234-1abc-4321-1234-1234abcd1234"
}
Expertise and address in your parent class are lists, not normal objectType entity. If any of these two lists are not present, try to set them as emptyList before saving.

Spring boot Restful API: DTO with relationships convert to entity using ModelMapper?

I'm now confused about how to do a CRUD in a Rest API with Spring.
Let me explain, I have two routes to POST and PUT an entity. I created two DTOs createPostRequest and updatePostRequest for this. Because when adding, the properties cannot be null, while when updating they can (nulled properties are ignored).
Problem 1:
On my frontend, the user is asked to choose a list of tags from the database (multi select html). This is why createPostRequest has a tags property typed TagDTO. But, how can I use modelMapper to map, for example, the createPostRequest to the Post entity making sure that the tags exist in the database?
if for example a user try to insert a tag that does not exist, I was thinking of doing something like this:
postEntity.setTags(tagService.findAllByIds(postEntity.getTagsId()));
This makes a lot of repetition in the code, because between create and update method of my entity in service, there is a lot of identical code.
Problem 2:
Based on my problem 1, how can I easily map my two DTOs to the same entity without repeating the code 2x?
Code example - PostService (see comment)
This is an example for the update, but there will be almost identical code for the create, so how do I proceed?
#Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
// here how to map non-null properties of my request
// into my post taking in consideration my comment above?
postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
================================
UPDATE:
As requested, found the code bellow.
The controller:
#RestController
#RequestMapping("/v1/posts")
public class PostController {
RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Valid #RequestBody CreatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Valid #RequestBody UpdatePostRequest updatePostRequest, #PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}
CreatePostRequest :
#Data
public class CreatePostRequest {
#NotNull
#Size(min = 10, max = 30)
private Sting title;
#NotNull
#Size(min = 50, max = 600)
private String description
#NotNull
#ValidDateString
private String expirationDate;
#NotNull
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
UpdatePostRequest :
#Data
public class UpdatePostRequest {
#Size(min = 10, max = 30)
private Sting title;
#Size(min = 50, max = 600)
private String description
#ValidDateString
private String expirationDate;
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
The service :
#Service
#Transactional
public class PostService {
#Transactional
public Post create(CreatePostRequest request) {
ModelMapper modelMapper = new ModelMapper();
Post post = modelMapper.map(request, Post.class);
// map will not work for tags : how to check that tags exists in database ?
return postDAO.save(post);
}
#Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setSkipNullEnabled(true);
modelMapper.map(request, post);
// map will not work for tags : how to check that tags exists in database ?
postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
To avoid duplication of two similar DTOs you could use #Validated group validations. This allows you to actively set which validations are to be done on each property. You can read more about this in the following online resource https://www.baeldung.com/spring-valid-vs-validated. You would begin with the creation of two market interfaces:
interface OnCreate {}
interface OnUpdate {}
You can then use these marker interfaces with any constraint annotation in your common DTO:
#Data
public class CreateOrUpdatePostRequest {
#NotNull(groups = OnCreate.class)
#Size(min = 10, max = 30, groups = {OnCreate.class, OnUpdate.class})
private Sting title;
#NotNull(groups = OnCreate.class)
#Size(min = 50, max = 600, groups = {OnCreate.class, OnUpdate.class})
private String description
#NotNull(groups = OnCreate.class)
#ValidDateString(groups = {OnCreate.class, OnUpdate.class})
private String expirationDate;
#NotNull(groups = OnCreate.class)
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}
Finally, you just need to annotate your methods in the Controller accordingly:
#RestController
#RequestMapping("/v1/posts")
#Validated
public class PostController {
#RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Validated(OnCreate.class) #RequestBody CreateOrUpdatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
#RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(#Validated(OnUpdate.class) #RequestBody CreateOrUpdatePostRequest updatePostRequest, #PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}
With this, you can have a single mapping function.
Still, keep in mind that using validation groups can easily become an anti-pattern given that we are mixing different concerns. With validation groups, the validated entity has to know the validation rules for all the use cases it is used in. Having said that, I usually avoid using validation groups unless it is really necessary.
Regarding tags I guess your only option is to query the database. The ones that do not exist you should create them (I guess), so something along the following lines:
List<Integer> tagsId = createOrUpdatePostRequest.getTagsId();
List<Tag> tags = tagService.findAllByIds(tagsId);
List<Integer> nonExistentTagsId = tagsId.stream().filter(id -> tags.stream().noneMatch(tag -> tag.getId().equals(id)));
if (!nonExistentTagsId.isEmpty()) {
// create Tags and add them to tags List
}

MockMvc PostRequest Exception

I have following post mapping.
#PostMapping(value = BULK_UPDATE)
#ApiOperation(value = "Bulk Update of Markets by pairs of Market Key and Tier Quantity Id", tags = "Bulk", code = 200)
#ApiImplicitParams({
#ApiImplicitParam(name = "MarketTierQuantityId", value = "List of Market Key and Tier Quantity Id pairs",
paramType = "body", allowMultiple = true, dataType = "MarketTierQuantityId", required = true) })
#ApiResponses({
#ApiResponse(code = 200, message = "Bulk update successful", response = MarketStatus.class, responseContainer = "List") })
#ResponseStatus(org.springframework.http.HttpStatus.OK)
public ResponseEntity<StreamingResponseBody> bulkUpdate(
#RequestParam(name = IGNORE_SYNC_PAUSE_FAILURE, required = false, defaultValue = "false")
#ApiParam(name = IGNORE_SYNC_PAUSE_FAILURE, value = "Ignore failure of the jobs pause command") boolean ignoreJobsPauseFailure,
#RequestBody #ApiParam(name = "MarketTierQuantityId", value = "List of Market Key and Tier Quantity Id pairs", required = true) List<MarketTierQuantityId> marketTierQuantities,
#RequestParam(name = MOVE_TO_PREAUTH_FLAG, required = false, defaultValue = "true")
#ApiParam(name = MOVE_TO_PREAUTH_FLAG, value = "Move new units to Preauth for the markets with active waitlists") boolean moveToPreauth) throws BusinessException {
String requestId = getRequestId();
boolean jobsPaused = pauseJobs(ignoreJobsPauseFailure);
return LoggingStopWatch.wrap(() -> {
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
.body(outputStream -> process(new SyncBulkProcessorHelper(outputStream),
marketTierQuantities, jobsPaused, requestId, moveToPreauth, LoggingStopWatch.create(LOGGER, "Bulk Update")));
});
}
and i have written the following test.
#RunWith(SpringRunner.class)
#WebMvcTest(BulkUpdateController.class)
#ContextConfiguration(classes = { BulkUpdateController.class, SharedExecutor.class })
#ActiveProfiles("dev")
public class BulkUpdateControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BulkStatusService bulkStatusService;
#MockBean
private BulkMarketService bulkMarketService;
#MockBean
private HttpService httpService;
#MockBean
private RestClient restClient;
#MockBean
private BulkProcessorHelper helper;
#Test
public void test() throws Exception {
String request = TestHelper.getSerializedRequest(getBulkUpdateRequest(), MarketTierQuantityId.class);
mockMvc.perform(post("/bulkupdate").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)
.content(request)).andExpect(status().is4xxClientError());
}
public MarketTierQuantityId getBulkUpdateRequest() {
MarketTierQuantityId market = new MarketTierQuantityId();
market.setMarketKey("00601|PR|COBROKE|POSTALCODE|FULL");
market.setTierQuantityId("10");
return market;
}
Getting the following error, have tried every possible way to resolve it but doesnt help.
Request failed. Error response:
{\"responseStatus\":{\"errorCode\":\"BadRequest\",\"message\":\"JSON
parse error: Cannot deserialize instance of java.util.ArrayList out
of START_OBJECT token\",\"stackTrace\":\"BusinessException(JSON parse
error:
P.S -> new to JUnits and mocks

Spring controller getting bodyRequest null

I am facing a problem while trying to process a POST request via POSTMAN.
In my controller I have :
#ApiOperation(value = "xxxx", notes = "xxxx", response =
String.class, authorizations = {
#Authorization(value = "basicAuth")
}, tags={ "saveCourse", })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful operation", response =
String.class),
#ApiResponse(code = 404, message = "Not found", response =
String.class),
#ApiResponse(code = 405, message = "Invalid input", response =
String.class),
#ApiResponse(code = 500, message = "Internal Server Error", response =
String.class),
#ApiResponse(code = 200, message = "unexpected error", response =
String.class) })
#RequestMapping(value = "/course/saveCourse",
produces = { "application/json"},
consumes = { "application/json"},
method = RequestMethod.POST)
ResponseEntity<String> saveCourse(#ApiParam(value = "xxxxx" ,required=true ) #RequestBody Course coure){
LOG.info(course.toString);
}
Class Course :
public class Course implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#JsonProperty("prof")
private Prof prof = null;
#JsonProperty("students")
private List<Strudent> students = new ArrayList<Strudent>();
// getters & setters
// ...
}
class Prof :
public class Prof implements Serializable {
#JsonProperty("profLastName")
private String profLastName = null;
#JsonProperty("profFirstName")
private String profFirstName = null;
#JsonProperty("age")
private int age = null;
// getters & setters
}
class Student :
public class Student implements Serializable {
#JsonProperty("studentId")
private String studentId = null;
#JsonProperty("studentName")
private String studentName = null;
#JsonProperty("studAge")
private int studAge = null;
// getters & setters
// ...
}
in POSTMAN I am sending a POST request with the header :
Content-Type : application/json
the body :
{
"prof": {
"profLastName":"test",
"profFirstName":"test",
"age":"30"
},
"students" :[
"{'studentId':'0','studentName':'','studAge':'00'}",
"{'studentId':'2','studentName':'','studAge':'21'}",
"{'studentId':'4','studentName':'','studAge':'40'}",
"{'studentId':'6','studentName':'','studAge':'60'}"
]
}
When I process the request I am getting the RequestBody null :
[http-nio-xxxx-exec-4] INFO com.test.myControllerIml - class Course {
prof: null
students: []
}
you request body is wrong
you should use
{
"prof": {
"profLastName":"test",
"profFirstName":"test",
"age":"30"
},
"students" :[
{"studentId":"0","studentName":"","studAge":"00"},
{"studentId":"2","studentName":"","studAge":"21"},
{"studentId":"4","studentName":"","studAge":"40"},
{"studentId":"6","studentName":"","studAge":"60"}
]
}

Resources