|
|
|
The Model-Driven System Builder
|
|
JeeWiz Architect's Guide
|
|
|
|
Contents >
9. Patterns
|
|
|
9.3 The Mechanics of Patterns
Patterns are rendered via Velocity scripts.
If a pattern or next-pattern does not have an extension (e.g. 'singleton') the '.vm' extension is added (so 'singleton.vm' is rendered).
Patterns are rendered in the context of the current model object,
so all the variables and object references you would use in a normal Velocity script are available.
This particularly includes any names set by the component.properties mechanism for patterns other than the preIncludeSpec.
For example, in a business object class,
ProxyClassName is defined in component.properties,
so you can (and should) use $ProxyClassName wherever you need to refer to the proxy class name.
Patterns are searched for in the same way as other Velocity scripts, as described here.
The first file with the right name is used: there is no addition of pattern files (like there is for component.properties).
The fact that pattern files are searched for means that you can use the pattern on sub-classes of the intended object type, using the template.properties file.
For example, the singleton pattern would be defined for a class (i.e. a 'jwclass' in JeeWiz) because that is the most fundamental meta-class it applies to.
However, it could equally well be used by a derivative of jwclass like business-object.
This is what you would expect: if a pattern is relevant to a class, it is relevant to its subclasses.
The format of the XML to be included, i.e. the output of the pattern script, is a standard XML document.
The root element can have one of two (case-sensitive) tag names:
- this - the contents of this document should be merged in starting at the current model object.
This would be appropriate in the singleton example, where we added a field and a method into a singleton class. The implementation shown would be nested within a '<this>' tag.
- parent - the contents of this document should be merged in starting at the parent of the current model object.
(Obviously, this cannot be used on the top-level node).
You must use <parent> to create peers of the current object.
For example, if a class like an entity is to create interfaces and other classes as part of the pattern, then you must specify '<parent>' for the root node and then add the peer objects as nested elements below the parent.
There is one special element name if the root node is the parent, namely 'this'.
When this element tag is used directly below 'parent', this does not create a new model object for this node;
instead, it locates to the current meta-class.
You can then set attributes and create nested elements for the current meta-class.
This feature is optional - it is quite possible to leave the current model object 'this' unchanged but just add peer model objects.
You can put attributes onto the root element.
When 'this' is the root node, the attributes will be set on the current node,
overriding any default or value explicitly set in the previous specification.
This could be useful in some situations. In particular, you can invoke the next pattern via '<this next-pattern="pattern2">'
(as an alternative to calling '$this.setNextPattern("...")').
When 'parent' is the root node, the attributes will also be set on the parent of the current model object.
This is a side-effect of the implementation you should be aware of: it is not a recommended practice.
If you have any included code in the includeSpec.vm, the convention about tab processing will apply to this code too.
In other words, use tabs to lay out patterns so the logic of the Velocity code and generated text is easy to read;
extra tabs will be removed (as long as 'discardLeadingTabs' is true) when the Velocity engine is run.
9.3.1 Setting the Root Model Object - setPatternRootObject()
|
Occasionally, the position where the new object(s) are to be inserted is somewhere other than the parent or the current object.
To specify the actual object to be used as the root for the pattern,
use the 'setPatternRootObject' on the current model object ('$this').
If you do set the pattern root element in this way, you must also use the 'this' style of pattern, not 'parent'.
For example:
$this.setPatternRootObject( $metaObject )
|
This identifies the meta object $metaObject to use as the parent for the XML generated by the pattern.
It is a one-shot: the value goes away after the pattern has been processed.
This is true even if the 'next-pattern' is used: the next pattern by default will use $this as the default root for its generated XML.
For example, to put a class in the first jar or assembly object in the application, you could say
$this.setPatternRootObject( $theApplication.jarList.get(0) )
|
You can specify attributes on the 'this' when using this technique and
the attributes will be set on the model object identified in the setPatternRootObject call.
Note that this feature does not affect the context for evaluating substitutions in Velocity.
In other words, $this is unchanged: $name will refer to the original model object's name, not the name of the pattern root.
it is only the target for the model object changes after the pattern is processed by Velocity that is changed.
9.3.2 Defining a Root Element - addPatternRootElement()
|
There are times when an overriding pattern needs to create additional objects at the same level as the overridden pattern.
For example, say the overridden pattern has
<parent>
<addedObjects...>
</parent>
|
This is adding the addedObjects as peers of the current model object.
Now, this gives the overriding pattern a problem
because the overridden pattern has already defined the root object for the pattern - '<parent>'.
We are bumping up against the issue of the generated XML having only one root node, which is an XML restriction.
This structure makes it impossible for the overriding pattern to insert its own objects.
Of course, it is possible to have the overridden pattern call out to overriding files via '#parse()'
but this supposes that the overridden pattern knows what the overriding pattern may want to do!
The 'addPatternRootElement()' method gives another approach to solving this problem.
'addPatternRootElement()' is available on all meta-classes and works by enclosing all the generated text from the pattern in a new root XML element.
The method takes a parameter which is the tag for the new root element.
Using this approach, the above example would be written as:
<addedObjects...>
$this.addPatternRootElement( "parent" )
|
The advantage of writing it this way is that the overriding pattern can then add its own objects into the structure
just by outputting more top-level XML objects.
These will be created as nested objects underneath the added root node.
Further rules and tips:
- The 'pattern root' value is a 'one-shot' - it is used at the end of the pattern execution
and then reset automatically.
- The use and reset is done at each stage of a multi-stage pattern.
- The 'addPatternRootElement' method can be invoked multiple times in each pattern stage.
This can be useful because the overriding pattern can also call 'addPatternRootElement' and guarantee that there is a root node.
When combined with the #parse("super") feature
(i.e. 'pull in the overridden pattern, if any'), this means that a free-standing pattern can be written as follows:
#parse( "super" )
$this.addPatternRootElement( "parent" )
<addedObjects...>
|
The first line pulls in the overridden pattern, if any, to add some objects and then
adds this pattern's objects.
As long as all patterns in the 'super' chain include each other via #parse("super") and agree on which
type of root is going to be added in addPatternRootElement, then each pattern in the 'super' chain
can be written independently.
- If the pattern root is set to different values within the same pattern,
the engine will report a validation error - it is not possible to override the containing tag.
- If the pattern creates an empty output (a 0 length string, or just whitespace)
no pattern processing is done even if the pattern root has been set.
This makes addPatternRootElement optimally efficient in cases where there may be no output;
if a pattern has '' or '' as an added element, just in case there are added elements,
the XML will have to be parsed and processed even when there is no output.
- 'addPatternRootElement' can be used in conjunction with 'setPatternRootObject'.
In this case, the parameter must be "this" - because the root node XML tag must be 'this' when
'setPatternRootObject' is used.
- When 'setPatternRootObject' is not used, the parameter to 'addPatternRootElement' is typically "parent"
for includeSpec's and extraIncludeSpec's.
This gives both the base pattern and the overriding pattern the opportunity to add both peers and children of the current
model object.
The exception to this rule is modeled components (e.g. jars)
which will only add model objects as children, so "this" makes more sense.
9.3.3 Adding Code and Other Text In Patterns
|
It is possible to add code or other non-XML text as character data into a pattern.
For example, here is a typical getter method expressed as a pattern:
<method name="getInstance"
return-type="String"
>
return instance;
</method>
|
However, the output of patterns is read as XML - and XML processors may remove new-lines in some circumstances.
Therefore, it is highly recommended to
include any implementation code (and any other text where new-lines are significant) in CDATA sections. For example,
<method name="getInstance"
return-type="String"
>
<![CDATA[
// for multi-line method implementations, use CDATA section
return instance;
]]>
</method>
|
If you don't do this,
some lines will be erroneously joined together. This 'gotcha' is particularly bothersome because it occurs very occasionally;
hence the recommendation to get into the habit of using CDATA sections for any multi-line character data.
Copyright (c) 2001-2008 New Technology/enterprise Ltd.
| |