Archive for January, 2008

Using Wicket’s jmx-panel to view Hibernate’s Statistics

The Java Management Extensions (JMX) is perfect to build manageable applications without the need to create any administration frontend. Managed Beans (MBeans) are similar to regular Java Beans. Hibernate offers an MBean-wrapper around its Statistics API which is really interesting to optimize caching. Therefore, I was looking for a simple way to use JMX with Wicket. Fortunately, Wicket can also be managed with JMX. All it needs is to drop the wicket-jmx JAR on the classpath – no configuration needed! It is therefore sufficient to add wicket-jmx to the POM:


<dependency>
 <groupid>org.apache.wicket</groupid>
 <artifactid>wicket-jmx</artifactid>
 <version>1.3.0-SNAPSHOT</version>
</dependency>

But Wicket (more precisely Wicketstuff) also contains a simple JMX client called wicketstuff-jmx-panel. Using this client is as simple as adding a single component to whatever Wicket-page:

POM:

<repository>
 <id>wicket-stuff-repository</id>
 <name>Wicket-Stuff Repository</name>
 <url>http://www.wicketstuff.org/maven/repository</url>
</repository>
<!-- SNIP -->
<dependency>
 <groupid>org.wicketstuff</groupid>
 <artifactid>wicketstuff-jmx-panel</artifactid>
 <version>1.3.0-SNAPSHOT</version>
</dependency>

Java:


add(new JmxPanel("jmx"));

However, documentation on this JMX panel is sparse. It took me some time to figure out the following: This panel filters all MBeans that do not have the Wicket-application’s name in their domain. This behaviour may be changed using an annonymous inner class:


add(new JmxPanel("jmx") {
 protected IDomainFilter getDomainFilter() {
   return DOMAIN_FILTER;
}
});

private static final IDomainFilter DOMAIN_FILTER = new IDomainFilter() {
 private static final long serialVersionUID = 1L;

 public boolean accept(String domain) {
   // your filter code
   return true;
}
};

It does make sense to filter out some MBeans as there are already some by default, e.g. for java.util.logging which you probably don’t use at all.

Now it’s time to add Hibernate’s statistics MBean. This MBean is can be used per SessionFactory. I therefore decided to do registration and unregistration together with SessionFactory creation. As I am also using Spring, I went for my own SessionFactoryBean:


public class MBeanRegisteringLocalSessionFactoryBean extends
   org.springframework.orm.hibernate3.LocalSessionFactoryBean {

 private String _sessionFactoryName;
 private ObjectName _objectName;
 private MBeanServer _mbeanServer;

 @Override
 protected void afterSessionFactoryCreation() throws Exception {
   super.afterSessionFactoryCreation();
   try {
     // register JMX MBean
     _objectName = new ObjectName("Hibernate:type=statistics,application="
       + getSessionFactoryName());
     _mbeanServer = getMBeanServer();

     final StatisticsService mBean = new StatisticsService();
     mBean.setSessionFactory(getSessionFactory());
     _mbeanServer.registerMBean(mBean, _objectName);

   } catch (final Throwable t) {
     log.warn("failed to register MBean for SessionFactory: " + _objectName, t);
     _objectName = null;
     _mbeanServer = null;
   }
   // if _objectName != null --> MBean was registered
 }

 @Override
 protected void beforeSessionFactoryDestruction() {
   if (_objectName != null) {
     try {
       _mbeanServer.unregisterMBean(_objectName);
     } catch (final Throwable t) {
       log.warn("failed to unregister MBean for SessionFactory: "
         + _objectName.getDomain());
     }
   }
 }

 /* SNIP: getMBeanServer(), getSessionFactoryName(), setSessionFactoryName(String) */
}

I copied (sorry, there is no nicer way yet) the code for getMBeanServer() from wicket-jmx’s Initializer class (sorry, can’t find an online version – you can get the source using Maven) in order to get the exact same behaviour. Now you only need to replace LocalSessionFactoryBean with MBeanRegisteringLocalSessionFactoryBean in your beans.xml and you are set. In order to enable statistics by default, add these two lines to your hibernate properties:


hibernate.generate_statistics=true
hibernate.cache.use_structured_entries=true

That’s it! Hibernate statistics directly from your webapp:


Wicket ChoiceRenderer for Enums

I now got to the point where I wanted to put some i18n into my DropDownChoices. What I found out pretty soon was that you need an IChoiceRenderer to customise your DropDownChoices. If you don’t use one, you’ll get by default short values and toString display names. E.g. the enum


enum { JOHN, PAUL, GEORGE, RINGO }

produces an output like that


<select name="beatles">
<option value="0">JOHN</option>
<option value="1">PAUL</option>
<option value="2">GEORGE</option>
<option value="3">RINGO</option>
</select>

But what I want is “John” instead of JOHN or “John Lennon, Rhythm Guitar” or anything but JOHN. On the mailinglist and in the wiki I discovered some complicated, overhead solutions using helper objects, so I decided to implement my own reusable enum-renderer that reads the display values from a properties file. All you have to do is to pass the Component that is responsible for the properties file.


public class EnumChoiceRenderer implements IChoiceRenderer {

private static final long serialVersionUID = 1L;

private final Component _resourceProvider;

public EnumChoiceRenderer(final Component resourceProvider) {
_resourceProvider = resourceProvider;
}

public Object getDisplayValue(final Object object) {
final Enum<?> v = (Enum<?>) object;
return _resourceProvider.getString(v.name());
}

public String getIdValue(final Object object, final int index) {
return Integer.toString(index);
}

}

Yeah that was quite easy, but maybe saves some of you the burdon of using helper-classes ;-)

Oh yes, the properties file looks something like that:

JOHN=John Lennon, Rhythm Guitar
PAUL=Paul McCartney, Bass
GEORGE=George Harrison, Lead Guitar
RINGO=Ringo Starr, Drums

what then produces the desired output:

<select name="beatles">
<option value="0">
John Lennon, Rhythm Guitar</option>
<option value="1">
Paul McCartney, Bass</option>
<option value="2">
George Harrison, Lead Guitar</option>
<option value="3">
Ringo Starr, Drums</option>
</select>