Monday, September 23, 2013

Adventure Time in Spring Roo

I've recently picked up Spring Roo and decided to build out a web application using the framework. It is certainly a rapid development framework, up to speed with the other popular framework by Spring, Grails.  What interested me the most with Roo though was the ability to easily plugin Spring-MVC, use multiple datasources for the Model, and to scaffold all the necessary JUnit test cases without me intervening.

In my use case, I wanted to utilize both structured and unstructured data, so I was able to easily define my Entities as belonging to either JPA or Mongo (my personal options).  The only problem with this is that a single Maven module can only be setup with a single datasource using Roo, which can easily be resolved by using modules.

roo> module create --moduleName relational --topLevelPackage com.example.relational
roo> jpa setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT

… Create jpa entities …

roo> module focus --moduleName ~
roo> module create --moduleName nosql --topLevelPackage com.example.nosql
roo> mongo setup

… Create mongo entities …

Which leads us to problem 1 - Failing Tests

The main reason I'm writing this post is to document a constant problem I have, both for my reference and to hopefully help others. In my pom for the nosql module, I require the relational.jar as a dependency. This is because I reference some of the JPA entities by id in my unstructured data. For some reason unknown to me, something occasionally gets corrupted in the Spring Tool Suite (STS) and all of my JUnit tests fail in the nosql module with the following errors:

[ERROR] The import com.example.relational.domain.Example1DataOnDemand cannot be resolved
[ERROR] The import com.example.relational.domain.Example2DataOnDemand cannot be resolved
[ERROR] The import com.example.relational.domain.Example3DataOnDemand cannot be resolved
[ERROR] Example1DataOnDemand cannot be resolved to a type
[ERROR] Example2DataOnDemand cannot be resolved to a type
[ERROR] Example3DataOnDemand cannot be resolved to a type
[ERROR] example1DataOnDemand cannot be resolved
[ERROR] example2DataOnDemand cannot be resolved
[ERROR] example3DataOnDemand cannot be resolved

This problem randomly arises, and I don't know what causes it. There is some sort of problem with the aspects managed by roo, and its tough to find out what the root cause really is since the compiler thinks everything is fine. Some of the things I have randomly tried in no specific order seem to finally get things working again

  • Project → Clean
  • Right click the project → Maven → Update Project
  • Right click the project → Refresh
  • # rm ~/.spring_roo_pgp.bpg
  • Close and reopen the roo console
  • Remove a roo annotation somewhere in the module, save the file, add the annotation back, save the file
  • # mvn clean test install

Another thing to check is making sure Spring doesn't run out of heap space in it's JVM. Vi the ./bin/ or ./bin/roo.bat and make sure you have the java params setup appropriately for the amount of memory you would like to allocate.

java -Xms512m -Xmx1024m .......

Eventually the tests will run like nothing was happening, awkward! If anyone else runs into a more direct solution, please leave a comment!

Spring Security and Hibernate - Problem 2

Eventually a Roo project needs to be secured, which is very simple to setup using roo

roo> security setup

All is fine and dandy using the prototype code and applicationContect-security.xml, but problems arise when you decide you want to implement your own UserDetails, GrantedAuthority, and UserDetailsService. After setting everything up and finally testing out your handcrafted code, you get the dreaded Lazy Loading exception in hibernate:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.domain.Permissions, no session or session was closed

Despite Roo already setting up a useful Filter for you in the web.xml called OpenEntityManagerInViewFilter, you still find that Spring Security is somehow trying to lazily load a collection somewhere in your domain mappings. To sum up the problem by quoting the author of Spring Roo himself:

When Spring Security initially authenticates the user, it calls UserDetailsService.loadUserByUsername(String). The UserDetails is returned. This is put inside an Authentication object. The Authentication object is put inside a SecurityContext and that in turn is put into the SecurityContextHolder. At this time it's still a good old Hibernate lazily loaded object; all is good. But now Spring Security's HTTP session integration will kick in and it will copy the SecurityContext into the HTTP session. In a subsequent request, the SecurityContext will be pulled from the HTTP session and put back into the SecurityContextHolder. Now if you try to access a UserDetails association at this point, it will fail because the original Hibernate Session that retrieved the UserDetails is long gone (it was in an earlier HTTP request).

Ben Alex - Project Founder, Spring UAA, Spring Roo and Spring Security

So the answer is to NOT do any of the following:

  • Do NOT eagerly fetch, thats bad mojo
  • Do NOT try to manipulate the sessions in Spring
  • Do NOT panic

The simplest solution, is to force the eager fetch EARLY so that the SecurityContext does not have to do it LATER. Here's a simple way I accomplished this in my custom implementation of UserDetailsService, outlined in bold:

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  Person person;
  if ( username == null || (person = personRepository.findOne(username)) == null ) {
    throw new UsernameNotFoundException("The username does not exist: " + username);
  person.getAuthorities(); // Trigger the lazy load
  return person;

Wrap the method in a Transaction to make sure your session isn't closed early, and simply touch the collection somewhere in that transaction to trigger the lazy load. Doesn't get much simpler than that!

No comments:

Post a Comment