Inspectors decouple the process of examining back-end metadata and generating inspection results. This section covers inspectors in general. For in-depth documentation of individual inspectors see Chapter 4, Inspectors.
All inspectors must implement the Inspector interface. This is a simple
interface that defines only one method:
String inspect( Object toInspect, String type, String... names );
Each inspector must look to the type parameter and the names array. These
form a path into the business object domain model. For example the type may be com.myapp.Person
and the names may be address and street.
This would form a path into the domain model of com.myapp.Person/address/street (ie. return information
on the street property within the address property of the Person type).
Depending on the type of inspector, it may use the given toInspect to access the runtime
object for the given type. Or it may ignore the toInspect and look up
information for the given type from an XML file or a database schema. This allows Metawidget
to inspect types that have no corresponding Java object. For example:
metawidget.setToInspect( null ); // No setToInspect metawidget.setPath( "Login Form" );
This could be combined with, say, an XmlInspector and a metawidget-metadata.xml:
<entity type="Login Form"> <property name="username"/> <property name="password"/> </entity>
This approach also allows Metawidget to inspect abstract classes:
metawidget.setToInspect( null ); // No setToInspect metawidget.setPath( MyAbstractClass.class.getName() );
Unless explicitly specified, each Metawidget will instantiate a default inspector. Typically this
will be a CompositeInspector composed of a MetawidgetAnnotationInspector
and a PropertyTypeInspector.
This default behaviour can be overridden either in code:
metawidget.setInspector( new MyInspector() );
Or via metawidget.xml
<swingMetawidget xmlns="java:org.metawidget.swing"> <inspector> <myInspector xmlns="java:com.myapp"/> </inspector> </swingMetawidget>
This allows easy plugging in of alternate inspectors. Note that overriding the default
means the default is no longer instantiated. In the example above, this would mean MyInspector
is used but the default inspectors are not. This is usually not what you want,
because MyInspector will be focussed on a particular type of back-end metadata and
will want to leave other metadata to other inspectors.
To achieve this, use CompositeInspector.
CompositeInspector composes the results of several inspectors into one and
returns a single, combined inspection result. As shown in Figure 2.2
CompositeInspector works by calling each inspector in turn, combining the
inspection result as it goes.
All inspectors are required to be threadsafe and immutable (see later). Therefore, although CompositeInspector
maintains a list of inspectors this must not be changeable. To enforce this, the list is set at instantation
time using CompositeInspectorConfig. This can either be set in code:
metawidget.setInspector( new CompositeInspector( new CompositeInspectorConfig() .setInspectors( new MetawidgetAnnotationInspector(), new PropertyTypeInspector(), new MyInspector() )));
Or via metawidget.xml
<swingMetawidget xmlns="java:org.metawidget.swing"> <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation" /> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <myInspector xmlns="java:com.myapp"/> </array> </inspectors> </compositeInspector> </inspector> </swingMetawidget>
All Metawidgets have default Inspectors. Overriding the default means the default is no
longer instantiated. Usually this is not what you want, so you should consider instantiating
the default along with your new Inspector (ie. use CompositeInspector).
You can see the default by looking in the
Metawidget JAR for the file metawidget-xxx-default.xml (where xxx
is your target platform, such as swing or struts).
For reference, the defaults are:
| Platform | Default |
|---|---|
| Android | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> </array> </inspectors> </compositeInspector> |
| GWT | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> </array> </inspectors> </compositeInspector> |
| JSF | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> <facesInspector /> </array> </inspectors> </compositeInspector> |
| JSP | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> <jspAnnotationInspector /> </array> </inspectors> </compositeInspector> |
| Spring | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> <springAnnotationInspector /> </array> </inspectors> </compositeInspector> |
| Struts | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> <strutsAnnotationInspector /> <commonsValidatorInspector config="CommonsValidatorInspectorConfig"/> </array> </inspectors> </compositeInspector> |
| Swing | <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <metawidgetAnnotationInspector /> <propertyTypeInspector /> </array> </inspectors> </compositeInspector> |
All inspectors are required to be threadsafe and immutable. This means you only
need a single instance of an Inspector for your entire application. If you are using
metawidget.xml (see later) then ConfigReader takes care
of this for you, but if you are instantiating Inspectors in Java code you should
reuse instances.
Note that immutable only means Inspectors cannot be changed once instantiated - it does not
mean they cannot be configured. Many Inspectors have corresponding xxxConfig
classes that allow them to be configured prior to instantation in a type-safe way. For example, a
JpaInspector can be configured in code:
metawidget.setInspector( new JpaInspector( new JpaInspectorConfig().setHideIds( false )));
Or in metawidget.xml:
<jpaInspector xmlns="java:org.metawidget.inspector.jpa" config="JpaInspectorConfig"> <hideIds> <boolean>true</boolean> </hideIds> </jpaInspector>
The inspection-result XML format is the 'glue' that holds everything together: the
Metawidgets request it, the Inspectors provide it, and
the WidgetBuilders base their choice of widgets on it.
It is a very simple format. As an example:
<inspection-result version="1.0"> <entity type="com.myapp.Person"> <property name="name" required="true"/> <property name="age" minimum-value="0"/> </entity> </inspection-result>
Only a handful of XML attributes are mandatory (see inspection-result-1.0.xsd). Most, such as retired
and minimum-value, are provided at the discretion of the Inspector
and recognised at the discretion of the WidgetBuilders, WidgetProcessors
and Layouts. This loose coupling
allows Inspectors to evolve independently for new types of metadata, WidgetBuilders
to evolve independently with new types of widgets, and so on.
Metawidget inspects a wide variety of back-end architectures. If your chosen back-end
architecture is not supported 'out of the box', you may need to implement your own Inspector.
All Inspectors must implement the org.metawidget.inspector.Inspector interface:
public interface Inspector { String inspect( Object toInspect, String type, String... names ); }
The interface has only one method: inspect. Its parameters are:
an Object to inspect. This may be null, or can be ignored for inspectors inspecting static metadata (such as XML files)
a type. This must match the given Object, or some attribute in the inspected config file
a list of names to be traversed beneath the type
The returned String must be an XML document conforming to inspection-result-1.0.xsd. To assist
development, deploy your inspector within ValidatingCompositeInspector to
automatically validate the returned DOM during testing.
A number of convenience base classes are provided for different inspectors:
BaseObjectInspector assists in inspecting annotations and properties (including
support for different property styles, such as JavaBean properties or Groovy properties). Here
is an example of a custom Inspector to inspect tooltip metadata from
a custom annotation. It extends the code from the tutorial (see Section 1.1, “Part 1 - The First Metawidget Application”).
package com.myapp; import java.lang.annotation.*; import java.util.*; import javax.swing.JFrame; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.impl.*; import org.metawidget.inspector.impl.propertystyle.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; import org.metawidget.util.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new TooltipInspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 210 ); frame.setVisible( true ); } @Retention( RetentionPolicy.RUNTIME ) @Target( { ElementType.FIELD, ElementType.METHOD } ) static @interface Tooltip { String value(); } static class TooltipInspector extends BaseObjectInspector { protected Map<String, String> inspectProperty( Property property ) throws Exception { Map<String, String> attributes = CollectionUtils.newHashMap(); Tooltip tooltip = property.getAnnotation( Tooltip.class ); if ( tooltip != null ) attributes.put( "tooltip", tooltip.value() ); return attributes; } } }
You could then annotate the Person class...
package com.myapp; import com.myapp.Main.Tooltip; public class Person { @Tooltip("Person's full name") public String name; @Tooltip("Age in years") public int age; @Tooltip("Whether person is retired") public boolean retired; }
...and TooltipInspector would pick up the custom @Tooltip
annotation and feed it into the Metawidget pipeline.
![]() | Note |
|---|---|
Because Metawidget decouples inspection from widget creation, by default SwingMetawidget
will not be expecting this new tooltip attribute and will ignore it. You will
need to further combine this example with a TooltipProcessor, see
Section 2.4.4, “Implementing Your Own WidgetProcessor”.
|
For inspecting XML files, BaseXmlInspector assists
in opening and traversing through the XML, as well as merging multiple XML files
into one (eg. merging multiple Hibernate mapping files). Here is an example of a
custom Inspector to inspect XML:
TODO: for now, see StrutsInspector for example usage.
When implementing your own inspector, try to avoid technology-specific XML attribute names. For example, FacesInspector
has an annotation @UiFacesNumberConverter. This annotation certainly has technology-specific parts to it, as
it names a JSF Converter that only applies in JSF environments, so
it is reasonable to name the XML attribute faces-converter-class. However, NumberConverters also
use other properties about the field, such as the maximum number of integer digits. Such properties are not JSF-specific (eg. we can
source the same property from Hibernate Validator's @Digits annotation), so are better named 'neutrally'
(eg. maximum-integer-digits).
![]() | Config classes must override equals and hashCode |
|---|---|
If your Inspector has a xxxInspectorConfig class, and you want
it to be cacheable and reusable by
ConfigReader and metawidget.xml, the xxxInspectorConfig
class must override equals and hashCode.
|
![]() | Generate an XML Schema |
|---|---|
If your Inspector has an xxxInspectorConfig class, consider
defining an XML Schema for it. This is optional, but allows users to validate their use of your Inspector in their
metawidget.xml at development time. There is an Ant task,
org.metawidget.config.XmlSchemaGeneratorTask, provided in the source distribution that can help with this
by auto-generating the schema. All the existing Metawidget schemas are generated using this Ant task.
|