|
JeeWiz Architect's Guide
|
|
|
|
Contents >
2. Overview
|
|
|
2.9 Patterns
We have so far shown how we can build meta-models, read models and create output files using Velocity or Ant.
Now we introduce JeeWiz patterns, which allow us to take advantage of this features, and effectively magnify their effect.
In JeeWiz we use the term 'pattern' in a particular way.
The original word comes from the idea of a 'design pattern', but in JeeWiz we do not just embellish the model - we also generate the implementation as an integral part
of the pattern.
2.9.1 Overview
|
The diagram below shows how patterns work, and how they differ from templates.
In JeeWiz,
- a template is a Velocity script, run by the <jwVelocity> task in a meta-class's Ant build.xml script.
Normally it produces an output file, which occasionally is a final product or, more usually, an artifact that
is then used later in the build.
- a pattern is a Velocity script of a special name like 'includeSpec.vm' and run directly by the engine, that produces - rather than output files - more specification - a model fragment - in simple XML format.
Both templates and patterns are part of the overall rendering process, and both are run on behalf of a particular model object.
As indicated by the back-arrow in the diagram, the output of a pattern is fed back into the JeeWiz engine as though it had been
read as part of the input. As usual, the input produces more model objects (mapped to Java) and these model objects may themselves
fire patterns ... so this process is recursive.
2.9.2 More Mechanics
|
We introduce here some additional aspects of how patterns work, that we will use later in the examples of patterns usage.
This is a short introduction; more details are given in the chapter on patterns
2.9.2.1 Invoking Patterns
|
Patterns can be introduced in two ways:
- By expressing them in the model, using the 'jw-pattern' attribute on a model object,
which can hold one or more pattern filenames.
- By creating specially-named files.
The standard pattern is named 'includeSpec.vm'.
We will also introduce an early-stage pattern, called 'preIncludeSpec.vm', which is run immediately after the model is read in.
The files for patterns are looked up using
rendering inheritance
This means that higher-level template directories can override patterns in lower-level directories.
2.9.2.2 XML Format
|
After Velocity has processed the pattern, the output must be either whitespace only -
i.e. no new model objects will be created - or a valid XML document, with a single root element.
If XML is generated, the root element must be either <this> or <parent>.
With <this>, the XML is used to embellish the current model element;
with <parent>, the XML embellishes its parent.
There is also an explicit way of specifying which model object the pattern embellishes:
this is done by calling the setPatternRootObject() method on the current object,
with the target model object as a parameter.
2.9.2.3 Multi-stage Patterns
|
Sometimes it is convenient to express a complex pattern in stages.
For example, in the J2EE system for entity EJBs, we create a boilerplate ejbCreate method that takes a value object.
But then, the ejbCreate methods are handled specially in the entity EJB patterns.
So it makes sense to use two stages to the pattern: first, create the ejbCreate; then, do the pattern that will further process this.
This is an example driven by modularity, and so is a design choice rather than being a necessity.
But there are situations where a multi-stage pattern is necessary:
- when the objects to be created have different parents
- when the operation of a later stage depends on the existence of an object created in an earlier stage,
or on a value defined in an earlier pattern stage.
An additional pattern stage is indicated using the 'next-pattern' property on the current object -
the first-stage pattern sets this to the name of the file to be used for the next stage of the pattern.
JeeWiz then recognises this immediately after processing the previous pattern (including creating any new model objects),
and runs the next pattern.
This is a 'one-shot' attribute: it is reset before the next pattern is run.
However, the next-pattern attribute can be set in a next-pattern too, so there can be any number of stages in a pattern.
The next-pattern property on the current object can be set in two ways: as a pattern or as a method call. The following example shows both:
<this next-pattern="includeSpec2.vm" />
#set( $this.nextPattern = "includeSpec2.vm" )
|
2.9.3 Examples Of Using Patterns
|
2.9.3.1 Implementing Design Patterns
|
'Design patterns' expressed in the literature create new model objects surrounding the current meta-class.
These create either peers of the current model object (i.e. having the same parent as the current model object)
or children.
Here is the includeSpec.vm for the java-bean, which adds getters and setters for a Java Bean.
<this>
#foreach( $field in $fieldList )
#if( $field.readable )
<method name="get${field.nameCapitalised}"
return-type="${field.type}"
access="public"
>
return ${field.name};
</method>
#end
#if( $field.writeable )
<method name="set${field.nameCapitalised}"
return-type="void"
access="public"
>
<parameter name="_p"
type="${field.type}"
/>
${field.name} = _p;
</method>
#end
#end
</this>
|
This creates children - methods - on the current model object, which is a class.
For each field in the class, the pattern creates a getter if the field is readable and a setter if the field is writeable.
Note that the Velocity logic (#foreach and #if lines) is left-justified whereas the XML is indented.
This uses a JeeWiz feature to ignore leading tabs, which the standard patterns use to
make it easier to distinguish the Velocity logic from the structure of the generated file.
In this example, this is a minor benefit, but with large and complex patterns it becomes almost essential to
understanding the script.
Inside the two method elements is the character data ('return...' and '$field.name}=_p') implementing the methods.
This uses the standard feature of JeeWiz's rendering of classes in object-oriented languages,
whereby character data in methods is interpreted as the implementation of the method.
Now here is an example that creates peers:
<parent>
#parse( "SessionEjb.inc" )
#parse( "classImpl.inc" )
#if( ${extends} )
#set( $extendSession = $parent.getSessionByNameNoPackage($extends) )
#set( $proxyExtends="${extendSession.ProxyClass}" )
#end
#parse( "classProxy.inc" )
#if( ${j2eeVersion}=="13")
#parse( "EjbLocalInterface.inc" )
#parse( "EjbLocalHomeInterface.inc" )
#end
#parse( "EjbRemoteInterface.inc" )
#parse( "LocalDelegate.inc" )
#if ( ${hasRemoteInterface} )
#parse( "EjbRemoteHomeInterface.inc" )
#parse( "RemoteDelegate.inc" )
#end
<parent>
|
This example is an extract of J2EE's session EJB pattern.
It creates the Session EJB class, the implementation class, a proxy, the various EJB interfaces,
and local and remote delegates.
Although we show the <parent> XML element,
the generated classes come from rendering the files pulled in by the #parse().
For patterns creating peers, this is a common approach:
separating the patterns out not only improves modularity,it also encourages inheritance and overriding
(because relative filenames in #parse() are looked up using the rendering inheritance searching technqiues).
Note the way that the technical knowledge of the environment is expressed in the interplay between Velocity logic and generated XML.
In other words:
- if the session between extends another session bean ('#if(${extends}' ),
we fix up parameters for the rendering of the proxy, so it to extends the proxy for its base
- if the J2EE Version is 1.3 do we generate local interfaces, because J2EE Version 1.2 didn't have this concept
- similarly, we only generated remote interfaces if the 'hasRemoteInterface' property is set on the session model object.
Finally, some other systems provide a special-purpose model-to-model transformation language,
in addition to the techniques for rendering to outputs.
The two examples above show that JeeWiz does not need a special-purpose language.
The pattern mechanism provides the ability to create additional model elements,
and Velocity has all the elements of execution languages to be able to express complex transformations.
This approach depends on the use of 'simple XML' as the native JeeWiz modeling paradigm:
it wouldn't be practical if XMI were the basic modeling language.
2.9.3.2 Fix-ups
|
An important use for patterns is to fix up existing model objects, typically by applying intelligent defaults to the input model.
Fix-ups are normally done in the preInclude.vm pattern - this is the one that is done first,
even before component.properties are evaluated.
Here is an example of fixing up an attribute in preparation for mapping it to a data-base:
#if( $name )
<this
access="private"
#if ( !$dbmsColumn && $parent.isSQLReservedWord($name) )
dbms-column="${name}_J"
#elseif ( !$dbmsColumn )
dbms-column="${name}"
#end
#set( $wrapType = $lang.getPrimitiveWrapperClass($type) )
type="$wrapType"
/>
#end
|
In English, this pattern reads:
If the name of the current object has been specified
(otherwise, a validation error occurs later)
Set the attribute's 'access' to private
(because public getters and setters are generated)
If the 'dbmsColumn' has not been specified
and the attribute name is a SQL reserved word
set the dbmsColumn to the attribute's name, suffixed by '_J'
otherwise it the dbmsColumn has not been specified
set the dbmsColumn to the attribute's name (with no suffix)
Adjust the type to the wrapped type for a primitive type.
|
This gives us a smart default for the DBMS column, normally using the attribute name but avoiding
conflict with SQL reserved words. This is what we mean by a 'smart default':
it is particular to the fact that we are using SQL for persistence;
for some other persistence mechanism, this might not be necessary or might have another set of keywords that would have to be avoided.
The other term we use for this is
predictive assumptions.
It also implements the policy that attributes are upgraded from primitives (like Java 'int') to their wrapper class (like 'Boolean'),
so that we can distinguish between missing (i.e. null) and default values.
The result of this pattern for a 'table' attribute of type 'int' after processing by Velocity would be:
<this
access="private"
dbms-column="table_J"
type="Integer"
/>
|
When read in again, this of course does not create a new model object; it is merged with the current model object.
Here is another example of a fixup;
in this case, a new model object, a child attribute, may be created.
This ensures that an entity destined for storage as a database table has a primary key:
#if( $keys.size() == 0 )
<this>
<attribute name="oid"
autokey="true"
type="Integer"
/>
</this>
$this.reset()
#end
|
In English this pattern reads:
If this entity has no keys (i.e. attributes that go to make up the primary key)
Underneath this object (which is the entity)
Create an attribute of name 'oid', as an auto-incrementing Integer
|
Whereas component.properties construct logical names for use in Velocity (and the names become extra properties),
fix-up patterns in preIncludeSpec.vm are used to adjust the model itself,
and these fixups are likely to then be used in the component.properties calculations..
2.9.3.3 Cross-Tier Patterns
|
When we build a meta-model, we can only reference meta-classes in the current or lower (parent) meta-models,
because of the limitations of Java.
However, in patterns, there are no such restrictions: we can create model objects from any meta-model.
For example, this means, from an entity we can use a pattern to create a session,
to handle the Create/Read/Update/Delete (CRUD) and standard search functions;
then we can project the session into pages in the UI tier using a separate pattern.
The follow diagram shows the process
This is what JeeWiz does in the standard business object and screen patterns, unless the patterns are turned off by the modeler.
The pattern to create a group of pages from a session is not only cross-tier, it also goes up the meta-model stack,
because the session is in the Business Object meta-model while the page is in the Screen meta-model.
-- put it somewhere else
-- high level
2.9.4 Taking Advantage of Patterns
|
Here as some design approaches for taking advantage of patterns:
-
Create the code.
Where possible patterns should create the code to connect the created object(s) to the creator.
By doing so, it adds more value to the patterns, and increases the amount that can be automated.
-
Use logical patterns.
The most powerful way to meta-program (i.e. create renderings) is to use patterns.
And the most powerful patterns make use of high-level, logical objects.
Such patterns create a large amount of generated code for very little effort in the patterns.
They will also be inherently cross-platform,
so mapping to new technologies can be done by changing the configured renderings in a build rather than changing the model.
Using logical patterns also promotes single-step generation of complex systems,
which preserves the intended architecture defined by meta-programmers.
-
Plan for smart defaults (predictive assumptions).
This means that it is worth the effort to accomodate models with minimal specifications,
to support fast prototyping.
The less a modeler has to input to get a working system,
the faster an end-user can give feedback on the suitability of a planned system.
In general, the starting point should that be no properties except names should be required on model objects,
and also that other objects (like primary keys) that are part of the technology should be generated by fix-up patterns.
 |
|
 |
Copyright (c) 2001-2008 New Technology/enterprise Ltd.