For my REST-API within a data-warehouse I need some role-based data access. First lets clarify the requirements with some small example. We define to entities Author and Book, both use the PagingAndSortingRepository for their default behaviour. An author can "own" several books, where an book can only depend to one author.
The simplified entities should look like the following:
#Entity
#Table(name = "Author")
public class Author{
// [..]
#OneToMany(mappedBy = "author")
private List<Book> books;
}
#Entity
#Table(name = "Book")
public class Book{
// [..]
#ManyToOne
#JoinColumn(name = "Author_ID")
private Author author;
}
Then I define two roles User and Admin. Normally an author is a common user, so the authorization mechanise only adds a SimpleGrantedAuthority for the role User. But there are some special authors which additionally have the role Admin.
When a normal author with role User calls the url \books, he should only get the books he own, while an an author with role Admin should get all books that exist. Also for the PUT/PATCH/DELETE request authors with the User role, should only be able to update / delete their own books, while the Admin role is able to do this for all books.
My Question: Is there a way to define the data access once in the Controller class? I know something like that from the Django-Framework, where I can override the method get_queryset(), which provides the dataset to work with for every "view"-method (GET/LIST/CREATE/UPDATE/etc.). The way I currently archive this, is to define the methods in the controller for the different API-endpoints and than mange the access there. Which causes two problems:
A lot of work, to implement the methods in the controller
If you have many dependencies between your entities, which is the case for my dwh, you can easily miss some endpoint. As result, I might have an endpoint, where every author has full access, no matter which role.
I thinks this should be a common problem, but I couldn't find a common solution yet. So I am thankful for every advice.
Edit: Example of "secured method"
#RequestMapping(value = "/dimensionAttributeValues", method = RequestMethod.GET)
#ResponseBody
public PagedResources<DimensionAttributeValue> getDimensionAttributeValues(Pageable pageable, PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<DimensionAttributeValue> result;
if (SecurityUtils.userHasRole(ADMIN) || SecurityUtils.userHasRole(TIMEMANAGER)) {
result = dimensionAttributeValueService.getAllDimensionAttributeValue(pageable);
} else {
result = dimensionAttributeValueService.getUserDimensionAttributeValue(SecurityContextHolder.getContext().getAuthentication().getName(), pageable);
}
PagedResources<DimensionAttributeValue> resources;
resources = this.toResource(result, persistentEntityResourceAssembler);
// TODO: Remove dirty Hack!
Link searchLink = linkTo(DimensionAttributeValueController.class).slash("/dimensionAttributeValues/search").withRel("search");
resources.add(searchLink);
return resources;
}
if you want to do it at repository level, spring security gives you access to the principal in the repo.
You need anyway to define a custom query for that.
Something similar to what described here: https://www.baeldung.com/spring-data-security in chapter 3.2
Otherwise you can add Service layer and than use the #PreAuthorize annotation
You could add a where condition to your repository call to filter the result by roles.
Therefor check this method of the JpaSpecificationExecutor:
Page<T> findAll(#Nullable Specification<T> var1, Pageable var2);
Example (Can't test it right now, but this should lead you in the right direction) :
import static org.springframework.data.jpa.domain.Specifications.where;
..
Specification<Book> roleFilter = (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("role"), "Admin");
Page<Book> page = this.yourRepository.findAll(where(roleFilter), pageable);
Related
I have entities that represent my database - User, Recipe and Tag.
For data manipulation I use DTO. So UserDTO, RecipeDTO, TagDTO. When I define a relationship between entities, I use its basic User, Recipe, Tag form, but when I define these relationships in a DTO class, I use its DTO form.
For example:
DTO Class looks like this
public class UserDTO{
private String name;
private String email
private List<RecipeDTO>
}
public class RecipeDTO{
private String title;
private String description;
private UserDTO user;
}
I know how to map a DTO to an entity so that I can perform operations (CRUD) on the data in the database.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle);
recipe.setDescription(recipeDTO.getDescription);
}
But the RecipeDTO also has a UserDTO in it, which I also need to map to an entity. How do I do this?
So I am trying to achieve a mapping inside the mapping .... (??)
I can think of the following solution.
Create method that converts UserDTO to User:
private User convertUser(UserDTO userDTO){
User user = new User();
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
}
And then use it while mapping RecipeDTO to Recipe.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle());
recipe.setDescription(recipeDTO.getDescription());
//Convert UserDTO
recipe.setUser(convertUser(recipeDTO.getUser()));
}
I'm not sure if this is the right solution, as there will be more and more mappings as the code gets bigger.
The approach you described is not wrong and will work, but doing it that way will indeed involve a lot of hard work.
The way this is usually done in the industry is by letting a library do that work for you.
The two most popular mapping libraries for java are:
https://mapstruct.org/ (which uses annotation processing at compile time and auto-generates basically the same mapping code as in your example)
and
http://modelmapper.org/ (which uses black magic and reflection)
They are both easy to setup/learn and either will do the job (including mapping nested objects as in your example), so take a look at the “getting started“ section and pick the one you find more intuitive to use.
My personal recommendation would be to pick Mapstruct, as it has way fewer gotchas, generates clean human-readable code and avoids using reflection.
Let's consider we have two entities. User entity can have many offers and an Offer must have one User.
class User {
...
#OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
}
At this moment there are two controllers. UserController and the OrderController. The UserController is mapped under /api/v1/users/ and the OrderController is mapped under /api/v1/orders/.
What should an endpoint look like that fetches the user's offer list?
Should it be in the same controller? I do have by functionality project structure.
How to modify or delete an Offer for a particular User? In case we would have /api/v1/users/{username}/offers/{offerId} to delete or update an offer, should we have also /api/v1/offers/{offerId} endpoint that allows to edit or remove an offer? Perhaps it is worth having it for an administrator?
General rules of thumb that I use when creating my endpoints are:
URLs need to be clean and easy to understand
They should be as short as possible while still informative.
Try to build it in a such manner that it allows you to reuse it within the reasonable amount
Think about the user experience(whether this is called from browser or mobile app etc.)
I am not sure if there is a written rule exactly how one should build an URL though.
In your specific case I would use /users/{username}/offers/{offerId} only if this is the only place you are exposing and using offers, since you are separating your code by functionality.
If you have any further logic around offers and/or have beans that have such logic I would create a separate controller for Offers which would be under /offers.
Concerning your last question. This very much depends on what you are trying to achieve. If you need to be able to update/delete/create offers then it makes sense to have such functionality. Even if it only used by the administrator. You can restrict the access to the endpoint. How to do that depends on the way you are authorize your users and the information that you have on them. Most people use roles.
If you decide to have the full CRUD functionality I would suggest to use a single path with combination of request methods.
Personally I would create the following:
#RestController
#RequestMapping(value = "/users")
class UserController {
#GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(#PathVariable("userId") String userId){
...
}
#GetMapping("{userId}/offers")
public Offer getOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
#PutMapping("{userId}/offers")
public Offer createOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#PostMapping("{userId}/offers")
public Offer updateOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#DeleteMapping("{userId}/offers")
public void deleteOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
}
In this scenario I think the POST/PUT for create and update will be cleaner as there will be no duplication of information. To be precise the IDs.
I agree that it should be in the same 'UserController', it makes sense because offers belong to the user, so having an endpoint like:
#GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(#PathVariable("user") String user) {
return offerService.getOffers(user);
}
You can define special DTOs for getting the meta-data from the offers if you wanted to display them in a list for example, and you could display them as a list to your user.
You could set up a similar endpoint for updating the offer which could be a POST endpoint, and a DELETE endpoint for deleting. You might want to think about what would happen if the user is looking at an offer when you delete it though, like making an asynchronous task for deleting the offer in a background thread and updating the UI to inform the user that the offer is deleted.
Spring has some really nice annotations for security stuff (check this and this), you could write your own annotation for administrator endpoints:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public #interface IsAdmin {}
Then annotate your method like this:
#DeleteMapping("/{user}/{offer}/delete")
#IsReceiverAdmin
public void delete(#PathVariable("user") String user, #PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
Of course the implementation of the service layer would be quite important, but it could be as simple as calling your repositories and performing the operations there :)
I am creating simple REST API. I want to create an object via post method. Object contains other object. When I want to post it, I am receiving EntityNotFoundException which is thrown when nested object does not exist.
Code of object that I want to post:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Book {
private String title;
#ManyToOne
private Author author;
#Id
#Column(unique = true)
private String isbn;
}
Service of this object:
#Service
#RequiredArgsConstructor
public class BookServiceImpl implements BookService {
private final BookRepository bookRepository;
private final AuthorRepository authorRepository;
#Override
public Book save(Book book) {
try {
Author byId = authorRepository.getById(book.getAuthor().getId());
} catch (EntityNotFoundException e) {
authorRepository.save(book.getAuthor());
}
return bookRepository.save(book);
}
}
After using post method I get this error:
javax.persistence.EntityNotFoundException: Unable to find com.jakubkolacz.qualificationTask.domain.dao.Author with id 0
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:163) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:216) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
I thought that using try catch to check if object exist, and saving author if necessary will help but it did not.
My question is where should I add some code to solve the problem. I understand why it is happening but do not know how to resolve it. The necessary thing is that I can not create service to add authors manually, they have to be added to repo only during adding new book.
The problem is that the save operation is not being cascaded down to the author object. You should add a cascade type inside ManyToOne annotation:
#ManyToOne(cascade = CascadeType.ALL)
Exception handling in Spring
If you are specifically wondering how to handle exceptions in Spring, then I would highly recommend THIS tutorial.
Entity Creation
First I would like to point out two minor problems with your entity creation.
1)#ManyToOne : while it is not necessary, I always like to annotate a many-to-one relationship with the #JoinColumn annotation. It just acts as a simple and friendly visual reminder that (assuming your relationship is bidirectional) this side is the owner of the relationship(has the foreign key)
2)#Id : as it currently stands, the JPA provider(lets assume hibernate) assumes that you the developer are taking care of assigning a unique identifier to the id field. Granted, this is sometimes neccessary when dealing with old legacy databases. However, if you are not dealing with a legacy database, I would recommend that you delete #Column(unique = true) and the String value to replace them with:
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long isbn;
#GeneratedValue will allow the system to automatically generate a value for isnb field.
strategy = GenerationType.IDENTITY tells the underlying database to handle the uniqueness and the auto incrementation in a way specific to the relational database.
Long instead of String because it can hold a larger number of bytes.
Service implementation
I have a few things to say about the BookServiceImpl class but first, good job on implementing an interface with BookService most people forget to program to an interface.
1) Single Responsibility
You are using both BookRepository and AuthorRepository which is of course fine(If it works it works). However, moving forward you should be weary not to add too many dependencies to one class. If you do so, you are breaking the Single Responsibility principle, which makes code harder to test and refactor.
2) Try and catch
The code inside your try catch blocks is a little confusing, especially since you have not shown the Author entity class. However, I am assuming you logic goes like this: if the author does not exist, save the author. Lastly save and return the book. Now you are correct in thinking that you handle the exceptions in the catch block. However, there is quite a lot to question here and only so little code to go on.
My overall recommendations
1) Break this method up : This method is trying to do three things at once. Create one method for saving the book, one for looking for the author and one for saving the author. This will allow for greater code reuse moving forward.
2) Read up on CascadeType : Specifically PERSIST, that might help you with your issues. Also, look into a many-to-many relationship as it is not uncommon for multiple books to have multiple authors.
In my application I use DTOs. My current solution in pseudocode is this - works well:
ResponseEntity<EntityDTO> RestController.get(String uuid){
EntityDTO dto = Service.get(uuid) {
Entity entity = Repository.loadEntity(id);
return EntityDTO.from(entity);
}
return ResponseEntity<EntityDTO>( dto , HttpStatus.OK);
}
Recently I saw an other solution without the transformation step in the service layer.
E.g. your Entity looks like this
:
#Entity
public class Book {
Long id;
String title;
String text;
.....
}
And the text is too 'heavy' to send it with the hole book you usually would create a DTO like this:
public class SlimBookDTO {,
static SlimBookDTO from(Book book) {
return new SlimBookDTO(book.id, book.title);
}
Long id;
String title;
.....
}
The "new" (for me) Solution is to create only an interface like this:
public interface SlimBookDTO {
Long getId();
String getTitle();
}
And your BookRepository gets a new method:
#Repository
public interface BookRepository extends JpaRepository<Book , Long> {
List<SlimBookDTO> findAllByTitle(String title);
}
With this method I don't need the service layer any more for direct requests. Is this common? Does somebody has experience with this? Has it some downsides that I can't see in a small application but will face in larger scale?
Those are couple of ways of returning data from the database.
You create DTO and map necessary fields and return
Other is create an interface which is directly a kind of return type from Repository. this is what we call as JPA interface projection.
For second one, you know in detail by referring below link
https://www.baeldung.com/spring-data-jpa-projections
JPA interface projections are very useful when we query two or more entities in the Repository class
This is totally fine for simple GETs if the objects are straightforward enough, although of course you can't add additional logic, formatting or constraints. But as long as you don't need to do that, this will work well.
I don't think Hibernate analyzes the dto to only select a few fields though, so if you want to improve the performance too you can define the queries yourself, i.e. #Query("select new com.bla.SlimbookDTO(book.id, book.title) from Book book"), at the cost of not being able to just use automagically generated queries anymore based on the method name.
My bean looks like that:
#Entity
public class Fattura {
#Id
Long id;
#NotEmpty
String numero;
#Min(value=0)
Double importo;
Key<User> utente;
// gets & sets....
}
The "utente" property is the key of another bean I created: a "Fattura" can have only one "User", one "User" can have many "Fattura"s
My Spring MVC controller will manage a request for a list of Fattura and display them in a simple jsp:
#RequestMapping( value = "/fatture" , method = RequestMethod.GET )
public ModelAndView leFatture() {
ModelAndView mav = new ModelAndView("fatture");
mav.addObject("fatture",fatturaService.listFatture());
return mav;
}
the code of the jsp is really simple: only a foreach cycle in a table
My question is:
how can I display the "utente"?
The only thing I have is its key, but I'd like to do something like ${fattura.utente.firstName} in my JSP, how can I do it?
Unfortunately you would have to manually fetch "utente" in your DAO class. There is no automatic fetching in Objectify like in Twig. In my POJOs I have following fields
#Transient private Organization sender; // Pickup location (for client RPC)
transient private Key<Organization> senderKey; // Pickup location (for Datastore)
I load entity from Datastore and then load manually Organization using senderKey.
In new Objectify4 you'll be able to do what you want like this:
class Beastie {
#Parent
#Load
ParentThing parent;
#Id Long id;
#Load({"bigGroup", "smallGroup"})
SomeThing some;
#Load("bigGroup")
List<OtherThing> others;
#Load
Ref<OtherThing> refToOtherThing;
Ref<OtherThing> anotherRef; // this one is never fetched automatically
}
Here is evolving design document of new version.
Update at Nov 17, 2011: This is big news. Twig author, John Patterson, joined Objectify project today.
I know it sounds annoying that you have to manually fetch the two objects, but it's actually very useful to know that you're doubling your work and time to do this - each "get" call take a while and the second won't start until the first is complete. It a typical NoSQL environment, you shouldn't often need to have two separate entities - is there a reason that you do?
There are only two reasons I can easily think of:
The class references another object of the same type - this is the example in the Objectify documentation, where a person has a reference to their spouse, who is also a person.
The class that you're embedding the other into ("Fattura" in your case) has masses of data in it that you don't want fetched at the same time as you want to fetch the "User" - and you need the user on it's own more often than you need the "Fattura" and the "User". It would need to be quite a lot of data to be worth the extra datastore call when you DO want the "Fattura".
You don't necessarily have to use temporary field for just getting a object.
This works:
public User getUtente() {
Objectify ofy = ObjectifyService.begin();
return ofy.get(utenteKey);
}
This will of course do a datastore get() each time the getter is called. You can improve this by using #Cached on your User entity, so they turn into memcache calls after the first call. Memcache is good, but we can do a little better using the session cache:
public User getUtente() {
Objectify ofy = myOfyProvider.get();
return ofy.get(utenteKey);
}
The key thing here is that you need to provide (through myOfyProvider) an instance of Objectify that is bound to the current request/thread, and that has the session cache enabled. (ie, for any given request, myOfyProvider.get() should return the same instance of Objectify)
In this setup, the exact same instance of User will be returned from the session cache each time the getter is called, and no requests to the datastore/memcache will be made after from the initial load of this Entity.