What is the right design to have a spring Entity that is different than what the REST API returns? - spring

Right now, I have an #Entity say Car that has a certain set of attributes. This gets persisted into a database.
Now, in the #RestController, if I want to accept a Car parameter except for certain properties, how do I do that? Right now, I am creating a different class called CarInput that is the same as Car minus those properties.
Again, for REST API response, same thing. If I want to return a Car but with a certain field removed. Right now I created CarResponse as a model.
Is there a cleaner way to do this?

I'd make the case that your external representation and your internal storage should hardly ever be identical. Sure, there'll be significant overlap, but your database and your API should be as independent from each other as possible.
I'd say it's a good practice to have separate domain models for the model and view layer (read: two different Car classes, in different packages). You can use a mapping framework like Dozer or MapStruct to map back and forth between these different entity types.

There are two common approaches to such problem.
Use #JsonIgnore on fields/getters that you want to exclude. However, this can lead to annotation hell or generally hard to read code.
#JsonIgnore
private String password;
Create a DTO class that data would be deserialized from or serialized to. What I mean is that when some user makes a POST request with a car definition, it would be deserialized by spring to CarDto and then parsed by you in the service layer to the Car object which you could save to a database. Similarly, Car object would be parsed to CarDto if the user asks for a data.
#GetMapping("/{userId}")
UserDto getUser(#PathVariable Long userId) {
return userService.getUser(userId);
}
#PostMapping
UserDto addUser(#RequestBody UserDto userDto) {
return userService.createUser(userDto);
}
This one, on the other hand, could lead to a situation where you sometimes use a Dto and sometimes the class itself. Because of that, consider parsing to/from CarDto only in the controller layer (unlike in the example above)
Also it's good to avoid creating two classes in one file. It makes hard to find a desired class afterwards.

You can still avoid of using a DTO class.
When you post Car object to controller your can control the wanted properties and operate on it.
For selecting fields to return as the response you can use json views.
Entity :
public Car {
private String color;
#JsonView(Views.Public.class)
private Integer weight;
// getters, setters
}
Controller :
#RestController
public CarController
#Autowired
private CarRepository carRepository;
#GetMapping("/{id}")
#JsonView(View.Public.class)
public Book get(#PathVariable Long id){
return carRepository.findOne(id);
}
#PostMapping
public Book update(#RequestBody Car car) {
// only properties we want to update
if(car.getColor() != null) {
// save in database or other operations
}
}
}
View :
public class Views {
public static class Public {
}
}
This way the controller's method "get" will send to client only "weight" property and "update" method will operate only on selected properties.

Related

Spring REST: Show certain data of a User only if requester is a friend

Scenario:
A community webapp where people can form communities about certain topics with a Spring REST backend.
Today I was wondering how one would implement a setting akin "Who can see your email adress".
When a User enters a community and a REST call to e.g. /api/community/1/users is being made, how would it be possible to stop the CrudRepository from serializing a field such as email of User B if the user A making the API call to the backend is not a friend / does not fulfill certain criteria of user B's settings, such as only showing emails to approved contacts. The resulting JSON should contain a list of users with some having a email field and some not.
While searching I was not able to find anything that matches my question. Following are some things I have discovered but don't feel like they are of much help.
Annotating Controller methods / Repository methods with #PreAuthorize, passing in the Principal.
Why I think this might not help: This seems to be a good solution if I want to block someone based on their ID from viewing a ressource completely. Such as Users only being able to see their own data but not others because the Principal ID does not match the requested ressource's id.
Using JsonFilter as described here: https://www.baeldung.com/jackson-serialize-field-custom-criteria
With this approach I don't see a way of checking WHO is making a request for e.g. my email.
This approach seems to fit well for a scenario such as having a boolean flag set to show email or not, for all cases and any requesters.
Creating a new domain object such as "Friend" extending "User", which is only there for overwriting the #JsonIgnore property of User. While a normal User would not have their Email field serialized due to #JsonIgnore, a friend would set #JsonIgnore(false) over email.
I dont like this approach because I feel like it must somehow be possible to implement this functionality without creating new classes only to overwrite Jackson annotations.
Sorry if there isn't any code to show. So far I have only been creating simple entities and mostly theorycrafting how it would be possible to accomplish the above when I saw that the repository exposes everything. I'm usually more home at the Frontend side of things but I want to learn backend with Spring as well, for private as well as professional reasons. I hope the question isn't too basic.
Thank you in advance.
You can use #JsonView from Jackson for it.
First, create a DTO with the fields you want to return and annotate them with #JsonView:
public class UserDto {
#JsonView(NoFriend.class)
private String name;
#JsonView(Friend.class);
private String email;
public static class NoFriend {}
public static class Friend extends NoFriend {}
}
The NoFriend and Friend inner classes are just markers to define what fields should be returned in what case.
Now in your controller, instead of returning a UserDto, you wrap the UserDto in a MappingJacksonValue:
public class UserController {
#GetMapping("/api/community/1/users")
public List<MappingJacksonValue> getUsers(#AuthenticationPrincipal Principal principal) {
List<User> users = service.getUsers();
return users.stream()
.map( user -> {
MappingJacksonValue value = new MappingJacksonValue(UserDto.fromUser(user));
value.setSerializationView(getView(principal, user));
})
.collectors(toList());
}
private Class getView(Principal princapl, User user) {
// return UserDto.Friend.class or UserDto.NoFriend.class, depending the relation of the authentication principal with the user
}
Probably, not the simplest way to implement it. But maybe it will help you to decompose a problem and find an appropriate solution.
I assume that you just want to clear fields on API level, but still gonna fill it in your Objects.
Let's define a model with some security metadata on it:
class UserDTO {
Long id;
String name;
#AllowOnly("hasRole('FRIEND')") // SPeL/any your custom defined language, or simpler:
//#AllowOnly(Role.FRIEND)
String email;
}
Then define a controller class
#RestController
class SomeController {
#GetMapping("/api/community/{id}/users")
public List<UserDTO> getUsers() {
return List.of(
new UserDTO(1, "Bob", "email-you#gonna.see"),
new UserDTO(2, "Jack", "email-you-NOT#gonna.see"))
}
}
So what i propose is to create an aspect, which is gonna clear fields based on your permission model.
#AfterReturning("within(#org.springframework.web.bind.annotation.RestController *)
&& execution(* *(..))", returning="objectToClear")
public void applyFieldPermissions(Object objectToClear) {
// Here i would parse annotations on object fields
// and if found #AllowOnly, check your role to a user.
// and clean up field, if necessary
}
Logic of the aspect is totally dependent on your cases, but for this simple example, need only to implement some method to check your role for specific object
boolean hasRoleOn(UserDto dto, Role role, Authentication currentUser)

Spring HATEOAS recursive representation model processors?

I have a question concerning the representation model processors of Spring HATEOAS. We are experimenting to process models before serializing them to the client. Our use case is to enrich the imageUrl field of UserModel objects at runtime, as we have to build the URL based on values from a config bean (AWS S3 bucket URL differs for DEV / PROD setup).
#Data
public class UserModel {
// ...
private String imageUrl;
}
Therefore, we create a UserProcessor to implement this:
public class UserProcessor implements RepresentationModelProcessor<EntityModel<UserModel>> {
private final ConfigAccessor configAccessor;
public UserProcessor(ConfigAccessor configAccessor) {
this.configAccessor = configAccessor;
}
#Override
public EntityModel<UserModel> process(EntityModel<UserModel> model) {
if (model.getContent() != null)
// do the enrichment and set "imageUrl" field
}
return model;
}
}
This works perfectly if we have a controller method like this:
#ResponseBody
#GetMapping("/me")
public EntityModel<UserModel> getCurrentUser(#AuthenticationPrincipal Principal principal) {
UserModel user = ... // get user model
return EntityModel.of(user);
}
However, we are struggling now with the enrichment whenever a UserModel is referenced in another model class, e.g., the BookModel:
#Data
public class BookModel {
private String isbn;
// ...
private EntityModel<UserModel> user; // or "private UserModel user;"
}
A controller method returning type EntityModel<BookModel> only applies the processor for its type, but not for types that are referenced. It seems the processors are not applied recursively.
Is this intentional or are we doing something wrong?
Thanks for any input and help,
Michael
I encountered the same issue and I resolved it by manually assembling resources, in your case that would be implementing RepresentationModelAssembler of the BookModel and then manually invoking the processor on the userModel object that is inside the book.
Make the outer resource a representation model
First consider the BookModel to extend RepresentationModel so that you can manually add links and assemble inner resources (which you would like for the EntityModel<UserModel> object)
#Data
public class BookModel extends RepresentationModel<BookModel> {...}
Write a model assembler
Now write the assembler that takes your book entity and transforms it into a representation model or a collection of these models. You will implement here what EntityModel.of(...) does for you automagically.
#Component
public class BookModelAssembler implements RepresentationModelAssembler<Book, BookModel> {
#Autowired
private UserProcessor userProcessor;
#Override
public BookModel toModel(Book entity) {
var bookModel = new BookModel(entity) // map fields from entity to model
// Transform the user entity to an entity model of user
var user = entity.getUser();
EntityModel<UserModel> userModel = EntityModel.of(user);
userModel = userProcessor.process(userModel);
bookModel.setUserModel(userModel);
return bookModel;
}
}
I might be going out on a limb but I suppose the reason for this is that the processors get invoked when an MVC endpoint returns a type that has a registered processor, which in the case of embedded types is not invoked. My reasoning is based on the docs for RepresentationModelProcessor, which states that processor processes representation models returned from Spring MVC controllers.

Can I "inject" values from message resources into model objects before implicit Jackson serialisation?

I have a REST API built with Spring Boot / Spring MVC, using the implicit JSON serialization via Jackson.
Now, just before the implicit serialization, I would like to "inject" some UI texts from message resources into the objects that Jackson converts into JSON. Is there some neat, simple way to do this?
As a much simplified example, below I'd like to set Section title to a user-visible value, based purely based on its SectionType.
(Sure, I could hardcode the UI texts in SectionType, but I'd rather keep them separate, in resource files, because it's cleaner, and they might be localised at some point. And I can't autowire MessageSource in the entities / model objects which are not Spring-managed.)
#Entity
public class Entry {
// persistent fields omitted
#JsonProperty
public List<Sections> getSections() {
// Sections created on-the-fly, based on persistent data
}
}
public class Section {
public SectionType type;
public String title; // user-readable text whose value only depends on type
}
public enum SectionType {
MAIN,
FOO,
BAR;
public String getUiTextKey() {
return String.format("section.%s", name());
}
}
Somewhere in a #RestController:
#RequestMapping(value = "/entry/{id}", method = RequestMethod.GET)
public Entry entry(#PathVariable("id") Long id) {
return service.findEntry(id);
}
UI texts that I'd like to keep separate from code (messages_en.properties):
section.MAIN=Main Section
section.FOO=Proper UI text for the FOO section
section.BAR=This might get localised one day, you know
And what I'd like to do in a Spring-managed service/bean somewhere (using Messages, a very simple helper wrapping a MessageSource):
section.title = messages.get(section.type.getUiTextKey())
Note that if I call entry.getSections() and set the title for each, it will not affect the JSON output, since the Sections are generated on the fly in getSections().
Do I have to go all the way to custom deseriazation, or is there a simpler way to hook into the model objects just before they get serialized by Jackson?
Sorry if the question is unclear; I can try to clarify if needed.
As I said in the comment you can write an Aspect around every controller method that returns Section.
I wrote a simple example. You have to modify it with the message source.
Controller:
#RestController
#RequestMapping("/home")
public class HomeController {
#RequestMapping("/index")
public Person index(){
Person person = new Person();
person.setName("evgeni");
return person;
}
}
Aspect
#Aspect
#Component
public class MyAspect {
#Around("execution(public Person com.example..*Controller.*(..))")//you can play with the pointcut here
public Object addSectionMessage(ProceedingJoinPoint pjp) throws Throwable {
Object retVal = pjp.proceed();
Person p = (Person) retVal; // here cast to your class(Section) instead of Person
p.setAge(26);//modify the object as you wish and return it
return p;
}
}
Since the aspect is also a #Component you can #Autowire in it.

How to use Spring MVC with Hibernate?

Or I'm doing smth wrong, or it's really not suitable to work together. I'm talking about nested entities, since almost each Hibernate entity has it.
Here's the simple entity:
#Entity
...
public class Car implements Serializable {
String name;
#ManyToOne Category category;
...
}
And the simple controller as well:
#RequestMapping(value = "/cars/add", method = RequestMethod.POST)
public void add(Car car) {
// persist the car here
}
Now I want to specify a category directly from the form. Since it isn't simple object but entity, I'm unable to do that, isn't it?
So, to specify the category seems I'm forced to send it separately from Car object... It's ugly approach that makes no sense to use Hibernate with Spring at all.
Or I'm wrong and there're ways better a lot?
How your form structure is?
I suggest you to use json(means #RequestBody in controller) to submit the form or use #CommandObj to submit the form

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

Resources