I have the following situation:
My project contains multiple entities, each one with its respective controller, service and JPA repository. All of these entities are associated with a specific company by a "companyUuid" property.
Every incoming request in my controllers will have a "user" header, which will give me the details of the User making that request, including which company he is associated with.
I need to retrieve the company associated with the user from the header and filter every subsequent query by this company, which would be essentially like adding WHERE companyUuid = ... to each query.
What I did as a solution was a generic function for creating the Specification object:
public class CompanySpecification {
public static <T> Specification<T> fromCompany(String companyUuid) {
return (e, cq, cb) -> cb.equal(e.get("companyUuid"), companyUuid);
}}
Implemented repository as follows:
public interface ExampleRepository extends JpaRepository<Example, Long>, JpaSpecificationExecutor<Example> { }
Changed the "find" calls to include the specification:
exampleRepository.findAll(CompanySpecification.fromCompany(companyUuid), pageRequest);
Of course, this requires adding #RequestHeader to the controller functions to get the user in the header.
Although this solution works absolutely fine, it would require a lot of copy-pasting and code repetition to get it done for all routes of my #RestControllers.
Therefore, the question is: how can I do this in an elegant and clean way for all my controllers?
I have researched this quite a bit now and I came across the following conclusions:
Spring JPA and Hibernate don't seem to provide a way of dynamically using a Specification to restrict all queries (reference: Automatically Add criteria on each Spring Jpa Repository call)
Spring MVC HandlerInterceptor would maybe help for getting the User out of the header in each request, but it doesn't seem to fit overall since I don't use views in this project (it's just a back-end) and it can't do anything about my repository queries
Spring AOP seemed like a great option to me and I gave it a go. My intention was to keep all repository calls as they were, and add the Specification to the repository call. I created the following #Aspect:
#Aspect
#Component
public class UserAspect {
#Autowired(required=true)
private HttpServletRequest request;
private String user;
#Around("execution(* com.example.repository.*Repository.*(..))")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object[] arguments = jp.getArgs();
Signature signature = jp.getSignature();
List<Object> newArgs = new ArrayList<>();
newArgs.add(CompanySpecification.fromCompany(user));
return jp.proceed(newArgs.toArray());
}
#Before("execution(* com.example.controller.*Controller.*(..))")
public void getUser() {
user = request.getHeader("user");
}
}
This would have worked perfectly, since it would require almost no modifications at all to controllers, services and repositories. Although, I had a problem with the function signature. Since I am calling findAll(Pageable p) in my Service, the signature of the function is already defined in my advice, and I can't change to the alternative version findAll(Specification sp, Pageagle p) from inside the advice.
What do you think would be the best approach in this situation?
Here is an idea:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
#Aspect
public class UserAspect {
#Around("execution(* com.example.repository.*Repository.findAll())")
public Object filterQueriesByCompany(ProceedingJoinPoint jp) throws Throwable {
Object target = jp.getThis();
Method method = target.getClass().getMethod("findAll", Specification.class);
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
return method.invoke(target, CompanySpecification.fromCompany(request.getHeader("user")));
}
}
The above aspect intercepts the findAll() methods from repository and, instead of proceeding the call it replaces with another call to findAll(Specification) method. Notice how I get the HttpServletRequest instance.
Of course, it's a starting point not an out of the box solution.
I am not a Spring or Java EE user, but I can help you with the aspect part. I googled a bit, too, because your code snippets without imports and package names are a bit incoherent, so I cannot just copy, paste and run them. Judging from the JavaDocs for JpaRepository and JpaSpecificationExecutor, both of which you extend in your ExampleRepository, you are trying to intercept
Page<T> PagingAndSortingRepository.findAll(Pageable pageable)
(inherited by JpaRepository) and call
List<T> JpaSpecificationExecutor.findAll(Specification<T> spec, Pageable pageable)
instead, right?
So in theory we can use this knowledge in our pointcut and advice in order to be more type-safe and avoid ugly reflection tricks. The only problem here is that the intercepted call returns Page<T> while the method you want to call instead returns List<T>. The calling method surely expects the former and not the latter, unless you always use Iterable<T> which is a super-interface for both interfaces in question. Or maybe you just ignore the return value? Without you answering that question or showing how you modified your code to do this, it will be difficult to really answer your question.
So let us just assume that the returned result is either ignored or handled as Iterable. Then your pointcut/advice pair looks like this:
#Around("execution(* findAll(*)) && args(pageable) && target(exampleRepository)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, ExampleRepository exampleRepository) throws Throwable {
return exampleRepository.findAll(CompanySpecification.fromCompany(user), pageable);
}
I tested it, it works. I also think it is a bit more elegant, type-safe and readable than what you tried or what was proposed by Eugen.
P.S.: Another option is to convert the list into a corresponding page manually before returning it from the aspect advice if the calling code indeed expects a page object to be returned.
Update due to follow-up question:
Eugen wrote:
For another entity, let's say Foo, the repository would be public interface FooRepository extends JpaRepository<Foo, Long>, JpaSpecificationExecutor<Foo> { }
Well, then let us just generalise the pointcut and assume that it should always target classes which extend both interfaces in question:
#Around(
"execution(* findAll(*)) && " +
"args(pageable) && " +
"target(jpaRepository) && " +
//"within(org.springframework.data.jpa.repository.JpaRepository+) && " +
"within(org.springframework.data.jpa.repository.JpaSpecificationExecutor+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaRepository jpaRepository) throws Throwable {
return ((JpaSpecificationExecutor) jpaRepository)
.findAll(CompanySpecification.fromCompany(user), pageable);
}
The part of the pointcut I commented out is optional because I am narrowing down to JpaRepository method calls already via target() parameter binding using the advice signature. The second within() should be used, however, in order to make sure the intercepted class actually also extends the second interface so we can cast and execute the other method instead without any problems.
Update 2:
As Eugen said, you can also get rid of the cast if you bind the target object to the type JpaSpecificationExecutor - but only if you do not need the JpaRepository in your advice code before that. Otherwise you would have to cast the other way. Here it seems it is not really needed, so his idea makes the solution a little more lean and expressive, indeed. Thanks for the contribution. :-)
#Around(
"target(jpaSpecificationExecutor) && " +
"execution(* findAll(*)) && " +
"args(pageable) && " +
"within(org.springframework.data.jpa.repository.JpaRepository+)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Or alternatively, if you do not want to merge execution() with within() (a matter of taste):
#Around(
"target(jpaSpecificationExecutor) && " +
"execution(* org.springframework.data.jpa.repository.JpaRepository+.findAll(*)) && " +
"args(pageable)"
)
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
Less type-safe, but also an option if you believe that the are no other classes with * findAll(Pageable) signature:
#Around("target(jpaSpecificationExecutor) && execution(* findAll(*)) && args(pageable)")
public Object filterQueriesByCompany(ProceedingJoinPoint thisJoinPoint, Pageable pageable, JpaSpecificationExecutor jpaSpecificationExecutor) throws Throwable {
return jpaSpecificationExecutor.findAll(CompanySpecification.fromCompany(user), pageable);
}
You might notice that this suspiciously looks like my original solution for one specific sub-interface, and you are right. I recommend to be a little more strict, though, and not use the last option, even though it works in my test case and you would probably be fine with it.
Finally, I think we have covered most bases by now.
Related
I have a requirement to execute method queries against a given mongo database and retrieve data. But for that, I have to check whether the field delete=false(Soft delete) for taking them for all the queries.
It can be achieved by the following kind of query
eg: Optional<User> findByIdAndDeletedIsFalse(String id);
But as you can see we have to put DeletedIsFalse for all the queries.
I tried the answer provided in How to add default criteria to all the queries by default in mongo spring boot but I could find out that it can be only used then we are running queries directly using the mongo template.
After some debugging, I could find out that even though method queries are executed through the Mogno template, they are using package-protected classes and methods to do it. So they cannot be overridden by an inherited class.
I cannot find an entry point for them to executed and where to inject default criteria for the method queries.
Eg: If we check the implementation of MongoTemple, at the end the execution is happening through a method
<S, T> List<T> doFind(String collectionName, Document query, Document fields, Class<S> sourceClass, Class<T> targetClass, CursorPreparer preparer)
and that method is invoked from an internal class called ExecutableFindOperationSupport. All those classes are package protected.
Is there any reason to make them package protected and not giving the chance to override them from an inherited class?
Also is there any other way of running method queries with default criteria without appending them to all the queries?
The main problem in extending MongoTemplate as I've suggested in the previous question is need to override a lot of methods because MongoTemplate uses all them in its inner work. This way is not flexible and robust.
Want to suggest you another solution. You can implement Aspect that executes some code before invoking the MongoTemplate methods. You just add the additional criterion to every query the MongoTemplate receives.
Add the spring-boot-starter-aop to your dependencies. Enable AOP in the configuration class:
#Configuration
#EnableAspectJAutoProxy
public class AspectConfiguration {
}
And implement a small aspect that will do all works:
#Aspect
#Component
public class RepositoryAspect {
#Before("execution(* org.springframework.data.mongodb.core.MongoTemplate.*(..))()")
public void before(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof Query) {
Query query = (Query) arg;
// add your criteria to the query
return;
}
}
}
}
But keep in mind that this approach may lead to bad performace of executing queries. If you build a highload system, set your criterion is better way - it's the cost for fast work.
I have created a simple controller
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(com.querydsl.core.types.Predicate predicate) {
return repository.findAll(predicate);
}
When I call the GET /playerAccount API, I get the exception IllegalStateException "No primary or default constructor found for interface com.querydsl.core.types.Predicate" (thrown by org.springframework.web.method.annotation.ModelAttributeMethodProcessor#createAttribute).
After some (deep!) digging, I found out that if I delete the following line in my spring.xml file:
<mvc:annotation-driven />
And if I add the following line in my Spring.java file:
#EnableWebMvc
then the problem disappears.
I really don't understand why. What could be the cause of that ? I thought that these were really equivalent (one being a xml based configuration, the other being java/annotation based).
I read this documentation on combining Java and Xml configuration, but I didn't see anything relevant there.
edit:
from the (few) comments/answers that I got so far, I understand that maybe using a Predicate in my API is not the best choice.
Although I would really like to understand the nature of the bug, I first want to address the initial issue I'm trying to solve:
Let's say I have a MyEntity entity that is composed of 10 different fields (with different names and types). I would like to search on it easily. If I create the following (empty) interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, QuerydslPredicateExecutor<MyEntity> {
}
then without any other code (apart from the xml configuration ), I am able to easily search a myEntity entity in the database.
Now I just want to expose that functionality to a Rest endpoint. And ideally, if I add a new field to my MyEntity, I want that API to automatically work with that new field, just like the MyEntityRepository does, without modifying the controller.
I thought this was the purpose of Spring Data and a good approach, but please tell me if there's a better / more common way of creating a search API to a given Entity.
I didn't see that it returned an exception, that's why I thought it was a dependency problem.
Try to make your code look like this, and it will do it.
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts() {
return repository.findAll();
}
If you have a parameter in your request you add #RequestParam.
Code time (yaaaaaay) :
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(#RequestParam(required = false) Long id) {
return repository.findById(id);
}
Ps: the request should keep the same variable name e.g
.../playerAccount?id=6
i.e. In he following query method in a spring repository neither a nor b are required from an HTTP request. Is it possible to enforce the presence of these parameters at the repository level?
I would like to be explicit with the API I expose to the client. Right now no params, a, b, a&b are all accepted by the exposed endpoint. However I only want to expose a&b.
List<Thing> findByBAndC(#Param(value="a") Long a,#Param(value="b") Long b);
Don't know of any Spring Data way to do it, but spontanously I can think of some ways...
You could use a custom #Query where only if both are present ( "is not null" ) something would be returned, if that's enough
You could also (ab)use security with #PreAuthorize to check if both parameters are not null, but that sounds smelly.
Probably the most easy (and least smelly) way I can think of is to write your own Aspect that wraps around the method and throws an exception of both parameters are not present... For example, create your own custom annotation, put it before your method and then write an aspect, something like (not tested):
#Around("#annotation(com.example.AllParametersRequired.class)")
public Object throwExceptionOnMissingParameters(ProceedingJoinPoint pjp) throws Throwable {
int nullCount = Arrays.stream(pjp.getArgs()).filter( o -> o == null).count();
if (nullCount > 0) throw new RuntimeException("Null is not allowed.);
return pjp.proceed();
}
You will probably have to experiment there a little bit, to see which pointcut is the best for your case, but I don't see why you shouldn't be able to wrap an aspect around Spring Data's repository methods. Anyway, here's a link to the Spring AOP documentation, which will probably be helpful if you want to go that way: Link
I have several APIs which retain a parameter "feature" from the url (path param). To avoid retrieving it in each method endpoint (eg.)
#GET
public void findAll(#PathParam("feature") String feature);
am trying to implement AOP using AspectJ.
Following is the implementation of the Aspect
#Aspect
public class FeatureAOP {
#Pointcut("execution(* x.y.z.rest.ModifiersFacadeWrapper.*(..)) && args(feature)")
public void pointCut(String feature) {
}
#Before("x.y.z.rest.aop.FeatureAOP.pointCut(feature)")
public void parseParams(JoinPoint jp, String feature) {
Object[] x = jp.getArgs();
System.out.println("Feature: " + feature);
}
}
The above method gives me the value of "feature" in the Aspect class but if I change the method findAll to following signature, it doesn't works.
#GET
public void findAll();
What I understand is the control is transferred to the Aspect after the parameters are resolved and removing it from the method definition is failing it.
Doing so, thus takes me to the same point where I have to define all method endpoints with the parameter in its signature. I would like to know if there is a way I can get the PathParams in the Aspect class without having to define my methods with the designated parameters.
I think you could probably do it by putting the resolved params in a globally accessible data structure (e.g. a Singleton having some sort of Map or Set), but
I wouldn't recommend that kind of approach. I don't know why you don't like having all the params in your method signatures, but that is the intended way of declaring rest services, e.g.
#GET
#Path("{feature}")
#Produces("text/plain")
public String getFeature(#PathParam("feature") String feature) {
return feature;
}
This way you don't have to write any code for retrieving the params, the rest library you are using (be it Jersey or a different one) will just do everything for you.
I am confused. I could not find out, how to define together custom "search" methods with methods that were loaded with help of spring-data-rest.
Could you answer me, does the framework has this possibility "out-of-box"?
And if has, could you tell me, where can i find it?
For a deeper understanding my situation i describe my issue:
class UserService {
public String getListOfWaitingUsers() {
return userRepository.findAll(UserSpecification.isWaiting());
}
}
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
Page<User> findByNameLike(#Param("name") String name, Pageable pageable);
}
I want that it to be like:
/user/search/
findByNameLike
findWaitingUsers
How to implement that my methods of specifications or services (there is not method in repository) will define with path "/resource_name/search/METHOD_NAME" ( methods of repository + ( METHODS SERVICES OR SPECIFICATIONS)
Spring Data REST framework is based on Spring Data Respository, so your service class can be ignored here.
All methods that are not part of CRUD/Paging Repository as exposed as "search" methods provided you annotated all parameters with #Param annotation. So in your case, you need to implement your method following the conventions outline in Spring Data commons docs. So once you have implementation for findByNameLike method, the method would be exposed as ../search/findByNameLike URL. If needed, you could customize the rel and path with #RestResource annotation.
Also note your UserRepository should ideally be working only on User object and hence the methods you defined. In your case, UserRepository is returning Process/ProcessContext objects? Instead it should be like below
public interface UserRepository extends PagingAndSortingRepository<User, Long>{
Page<User> findByNameLike(#Param("name") String name, Pageable pageable);
}