Copyright © 2008
New Technology / enterprise Ltd.
Objective
We are going to look at a transform using specialist meta-model classes for the object tree, which will allow a bit more flexibility and specificity in the templates.
Meta-model and Meta-class Objects
In the last tutorial we saw that JeeWiz took an XML model and used it to build an object tree in memory, then walked the tree and used JeeWiz templates to produce output files. The meta-class objects all used the same default class, called ExtraBuildComponent. This class is capable of holding information about the properties and the children of the node, and any plain text it might contain, but it has to make assumptions about the form of this information. It holds all children in lists, even though there might only be one of them, and it assumes all properties are text. If we want to correct these assumptions, we have to build a model of the modelling language - the meta-model.
Each class in the meta-model has a definition of the children and properties it expects, their type and whether they need to be in a list. It also allows default values, descriptions and validation rules.
When the model is read in, JeeWiz looks for a meta-class with the same name as the top model element. If it finds it, it uses an object of that type rather than the ExtraBuildComponent. For its children it looks to see what type has been specified in the top level class for children of that name and so it builds the tree using specialist meta-class objects. Because all meta-class objects extend BaseBuildComponent (including ExtraBuildComponent) all the functionality available in handling unknown elements is still available, but at the moment the children of ExtraBuildComponent objects are always also ExtraBuildComponent objects. Note that for a child list, the type of objects in the list defaults to the list name (although it can be set explicitly), so a list of name attribute will default to a list of Attributes. Properties with no type set default to Strings.
Specifying a Meta-Class
The meta-model objects used by the JeeWiz engine are just java objects and we could write the java class for the meta-model objects in the same way that we could write any java objects. But we don't. We want our meta-classes accessible to the modeller and to the modelling tools. This is expected in most integrated development environments (IDE), and someone wanting to model using our defined meta-modelling language will want to know what's expected, what are the defaults, the constraints and the description of the items they are working with.
That still doesn't stop us working in java directly, but we'd have to do a lot of code introspection to reverse engineer the meta-model to allow us to use the information to guide the modeller. It's easier to specify the meta class in xml and convert that to the java class and to IDE plugins, documentation, etc.
This means an extra step in that our meta-class definitions have to be build into java code and compiled into classes, before the main transform is run. If you expected these meta-model definitions to change a lot in tandem with the model, you might want to build a compilation in as part of the main transform, but for the type of use we normally make of JeeWiz they change infrequently in comparison with the actual model, and we compile them separately.
So if we want to write a set of meta-model classes to address the sort of web-site model we had before, we might have meta-model objects called site, screen, siteMenu, menuItem. It's a matter of judgement how far you go and whether you feel you need to create a meta-class. Do we create a meta-class for logo, or section? Part of the decision is down to whether we intend to use the specification for anything else, such as documentation, modelling help or validation. Partly it's down to adding specificity to the object tree making our template coding easier.
I'm going to put a qualifier in the site meta-class that says we must have at least one screen, which just means adding a minOccurs="1" property to the screen list. We'll come to some of the other aspects of the meta-model, defaults, validation and methods later.
So our XML specification for site looks like:
<meta-class name="site"
description="This defines the top level element of a suite of screens"
>
<list name="menu"
required="true"
type="site-menu"
/>
<list name="screen"
required="true"
type="screen"
/>
</meta-class>
If we refer to $menu in the site templates (or $parent.menu in the screen templates), we are referring to a SiteMenu object. Similarly, we can change the assembly.xml specification to use a menu tag. But we can still keep the name of the element the same as that type if we want, and the name of the screen list will still be called $screenList as before. Having the ability to rename the properties and objects like this allows us to use meta-model definitions for more than one purpose. I could define two lists, say top-menu and left-menu, both of type site-menu, and then the specification could load in two lists of menu-items, each of which would have the same validation applied.
Building the Meta-Model
We call the overall list of meta-classes available at the template level a meta-model, and JeeWiz expects to find these in the form of a jar of compiled java classes in the template directory, by default called components.jar. We convert the meta-class descriptions into java, compile them and jar them up using ant and JeeWiz, in much the same way as we do any transform. In the worked example, I've added another target to the examples build.xml called metamodel. Instead of just calling ant from the command line, we call ant metamodel, and this creates the jar and puts it in the template directory (control).<\p>
If we are using JeeWiz to transform the specification, we need a template and this can be found in ${jwhome}/resources/JeeWiz/control - giving us a standard transform for meta-models. I've put the meta-class specifications in a subdirectory of the example called meta-specification and have made the java class sources and compiled classes available in src and classes respectively so you can see what gets built. Among other things, ant builds an antfile called buildJar.xml, that when called compiles the java and jars the classes.<\p>
After running ant metamodel to generate the jar, we can run ant to build the web site, as before.<\p>
If we wanted to call the jar something else or place it somewhere else, there's another properties file we can use called model.properties, which goes in the template directory (along with system.properties if it exists). We can set a property called componentJar to point to the file to use to find the metaclasses, so that doesn't even have to be in the template directory. Also in the model.properties we can set the package that we want the meta-classes to be in and a topComponent restriction. In our case we always want the top component to be "site".<\p>
The standard metamodel source code provided with JeeWiz can be rebuilt by just calling ant on the top level of Jeewiz, but it's not normally necessary to do that!
Adding Methods to the Meta-Classes
We can add java methods directly to metamodels by putting java into the simple text area of the element. It is normally best to add CDATA brackets like before, even though we won't be adding markup, as we don't want any greater than or less than operators misinterpreted.<\p>
Now we've specified a cross-site menu. This is shown at the top of each page and is the same for each screen. But to access it we have had to loop through a list because child objects become a list. But now we can create a proxy property, which means we can get rid of the #foreach loop in the template. When we specify the name of a property, JeeWiz actually checks for a getter of the same name - so $menu will access the getMenu() method. So we can implement a singleton pattern by writing a getMenu() method that returns the first item on the menuList.<\p>
One of the properties I put on the menu-item specification was position, but I didn't use it. Let's say I want the menu-item list by sorted by position. We can add a method to the siteMenu meta-class, say getItemList(), which will return a List of menu items by position order, rather than the default getMenuItemList(), which returns them in the order they appear in the specification xml. As with getMenu(), we can access the getItemList() method using $menu.itemList. This syntax only works if the get method has no parameters - otherwise we need to use the full syntax $object.getThing(param) etc.<\p>
We want to sort the menu list by position if it's there, but we don't want to leave all the pages out if there is no position, so we can say that for position 0 or null position we'll show all the menu-items in the order they are in the xml, but if there's a position specified the items will be shown in numeric order. To make the coding easier, we can put a default value of "0" on the position property in the menu-item meta-class, which will treat all unspecified items as position zero.<\p>
Adding Utility Methods
As well as adding methods directly on to the meta-model, we can create methods that are accessed the same way but that are in JeeWiz script. These go in component.methods analogous to properties for an object which go into component.properties.
Because they are scripting based they can produce output directly when called from a template. For example we could take out the menu rendering and turn it into a called method.
#method (renderTopMenu)
<ul>
#foreach ($item in $this.menu.itemList)
<li>
<a href="./${item.page}.html">${item.linkCaption}</a>
</li>
#end
</ul>
#end
then in screen.vm we have
<div id="top menu"> $parent.renderTopMenu() </div>
Note that $this in the method refers not to the screen, but to the object the method is called on. In this case that would be the site. Actually we don't need the $this and we could just say #foreach ($item in $menu.itemList).
To do rendering type methods like this as a java method in the meta-class would normally involve loading line after line into a StringBuffer and then returning the String equivalent for output. The component method is usually easier.
If we wanted to pass a parameter in we could specify it like this
## We can have a parameter, the menu to be rendered as output. #method (renderTopMenu $menu)
etc.
We could also use JeeWiz methods to return a value with #return. So we could rewrite the sorted menu items routine in script fairly easily.
#method (getOrderedItems)
#set ($positives = $this.newTreeMap )
#set ($negatives = $this.newTreeMap )
#set ($zeros = $this.newArrayList )
#set ($sorted = $this.newArrayList )
#foreach ($item in $menuItemList)
#if ($item.position > 0)
#set ($dummy = $positives.put( $item.position , $item ))
#elseif ($item.position < 0)
#set ($dummy = $negatives.put( $item.position , $item ))
#else
#set ($dummy = $zeros.add($item))
#end
#end
#set ($dummy = $sorted.addAll( $negatives.values() ))
#set ($dummy = $sorted.addAll( $zeros ))
#set ($dummy = $sorted.addAll( $positives.values() ))
#return ($sorted)
#end
$this.newTreeMap and $this.newArrayList are helper methods, short form for $this.getNewClassInstance("java.utils.TreeMap"), etc. Because they have no parameters we can shorten them further to just $newTreeMap and $newArrayList.
If we added this to the component.methods of site-menu, the renderTopMenu method could use either the java method on the meta-class
#foreach ($item in $this.menu.itemList)
or the component.method
#foreach ($item in $this.menu.orderedItem)
equally well.
(Obviously in the real world you'd only do one of them!)
One final thing that might help you decide which way you think is better to write a method is that java is strongly typed whereas JeeWiz scripting isn't. So if you wanted to change the method to a generalized list sort, where you pass in the name of the field to sort by, this is easy in script
#method (orderList $orderBy)
..
..
#foreach ($item in $this)
#set ($pos = $item.getAttributeOrProperty($orderBy))
#if ($pos > 0)
..
whereas in java, you'd have to introspect or implement an interface.
You can generalize java helper methods, add them to a class called ExtraHelperMethods and put this somewhere in the path and they will get picked up by JeeWiz. Helper methods written in script can be placed in system.methods, which like system.properties is generally available, whereas component.methods and component.properties are for specific object types. (The system.methods feature is available in engine releases after 5.1.1.)
There are ways other than methods to add extra callable functionality: controls, macros, snippets, diversions, etc., and there's a tutorial on controls later in this series.
Validation
It's possible to set up validation on the meta-classes and their elements. This can be done by marking up property or list specifications, such as required="true" on a property, or unique="true" on a list (to ensure that no duplicate items are entered). In fact we've already met one of these; I put min-occurs="1" on the screen list, to make sure we weren't generating an empty site. Similarly I could put max-occurs="1" on the site's menu list. These are converted to validation code in the java class, and the validate method is run after the object tree has been read in and before any template rendering.
Explicit validators can be set up for a meta-class, and these are specified in XML in the meta-class definition. These encode an expression which the validator asserts, and a message to return if the assertion fails.
Here's a couple of examples from the standard transforms
<validator message="Attributes can not be defined as both autokey and key" expression='!getAutokey() || !getKey()' /> <validator message="The 'extends' attribute on an entity must reference another entity" expression="getExtendsObject() == null || getExtendsObject() instanceof Entity" />
Validators can be quite flexible, running setup code or code that only fires if the assertion succeeds. They can fire conditionally and can either terminate the generation on failure, or just fire warning messages. For more details look up the MetaValidator Object in the Architect's Guide.
Summary
The objects used to create the object tree are all java objects that extend BaseBuildComponent. They all support adding String properties and child objects from the XML specification. To get more functionality it's possible to create java meta-class objects that define more about an object type.
If JeeWiz finds a top level meta-class of the same name as the root node of the XML, it will use that to instantiate the top level object of the tree. The children of the object can be named in the meta-class, and if so their meta-class and type can also be defined. And so the tree can be created using specialist java objects. But if the child is not defined in the meta-class, JeeWiz will just use ExtraBuildComponent.
Special meta-classes allow extra methods specific to the meta-class to be defined, also validators, defaults and properties not typed as Strings.
The java classes of the meta-model are defined as XML input and the java is generated using a JeeWiz build.
Exercise
The results of the tutorial will be found in %jwhome%\examples\tutorial\03website. If you have an older release of JeeWiz without the tutorial, you may have to download it separately from here.
- Write two meta classes for an event and a runtime script-method. The event may take you to another screen, like a menu item does, and may run a script-method first. The script-method will generate something like a javascript method that will appear on the screen.
Possible properties for the script-method are name, language and description, and the code can be handled in the same way as text in a section, using CDATA. If you feel brave, you can add parameters.
Possible properties for an event might be name, display-style, link-caption, image, script-name, event-trigger, page. The display-style might be button or anchor. The link-caption and page are like on menu-items. The image might be a background image on the button, or an image that can be clicked to fire the event. The event-trigger should probably default to OnClick, but could include mouseOver or doubleClick, etc.
- Add event and script-method specifications to screen specifications in the assembly.xml, display the events on the bottom of the screen in the appropriate style. Check that they fire the script-method - an appropriate test might be to fire an alert.
<script language="javascript"> function alertMe(){ alert ("You have been alerted") } </script>
This should be generated somewhere in the HTML body element. If you are displaying the event as an anchor, you might end up with something like
<a href="./faq.html" onClick="alertMe()">FAQ</a>
Try to group script-method functions on a page that use the same language within a single HTML script element.
- Decide what event display-styles you want to permit and add a validator to the event meta-class to enforce it. Add a default display-style if you have a favourite. If you haven't already, default the event-trigger to "OnClick". Add a default of "javascript" to the script language property.
