#EntityGraph annotation doesn't work properly? - spring-boot

I need to implement two different implementations for the same findAll() method by following different EntityGraphs annotations. By referencing through another StackOverflow post, I found a way to implement the same findAll() method with different EntityGrpahs. But when I use default methods as mentioned in that post, I am not getting the expected behavior. It neglects the #EntityGraph annotation and returns lazy Collections by following the default behavior.
Please provide a fix for this issue or state any other better solution that I can implement to solve this problem.
public interface BspCategoryRepository extends JpaRepository<DbpMetaBspCategory, String> {
#EntityGraph(attributePaths = {"dbpBspMetaCollection","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection","dbpBspMetaCollection.bspType","dbpBspMetaCollection.bankCode","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection.fieldType"}, type = EntityGraph.EntityGraphType.FETCH)
default List<DbpMetaBspCategory> findAllCategories(){
return findAll();
}
}
Please refer to the second answer in the post which was answered by Femi.
References

Spring Data simply can not know about this annotation, as the method is not abstract. You should be able to declare the method just like this:
#EntityGraph(attributePaths = {"dbpBspMetaCollection","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection","dbpBspMetaCollection.bspType","dbpBspMetaCollection.bankCode","dbpBspMetaCollection.dbpBspMetaCustomFieldCollection.fieldType"}, type = EntityGraph.EntityGraphType.FETCH)
List<DbpMetaBspCategory> findAllCategories();

Related

Spring boot not able to read custom #Query annotation

I have requirement when we apply #CustomQUery annotation, then I need to intercept this method and append the query predicate which I already know.
Created
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
#Documented
public #interface CustomQuery {
Query query();
}
#Repository
public interface FloorRepository extends JpaRepository<TnFloor, Integer> {
public String query="select distinct tnFloor from TnFloor tnFloor where tnFloor.tnBuilding.buildingId in ?1 ";
#CustomQUery(query=#Query(query))
public List<TnFloor> findByBuildingIds(List<Integer> buildingIds);
}
Here, Spring is unable to read this #CustomQUery because I have not mentioned anywhere to read this annotation.
Is this the correct way to create custom query annotation ?
I am getting below exception on application startup.
Could not create query for public abstract java.util.List
FloorRepository.findByBuildingIds(java.util.List)!
Reason: Failed to create query for method public abstract java.util.List FloorRepository.findByBuildingIds(java.util.List)!
No property buildingIds found for type TnFloor!
Did you mean 'buildingId'?;
nested exception is java.lang.IllegalArgumentException: Failed to c
As other people have already said in comments, I think your way of extending the Query annotation is usefull only if you need to do some things more than just extending it.
If you need some paths to enhance the behavior of the #Query annotation, maybe using #Modifying annotation could get the point, or using #NamedQuery and #NamedNativeQuery annotations too.
If it is a requirement you could not resolve with these other annotations, maybe make some click on them to see how they are declared and raised in the Spring IoC ecosystem using Aspect programming.
The problem here, to my point of view, seems not to be related to your annotation, but a missing property, as the error message has told to you :
No property buildingIds found for type TnFloor! Did you mean 'buildingId'?;
Maybe because of a typo error in your annotation when you are using it, which is not found :
declared as public #interface CustomQuery and used as #CustomQUery(query=#Query(query)). Write the things as they are declared will work better I think.
Did you try using native query feature like that too ?
Query q = em.createNativeQuery("SELECT a.firstname, a.lastname FROM Author a");
// of course, update with your own code.
You can look at what JPA is capable of and switch to native query as I have just added if it is not supported.

Jackson deserializer priority?

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

Spring Repository PreAuthorize gives 'failed to evaluate expression' error

i'm adding lots of bold because someone downgraded my question which is think is strange...
i went from this which worked which means things are configured correctly for #PreAuthorize...
#RestController
#RequestMapping('/people')
public PersonController extends BaseController {
#PreAuthorize("#pesonId != principal.id")
#RequestMapping(value="updatePerson", method={RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody SimpleResponseStatus updatePerson(#RequestParam(personId) final Long personId, #RequestParam(value) final String value, final HttpServerRequest request, final HttpServletResponse response)
{
Person p = personRepo.findById(personId);
p.setValue(value);
personRepo.save(p);
}
}
and moved to this which doesn't work ... the #PreAuthorize in the Repository save()...
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(person p);
}
and now i get a "Failed to evaluate expression '#p.id != principal.id'
One difference between when it was working on the Controller was i did #personId and not #p.id so i don't know if the object vs primitive in the expression is the problem or if Controller vs Repository (where i do the evaluation) is the problem.
So i have a few questions...
Do i have to do anything special to get the PreAuthorize working in the Repository?
Nothing to do with Spring security but why was i forced to add the SuppressWarnings? i can see if i was returning List<Person> maybe but i thought that was strange.
There is another instance where i will want to do an a PreAuthorize expression like "#p.group.id != 3" ... is there a limit to the levels that can be in an evaluation? i.e. level = obj.obj.obj.obj.value
Another interesting thing is that when i had it working with the Controller i didn't need curly braces "#{userId != 3}" but it worked with "#userId != 3" and i got that syntax from here.
Bottom line, i had it working in a Controller but without an object parameter and now i need it to work in a Repository and with an object parameter. And i've tried #person.id != 3 as well as #{person.id != 3} and neither work.
i found the answer to my own question: here
which is basically for the Repository you have to add a parameter name via annotation since the debug isn't compiled into the interface.
it took me a long time to finally find the answer as it took trial and error of me trying different EL syntax and finally one syntax i chose gave me a different (and better) error message and from there i found the link above.
Anyway, whoever downgraded my question should have just posted the link i just did above instead of downgrading me. that is just mean, really.
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(#Param("p") person p); //the #Param annotation is needed!
}
also, it is interesting how some places i see {} are needed and other places not. i did not need braces for this to work.
you must first:
- enable global method security
in your spring security config just add
#EnableGlobalMethodSecurity(prePostEnabled=true)
the you can safely use #PreAuthorize and #PostAuthorize
correct SPEL syntax should be
#{pesonId != principal.id}
but...before this you should ensure both parameter are present in SPringEvaluationContext.
I suggest to use Spring approach, which doesn't fit you question but gives you a different point of view of the problem.
Usage of expression bases access control
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
ex: #PreAuthorize("hasRole('ADMIN')")
Spring security accept any valid Spel inside annotation.
Take a look here:
http://www.baeldung.com/spring-security-expressions-basic

targetDomainObject is always null in PermissionEvaluator

I have a CRUD repository from this example of Spring Data. I'm trying to add custom permission evaluation, but in my implementation of PermissionEvalutor, targetDomainObject is always null.
ItemRepository
#PreAuthorize("hasRole('ROLE_USER')")
public interface ItemRepository extends CrudRepository<Item, Long> {
#PreAuthorize("hasPermission(#entity, 'DELETE')")
<S extends Item> S save(S entity);
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long id);
}
Following the suggestions in the answers to this question of making the interface and implementation parameter names to match, I've tried changing entity by item in both the expression and the method parameter. I'm not sure what implementation should match against what interface here, so I'm guessing is SimpleJpaRepository against ItemRepository/CrudRepository. Anyway, it doesn't work, targetDomainObject is always null. Same for targetId in the other method.
Debugging MethodSecurityEvaluationContext.lookupVariable shows that args.length = 0, inside the method addArgumentsAsVariables(), that then logs Unable to resolve method parameter names for method: public abstract xx.xxx.Item xx.xxx.ItemRepository.save(xx.xxx.Item). Debug symbol information is required if you are using parameter names in expressions.. At lookupVariable, everything is null.
Is the debug symbol not #? What am I doing wrong?
Haven't looked in the actual code, but judging from what you write about the debug information, Spring isn't able to find the parameter names, probably since the come from interfaces and those aren't included in the bytecode by default.
Try adding a -parameters compiler flag. Also see this answer for a probably similar problem: https://stackoverflow.com/a/40787280

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