Monday, December 21, 2009

#jsf2next: Bean Validation's Final Frontier

There's a scene in The Hitchhiker's Guide to the Galaxy that goes:

He reached out and pressed an invitingly large red button on a nearby panel. The panel lit up with the words Please do not press this button again.

Clearly this is a joke: it's funny because it's obviously bad user interface design. Yet this is precisely where we are today with Java EE 6.

Don't get me wrong: I think Java EE 6 is an awesome spec, and I think Bean Validation and JSF2 work really well together, but just like Seam's s:validateAll, the best you can do is validate the input after the user interaction:

  1. give them a text box, then afterwards tell them their input was too long

  2. give them a text box, then afterwards tell them number must be less than 100

  3. give them a date picker, then afterwards tell them date of birth must be less than today
In short - we let them press the button, then afterwards tell them they shouldn't have pressed it. Ideally, we should:

  1. give them a text box limited to a certain number of characters

  2. give them a number spinner (or slider) capped at 100

  3. give them a date picker capped to before today
In short - we should apply Bean Validation constraints to the component before the user interaction.

There are a variety of ways to achieve this, and obviously I'm biased. But whichever approach we use, I think it's the next logical step - and the final frontier in establishing validation constraints that apply all the way from the back end to the front end.

Tuesday, December 8, 2009

Newbie's Guide to using JBoss Cache in JBoss AS


The promise of JBoss Cache is that it makes distributed caching really easy. But it can be hard to figure out how to use it, because many of the docs are concerned with installation and configuration. Yet the promise of JBoss AS is that everything is already installed, configured and integrated together, so how do you skip all that and get straight to The Good Stuff?

So, this is the blog I was looking for:

If Today You Are Running JBoss AS And Have...

public class MyBeanOrControllerOrWhatever {

   private final static Map MY_CACHE = new HashMap();

   public Object lookupSomething( Object key ) {

      Object value = MY_CACHE.get( key );

      if ( value == null ) {
         value = lookupUncached();
         MY_CACHE.put( key, value );
      }

      return value;
   }
}
(ie. you are using a static Map for your cache - which is fine but will not distribute across VMs)

...Then Using JBoss Cache You Can Do

public class MyBeanOrControllerOrWhatever {

   public Object lookupSomething( Object key ) {

      CacheManager cacheManager = CacheManagerLocator.getCacheManagerLocator().getCacheManager( null );
      Cache myCache = cacheManager.getCache( "sfsb-cache", true );


      Fqn fqn = Fqn.fromString( MyBeanOrControllerOrWhatever.class.getName() );
      Object value = myCache.get( fqn, key );

      if ( value == null ) {
         value = lookupUncached();
         myCache.put( fqn, key, value );
      }

      return value;
   }
}

Where:
  • fqn is a unique key in the JBoss Cache tree so that you don't conflict with other caches

  • sfsb-cache is a pre-configured JBoss AS cache that replicates asynchronously and never expires

  • CacheManagerLocator is in common/lib/jboss-ha-server-api.jar

  • CacheManager is in server/all/lib/jbosscache-core.jar

You'll also need to edit server/all/deploy/cluster/jboss-cache-manager.sar/META-INF/jboss-cache-manager-jboss-beans.xml to pre-start the cache:

<!-- Start these caches as part of the start of this CacheManager -->
<property name="eagerStartCaches">
   <set>
      <value>sfsb-cache</value>
   </set>
</property>

This should be enough to get you working and get you excited. All this actually is in the JBoss Cache docs, just waaaay at the back in Chapter 11. After a while you should probably look at configuring your own cache rather than hijacking sfsb-cache, as that's not strictly meant for your own stuff. Here's a simple one that's kind of a 'distributed Map with max 300 entries':

   <entry>
      <key>my-cache</key>
      <value>
         <bean name="MyCache" class="org.jboss.cache.config.Configuration">
            <property name="isolationLevel">NONE</property>
            <property name="cacheMode">REPL_ASYNC</property>
            <property name="evictionConfig">
               <bean class="org.jboss.cache.config.EvictionConfig">
                  <property name="wakeupInterval">5000</property>
                  <property name="defaultEvictionRegionConfig">
                     <bean class="org.jboss.cache.config.EvictionRegionConfig">
                        <property name="regionName">/</property>
                        <property name="evictionAlgorithmConfig">
                           <bean class="org.jboss.cache.eviction.LRUAlgorithmConfig">
                              <property name="maxNodes">300</property>
                           </bean>
                        </property>
                     </bean>
                  </property>
               </bean>
            </property>
         </bean>
      </value>
   </entry>


Though note that maxNodes refers to the number of Fqn nodes, rather than the Map entries within one of those nodes. So instead of:

myCache.put( fqn, key, value )


You'll have to start doing:

myCache.getChild( fqn ).addChild( Fqn.fromElements( key )).put( key, value )


(ie. create a full-blown child node based on converting each key to an Fqn)

Improvements welcome!

Thursday, November 26, 2009

Metawidget: King of the Hill


Nice article here (at least I assume it's nice, my German is even worse than Google Translate's):


My thanks to the author.

Wednesday, November 18, 2009

We're Hiring!

Here's the job ad we're posting today. A chance to work from home, and work for me! What a dream job :)


Senior Java EE 5 (J2EE) Developer: $90,000 - $100,000 AUD
  • Small, focused team

  • Modern, well-crafted software

  • Work in city or from home
A rare opportunity to join our small consultancy team. We have recently received substantial investment from a leading Australian financial institution and we must grow to meet demand.

You will work side-by-side with one of Australia's premier Java EE developers, and be intimately involved in all design decisions.

You will be a pragmatic programmer. Software development will be in your blood: a natural instinct. You must be passionate about Open Source and Java EE. You will demonstrate advanced analytical skills; an eye for elegance and craftmanship; a passion for refactoring, unit testing, and delivering secure and robust code.

Your skills will include many of the following:
  • JBoss

  • JSF/RichFaces

  • XSL (T and FO) and XPath

  • EJB 3/JMS

  • JPA/SQL

  • Metawidget

  • HtmlUnit

  • HTML/CSS
In addition, you will have excellent communication skills and be comfortable interacting with stakeholders at all levels.

Work from home will be offered providing that you can demonstrate an appropriate working environment. You must be a citizen or permanent resident of Australia.

To help us sort applicants, include your CV and a 1 page overview highlighting your suitability for the role. Links to examples of your work (eg. web sites you have built, blogs you have written, Open Source contributions etc) will be highly regarded.

Please send your application to jobs@kennardconsulting.com.

Wednesday, November 11, 2009

Safely manipulating the component tree with JSF 2

The information in this blog post has been superseded. Please see Safely manipulating the component tree with JSF 2, revisited

The Problem

A common problem encountered by writers of dynamic JSF 1.x components (like Metawidget) was "at what point in the lifecycle can I safely manipulate the component tree"?

For a page's initital GET request, there weren't many lifecycle methods you could hook into - you basically had encodeBegin. For subsequent POST-backs there were a slew of hooks, including decode, processValidators and processUpdates, but none were at exactly the right place in the lifecycle to do safe component tree manipulation. What would be 'exactly the right place'? Namely:
  1. the component tree should be fully realized (ie. all parents and children should be set)

  2. the component tree should not have been serialized yet
Because there was no 'official' place, writers of dynamic JSF 1.x components had to resort to all kinds of tricks. As Ken Paulson wrote "...people use constructors, bindings, action methods... people create their own renderer which dynamically adds children each request". To which Jacob Hookum opined "what's actually needed is post component tree creation hooks, providing the ability to then modify the component tree".


The Saviour

Enter JSF 2. From the spec "System Events are introduced in version 2 of the specification and represent specific points in time for a JSF application". They are like the old PhaseEvents, but much more fine-grained. For the purposes of this blog, the SystemEvent we are interested in is PostAddToViewEvent.

To use it, first subscribe to the event in your UIComponent's constructor...

public class UITestComponent
   extends UIComponentBase
   implements SystemEventListener {

   public UITestComponent() {
      FacesContext ctx = FacesContext.getCurrentInstance();
      ctx.getViewRoot().subscribeToViewEvent(PostAddToViewEvent.class, this);
   }

...then, when it gets fired, you can safely manipulate the component tree ...

public void processEvent( SystemEvent event ) throws AbortProcessingException {

   FacesContext ctx = FacesContext.getCurrentInstance();
   UIComponent dynamicallyGenerated = ctx.getApplication().createComponent( "javax.faces.HtmlInputText" );
   getChildren().add( dynamicallyGenerated );
}


The Gotchas

While working through this solution, there were a number of gotchas I encountered. Huge thanks to Ryan Lubke for holding my hand through them:

First, you should use subscribeToViewEvent, not subscribeToEvent. The latter will mean the event subscription itself get serialized into the component tree, so you'll keep doubling up subscribers every time the page refreshes.

Second, you must hook into UIViewRoot's PostAddToViewEvent, not UITestComponent's. This is because, at the time UITestComponent's PostAddToViewEvent is fired, its children will not be initialized yet.

Third, you cannot use the @ListenerFor annotation to hook into SystemEvents from a UIComponent. This is because UIComponent implements ComponentSystemEventListener, and @ListenerFor is designed to ignore the SystemEventListener interface for any class that implements ComponentSystemEventListener.


The Ugly

There is, unfortunately, still a problem for certain types of dynamic component.

If your component, in its processEvent method, dynamically creates not just simple HtmlInputText components but nested components that are themselves dynamic, we have a problem. This is because the 'official' way to safely manipulate the component tree is through SystemEvents, but there is no official way to safely manipulate the SystemEvent list.

In the current release of the JSF Reference Implementation (2.0.1), attempting to dynamically create a component that in turn tries to subscribe to a SystemEvent will get you a ConcurrentModificationException. In future releases, there will not be an exception but the newly added SystemEvent will simply not fire, so your newly added dynamic component will never get an opportunity to populate itself.

This new issue is being tracked by the JSF RI team. Until it is resolved, JSF 2 can safely create dynamic components (a big step forward), but it cannot safely create dynamic components that themselves create dynamic components.

Suggestions welcome!

Friday, October 30, 2009

XSL-FO number-columns-spanned="remainder"

Every so often when developing some XSL-FO I get the dreaded...

column-number or number of cells in the row overflows the number of fo:table-column specified for the table

...these tend to be a nightmare to track down, and very fiddly to fix, because often you need to add a number-columns-spanned attribute to one of the fo:table-cell elements, and in a dynamic world you have to figure out exactly how many cells to span. Agh!

It'd be much easier if XSL-FO let you just say number-columns-spanned="remainder". So today I wrote a little XSLT that you can put after your XSL-FO and before you try to serialize it (with something like Apache FOP).

<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
            xmlns:fo="http://www.w3.org/1999/XSL/Format">

   <!-- number-columns-spanned: remainder -->
   
   <xsl:template match="fo:table-cell[@number-columns-spanned = 'remainder']" mode="#all">
   
      <xsl:copy>
         <xsl:attribute name="number-columns-spanned">
            <xsl:variable name="number-columns-in-table" as="xs:integer">
               <xsl:value-of select="count(ancestor::fo:table[1]/fo:table-column)"/>
            </xsl:variable>
            <xsl:variable name="number-columns-in-row" as="xs:integer">
               <xsl:value-of select="count(preceding-sibling::fo:table-cell[not(@number-columns-spanned)]) + count(following-sibling::fo:table-cell[not(@number-columns-spanned)])"/>
            </xsl:variable>
            <xsl:variable name="number-spanned-columns-in-row" as="xs:integer">
               <xsl:value-of select="sum(preceding-sibling::fo:table-cell[@number-columns-spanned ne 'remainder']/@number-columns-spanned) + sum(following-sibling::fo:table-cell[@number-columns-spanned ne 'remainder']/@number-columns-spanned)"/>
            </xsl:variable>
            <xsl:value-of select="$number-columns-in-table - $number-columns-in-row - $number-spanned-columns-in-row"/>
         </xsl:attribute>
         <xsl:apply-templates select="@*[not(local-name() = 'number-columns-spanned')]|node()"/>
      </xsl:copy>
      
   </xsl:template>


   <!-- identity transform -->
   
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
   </xsl:template>

</xsl:stylesheet>

I should've done it years ago. Note unfortunately it won't work if you've got other rows that are using number-rows-spanned. Improvements welcome!

Thursday, October 15, 2009

User Interface Generator: Metawidget v0.85

Version 0.85 of Metawidget, the user interface generator, is now available! This release has something for everyone:

For first time users, we have updated and improved the User Guide. For intermediate users, we have greatly expanded the architecture documentation, with lots more diagrams and detailed explanations. And for advanced users, we have substantially refactored the internals, including introducing new pluggable WidgetProcessors to handle more demanding requirements. Please note there are unfortunately some breaking changes, as covered in the Migration Guide.

In addition we have improved our support for RichFaces (now includes SuggestionBox, TabPanel and RichPanel), ExtGWT (now includes Slider), Swing (now includes configurable labels), and have added XML schemas for every component and lots more unit tests.

Special thanks to Girolamo Violante and Illya Yalovyy for their help with this release!

As always, the best place to start is the Reference Documentation:

http://metawidget.org/doc/reference/en/pdf/metawidget.pdf

Your continued feedback is invaluable to us. Please download it and let us know what you think.

Friday, October 2, 2009

Metawidget 0.8 to 0.85 Migration Guide

The next release of Metawidget (v0.85, due in late October) will represent a significant refactoring of the Metawidget internals, aimed at increasing API clarity and consistency, code reuse, type safety and extensibility - all in preparation for our 1.0 release. As such, there will be a number of breaking changes.

We apologise for this disruption and provide this Migration Guide to help in moving from v0.8 to v0.85. All of the existing documentation and examples have already been migrated, as a reference point.

WidgetProcessors

The big new change is the concept of a 'widget processor'. These are lightweight, pluggable classes who can 'process' a widget just after its building (by the WidgetBuilder) and before it is laid out (by the Layout).


The interface is simply:

W processWidget( W widget, String elementName, Map<String, String> attributes, M metawidget )

This is great because:

  • It allows you to plug-in arbitrary processing of a widget without touching the Metawidget class itself. For example you could write a widget processor to add a tooltip to every component:

    JComponent processWidget( JComponent widget, String elementName, Map attributes, SwingMetawidget metawidget ) {
       widget.setToolTipText( attributes.get( NAME ));
       return widget;
    }

  • It provides more ways to identify and process a widget than getComponent( String... names ), for situations where you don't know the component's name in advance

  • It allows you to attach Event Handlers as inner classes that have connections to their parent class:

    final Object someObject = ...;

    metawidget.addWidgetProcessor( new BaseWidgetProcessor() {
    JComponent processWidget( JComponent widget, String elementName, Map<String, String> attributes, SwingMetawidget metawidget ) {
       ...decide whether to attach event handler...

       widget.add( new AbstractAction() {
          public void actionPerformed( ActionEvent event ) {
             someObject.doSomething();
          }
       }
    }
    }

  • Standardizing this concept allows us to refactor a lot of previously ad-hoc functionality. For example all binding and validation functionality are now implemented as widget processors:


    They can be chained together either programmatically or in metawidget.xml:

    <widgetProcessors>
       <list>
          <processor:requiredAttributeProcessor />
          <processor:immediateAttributeProcessor />
          <processor:standardBindingProcessor />
          <processor:readableIdProcessor />
          <processor:labelProcessor />
          <processor:standardValidatorProcessor />
          <processor:standardConverterProcessor />
       </list>
    </widgetProcessors>

Refactored save(), rebind(), validate()

As mentioned, binding and validation have now been refactored into WidgetProcessors. This has the advantage of moving methods such as save() and validate() out of the Metawidget (where they weren't always applicable) and into the responsible WidgetProcessor (where they will always be applicable, by definition).

This unfortunately means a slightly more cumbersome API. Instead of...

metawidget.save()

...you now have to do...

metawidget.getWidgetProcessor( BeansBindingProcessor.class ).save( metawidget )
...however we think the tradeoff is worth it.

metawidget.xml now supports (and mostly uses) <array>

ConfigReader now natively supports an <array> element. This allows it to natively call vararg methods, removing the need for some of the overloaded methods in the xxxConfig classes.

Most current usages of <list> should be replaced with <array>. Lists (and Sets) are still supported for your custom use-cases.

Refactored setCreateHiddenFields

JSF hidden field creation has also been refactored into a WidgetProcessor. For example:

<widgetProcessors>
   <list>
      <hiddenFieldProcessor />
   </list>
</widgetProcessors>

Layouts must now be immutable

Layouts are now a standardized part of the Metawidget pipeline. They must implement the new org.metawidget.layout.iface.Layout interface, and must be threadsafe and immutable just like Inspectors, WidgetBuilders and WidgetProcessors. Custom Layouts that are currently thread-unsafe or mutable need to be refactored in a couple of ways:

  • Configuration settings, that do not change over the lifetime of the Layout and would previously have been retrieved via metawidget.getParameter (and possibly saved into member variables), should now be configured by an external xxxConfig class. For example:

    public GridBagLayout() {
       this( new GridBagLayoutConfig() );
    }

    public GridBagLayout( GridBagLayoutConfig config ) {
       mNumberOfColumns = config.getNumberOfColumns();
       mLabelAlignment = config.getLabelAlignment();
       mSectionStyle = config.getSectionStyle();
       mLabelSuffix = config.getLabelSuffix();
       mRequiredAlignment = config.getRequiredAlignment();
       mRequiredText = config.getRequiredText();
    }

    ...and configured either programmatically or in metawidget.xml:

    <layout>
       <htmlTableLayout xmlns="java:org.metawidget.jsp.tagext.html.layout" config="HtmlTableLayoutConfig">
          <tableStyle>
             <string>aTableStyle</string>
          </tableStyle>
       </htmlTableLayout>
    </layout>

  • Internal state that changes during layout and was stored in member variables should now be stored in the parent Metawidget. For example:

    State state = (State) metawidget.getClientProperty( GridBagLayout.class );
    state.currentRow++;

No more metawidget.setParameter or m:param

The previously (type unsafe) metawidget.setParameter methods and m:param tags have now been removed, in favour of typesafe configuration objects on the WidgetProcessors and Layouts. So instead of...

metawidget.setPropertyBindingClass( BeansBinding.class )
metawidget.setParameter( "updateStrategy", UpdateStrategy.READ )

...you now do...

metawidget.addWidgetProcessor( new BeansBindingProcessor(
   new BeansBindingProcessorConfig()
      .setUpdateStrategy( UpdateStrategy.READ )));

...or equivalent in metawidget.xml.

ConfigReader Moved

ConfigReader has now been moved out of org.metawidget.inspector and into the new org.metawidget.config. This reflects it outgrowing its roots as a purely Inspector-focussed mechanism, into a general purpose way to configure Metawidgets.

XSDs

Existing XSDs have now been moved under http://metawidget.org/xsd. In addition, all the XSDs for the Inspectors, WidgetBuilders etc have been generated.

PropertyStyles and ActionStyles are now objects

ConfigReader is now smart enough to reuse PropertyStyle and ActionStyles instances between Inspectors. This means they are now specified on the Inspector as an object, not their class. This opens the door to people writing their own, configrable property styles if they need to.

Ant Build

The formerly monolithic build.xml has been split into several smaller ones. The original (/build.xml) now only builds Metawidget itself. There is a separate /examples/build.xml (builds examples), /test/build.xml (runs unit tests), /src/doc/build.xml (builds documentation) and /src/web/build.xml (builds Web site).

Similarly, the lib folder has now been split into /lib, /examples/lib and /test/lib.

Java Puzzler: enforcing whether a class overrides equals/hashCode


The Problem

Let's say I'm writing a Java class that requires users of my class override equals and hashCode on their objects. I'd be in good company: lots of classes do this, not least the java.util.Collection classes like List and Set.

This important requirement is generally only recorded in the JavaDoc, with a stern warning that Bad Things Will Happen if you forget. But the requirement is not enforced at runtime, much less compile time.

The Question

What if I wanted to enforce it? What if I wanted to write a class like HashMap that made sure anything you put in it overrode equals and hashCode? What if I'm prepared to sacrifice a little performance and/or code complexity to do this? What are my choices? Can I do it at runtime? Better yet, can I do it at compile-time?

The Answer?

I don't know a great answer. I don't know of any libraries that do. I'd love some feedback on helping eliminate this important category of subtle bugs.

To get the ball rolling, this is what I'm thinking of doing in Metawidget:

Class classToTest = objectToCache.getClass();
Object dummy1 = classToTest.newInstance();
Object dummy2 = classToTest.newInstance();

if ( !dummy1.equals( dummy2 ))
throw new Exception( classToTest + " does not override .equals(), so cannot be reliably cached" );

if ( dummy1.hashCode() != dummy2.hashCode() )
throw new Exception( classToTest + " does not override .hashCode(), so cannot be reliably cached" );

This approach takes advantage of the fact that, given your class has internal state, two brand new instances should always have equivalent state, but the default Object.equals (which uses ==) will return false. It's not perfect. It'll work if you write a POJO and forget to override equals. But it won't work if your superclass overrides equals but your subclass, which adds some more internal state, forgets to. But it's a start.

Note that, annoyingly, this doesn't seem to work:

class.getDeclaredMethod( "hashCode" )

Public methods like hashCode and equals are always considered 'declared methods', even if the class doesn't actually declare them. Similarly:

class.getDeclaredMethod( "hashCode" ).getDeclaringClass()

Always returns the subclass name, even if the subclass doesn't override hashCode.

Suggestions welcome!

Update

Thanks to everyone for the really helpful comments. The first thing to note is that...

class.getDeclaredMethod( "hashCode" ).getDeclaringClass()

...does actually work. I don't know why it didn't seem to when I tried it originally. Evidentally I am an idiot!

Anyway, here's what's currently going into Metawidget, based on everyone's feedback:

Class<?> configClass = configToStoreUnder.getClass();

// Hard error

// equals

Class<?> equalsDeclaringClass = configClass.getMethod( "equals", Object.class ).getDeclaringClass();

if ( Object.class.equals( equalsDeclaringClass ) )
throw new Exception( configClass + " does not override .equals(), so cannot cache reliably" );

// hashCode

Class<?> hashCodeDeclaringClass = configClass.getMethod( "hashCode" ).getDeclaringClass();

if ( Object.class.equals( hashCodeDeclaringClass ) )
throw new Exception( configClass + " does not override .hashCode(), so cannot cache reliably" );

if ( !equalsDeclaringClass.equals( hashCodeDeclaringClass ) )
throw new Exception( equalsDeclaringClass + " implements .equals(), but .hashCode() is implemented by " + hashCodeDeclaringClass + ", so cannot cache reliably" );

if ( !configClass.equals( equalsDeclaringClass ) )
{
// Soft warning
//
// Note: only show this if the configClass appears to have its own 'state'.
// Base this assumption on whether it declares any methods. We don't want to
// use .getDeclaredFields because that requires a security manager
// check of checkMemberAccess(Member.DECLARED), whereas we may only have
// checkMemberAccess(Member.PUBLIC) permission

for ( Method declaredMethod : configClass.getMethods() )
{
if ( configClass.equals( declaredMethod.getDeclaringClass() ) )
{
LOG.warn( configClass + " does not override .equals() (only its super" + equalsDeclaringClass + " does), so may not be cached reliably" );
break;
}
}

// Note: not necessary to do !configClass.equals( hashCodeDeclaringClass ),
// as will already have thrown an Exception from
// !equalsDeclaringClass.equals( hashCodeDeclaringClass ) if that's the case
}

Improvements welcome!

Thursday, September 17, 2009

Metawidget Elevator Pitch


I've been having fun lately drawing an Elevator Pitch for Metawidget.

I was inspired by the style of the Google Chrome Book, though clearly I'm no Scott McCloud!

Check it out here: http://metawidget.org/elevator.html

As always, your feedback is much appreciated.

Tuesday, August 11, 2009

GWT Metawidget takes a walk on the client side

The latest release of Metawidget upgrades our GWT support to 1.7 and includes a new example of running pure client-side GWT:

By default, GwtMetawidget inspects business objects server-side. This is because client-side JavaScript does not support reflections or annotations.

However if you don't need reflections or annotations, and have your own way of retrieving inspection results, you can plug in your own Inspector and keep everything client-side. This example retrieves inspection results from a textarea and generates the UI.

Download the example here. More documentation can be found here.

Metawidget on ICE

The latest release of Metawidget includes support for the ICEfaces component library.

Metawidget's philosophy of not 'owning' the UI, of integrating with existing UI frameworks and component libraries, means it can easily take advantage of awesome component libraries such as ICEfaces and all the AJAX-goodness they provide.

The Metawidget distribution includes an example of Metawidget and ICEfaces working together to deliver a rich, AJAX-driven UI with minimal code:


All the input boxes and command buttons in the screenshot are generated at runtime by Metawidget, and update dynamically using AJAX.

Download the example here. More documentation can be found here.

AJAX User Interface Generator: Metawidget v0.8

Version 0.8 of Metawidget, the AJAX user interface generator, is now available. This release includes:Special thanks to Ted Goddard for his help with this release!

As always, the best place to start is the Reference Documentation:


Your continued feedback is invaluable to us. Please download it and let us know what you think.

Monday, August 3, 2009

Geek mid-life crisis

I recently completed my 'geek mid-life crisis' and joined the ranks of those who attempt to recapture their youth and relive by-gone days by building their own arcade cabinet.

I was inspired by watching The King of Kong and considered purchasing a professional cabinet, but installing something bigger than a fridge freezer in the living room is a tough sell for a family home: I needed something more slimline and less obtrusive, which is when I came across the awesome job this guy had done, so I decided to have a go myself.

Here are the initial plans (they changed a little bit during construction):


The work in progress (this was my first time with a router, or even a drill for that matter, so I asked a grown up to help me):


And the finished product (a few months later):



Costs (in Aussie dollars):
  • Joystick and Encoder board (incl shipping) - $203.85 - Replay Arcade

  • 20" L200P LCD monitor - $172 (incl shipping) - eBay

  • Coin buttons - $18 (incl shipping) - OzStick

  • Wood - $120 - Bill's boards

  • Artwork - $170.50 (incl shipping) - MAME Marquees

  • 1L Black paint - $30

  • 6mm laminated glass - $40 - JB Glass

  • 2 perspex sheets - $20

  • 2.8GHz 1GB P4 PC - free (a local school was getting rid of one)
All up about $800 Aussie dollars, plus a bunch of sweat and tears and, yes, even blood (I cut myself a couple times). Sincere thanks to the many people who either helped or were inconvenienced during this hair-brained project!

Saturday, August 1, 2009

Home office

I thought I'd post a couple snaps of where I've been spending my working day for the last 18 months:


I'm a big advocate of multiple screens: two is definitely better than one; after a while with two you start thinking you need a third; and if you go three you need 2 video cards so you may as well have four! What do I use four screens for?
  • Far left: e-mail, reading PDFs of specifications etc

  • Middle left: running version of whatever application I'm developing

  • Middle right: my IDE, spread over two screens, with this screen being a full screen of source code (bliss :)

  • Far right: the rest of my IDE, including server console, debugging tree, folder navigator etc
It took a little while to get used to, but now I'd never want to go back.

Friday, July 17, 2009

On the value of reflection

The research methodologies of Action Research and Reflective Practice instruct that 'reflections' (as in contemplation, not as in type introspection) from a previous software development phase should drive the planning for the next phase (much like the industry methodology of Iterative Development). It is perhaps worthwhile to reinforce the value of this reflection. After all, reflection is expensive. Conducting experiments, interviews and case studies consumes valuable time and resources, and it is legitimate to question whether its benefits outweigh its cost.

One of the most important factors in software development is scope: deciding what to include and what to leave out. Scope creep and feature bloat are recognised risks, impacting development costs and release schedules. Good architects carefully apply rules of thumb: every design decision should 'carry its own weight', and strive to 'kill several birds with one stone'. But an implicit difficulty in evaluating this is knowing what the 'birds' are. Once out of its initial planning phases, software development has a tendency to lurch from immediate issue to immediate issue, dealing with each new requirement as it arises. Considering new requirements in isolation invariably means the burden of large-scale redesign to satisfy any one requirement will seem onerous: a smaller-scale, less impactful alternative will always seem the better option. Reflection, on the other hand, allows the practitioner to consider many weeks worth of problems in a holistic light: he can see all the birds at once, and an approach that once seemed over-engineered now appears justified. Surfacing all the issues at the same time clears a path forward that otherwise would have seemed prohibitive.

This phenomena is analogous to neural networks. While progressing to solve a given problem, a neural network may get trapped, still short of the best solution, in a local minima. The local minima itself does not represent the best answer, but none of the immediate ways out of the minima are enough of an improvement to overcome the walls of the valley. It takes a combined push, a sort of disruptive excitation, to escape the trough so that a better solution can be found:

So much for the theory - is it demonstrable in practice? Here I will give personal testimony. One of the themes from Metawidget's alpha cycle reflections was support for 1-to-M relations. In itself, this seemed a corner case: difficult to support within the current architecture without a slippery slope of requirements around sorting, pagination and summary-to-detail navigation. Another theme was support for third-party UI components. The most challenging case study indicated this would have improved adoption, though it was not a primary factor. A third issue was around supporting the SWT library: the current design of 'return null to render nothing, return a dummy Metawidget to trigger nesting' was backwards for SWT's purposes, though this was being worked around in a sub-optimal way.

Individually, none of these requirements seemed enough to justify a significant reworking of the widget creation subsystem. Indeed, the theme of 1-to-M relations gnawed at me for months with no obvious solution within the existing architecture. It was only reframing it within the context of the additional requirements of 'supporting third-party components' and 'turning widget creation inside out' that a new path presented itself (see WidgetBuilders). Looking back, I realise I was probably especially resistant to seeing this path because it was in an area I had already considered and decided against.

In summary, I have found explicit reflection to be an enlightening and worthwhile use of a project's time. It is easy to skip this phase in the heady rush of pumping out release and release, but when one takes the time to properly pause for breath important insights can be gained.

Friday, July 10, 2009

WikidPad: does just what it says on the tin

Just a quick shout out to WikidPad. It's always great when you find a piece of software that 'just works', in just the way you expect it to, and does just what you want it to.

I've been looking for a good place to file away all those random bits of knowledge you accumulate during a day, but are not suitable for public consumption (either because they're confidential, or not properly formatted, or whatever) and having a personal, standalone Wiki on my desktop is just perfect!

Thanks guys!

Monday, June 1, 2009

HtmlUnit: listening to their customers

I'm delighted to say I received an e-mail from the HtmlUnit team this morning that a couple of the requests I made have been incorporated into their next build. Thanks guys! Such prompt attention and turn-around really reinforces my confidence in the decision to switch.

They've added getAnchorByText and getOptionByText. This means I can remove some of the code from my ad hoc HtmlUnitUtils class. Of course, there are still more it'd be nice to see. I include here the whole of my utils class so that they may pick away at it for anything else they may want to incorporate.

Naturally I don't expect it all, or even most. But whatever they may add is awesome as it means less code for me to maintain!

package com.kennardconsulting.core.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

import org.metawidget.util.CollectionUtils;
import org.metawidget.util.simple.StringUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlFileInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlRadioButtonInput;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlTable;
import com.kennardconsulting.core.enumeration.FileFormat;

/**
* Utilities for working with HtmlUnit.
*/

public final class HtmlUnitUtils
{
   //
   // Public statics
   //

   public static HtmlAnchor getLink( HtmlPage response, String text )
   {
      List<HtmlAnchor> links = getLinks( response, text );

      if ( links.isEmpty() )
         throw new RuntimeException( "No such link with exact text of '" + text + "'" );

      if ( links.size() > 1 )
         throw new RuntimeException( "More than one link with exact text of '" + text + "'" );

      return links.get( 0 );
   }

   public static List<HtmlAnchor> getLinks( HtmlPage response, String text )
   {
      return getLinks( response, text, false );
   }

   public static List<HtmlAnchor> getLinks( HtmlPage response, String text, boolean contains )
   {
      List<HtmlAnchor> anchors = CollectionUtils.newArrayList();

      for ( HtmlAnchor anchor : response.getAnchors() )
      {
         String anchorText = anchor.asText();
         anchorText = anchorText.replaceAll( "\r", "" );

         if ( contains )
         {
            if ( !anchorText.contains( text ) )
               continue;
         }
         else
         {
            if ( !anchorText.equals( text ) )
               continue;
         }

         anchors.add( anchor );
      }

      return anchors;
   }

   @SuppressWarnings( "unchecked" )
   public static <T extends HtmlElement> T getInputByNameEndingWith( HtmlForm form, String nameEndingWith )
   {
      for ( HtmlElement element : form.getHtmlElementsByTagName( "input" ) )
      {
         String elementName = element.getAttribute( "name" );

         if ( elementName == null )
            continue;

         if ( elementName.endsWith( nameEndingWith ) )
            return (T) element;
      }

      return null;
   }

   public static HtmlOption getSelectedOption( HtmlForm form, String selectName )
   {
      return getSelectedOption( form.getSelectByName( selectName ) );
   }

   public static HtmlOption getSelectedOption( HtmlSelect select )
   {
      List<HtmlOption> selectedOptions = select.getSelectedOptions();

      if ( selectedOptions.isEmpty() )
         return null;

      if ( selectedOptions.size() > 1 )
         throw new RuntimeException( "'" + select.getNameAttribute() + "' has more than one option selected" );

      return selectedOptions.get( 0 );
   }

   public static String setSelectedOption( HtmlForm form, String selectName, String option )
   {
      return setSelectedOption( form.getSelectByName( selectName ), option );
   }

   public static String setSelectedOption( HtmlForm form, String selectName, String option, boolean allowOnlyOne )
   {
      return setSelectedOption( form.getSelectByName( selectName ), option, allowOnlyOne );
   }

   public static String setSelectedOption( HtmlSelect select, String option )
   {
      return setSelectedOption( select, option, true );
   }

   public static String setSelectedOption( HtmlSelect select, String option, boolean allowOnlyOne )
   {
      String selectedValue = null;

      for ( HtmlOption htmlOption : select.getOptions() )
      {
         String htmlOptionText = htmlOption.asText();

         // Special support for trimming off  , which we use for indenting select options

         htmlOptionText = htmlOptionText.trim();

         if ( htmlOptionText.equals( option ) )
         {
            if ( selectedValue != null )
               throw new RuntimeException( "Select '" + select.getNameAttribute() + "' has more than one '" + option + "'" );

            selectedValue = htmlOption.getValueAttribute();
            htmlOption.setSelected( true );

            if ( !allowOnlyOne )
               break;
         }
      }

      if ( selectedValue == null )
         throw new RuntimeException( "Select '" + select.getNameAttribute() + "' does not contain '" + option + "'" );

      return selectedValue;
   }

   public static String setSelectedOptionValue( HtmlForm form, String selectName, String option )
   {
      return setSelectedOptionValue( form.getSelectByName( selectName ), option );
   }

   public static String setSelectedOptionValue( HtmlSelect select, String optionValue )
   {
      String selectedValue = null;

      for ( HtmlOption htmlOption : select.getOptions() )
      {
         if ( htmlOption.getValueAttribute().equals( optionValue ) )
         {
            if ( selectedValue != null )
               throw new RuntimeException( "Select '" + select.getNameAttribute() + "' has more than one '" + optionValue + "'" );

            selectedValue = htmlOption.getValueAttribute();
            htmlOption.setSelected( true );
         }
      }

      if ( selectedValue == null )
         throw new RuntimeException( "Select '" + select.getNameAttribute() + "' does not contain value '" + optionValue + "'" );

      return selectedValue;
   }

   public static boolean hasOption( HtmlForm form, String selectName, boolean selected, String... options )
   {
      int found = 0;

      for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )
      {
         for ( String option : options )
         {
            if ( htmlOption.asText().equals( option ) )
            {
               if ( selected && !htmlOption.isSelected() )
                  return false;

               found++;
               break;
            }
         }
      }

      return ( found == options.length );
   }

   public static boolean hasOptionValue( HtmlForm form, String selectName, boolean selected, String... options )
   {
      int found = 0;

      for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )
      {
         for ( String option : options )
         {
            if ( htmlOption.getValueAttribute().equals( option ) )
            {
               if ( selected && !htmlOption.isSelected() )
                  return false;

               found++;
               break;
            }
         }
      }

      return ( found == options.length );
   }

   public static String getOptionValue( HtmlForm form, String selectName, String option )
   {
      for ( HtmlOption htmlOption : form.getSelectByName( selectName ).getOptions() )
      {
         if ( htmlOption.asText().equals( option ) )
            return htmlOption.getValueAttribute();
      }

      throw new RuntimeException( "No option with text '" + option + "' found" );
   }

   public static void setSelectedRadio( HtmlForm form, String radioName, String value )
   {
      boolean selectedOne = false;

      for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )
      {
         if ( !htmlRadioButtonInput.getValueAttribute().trim().equals( value ) )
            continue;

         if ( selectedOne )
            throw new RuntimeException( "Radio button group '" + radioName + "' has more than one '" + value + "'" );

         selectedOne = true;
         htmlRadioButtonInput.setChecked( true );
      }

      if ( !selectedOne )
         throw new RuntimeException( "Radio button group '" + radioName + "' has no option '" + value + "'" );
   }

   public static boolean hasRadio( HtmlForm form, String radioName, String value )
   {
      for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )
      {
         if ( htmlRadioButtonInput.getValueAttribute().trim().equals( value ) )
            return true;
      }

      return false;
   }

   public static String getSelectedRadioValue( HtmlForm form, String radioName )
   {
      HtmlRadioButtonInput radioButtonInputSelected = null;

      for ( HtmlRadioButtonInput htmlRadioButtonInput : form.getRadioButtonsByName( radioName ) )
      {
         if ( !htmlRadioButtonInput.isChecked() )
            continue;

         if ( radioButtonInputSelected != null )
            throw new RuntimeException( "Radio button group '" + radioName + "' has more than one selected" );

         radioButtonInputSelected = htmlRadioButtonInput;
      }

      if ( radioButtonInputSelected == null )
         return null;

      return radioButtonInputSelected.getValueAttribute();
   }

   @SuppressWarnings( "unchecked" )
   public static <E extends HtmlElement> E getElementByAttribute( HtmlPage page, String elementName, String attributeName, String attributeValue )
   {
      return (E) getElementByAttribute( page.getDocumentElement(), elementName, attributeName, attributeValue );
   }

   public static <E extends HtmlElement> E getElementByAttribute( HtmlElement element, String elementName, String attributeName, String attributeValue )
   {
      List<E> elements = element.getElementsByAttribute( elementName, attributeName, attributeValue );

      if ( elements.isEmpty() )
         return null;

      if ( elements.size() > 1 )
         throw new RuntimeException( "More than one " + elementName + " with " + attributeName + " of '" + attributeValue + "'" );

      return elements.get( 0 );
   }

   public static <E extends HtmlElement> E getElementByAttributeContaining( HtmlPage page, String elementName, String attributeName, String attributeValueContained )
   {
      List<E> elements = getElementsByAttributeContaining( page, elementName, attributeName, attributeValueContained );

      if ( elements.isEmpty() )
         return null;

      if ( elements.size() > 1 )
         throw new RuntimeException( "More than one " + elementName + " with " + attributeName + " containing '" + attributeValueContained + "': " + CollectionUtils.toString( elements ) );

      return elements.get( 0 );
   }

   /**
    * @return the elements, in the order they are declared in the HTML.
    */

   @SuppressWarnings( "unchecked" )
   public static <E extends HtmlElement> List<E> getElementsByAttributeContaining( HtmlPage page, String elementName, String attributeName, String attributeValueContained )
   {
      List<E> toReturn = CollectionUtils.newArrayList();

      NodeList nodeList = page.getElementsByTagName( elementName );

      for ( int loop = 0, length = nodeList.getLength(); loop < length; loop++ )
      {
         Node node = nodeList.item( loop );
         Node nodeValue = node.getAttributes().getNamedItem( attributeName );

         if ( nodeValue == null )
            continue;

         if ( nodeValue.getNodeValue().contains( attributeValueContained ) )
            toReturn.add( (E) node );
      }

      return toReturn;
   }

   @SuppressWarnings("unchecked")
   public static <T extends Page> T waitForAjax( T page )
   {
      WebWindow window = page.getEnclosingWindow();
      window.getThreadManager().joinAll( 10000 );

      return (T) window.getEnclosedPage();
   }

   public static void setUpload( HtmlForm form, String uploadName, String url )
   {
      setUpload( form, uploadName, CoreStringUtils.substringAfterLast( url, StringUtils.SEPARATOR_FORWARD_SLASH ), url );
   }

   public static void setUpload( HtmlForm form, String uploadName, String name, String url )
   {
      try
      {
         setUpload( form, uploadName, name, new URL( url ).openStream() );
      }
      catch ( IOException e )
      {
         throw new RuntimeException( e );
      }
   }

   public static void setUpload( HtmlForm form, String uploadName, String name, InputStream streamIn )
   {
      HtmlFileInput fileInput = form.getInputByName( uploadName );

      ByteArrayOutputStream streamOut = new ByteArrayOutputStream();

      try
      {
         IOUtils.streamBetween( streamIn, streamOut );
      }
      catch ( IOException e )
      {
         throw new RuntimeException( e );
      }

      fileInput.setValueAttribute( name );
      fileInput.setData( streamOut.toByteArray() );
   }

   public static HtmlTable getTableStartingWith( HtmlPage page, String startingWith )
   {
      NodeList tables = page.getElementsByTagName( "table" );

      for( int loop = 0, length = tables.getLength(); loop < length; loop++ )
      {
         HtmlTable table = (HtmlTable) tables.item( loop );

         if ( table.asText().trim().startsWith( startingWith ))
            return table;
      }

      throw new RuntimeException( "No table starting with '" + startingWith + "'" );
   }

   //
   // Private constructor
   //

   private HtmlUnitUtils()
   {
      // Can never be called
   }
}

Tuesday, May 26, 2009

Metawidget and Seam: saying goodbye to boilerplate code

This blog is to celebrate the inclusion of the Metawidget examples in the upcoming release of Seam. What is Metawidget? It's a 'smart User Interface widget' that populates itself, at runtime, with UI components to match the properties of your business objects.

If you think of a typical Seam stack, you may have JPA at the bottom, then EJB, then Seam itself, then JSF, then maybe RichFaces to add a bit of polish. But there is still a gap at the very top - a gap that leads to a lot of 'boilerplate code'. This is where Metawidget comes in.

Let me try and convey it visually using the image below. On the left are the complete contents of the book.xhtml file from the Seam Groovy Booking example. On the right are the complete contents from the new Seam Metawidget Groovy Booking example. The red boxes and lines highlight the chunks of boilerplate that have been replaced:

The original file is 177 lines long. The Metawidget equivalent is 52 lines. That represents a 70% reduction in code, on top of the considerable reductions Seam already affords when developing enterprise applications. This ability to retrofit existing UIs, integrating with existing front-end and back-end technologies, is unique to Metawidget.

Now I want to be completely fair here - I'm a developer, not a marketing guy! You also have to add a few extra annotations to your business classes, add the Metawidget JAR to your project and create a little metawidget.xml file. If we compare all the source files that change:


By comparing file sizes, we see the overall code reduction is around 20%. We could allow that in such a small example the size of metawidget.xml is more significant than it should be: for apps with hundreds of screens, the impact of 2,789 bytes of metawidget.xml will be negligible. So if we compare file sizes without including metawidget.xml, we see the overall code reduction is around 40%. If you repeat this exercise with the Seam Metawidget DVD Store example (also included in the Seam distribution) the overall reduction is around 30%.

Of course, on top of all that, lines of code is never a wondeful metric for comparing implementations. But anyway, hopefully you get the idea. Still, if you want a soundbite: Metawidget can save you up to 40% of your UI code.

My deepest thanks to the Seam guys, especially Dan Allen, for all their help integrating Metawidget into the Seam 2.1.2.GA build.

Monday, May 11, 2009

Dynamic User Interface Generator: Metawidget v0.75

Update: the APIs shown in this blog entry have changed slightly in newer releases of Metawidget. Specifically metawidget.xml uses <array> instead of <list>. Please download the latest documentation from http://metawidget.org

Version 0.75 of Metawidget, the dynamic user interface generator, is now available. This release includes:
  • Pluggable widget libraries
  • SwingX support
  • DisplayTag support
  • Improved documentation

'Pluggable widget libraries' represents a significant refactoring of the widget generation code, intended to:

  • simplify support of third party libraries, including mixing multiple third party libraries in the same application
  • pave the way for supporting Collections
  • pave the way for supporting some more UI toolkits (ie. SWT)

It is also, unfortunately, a breaking change. Sorry!

Migration Guide

To migrate from v0.7 to v0.75:

Change #1: inspector-config.xml is now metawidget.xml

The role of inspector-config.xml has been expanded from configuring pluggable inspectors to configuring pluggable inspectors and widget builders. It is also now a general configuration mechanism for all aspects of your Metawidget, such as default CSS settings etc.

You will need to refactor inspector-config.xml files of the form...

<inspector-config>
   <compositeInspector xmlns="org.metawidget.inspector.composite">
      <myinspector>
      <...
      </myinspector>
</inspector-config>
...to be metawidget.xml files of the form...

<metawidget>
   <swingMetawidget xmlns="org.metawidget.swing">
      <inspector>
         <compositeInspector xmlns="org.metawidget.inspector.composite">
            <list>
               <myinspector>
               ...
               </myinspector>
            </list>
         </compositeInspector>
      </inspector>
   </swingMetawidget>
</metawidget>
The main differences with this new XML format are:

  • it is now concerned with the top-level Metawidget, not just the inspectors inside it. This means you can also configure other Metawidget properties (see below)
  • method values must now be wrapped with their type (ie. <list>) - this allows us to support configuring multi-value methods such as setParameter
  • Full documentation can be found here
Use metawidget.setConfig to set this new format, instead of metawidget.setInspectorConfig.

Change #2: Metawidget.buildWidget is now WidgetBuilder.buildWidget

If you had previously extended Metawidget to add support for a third party widget, you'll need to refactor your code into a WidgetBuilder. WidgetBuilders can be configured programmatically, or with the new metawidget.xml:

<metawidget>
   <swingMetawidget xmlns="org.metawidget.swing">
      <widgetBuilder>
         <compositeWidgetBuilder xmlns="org.metawidget.widgetbuilder.composite">
            <list>
               <myWidgetBuilder />
               <swingWidgetBuilder />
            </list>
         </compositeWidgetBuilder>

      </widgetBuilder>
      <inspector>
      ...
      </inspector>
   </swingMetawidget>
</metawidget>
WidgetBuilders higher in the list get called first. If they return null the next WidgetBuilder in the list will be called. If all WidgetBuilders return null the parent Metawidget will instantiate a nested Metawidget.

Change #3: RichFacesMetawidget has been removed

To use JBoss RichFaces, you now use a regular UIMetawidget with a RichFacesWidgetBuilder. Full documentation can be found here.

Thanks!
We apologise for the disruption these changes will cause, but strongly believe they will make Metawidget a better product for our v1.0 release. All documentation and examples have already been migrated.