Spring: How do I construct this command object through a GET HTTP request? - spring

I'm using Spring 3.1.0.RELEASE with Hibernate 4.0.1.Final. I want to invoke a search method in a controller that takes as input a search bean (the Event bean below) ...
#RequestMapping(value = "/search_results.jsp")
public ModelAndView processSearch(final HttpServletRequest request, final Event searchBean, final BindingResult result) {
...
}
The event bean contains the following field ...
#Entity
#Table(name = "EVENTS")
public class Event implements Comparable {
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="EVENT_FEED_ID")
private EventFeed eventFeed;
...
}
in which the EventFeed object contains the following fields ...
#Entity
#Table(name = "EVENT_FEEDS")
public class EventFeed {
#Id
#Column(name = "ID")
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#NotEmpty
#Column(name = "TITLE")
private String title;
...
}
How do I construct a URL such that the search bean's Event.getEventFeed().getId() field is populated?
I realize I could submit a GET request with a parameter like "eventFeedId=2" and populate everything manually, but since other pages are submitting requests that populate the command object, I'd like to continue to use the same logic.

It would be
/search_results.jsp?event.eventFeed.id=...&event.eventFeed.title=...
event is a default model attribute name as defined in #ModelAttribute, other binding rules are described in 5.4.1 Setting and getting basic and nested properties.
Note, however, that this approach can cause problems if you'll associate these bean with Hibernate session later. For example, if you want to attach new Event to the existing EventFeed by calling merge() it would also override the title property. Thus, in such a case it would be better to avoid overuse of data binding and pass primitives as parameters instead.

Related

Throw error when properties marked with #JsonIgnore are passed

I have a requirement to mark certain properties in my REST beans as ignored using #JsonIgnore. (I am using Spring Boot). This helps in avoiding these properties in my Swagger REST documentation.
I also would like to ensure that if the client passes these properties, an error is sent back. I tried setting spring.jackson.deserialization.fail-on-unknown-properties=true, but that works only for properties that are truly unknown. The properties marked with #JsonIgnore passes through this check.
Is there any way to achieve this?
I think I found a solution -
If I add #JsonProperty(access = Access.READ_ONLY) to the field that is marked as #JsonIgnore, I get back a validation error. (I have also marked the property with #Null annotation. Here is the complete solution:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
#Null(message = "Id must not be passed in request")
private String id;
private String name;
//getters and setters
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class EmployeeRequest extends Employee {
#Override
#JsonIgnore
#JsonProperty(access = Access.READ_ONLY)
public void setId(String id) {
super.setId(id);
}
}
PS: By adding #JsonProperty(access = Access.READ_ONLY), the property started showing up in Swagger model I had to add #ApiModelProperty(hidden = true) to hide it again.
The create method takes EmployeeRequest as input (deserialization), and the get method returns Employee as response (serialization). If I pass id in create request, with the above solution, it gives me back a ConstraintViolation.
PS PS: Bummer. None of these solutions worked end-to-end. I ended up creating separate request and response beans - with no hierarchical relationship between them.

How to use #RestController (Spring) with a child List of object

I'm trying to create a REST service with Spring.
Everything works until I try to add a List of object (CartItem) to my main object (Cart).
This is my main object
#Entity
#Table(name="cart")
public class Cart implements Serializable{
...
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
/*when I add this I get the error. If I remove this, the
REST service works*/
#OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
//getter, setter, constructors, other fields ecc.
}
This is the object inside the List:
#Entity
#Table(name="cart_item")
public class CartItem implements Serializable{
...
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#OneToOne(targetEntity = Product.class, cascade = CascadeType.ALL)
#JoinColumn(referencedColumnName="productId", name="product_id" )
private Product product;
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
//getter, setter, constructors, other fields ecc.
}
This is my controller
#RestController
#RequestMapping(value="rest/cart")
public class CartRestController {
...
#RequestMapping(value = "/", method = RequestMethod.GET)
public List<Cart> readAll() {
return cartService.read();
}
...
}
I get this error:
SEVERE: Servlet.service() for servlet [dispatcher] in context with path
[/webstore] threw exception [Request processing failed; nested exception
is org.springframework.http.converter.HttpMessageNotWritableException:
Could not write JSON: Infinite recursion (StackOverflowError); nested
exception is com.fasterxml.jackson.databind.JsonMappingException:
Infinite recursion (StackOverflowError) (through reference chain:...
I suppose that I had to manage the List inside the Cart object in a particular manner, maybe because i'm using JPA, but I still didn't find a solution on the internet.
Can anyone help me?
This is a serialization recursion problem, it happens because CartItem has a bidirectional mapping back to Cart. So what happens is that
a Cart gets serialized to JSON
all the CartItems inside it get serialized to JSON
the Cart property inside CartItem get serialized to JSON
the CartItems inside the cart get serialized to json, etc. etc.
You will probably want to exclude the CartItem.cart field from serialization by marking it with the #JsonIgnore annotation.
It is only too easy to expose far too much information to the outside world if you use JPA entities directly inside your webservices. Jackson actually has a useful feature called a JsonView which allows you to define which properties get exposed, you can even tailor it per webservice call if you want.
Never ending list? Did you mean a stackOverFlow exception?
If the situation is just like I said,then you should check something like fetch type and the entities' toString() or equal() method or something like that.
For example,there are to entities named A and B and their relationship is one to many(A is the one).If you config both of their fetchType as Eager,then when jpa query A,it will query B too.But B also contains A,so jpa will query A again.This kind of circle loop will cause a stackOverFlow.
By the way, how about providing more info about your problem like the Exception name?It's too hard for me to give you a specific solution,All I can do is to tell you some experiences I have met before.
Well,I created a small project with SpringBoot 2.1.0 and MySql.
It's my cartItem
public class CartItem {
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#JsonIgnore
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
}
and my cart:
public class Cart {
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Id
#Column(name="id")
private Integer id;
#OneToMany(mappedBy="cart", fetch = FetchType.EAGER)
private List<CartItem> cartItems;
}
Controller is as same as you wrote.After adding a #JsonIgnore to cart filed of CartItem,circle loop is over(before i do that,the program did had a circle loop problem).
Every time you use jpa with #oneToMany,#ManyToOne or #ManyToMany,you should be careful about this problem.This circular reference case could happen when instantiating a object, printing a object or something like this.And of course there is a lot of way to solve it like changing fetch type to LAZY,adding #JsonIgnore,overriding toString() and equal() method.

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

How can I reload my hibernate dependent objects before de validation

I have an Spring controller with code like:
#RequestMapping("save")
public String save(#ModelAttribute #Valid Form form, BindingResult result){
if( result.hasErrors()){
[...]
My form contains a list of hibernate objects. All have their properties setted. I create an edit HTML form and in the controller I find that all the objects on the ManyToOne relationships is lost. I only have the ID. I could reload data from the database but it is too late for the validation casued by the #valid annotation.
public class Form{
#Valid
#NotNull
private List<Item> item;
#NotNull
private Foo foo;
[...]
And Item
#Entity
#Table(name = "item")
#XmlRootElement
public class Item{
#ManyToOne()
#JoinColumn(name = "dependent", referencedColumnName = "id", nullable = false)
#NotNull
private Dependent dependent;
#NotNull
private Currency currency;
How could I set the Dependent and Currency fields before the validation? Is there any alternative to reload data from the database?
(Disclaimer some names have been changes to protect the inocent)
If you are using Spring-Data-JPA you can register DomainClassConverter to do this work for you. In another case you may write such converter by yourself.
I found one way to do it:
Add to the controller a reference to SmartValidator.
#Autowired private SmartValidator validator;
Remove the #valid annotation. Reload all ManyToOne tables and call manually the validator.
#RequestMapping("save")
public String save(#ModelAttribute Form form, BindingResult result){
for(Item item : form.getItems()){
item.setDependant( myDAO.reload(item.getDependent()));
}
validator.validate(form, result);
if( result.hasErrors()){
[...]

Spring #PathVariable returns HTTP Status 404

What I'm trying to do retrieve a user by their id and also retrieve the modules assigned to them on a page. I have mapped the one to many relationship in the models.
User Model
#Entity
#Table(name = "user")
#Component
public class User implements Serializable
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="user")
private Set<Module> sModule = new HashSet<Module>();
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, mappedBy="manager")
private Set<Module> cModule = new HashSet<Module>();
Module model
#Entity
#Table(name = "modules")
#Component
public class Module implements Serializable
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="user_id", insertable=false, updatable=false)
private User user;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="manager_id", insertable=false, updatable=false)
private User manager;
Controller for it -
#RequestMapping(value="/home/user_page/{userId}", method =
RequestMethod.GET)
public String showUserModules(#PathVariable("userId") String userId, ModelMap
map, HttpServletRequest request) {
map.addAttribute("cp", request.getContextPath());
map.addAttribute("user", userService.getWithModules(userId));
return "/home/user_page";}
When I try to open the user_page it returns an error showing:
The requested resource is not available
So how do I get the user and the required modules for them when I go their user page.
Edit: Stacktrace
WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP
request with URI [/professional/home/user_page] in DispatcherServlet
with name 'servlet-context'
I think the problem in your case coused by similar mapping for two methods in controllers. As I understood one of the methods has mapping like "/home/user/setting_page" and another one "/home/user/{userId}". That's why after you have added userId path variable controller tries to parse "setting_page" as userId, and you get this error because such user doesn't exists. Try to change one of this URL in order to make them unique.
EDIT:
Ok. As I see from your updates the reason of the problem is that Spring tries to find mapping for url "home/user_page" but it can't because you don't have method with such mapping in controllers. If you want to display JSP (or html) as a result of this method call then you should define InternalViewResolver in your config file and return name of the page. Here is tutorial where among other described how to use InternalViewResolver.

Resources