Darwin's Theories Blog

New Theories for a New Time

Using Java enums properly in Seam2/JSF/JPA

2009-01-27
The Seam documentation covers this now, but it's a bit short of complete examples. Most of this probably applies to other JSF/JPA deployments too..

This first part is Seam-specific: To make the list of a given enum's values available to the view,  just add one new annotated method to a "Factories" class.

@Name("factories")
public class Factories {

    @Factory("countries")
    public Country[] getCountries() {
       return Country.values();
    }

Because you want to display a user-friendly name in the web form, but store a short and consistent name in the database, the enum must have a "label" or "name" field and a getter method (many enums already do). For example, we'll display Canada in the drop-down but enter the ISO-3166 country code CA in the database.

/** See ISO-3166 */
public enum Country {
   CA("Canada"),
   US("United States"),
   AF("Afghanistan"),
   ...
   ;
   private String longName;
   private Country(String longName) {
       this.longName = longName;
   }
   public String getLongName() {
       return longName;
   }    
   @Override
   public String toString () {
       return getLongName();
   }

You may want to save the string name rather than the ordinal value into the database (especially with Countries where the list order will change in future as countries split or join!). OTOH a simple enum like Status { Unkown, Active, Defunct } is unlikely to pick up new enum values, so use integers, and if you do add any, add at the end.

The @Entity class should be annotated thusly:

   @Column(name="Country")
   @Enumerated(EnumType.STRING)   // default and only other choice is ORDINAL
   public Country getCountry() {
       return this.country;
   }

Then, to select it (display a drop-down), you'd use, for example:

      <h:selectOneMenu value="#{register.personalAddress.country}">
             <s:selectItems var="country" value="#{countries}"
                        label="#{country.longName}" noSelectionLabel="-- Select --"/>
              <s:convertEnum/>
       </h:selectOneMenu>

If you've used another framework than Seam, there's a bit of Seam's beauty: the Seam convertEnum tag fixes JSF 1's mess of having your data classes be required to know about UIComponent. Of course this is fixed in JSF 2.x..

To display the current value in a view-only page,  you just need, for example,

  <h:outputText id="country"
      value="#{person.personalAddress.country.longName}"/ >

If you're storing Strings in the database for the enum, the data must be basically spotless for the enum converter to work, so - if you didn't have enumerating check constraints on the database from day one - you may e.g., have to "sanitize" the data a little to ensure there are no non-null but blank values in the database, or convert any lower-case values to upper case to match the enum constants. If not you'll get strange message like the famous "can't find resultList" - always check the JBoss console log for a stack trace!

These can be cleaned with SQL like this, using whatever tool you use to feed raw SQL into whatever database you're using:

   update address set country = 'CA' where country = ''
Or you could set these values to NULL.

   update address set country = upper(country)

To check the values, you can use SQL similar to this:

    select distinct(country) from address

Then it all works nicely, as it should, without any need for EntityConverter or for mixing UIComponents into your data layer. Seam rocks!
Reply from Tatiana at 2009-03-13 07:33:45.282

It is very useful article. It helped me very much. Thank you!

Reply from Wagner at 2009-05-28 10:31:09.431

Really very good! Thank you Ian!

Reply from Shervin at 2009-09-23 09:29:39.24

Thanks alot. Very helpful

Reply from mailtoox at 2010-04-05 20:33:59.592

ok, but what about using these factories with rich:combobox.
i used it with rich:combo and still dont have userfriendly names.

maybe you know what im doin wrong.

btw. with h:selectOne your solution works fine

Reply from Farhad Khan at 2011-01-01 11:14:50.401

Hi,
I used the example above to add a Country select field in a registration form in a seam project, but am getting an illegalargument exception on submitting it.

/signup.xhtml @114,66 value="#{profileHome.instance.id.country}": java.lang.IllegalArgumentException: argument type mismatch

I am using the seam generated EntityHome class from my action bean and the country field is varchar(255) in the mysql database.

Any thoughts on what went wrong?

Thanks,
Farhad

Reply from Ian Darwin at 2011-01-05 16:15:54.397

Sorry, I can't tell without seeing the whole mess. I can take a quick look if you contact me by email, my address is pretty obvious.

Reply from Anonymous at 2012-05-03 15:44:16.692

Very good implementation. Was very helpfull!

Tags