Spring Web MVC Post request multiple object - java

I have a method which inserts an object. But this object mapped with another object as one to many.
#Entity
public class FutureMessage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String message;
#ManyToOne
private User user;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
This FutureMessage object has mapped to User object but there is a problem with post request.
{
"message":"qwerasdf",
"user":{"id":15,"username":"XXX"}
}
This request Returns 200 but in database XXX is not the right user name for id 15.
At the same time if user not exists in this request, it returns 200(OK) too.
My problem is this, I do not want to send user id. I just want to send message and username.(There are more than 2 attributes in FutureMessage objects)
#RequestMapping(path="message/new",method=RequestMethod.POST)
public FutureMessage newMessage(#RequestBody FutureMessage newMsg){// how to add user name here ?
FutureMessage msg = null;
if(newMsg==null)
return null;
if(newMsg.getUser()==null){
return null;
}
try{
msg = messageRepository.save(newMsg);
}catch(Exception ex){
ex.printStackTrace();
}
return msg;
}
EDIT : I only want to send FutureMessage object and username as ResponseBody. This is my question not returning 404 or anything else.

Spring has parameterized object named ResponseEntity that you should return from your controller methods. It allows you to programmatically send different HTTP responses based on the request. Look at this post for an example of returning a 400 response when the request is bad, in your case when the User is null.
As for the mismatch between user XXX and id 15 I don't fully understand what your problem is. It sounds like you just want to send in the message and username? if that is the case your repository needs to be smart enough to check on username and the usernames better be unique or you'll get collisions.

Responding to your first problem. Even though you return null, it will be SUCCESS (Status code - 200) http request. If you specifically want to send the request as bad request when you find your logic returns null, then return like
return new ResponseEntity<String>(HttpStatus.BAD_REQUEST);
How to respond with HTTP 400 error in a Spring MVC #ResponseBody method returning String?
Second, when you don't want to send the user id. The make sure you have configured the correct relationship between your models (message, user ) and you have placed the user Id's generated type to any of your Id generation strategy such as auto increment

Related

Check if resource belongs to user before deleting/updating it

Here's my spring endpoint:
#DeleteMapping("/{id}")
public ResponseEntity<Package> deletePackage(JwtAuthenticationToken principal,
#PathVariable Long id) {
Package parcel = packageService.retrievePackage(id);
checkResourceBelongsToUser(principal, parcel);
return new ResponseEntity<>(packageService.deletePackage(parcel), HttpStatus.ACCEPTED);
}
And here's my current way of checking if the current resource the user is trying to update/delete belongs to the user:
private void checkResourceBelongsToUser(Address address, AppUser user) {
if (!address.getUser().getId().equals(user.getId())) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Not allowed to access/modify resource");
}
}
This method will check if the Address belongs to the user.
The address was gotten from a id in the path variable
The user was gotten from the id in the JWT
Update and delete endpoint call the checkResourceBelongsToUser method before doing any processing (updating or deleting). I'm comparing ids. Is my method enough to check if the user truly has access?

Patch udpates and Validate RequestBody

I have been studying spring boot for a few weeks.
I am building a simple api using hibernate + jpa with a mysql database.
I have a resource call TvShowReminderResponseDTO :
public class TvShowReminderResponseDTO {
// Attributes
private Integer idTvShowReminder;
private User user;
private UserTvShow userTvShow;
private TvShowDetailsResponseDTO tvShowDetailsResponseDTO;
private Boolean completed;
private Integer currentSeason;
private Integer currentEpisode;
private Integer personalRating;
// rest of the code omittedfor brevity
}
In my controller i have a basic update PATCH endpoint, that receives the id of the tv show reminder (entity) that is stored in my database and also i receive a TvShowReminderPatchDTO with the information i want to update:
PatchDTO and Controller:
public class TvShowReminderPatchDTO {
// Attributes
private Optional<Boolean> completed;
private Optional<Integer> currentSeason;
private Optional<Integer> currentEpisode;
private Optional<Integer> personalRating;
// rest of the code omittedfor brevity
}
#PatchMapping("/{idTvShowReminder}")
public void updateTvShowReminder(#RequestBody #Valid TvShowReminderPatchDTO tvShowReminderToUpdate,
#PathVariable Integer idTvShowReminder){
tvShowReminderService.updateTvShowReminder(tvShowReminderToUpdate,idTvShowReminder);
}
Also I have my service method that is in charge of searching the TvShowReminder entity by its id, and then update the information we get from the client.
public void updateTvShowReminder(TvShowReminderPatchDTO tvShowReminderToUpdate, Integer idTvShowReminder) {
Optional<TvShowReminder> tvShowReminder = getTvShowReminder(idTvShowReminder);
TvShowReminder currentTvShowReminder = tvShowReminder.get();
if(tvShowReminderToUpdate.getCompleted() != null) {
if (tvShowReminderToUpdate.getCompleted().isPresent()) {
currentTvShowReminder.setCompleted(tvShowReminderToUpdate.getCompleted().get());
} else {
currentTvShowReminder.setCompleted(null);
}
}
if(tvShowReminderToUpdate.getCurrentSeason() != null) {
if (tvShowReminderToUpdate.getCurrentSeason().isPresent()) {
currentTvShowReminder.setCurrentSeason(tvShowReminderToUpdate.getCurrentSeason().get());
} else {
currentTvShowReminder.setCurrentSeason(null);
}
}
if(tvShowReminderToUpdate.getCurrentEpisode() != null) {
if (tvShowReminderToUpdate.getCurrentEpisode().isPresent()) {
currentTvShowReminder.setCurrentEpisode(tvShowReminderToUpdate.getCurrentEpisode().get());
} else {
currentTvShowReminder.setCurrentEpisode(null);
}
}
if(tvShowReminderToUpdate.getPersonalRating() != null) {
if (tvShowReminderToUpdate.getPersonalRating().isPresent()) {
currentTvShowReminder.setPersonalRating(tvShowReminderToUpdate.getPersonalRating().get());
} else {
currentTvShowReminder.setPersonalRating(null);
}
}
tvShowReminderRepository.save(currentTvShowReminder);
}
I have a question about the #valid annotation in the controller: i thought that it will check if the object that we send from postman for example is of type TvShowReminderPatchDTO , but i can send an entire different object and the controller will start its excecution, and the TvShowReminderPatchDTO will have all its attributes in NULL.
Whats the best way to check if the request body its in fact a TvShowReminderPatchDTO ?
I want to validate if the object we get from the Request is an instance of the TvShowReminderPatchDTO, and if not, throw an Exception.
The method that is doing the PATCH is working but its very ugly, I use optional as attributes in the TvShowReminderPatchDTO , so i can distinguish if the client wants to set a NULL (send an attribute with a null value ) or if the attribute was ommited (it does not appear on the request body) so we dont need to do anything, meaning we dont update it.
Can you guys recommend a better way to do this or improve the existing code?
Add some required fields using #NotNull annotation in your dto to help Spring understand which attributes should be present in your type
Don't use Optional. There is already JsonNullable for this purpose
public class TvShowReminderPatchDTO
{
#NotNull
private JsonNullable<Boolean> completed = JsonNullable.undefined();
}
And in controller method:
if (dto.getCompleted().isPresent()) {
object.setCompleted(dto.getCompleted().get());
}
That's it, no null-checks required, just set the value

Hide ID on POST in RequestBody, but return ID on created

For example I have a bean
public class Order
{
int orderID;
String name;
}
And I have a POST operation
#ApiOperation(value = "Insert a new order", response = Order.class)
#RequestMapping(value = "/addOrder", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public Order addOrder(#Valid #RequestBody Order order)
{
//Set random id here
order.id = 'xxxxx';
Order o = orderService.insertOrder(order);
return o;
}
And in Swagger I have the following:
So my question is, how do I hide id on POST but show ID on GET?
Or should I add a description saying that even if you choose to add an ID it wont do anything and just return my random id? Just like in Kubernetes (uid)
And properties like read-only in #ApiModelProperty will solve anything?
A simple approach is to split your bean in two - one for creating a new object, and another one which extends that for data about an existing object.
e.g.
public class IncompleteOrder {
String name;
}
public class ExistingOrder extends IncompleteOrder {
int id;
}
Then have your POST method take an object of IncompleteOrder and return one of ExistingOrder. I'd also delegrate responsibility for assigning a random order id to the underlying service...
public ExistingOrder addOrder(#Valid #RequestBody IncompleteOrder order) {
ExistingOrder o = orderService.insertOrder(order);
return o;
}
The same thing could be achieved by having two completely separate classes with no inheritance relationship, which would probably be appropriate if there was a significant divergence between the information needed to create a new order from the information which is on an existing order.
An alternative is to ask what the id is actually for - why are your clients getting integer id's for anything? Ideally, if they want any information about the order they should be querying the API for the resource, and to do that they need the URI of the order rather than the integer id. So external services communicating about an order should be passing the URIs back and forth rather than ids. Perhaps you could encourage your clients to communicate with each via the URI you return in the Location header from your POST request? Then you could do away with exposing the id on your response and have a purely symmetric request / response body.

Spring Rest Json Mapping on POST (Invalid Property)

This is a weird one for me. I've done the entities and the controllers and the form validation before, but I'm confused on this error.
So backstory. This is spring-boot w/Hibernate, connecting to a PostgreSQL Db. What I am attempting to do, is map a POST request to creating a resource. I'm trying to do this with pure JSON. I've been able to achieve this before.
The error in question is...
Invalid property 'Test' of bean class [com.example.api.entities.forms.OrganizationRegistrationForm]: Bean property 'Test' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
The request body, as it is in Postman is...
{
"organizationName":"Test",
"employees":10
}
The OrganizationRegistrationForm class it's complaining about...
public class OrganizationRegistrationForm {
#NotEmpty
private String organizationName = "";
#NotNull
private int employees;
private JsonNode contactInfo;
private JsonNode locationInfo;
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String name) {
this.organizationName = name;
}
public int getEmployees() {
return employees;
}
public void setEmployees(int employees) {
this.employees = employees;
}
public JsonNode getContactInfo() {
return contactInfo;
}
public void setContactInfo(JsonNode contactInfo) {
this.contactInfo = contactInfo;
}
public JsonNode getLocationInfo() {
return locationInfo;
}
public void setLocationInfo(JsonNode locationInfo) {
this.locationInfo = locationInfo;
}
}
And in case you need it, the request method...
#RequestMapping(value="/organization", method = RequestMethod.POST)
public Organization registerOrganization(#Valid #RequestBody OrganizationRegistrationForm form,
BindingResult bindingResult) throws Exception {
if(bindingResult.hasErrors()) {
LOGGER.error("The registration form entered has errors: {}", bindingResult.getAllErrors().toString());
throw new InvalidForm();
}
try {
Organization org = orgService.registerOrganization(form);
if(org!=null)
return org;
} catch(DataIntegrityViolationException e) {
bindingResult.reject("name.exists", "The supplied name is already in use");
}
throw new InvalidForm();
}
Although I'm guessing it doesn't even get that far. Originally the orginazationName field was called "name", but I changed it to see if maybe that was the issue.
The even weirder part for me is when I used this JSON object it worked. But created an organization named "organizationName".
{
"organizationName":"organizationName",
"employees":10
}
And one time it even complained that the invalid property was ''. As in empty. What am I doing wrong here?
I don't know how, or why. But for some reason the answer seemed to be in the OrganizationRegistrationFormValidator class that the binder uses.
The evil line in question was in validate(Object target, Errors errors) method...
ValidationUtils.rejectIfEmptyOrWhitespace(errors, target.getOrganizationName(), "name.empty", "Please enter a name");
Changing that line to a classic check worked.
if(target.getOrganizationName.isEmpty())
errors.reject("name.empty", "Please enter a name");
For documentation sake, anyone know why that happened? Are my api docs wrong when IntelliSense suggested that method signature?
I know this is old but I just stumbled over it:
to me ValidationUtils.rejectIfEmptyOrWhitespace(errors, target.getOrganizationName(), "name.empty", "Please enter a name"); looks wrong.
It should be:
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "organizationName", "name.empty", "Please enter a name");
The second attribute is the Field Name, not its content. ValidationUtils will take that name and transform it to the standard getter (getOrganizationName in that case) to retrieve its value and validate that.
This is why it tells you ther is no property named Test. Because there is none.

foreign keys in objectify / google app engine

I'm trying to write an application for the google app engine using Objectify and am having some trouble. I'm new to noSQL datastores so it's probably a conceptual problem.
I have an entity called a message, and each message has a from User - the user who created the message.
#Entity
public class Message {
#Index private Key<User> fromUserKey;
#IgnoreSave private User fromUser;
Annoyingly, I have to have both a User and a Key field in the message. JSON needs the User object to populate the response with useful fields, and the google app engine needs the Key to be able to store the reference to the user. The #IgnoreSave annotation is used to stop Objectify asking the google app engine to try to store the User object (which will fail).
When fetching messages, the from user key is populated, but the from User object is not. Here's what the DAO code looks like for my "getMessages" operation:
public static List<Message> getSentMessages(long userId) {
List<Message> result;
result= ofy().load().type(Message.class).filter("from", Key.create(User.class, userId)).limit(1000).list();
return result;
}
But the fromUser object is not populated on each Message, only the fromUserKey. How do I get the actual User object populated in the response? I need to access such fields as forename, surname, etc - not just the ID of the User. I could get the from User from the DB, loop through the results, then call setFromUSer on each message, but it's ugly. And it also causes a ConcurrentModificationException...
I'm sure I'm doing something wrong here, but I can't work out what.
What you can do is have a property Ref<User> on the Message entity and annotate it with '#Parent'. This means that each Message entity will become part of the user's entity group.
The Ref<?> works like a key but allows you to directly access the actual entity object; that way you can easily get the forename, surname etc.
Change your class as follows:
#Entity
#Cache
public class Message
{
#Id Long id;
#Load #Parent private Ref<User> user;
public User getUser() { return this.user.get(); }
public void setUser(User value) { this.user = Ref.Create(value); }
}
Having done that, you will be able to perform ancestor queries to retrieve all the message entities associated with a particular user:
public static List<Message> getSentMessages(long userId)
{
User parent = ofy().load().type(User.class).id(userId).now();
List<Message> results = ofy().load().type(Message.class).ancestor(parent).limit(1000).list();
return results;
}
You should now be able to do what you wanted, and hopefully not get any more errors.

Categories