2.2 Inspectors

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.

2.2.1 Interface

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() );

2.2.2 Usage

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.

2.2.3 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.

CompositeInspector composes multiple inspectors into one

Figure 2.2. CompositeInspector composes multiple inspectors into one


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>

2.2.4 Defaults

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>

2.2.5 Immutability

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>

2.2.6 inspection-result

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.

2.2.7 Implementing Your Own Inspector

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.

    [Tip]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).

[Important]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.
[Tip]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.