Some time ago (in years now) I wanted to make Wicket the most agile framework around by creating a mini-framework on top of Wicket that allows automatic data source inspection to drive automatic view generation in a completely general and ideally very simple way.
Unfortunately, the time and energy was not there to do it and, of course, the months rolled by... until this Christmas break, when I finally got a chance to sit down and really think this problem out. The results have turned out pretty well and I plan to publish them in my forthcoming book “Twenty-Six Wicket Tricks” as one of the major tricks (there are also some more minor tricks).
There have been several stabs at this problem already, including Al Maw’s wonderful bean editor teaser, Will Faler’s wicket-rad as well as the original “Wicket Web Beans”. While these are all good and useful tools, I believe a better solution is possible still.
In my analysis, the problem of automating and data-driving views breaks down into three separate problems (each, along some kind of axis like a “problem joint”):
1. Extracting models and metadata about them (see IComponentFactorySource, BeanExtractor below)
2. Creating and configuring a user-defined component using a model and any metadata (see IComponentFactory below)
3. Placing the created components (using the model and metadata) in a larger view
3a. By automatic layout for full RAD agility (see AutomaticGridView below for one example layout)
3b. By manual, HTML-driven layout for agility with more precise view control (see FreeFormAutomaticView below)
4. Bean editors, property tables, custom forms, CRUD forms, etc. are composed from 1-3 for convenience
The rough architecture of my present solution breaks down as follows.
I’d welcome your thoughts on these interfaces and classes.
/**
* An arbitrary source of component factories.
* <p>
* An extractor such as {@link BeanExtractor} would implement this method to
* serve as a source of factories that construct components for each property of
* a bean.
* <p>
* A view implementation like {@link AutomaticGridView} can consume this
* interface and use the extracted factories to create components, decoupling it
* from any concrete source of components.
*
* @author Jonathan Locke
*/
public interface IComponentFactorySource {
/**
* @return A list of component factories
*/
List<IComponentFactory<?>> factories();
}
/**
* Extracts a list of component factories from a bean {@link IModel} by
* inspecting it.
*
* @author Jonathan Locke
*/
public class BeanExtractor implements IComponentFactorySource {
/**
* Construct.
*
* @param model
* The model to extract from
*/
public BeanExtractor(IModel<?> model) { ... }
/**
* {@inheritDoc}
*/
public List<IComponentFactory<?>> factories() { ... }
...
}
/**
* Interface to instantiate a component with a given model. Also contains
* meta-data extracted from the model (for example, a bean
* {@link PropertyDescriptor}) which can be used to order and place constructed
* components in a larger view.
*
* @author Jonathan Locke
*/
public interface IComponentFactory<T> extends Serializable {
/**
* @return Meta-data for use in constructing and placing components
*/
MetaData getMetaData();
/**
* @return The model which will be given to new component instances
*/
IModel<T> getModel();
/**
* @param id
* The id of the component to create
* @return The component
*/
Component newComponent(String id);
}
/**
* An automatic grid view that places components created by a factory source in
* a grid that is columnCount cells wide and any number of rows long.
*
* @author Jonathan Locke
*/
public class AutomaticGridView extends AbstractAutomaticView {
/**
* Construct.
*
* @param id
* Component id
* @param factorySource
* Source of component factories
* @param columnCount
* Number of columns in the grid
*/
public AutomaticGridView(final String id,
final IComponentFactorySource factorySource, final int columnCount) { ... }
}
/**
* A free-form automatic view allows a subclass to override markup and define
* the precise layout of the automatic components in the view (as constructed
* from the {@link IComponentFactorySource}). Each component in the view is
* simply given the {@link IComponentFactory#NAME} metadata value as its id.
*
* @author Jonathan Locke
*/
public class FreeFormAutomaticView extends AbstractAutomaticView {
/**
* Construct.
*
* @param id
* Component id
* @param factorySource
* Source of component factories
*/
public FreeFormAutomaticView(final String id,
final IComponentFactorySource factorySource) {
super(id, factorySource);
for (Component component : getComponents()) {
add(component);
}
}
}