2012-01-16

Hibernate - collection was not processed by flush

Recently I got the following exception from Hibernate:
collection was not processed by flush
What happened? I have the following class structure.
Artist -> Album -> Song (parent -> child)

Library -> Stat (parent -> child)

Song - Stat (association)
Artist and Library are aggregates in DDD sense. Artist is the parent of Album, Album is the parent of Song. Library is the parent of Stat. What's important, there's also a bidirectional relationship between Song and Stat (but no ownership). The exception occurred in the following situation:
  1. I had an Artist with one Album with one Song that had an association with one Stat being a child of its Library and that was created in Transaction 1.
  2. In Transaction 2, the whole thing was loaded again, a new Stat added, being a child of a new Library.
  3. Transaction 2 ended. Exception thrown.
So at the time when Transaction 2 ended I had a Song with 2 Stats, each referring to a different Library, but one of those Libraries was out of context, as it is lazy loaded from Stat.

The fix

The fix (or hack) was to eagerly load Library from Song. It's not ideal but it fixed the problem. There was no need to update this Library instance when transaction ended, so I'm wondering if it's a Hibernate defect...

2011-12-23

Firefox - Remove dotted line around links

Oh, how annoying that dotted line around linked elements in Firefox is! Dotted line around links. Courtesy sonspring.com There are many articles describing how to remove it for your website. I don't like the idea - it should be up to the user to decide. So I, as a user, decided to remove them from my browser. How?
  1. Type about:config in Firefox address bar.
  2. If it displays a warning, confirm you know what you're doing.
  3. Find this property (you can use the filter): browser.display.focus_ring_width
  4. Set its value to 0 (zero).
Ahh...

2011-12-21

[FIXED] VirtualBox kernel driver not installed rc 1908

This time this helped with my VirtualBox problems (Ubuntu host, Windows guest).
sudo dkms install virtualbox/4.1.2
sudo modprobe vboxdrv

2011-12-18

The Bedouin - my photo gallery

This is my first photo gallery from my trip to the Middle East, dedicated to the Bedouin.

2011-11-20

Use SLF4J with JBoss AS7

SLF4J is a module in JBoss AS7. I therefore assumed it would not be necessary to ship SLF4J with my web application. To my surprise, my application failed with a ClassNotFoundException.
Caused by:
java.lang.ClassNotFoundException:
org.slf4j.Logger
Hmm... How can I use a JBoss AS7 module as a dependency? The solution is to add the org.slf4j entry as a MANIFEST.MF dependency. The MANIFEST.MF file looks then like that:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: M1key
Build-Jdk: 1.6.0_27
Dependencies: org.slf4j
Maven can do it for you automatically.
<plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-war-plugin</artifactId>
               <configuration>
                   <archive>
                       <manifestEntries>
                           <Dependencies>org.slf4j</Dependencies>
                       </manifestEntries>
                   </archive>
               </configuration>
           </plugin>
I guess I should have RTFM... More about class loading in AS7.

2011-10-30

Wizard form with Spring MVC

Today I will show you how to create a wizard form with Spring MVC. A wizard form is a multi-step form that allows users to submit information gradually. With Spring MVC it's very easy to make such a form, as well as validate already submitted data on each step and on finish.

Controller

This is the main thing. Let's take a detailed look.

The controller is annotated with the @Controller annotation. @RequestMapping specifies its path. Also, we are storing user submitted data (an instance of my custom transfer object called PersonTo) in session with the @SessionAttributes annotation.
@Controller
@RequestMapping("/personRegistration")
@SessionAttributes("personTo")
public class PersonRegistrationController {
Now, let's take a look at initialisation code. The controller expects an appropriate validator to be passed during construction time. The validator is, in our case, a Spring bean, so we can have it autowired.
Then we initialise an init binder for the custom Gender enum. That allows incoming string parameters to be converted into enum instances.
@Autowired
   public PersonRegistrationController(PersonRegistrationValidator validator) {
       super();
       this.validator = validator;
   }

   @InitBinder
   public void initBinder(WebDataBinder binder) {
       binder.registerCustomEditor(Gender.class, new GenderEditor());
   }
Next, look at the method that handles GET requests. This method resets the potentially submitted data by creating a new PersonTo object and storing it in session. It requests the registration name form to be displayed. (The forms code will soon follow)
   @RequestMapping(method = RequestMethod.GET)
   public String setupForm(Model model) {
       PersonTo personTo = new PersonTo();
       model.addAttribute(PERSON_TO, personTo);
       return REGISTRATION_NAME_FORM;
   }
And now the big thing. The method that handles POST requests which are those where the user actually submitted data.
@RequestMapping(method = RequestMethod.POST)
   public String submitForm(HttpServletRequest request,
           HttpServletResponse response,
           @ModelAttribute(PERSON_TO) PersonTo personTo, BindingResult result,
           SessionStatus status, @RequestParam("_page") int currentPage,
           Model model) {

       Map<Integer, String> pageForms = new HashMap<Integer, String>();
       pageForms.put(0, REGISTRATION_NAME_FORM);
       pageForms.put(1, REGISTRATION_GENDER_FORM);

       if (userClickedCancel(request)) {
           status.setComplete();
           return REDIRECT_TO_HOMEPAGE;
       } else if (userIsFinished(request)) {
           validator.validate(personTo, result);
           if (result.hasErrors()) {
               return pageForms.get(currentPage);
           } else {
               log.info("Registration finished for person [{}: {}].",
                       personTo.getGender(), personTo.getName());
               personTo.setRegistrationComplete(true);
               return REDIRECT_TO_SUCCESS_PAGE;
           }
       } else {
           int targetPage = WebUtils.getTargetPage(request, "_target",
                   currentPage);
           if (userClickedPrevious(currentPage, targetPage)) {
               return pageForms.get(targetPage);
           } else {
               switch (currentPage) {
               case 0:
                   validator.validateName(personTo, result);
                   break;
               }
               case 1:
                   validator.validateGender(personTo, result);
                   break;
               }


               if (result.hasErrors()) {
                   return pageForms.get(currentPage);
               } else {
                   return pageForms.get(targetPage);
               }
           }
       }
   }
  1. If the user clicked cancel, cancel the whole thing by ending the session and redirect to the home page.
  2. If the user is finished, validate the whole session stored transfer object. Redirect to success page or display errors.
  3. Otherwise, this is another step in the wizard. In that case, establish which step it is and apply correct validation. If there are errors, show the current page where Spring will display error messages. If everything is OK, go to the next page.

View

I'm using Velocity. Here's the first form where the user types their name. Look at the Next and Cancel buttons (the names are important, _target1 and _cancel) and at the hidden input with page number (zero, since this is the first page in the wizard).
       <form method="post" modelAttribute="personTo">
           <table>
               <tr>
                   <td>Your name:</td>
                   <td>#springFormInput("personTo.name" "")
                   </td>
                   <td>#springShowErrors("" "")
                   </td>
               </tr>
               <tr>
                   <td colspan="3">
                       <input type="submit" value="Next" name="_target1">
                       <input type="submit" value="Cancel" name="_cancel">
                       <input type="hidden" value="0" name="_page">
                   </td>
               </tr>
           </table>
       </form>
And the gender selection page. Note we are using a drop down list here.
       <form method="post" modelAttribute="personTo">
           <table>
               <tr>
                   <td>Your gender:</td>
                   <td>#springFormSingleSelect("personTo.gender" $genders "")
                   </td>
                   <td>#springShowErrors("" "")
                   </td>
               </tr>
               <tr>
                   <td colspan="3">
                       <input type="submit" value="Previous" name="_target0">
                       <input type="submit" value="Finish" name="_finish">
                       <input type="submit" value="Cancel" name="_cancel">
                       <input type="hidden" value="1" name="_page">
                   </td>
               </tr>
           </table>
       </form>

Property editor

Here's the GenderEditor, if you're interested.
public class GenderEditor extends PropertyEditorSupport {

   @Override
   public String getAsText() {
       if (getValue() == null) {
           return null;
       } else {
           Gender gender = (Gender) getValue();
           return gender.toString();
       }
   }

   @Override
   public void setAsText(String text) throws IllegalArgumentException {
       if (StringUtils.isEmpty(text)) {
           setValue("");
       } else if (text.equalsIgnoreCase("m")) {
           setValue(Gender.MALE);
       } else if (text.equalsIgnoreCase("f")) {
           setValue(Gender.FEMALE);
       } else if (text.equalsIgnoreCase("o")) {
           setValue(Gender.OTHER);
       }
   }
}
We need the getAsText() method for validation purposes. That makes sure that if an unknown value is passed from the view, the resulting value is null (as opposed to the String "null").

Download the source code

The source code for this is available on GitHub.