2.2 Inspectors

Inspectors inspect the back-end architecture looking for useful metadata. 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 domain object 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 (i.e. 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. For example:

metawidget.setToInspect( myPerson );	// Will be passed to Inspector

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() );
[Tip]Note
In general, a non-null setToInspect is preferrable, as many binding and validation technologies (e.g. see the section called “Property Binding”) will be expecting a concrete object.

2.2.2 Usage

Unless explicitly specified, each Metawidget will instantiate a default Inspector. Typically this will be a CompositeInspector composed of a PropertyTypeInspector and a MetawidgetAnnotationInspector.

This default behaviour can be overridden either in code:

metawidget.setInspector( new MyInspector() );

Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

<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 focused 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 immutable (see Section 2.2.5, “Immutability”). 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 PropertyTypeInspector(),
		new MetawidgetAnnotationInspector(),
		new MyInspector()
	)));

Or via metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

<swingMetawidget xmlns="java:org.metawidget.swing">
<inspector>
	<compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/>
				<metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation" />
				<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 (i.e. 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>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>
GWT
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>
JavaScript (incl. AngularJS and JQuery UI)
new metawidget.inspector.PropertyTypeInspector()
JSF
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
				<facesAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>
JSP
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
				<jspAnnotationInspector />				
			</array>
		</inspectors>
	</compositeInspector>
Spring
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
				<springAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>
Struts
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector>
					<propertyStyle>
						<javaBeanPropertyStyle>
							<excludeBaseType>
								<pattern>^(java|javax|org\.apache\.struts)\..*$</pattern>
							</excludeBaseType>
						</javaBeanPropertyStyle>
					</propertyStyle>
				</propertyTypeInspector>				
				<metawidgetAnnotationInspector />
				<strutsAnnotationInspector />
				<commonsValidatorInspector />
			</array>
		</inspectors>
	</compositeInspector>
Swing
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>
SWT
<compositeInspector xmlns="java:org.metawidget.inspector.composite"
		config="CompositeInspectorConfig">
		<inspectors>
			<array>
				<propertyTypeInspector />
				<metawidgetAnnotationInspector />
			</array>
		</inspectors>
	</compositeInspector>

2.2.5 Immutability

All Inspectors are required to be immutable. This means you only need a single instance of an Inspector for your entire application. If you are using metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”) 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 (see Section 2.7, “metawidget.xml and ConfigReader”):

<jpaInspector xmlns="java:org.metawidget.inspector.jpa" config="JpaInspectorConfig">
	<hideIds>
		<boolean>false</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 xmlns="http://metawidget.org/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 required 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.

For JavaScript-based Metawidgets, inspection results are returned as JSON Schema:

{
	properties: {
		name: {
			required: true
		},
		age: {
			minimum-value: 0
		}
	}
}

2.2.7 Implementing Your Own Inspector (Java)

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 (Java version) - 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, 250 );
    		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.5.6, “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 (e.g. merging multiple Hibernate mapping files). Here is an example of a custom Inspector to inspect Castor XML mapping files:

    package com.myapp;
    
    import static org.metawidget.inspector.InspectionResultConstants.*;
    
    import java.util.*;
    import org.metawidget.inspector.impl.*;
    import org.metawidget.util.*;
    import org.w3c.dom.*;
    
    public class CastorInspector
    	extends BaseXmlInspector {
    	
    	public CastorInspector( BaseXmlInspectorConfig config ) {
    		super( config );
    	}
    
    	protected Map<String, String> inspectProperty( Element toInspect ) {
    	
    		if ( !"field".equals( toInspect.getNodeName() ) )
    			return null;
    
    		Map<String, String> attributes = CollectionUtils.newHashMap();
    		attributes.put( NAME, toInspect.getAttribute( getNameAttribute() ) );
    		attributes.put( TYPE, toInspect.getAttribute( getTypeAttribute() ) );
    		return attributes;
    	}
    
    	protected String getTopLevelTypeAttribute() {
    		return NAME;
    	}
    }

When implementing your own Inspector, try to avoid technology-specific XML attribute names. For example, FacesAnnotationInspector has an annotation @UiFacesNumberConverter. This annotation certainly has a technology-specific part to it, as it names a JSF Converter that only applies in JSF environments, so it is reasonable to name that XML attribute faces-converter-class. However, it also describes other parts of the property, such as the maximum number of integer digits. Such parts are not JSF-specific (i.e. we can source the same property from Hibernate Validator's @Digits annotation), so are better named 'neutrally' (i.e. maximum-integer-digits).

Like InspectionResultProcessors, WidgetBuilders, WidgetProcessors and Layouts, Inspectors are required to be immutable. However they will occasionally need to store some internal state, such as which PropertyStyle to use. This can be achieved in two ways:

  1. For state that will remain constant throughout the life of the Inspector, such as which PropertyStyle to use, use xxxInspectorConfig classes. For example:

    public class MyInspectorConfig {
    	private PropertyStyle mPropertyStyle;
    	
    	public MyInspectorConfig setPropertyStyle( String propertyStyle ) {
    		mPropertyStyle = propertyStyle;
    		return this;
    	}
    
    	public String getPropertyStyle() {
    		return mPropertyStyle;
    	}
    	
    	// ...must override equals and hashCode too...
    }

    These xxxInspectorConfig classes are then passed to the Inspector at construction time, and stored internally:

    public class MyInspector {
    	private PropertyStyle mPropertyStyle;
    	
    	public MyInspector( MyInspectorConfig config ) {
    		mPropertyStyle = config.getPropertyStyle();
    	}
    }

    This mechanism can then be controlled either programmatically:

    metawidget.setInspector( new MyInspector(
    	new MyInspectorConfig().setPropertyStyle( new JavaBeanPropertyStyle() )));

    Or in metawidget.xml (see Section 2.7, “metawidget.xml and ConfigReader”):

    <myInspector xmlns="java:com.foo" config="MyInspectorConfig">
    	<propertyStyle>
    		<javaBeanPropertyStyle xmlns="java:org.metawidget.inspector.impl.propertystyle.javabean"/>
    	</propertyStyle>
    </myInspector>
    [Important]Config classes must override equals and hashCode
    If you want your configurable Inspector to be cacheable and reusable by ConfigReader and metawidget.xml, the xxxInspectorConfig class must override equals and hashCode.
    [Tip]Generate an XML Schema
    If you intend your Inspector to be configurable via metawidget.xml, 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.
  2. For state that will change across multiple inspections, store it in a ThreadLocal. This is straightforward because Inspectors are not re-entrant.