Copyright © 2008
New Technology / enterprise Ltd.
Objective
To understand how the various techniques we've looked at so far, meta-models, patterns, templates, properties and methods can all fit into plug and play levels that make up a stack. By choosing which levels make up the stack, the transform will produce appropriate output.
Introduction to the Stack
For example, we could choose to take out the elements of the last build that were specific to Tomcat and MySql and put them into two new levels.
Then if we developed variations for other technologies, we could then swap the Tomcat level for a Jboss level and the MySql level for an Oracle level.
Of course, something as simple as that, we could just add a few #if statements and it wouldn't be too difficult. But the idea goes beyond that.
By separating the persistence layer we could swap out the lightweight jdbc services and swap in Spring/Hibernate or EJB2.
If we select a stack: Oracle, EJB2, JBoss, Struts, we'd get a very different build from MySql, Hibernate/Spring, Tomcat, Spring MVC. Often specific renderings for a technology have common elements with others fulfilling the same role, so we might figure in a general persistence layer and use it to abstract out the elements common in EJB2, Hibernate and the lightweight JDBC services.
We can build and choose between different skins in the same way. Have the application generated in red and black or blue and gold. We can create a look for a company or a department and apply it throughout for all applications we generate for that company, but swap in a different look and feel for a different company.
We can even select for a target language. So our stack might end up being: department skin, company, jsf, mvc, tomcat6, spring, services, hibernate, mysql, persistence, java, base.
As we shall see, we can override and inherit almost any factor at almost any level, making this a very powerful feature.
Specifying the Stack
Up until now we've been using a single stack layer for the examples, the control directory. This contains the meta-model classes (in the components.jar), the model and system properties, and the template sub-directories. The control directory is called templateDir in the ant build file, and as you may remember from the first part of the tutorial, templateDir is one of the obligatory properties that need to be passed to the JeeWiz engine. Now templateDir can also support a comma separated list of directories, so we could create an area for our stack, calling it resources, and then have a number of levels under it called resources/hibernate/control, resources/struts/control and so on. Then by specifying the stack as a list of all the control directories you want to include in your build, you can vary the output to your requirements.
The second method of specifying the stack is by chaining references in the model.properties file. It's possible to specify the parent of a stack level (by setting the parent property) then if the templateDir only points to the most specific template directory, the engine will check to see if a parent level has been specified, and if so it will use that. If that also has a parent level defined in its model.properties file the engine will keep going until it find a level which doesn't have a parent defined. It isn't possible to mix the two techniques. If more that one templateDir is specified in the build, the engine will not look for parent levels.
Meta-Models
The package of a meta-class is defined in the model.properties, and when there are multiple stack levels, there can be multiple model.properties files and multiple meta-class specification directories. JeeWiz uses the model.properties of the relevant stack level to define the package of a metaclass at that level.
Meta-model classes are defined in java and like any other java class they can extend other classes. As I've already mentioned they eventually extend the BaseBuildComponent which holds the functionality for anonymous objects (and which ExtraBuildComponent also extends). But is possible to define a meta-model class as extending another defined meta-model class.
For example, you might decide to split the functionality of attribute group, so the bits that refer to persistence go in one meta-model class (say entity) and the bits that specify how it's shown on the screen go in another (say ui-view). Now these two specifications may have a lot in common. They both hold attributes of particular types, and they both might want to order the attributes or hide them. (Hiding a persistence attribute is sometimes done when it has a fixed value relative to a column in a table, such as status. That lets a logical entity work with a subset of table rows without the programmer having to keep writing "WHERE status = 'live'" or some such.) So we might want to just define common elements once and call it say base-attribute-group, and then define entity and ui-view to inherit from that.
Where the meta-model extends a uniquely named class somewhere in the stack it is possible to use the format extends="base-attribute-group". To find the class, the engine looks at the most specific level in the stack, say the departmental skin and works on to the most general level. Once it finds a meta-model class with the right name it assumes that's the right class and uses the appropriate model.properties package reference to create the java extends clause.
Sometimes that's not what you want. It's often the case that a particular class is defined at multiple levels so that the same model specification will have different effects depending on whether the higher level layer overrode the behaviour. So it may be necessary to specify in the extends which level you want to inherit from and this can be done using a model name specified in the model.properties to label a level and the syntax extends="modname:class".
So the metaclasses support full inheritance and polymorphism with respect to the specification model. A reference to say entity, could mean the metaclass entity in Hibernate in EJB2 or in a general persistence entity. It all depends on how the stack is defined.
Templates and Patterns
Output files are produced using an ant buildfile in the template sub-directory for a particular tree object type. So if the object type is entity and there was only one level, the engine would look for a build.xml in the /entity subdirectory. If there was no build.xml nothing would be output for the object. If there's a stack, the engine looks down the stack (starting at the most specific level as before) for the first build.xml in can find. Only if there is no build.xml at any level will it do nothing. So by tweaking higher level build.xml files, it's possible to alter output behaviour set up at the lower general level.
The same principle applies to finding the template script files referred to in the build file. Just because a template file is referred to at one level doesn't mean that the template file itself is to be found at the same level. Once again the engine works it's way down the stack looking for the template file and will by default use the most specific version of the template it can find. This behaviour can be overridden by specifying the full path of the template file, but it isn't usually necessary to do that.
The three pattern phase tree-walks look for the files preIncludeSpec.vm, extraIncludeSpec.vm and includeSpec.vm with the same traversal of the stack. So the most specific pattern files are used like the most specific template files.
If substantial parts of a pattern or template file are the same, and you want override only a section of it, you can use the #parse directive to include a second file's content in the first one. The #parse directive also finds files by checking the down the stack. By splitting the file into two parts, generating a set of common code in one and a set of variant code in the other, only the variant file (typically the parsed one) needs to be overridden.
As an example we can use a very simple stack: we start with the case that we defined in tutorial four - a level called base, say and we add a more specific level for jboss, that overrides the default Tomcat code at the base. We can put the servicesBean.vm at the base level of attribute-group and parse a variant file for handling database connections, say dbCon.vm. There would be two dbCon.vm files, one at the base level and one at the jboss level. If the jboss level was included in the stack, the #parse("dbCon") would find and use the jboss level dbCon.vm before coming across the base level variant.
With #parse we don't just need to override, we can also include. In the case of the dbCon code, there might not be any content for Tomcat, but if we don't include a dummy file at the same level we will make the base level dependant on the jboss level because #parse fails if it can't find the file. So a placeholder script files, devoid of content other than comment, can be used to let us generate using just the base level.
It is possible to avoid placeholders in some cases by using #parse("super"). This is a special syntax that allows a file on a level to call the first file of the same name (if any) further down the stack. If we put also a servicesBean.vm at the jboss level, we can parse("super") the base level code from the jboss level and add extra code before or after by also using parse("dbCon"). We could even generate the extra code into a variable using #divert and then call it in the middle of the file, but it might be simpler to use a property or a method.
Properties and Methods
These also are found by checking through the stack levels, so if we want to output a piece of rendering that differs depending on the level, we can call a method and override the method at the more specific level. Unlike with pattern, template and #parse files, whole component.properties files or component.methods files are not overridden. Only the individual properties and methods are overridden and otherwise the methods and properties at the base level are still accessible. This means that component and system properties and methods can add to each other up the stack.
Properties from builds at the various levels also will add into the mix, and this can become confusing and hard to track. That is especially the case if jwcall is used, which automatically picks up the ant properties and feeds them into the JeeWiz environment. To help control this you can use jwrun instead of jwcall. This is essentially identical, but you have to pass any ant variable you want to be used explicitly. The syntax is to use a variable called passN (where N represents a number) with a value equal to a name value pair prefaced with a -D. For example
<property name="pass3" value="-DtemplateDir=${baseDir}/control" />
would be the same in jwrun as
<property name="templateDir" location="${basedir}/control" />
would be in a buildfile using jwcall.
Inheritance and Template Hierarchies
Sometimes it can seem that JeeWiz has too many hierarchies, but there are really only four. The first that we've been using all along is the object tree hierarchy, where $this and $parent are used to place context and work up and down the tree. We've also met the idea of meta-model inheritance, where meta-classes inherit from each other and so methods defined in the meta-class can override and supplement each other. This is defined when the meta-classes are built, before the main generation starts. The template hierarchy performs a similar function to the inheritance hierarchy, but at generate time for patterns, templates and the like. It is used to find files, methods and properties. There is one final hierarchy for controls which we'll meet in the next part of the tutorial, but once you understand the template hierarchy you won't have any problems with that.
If an entity meta-class extended an attribute-group class, we'd like it if templates in the attribute-group template directory also applied to entities. Of course if the entity has a template or a pattern file of the same name, it should take precedence, but if it doesn't those in the attribute-group directory should be used instead. After all, entity is a type of attribute-group.
We can do this using another type of properties file called template.properties. Setting the property goto to be the name of the inheritance type will cause the engine to behave in exactly the manner we want. In this case we would put a template.properties file at the base level of entity and set attribute-group. So each type of object not only can access its own template stack for patterns, templates, properties and methods, in can go on to search other types' template stacks too.
This can diverge from the inheritance hierarchy for several reasons, most obviously because there might be no meta-class! The template hierarchy, like the meta-model hierarchy is capable of inheritance at multiple stack levels and we might not want to use exactly the same stack levels.
For example we might have a stack: jsf/jboss/ejb2/persistence/mysql/database. We might define the metaclass entity at the persistence level and extend it with extra functionality at the ejb2 layer. But the entity meta-class at the ejb2 layer might inherit from an attribute-group meta-class at the jsf level.
Now when we go through the template build tree walk, we might find we want to create a configuration file that requires knowledge that we are using the MySql database. So we want to make sure that files in the entity template subdirectory at the mysql level are reached, so we put the template.properties file with the goto=attribute-group at the mysql. That way the engine will go down to the mysql level before continuing its search at the top level of the attribute-group stack. In this case the meta-model inheritance hierarchy skips from the ejb2 level of the entity stack to the jsf level of the attribute stack, missing out any entity meta-classes defined at the persistence layer or below. Whereas the template stack finds patterns and templates at the perisistence and mysql levels, only missing out templates in the database level of the entity stack.
If the attribute stack had a goto defined, the template hierarchy would move on to the next stack. If there is no goto anywhere in a particular template stack, the template hierarchy stops at that stack.
Occasionally we want a slightly different behaviour to goto. We don't want to go to a stack so much as include a different stack somewhere in the middle and the syntax to achieve that is include=objectType, where objectType represents the stack to include. In this case after the engine has searched down the stack as far as the level with the template.properties file in it, if it can't find the file it's looking for it searches the new stack and its hierarchy. If that hierarchy is completed, without finding the file, the search resumes in the original stack at the next level following the one with the include. Normally that would be the level below in the same template stack, but it's possible to have an include and a goto in the same properties file, in which case the search will resume at the top of the goto stack.
Component methods follow the template hierarchy except for one subtle distinction involving overriding. If a component.method called $myMethod() in the entity stack wants to access functionality it has overridden it can use $super.myMethod(). However this will not look for a method of the same name defined further down the entity stack, but will look in the next stack in the template hierarchy - attribute-group. If there were no goto/include in the entity stack, an exception would be thrown. That is because the stack is flattened for performance reasons by the time the methods come to be accessed, so only one method per type can be available to call.
Template diversions do not apply to the way system properties and methods are found.
An Example of A Simple Stack
I've converted the example from the last part of the tutorial to work with JBoss as well as Tomcat and this can be found at %jwhome%\examples\tutorial\05webapp. I've left the Tomcat build as a base level and put the jboss level as a variation. If we build with just the base level, the output will be tailored to Tomcat and with both levels in the stack the output will be for JBoss. The jboss level has a model.properties with a parent pointing to the base.
The first thing we have to do is change the structure of the project to account for the multiple levels. Instead of a single control template directory, we are going to have two areas called base and jboss and I've put them both at a lower level, under a directory called resources. Each has a control template directory.
In this case there is no need to override the meta-models at the jboss level, but I've still moved the metamodel section under base, where it now belongs. As before the specification in the meta-specification directory is used to generate the java source in a src directory, where it is compiled into a classes directory, all under the base directory, before creating the components.jar in the resources/base/control directory.
There are several small differences to take into account between building for Tomcat and JBoss , primarily in the database access and JNDI configuration.
The first is that the JBoss configuration we are using needs a jboss-web.xml file to add another resources-ref element for the data source. For this I've added a template under the site area in the jboss level called jbossWebXml.vm and a different version of the build.xml that includes a jwVelocity task to build the jboss-web.xml file, but that is otherwise the same as the one at the base level.
Second the resource-ref in the web.xml differs slightly with a prefix of jdbc:/ being put in front of the data source reference name. This is and is implemented as a simple property reference $!{dsPrefix} only set for JBoss (as a system.property, but it could have been a component.property).
The third is that the context lookup in the services bean is a little different, with the Tomcat code creating an explicit environment context, whereas JBoss code doesn't. This code fragment has been moved to a component method called getContextLookup(), and when that is called from servicesBean.vm for Tomcat, it will use the method written in the base level, whereas a JBoss build will find the method of the same name in the jboss layer. So there is a new component method files added in the attribute-group subdirectory at each level.
Finally the deployment directory within the application server varies, Tomcat expects deployments to go under /webapps, and JBoss can accept them under /server/default/deploy. I decided to expand the use of the generated application.properties file and renamed it deployment.properties. This is generated from a depProperties.vm template in base and overridden in jboss.
I find that hot deployment on JBoss works better if the application is archived as a war file, so I've changed the project build deploy target to do that.
Have a look at the structure in the example, particularly at the overriding elements in resources/jboss/control. While the tutorial is under construction, you may have to download it separately from <a href = "./Downloads/05webapp.zip">here.</a>
Structuring Levels
Of course there's nothing really to choose between JBoss and Tomcat, so why make Tomcat the base layer and JBoss the variant layer? There's no good reason other than the historical chance that meant I developed a Tomcat build first. Sometimes it's a good idea to build an abstract base level and then make all the alternatives variant levels.
So we could develop an abstract base level called base and then put the variations in two peer levels sitting on the top. This can be signalled in the base model.properties by setting abstract=true.
Another way we could look to split the stack is by separating out the front end aspects from the persistence layer of the generated application. That would allow us to change the jsp GUI layer for an MVC framework such as Java Server faces or Struts. It would also let us swap out the back end lightweight JDBC services for a scalable persistence tier such as Hibernate.
Remember the diagram in the introduction with the different layers? We could look to that for inspiration as to how to structure the application to allow us to plug and play a little more later.
This is combining the structure of the output and the architectural elements to form the generational layering.
It would be definitely be overkill to have an abstract and concrete version of all layers, but ultimately that's the route to highest flexibility, so there could be an abstract application server layer and all common aspects of using the application servers would go in that. An abstract GUI layer or persistence layer would be difficult until we started to develop other GUI or persistence frameworks. Only then can we abstract out what's common.
The abstract layers could be very thin if there is not a lot in common. Or it might be the concrete layers if there's little variation. We don't need to worry too much about generation efficiency as a JeeWiz build doesn't take very long to search the levels - it collapses the layers when necessary.
The JeeWiz Resource Area
The structure of the simple example mimics the way that the standard generation levels are delivered in JeeWiz. Under the main JeeWiz home directory you'll find the resources directory with about thirty or so levels, which can be put together to make different builds. Exactly how to do this can be found in the documentation.
We've already used one of these standard levels to build the meta-model structure. The build metamodel target uses the resources/JeeWiz/control area as its initial templateDir. That calls a separate generation task, but there's nothing to stop you creating your own levels in your own area and chaining them to the standard ones in a single build. That let's you vary a standard build without necessarily altering the standard code.
There are other considerations that need to be made when combining stacks. Are the runtime method calls that bind say the GUI layer to a mid-tier or persistence layer common? Are the specifications objects and their meanings the same, and is it possible to create a common inheritance hierarchy? Although it's possible to crowbar levels to make them plug and play, and even rewrite levels to get the required commonality, it's a lot simpler if thought goes in to the initial design.
Chaining Meta-Model Builds
This can be a little complicated but if you plan on doing one of the suggested exercises, you will probably be splitting the meta-model definitions between levels and you'll need to get into some of the nitty-gritty.
Meta-models are built into component.jar files that reside in their respective template directories. The build uses a call to jwrun (or jwcall) with a templateDir of resources/JeeWiz/control. For a single meta-model build that works just fine. For several independent levels, we can call an identical build multiple times, once for each of the levels.
Let's say we wanted to split the lightweight services from the base jsp as suggested in the Structuring Levels section. We'll call the lower level services and the jsp level uibase. It's worth renaming it from base as it is actually a higher level than services, so calling it base would be misleading.
The meta-model specification for the JeeWiz/control build is called assembly.xml and sits in the meta-specification directory. At the moment we have a build called from the project directory using the ant metamodel target, but we are going to need two builds, one for uibase and one for services. It will make life easier if we split the two builds and put them down in the relevant directories. Let's look at base first, as we already have some of the structure there.
First we'll rename the directory from base to uibase. The assembly.xml meta-model name should be changed, and possibly the package. Next we'll create a build.xml file in the uibase directory with a single target called metamodel, which we are going to use to build the uibase/control/component.jar. This calls the JeeWiz build in exactly the same way as the metamodel target at the project level did before. In fact the target is identical, apart from repointing the mmDir metamodel directory, which is now just the current directory (".").
This will build, but if we changed the package in assembly.xml, we'll need to change the package in the model.properties (in the control directory) too, so the generate time references will be correct. While we're in the model.properties we can change the parent to point to ../../services/control. The parent specification needs to point to the parent model.properties relative to the directory containing the model.properties we are using.
Next we need to sort out the services area. Under the service directory, we need to create a template control directory and a meta-specification (or specification) directory for the meta-class definitions. We nee to create a new meta-model specification for the build, which will be an assembly.xml file in the meta-specification directory, and a model.properties in the template directory (with matching packages). We can now create the build.xml in the services directory, probably just by copying the one in uibase - they are identical.
Finally we need to change the project level metamodel target to call the two builds, using two ant tasks in the target like
<ant antfile="${basedir}/resources/services/build.xml"
target="metamodel"
dir="${basedir}/resources/services/"
inheritAll="false"
/>
The problems with this occurs when metamodel levels depend on each other. Of course we'd need to make sure that the services build was called before the uibase build, but that isn't enough.
If a meta-class at one level inherits from a metaclass at another level, there has to be some way for the build to know who its parent is. Unfortunately it's not enough to specify the parent in the model.properties as this is what's used at generate time. At meta-model build time we need to add a couple more properties and do a little more restructuring. Don't worry, not much.
The uibase metamodel's parent is the services directory. When compiling and jarring up the uibase metaclasses, we have to add the services component.jar to the classpath. This is done by specifying the addModel property in the uibase build. We can put that as a property in the build.xml target or we could add it to a properties file, where it needs to point to the template directory holding the parent jar.
addModel=../services/control
The standard is to add it to a build properties file called build.jwp.
The second thing we need to do is create a context for the meta-model object defined in the uibase meta-specification assembly.xml of its parent. This is done using another property to specify a properties file holding information about the parent build.
addSpec=parent:../services/build.jwp
The build.jwp file in the services area must contain the assemblyDir and templateDir of the services build. At the moment these are defined in the build.xml target as properties, but we need to move them up to a properties file so they can be accessed both from the services build and the uibase build.
That's it. Of course, if there's multiple chaining going on, all the build.jwp files are likely to end up holding the addSpec, addModel, assemblyDir and templateDir definitions so all levels can be chained to and can chain onward.
Summary
- Major variations in the architecture and target platform can be achieved by selecting options to form a stack of levels. Each level contains its own template directory (usually named control) and can contain its own meta-model and meta-classes.
- Types of object in the object tree can be mirrored by template subdirectories where the appropriate patterns, templates, component methods and properties can be found. The template directory for each level in the stack may contain subdirectories for any of the object types, but don't have to.
- Lightweight levels can be used to add minor variations to a working stack you don't want to touch, for example to create a new look (skinning).
- When trying to find an element in the stack, JeeWiz searches from the top, most specific level, to the base, most general level until it finds the element.
- Meta-classes can inherit from objects in other levels as well as in their own level, forming an inheritance hierarchy.
- Templates, patterns methods and properties can effectively inherit by using a goto setting in a template.properties file. An element search will search down the stack as before but if it encounters a goto before it has found the element it will continue an element search in a different sub-directory, once again starting from the most specific level of the stack and working downward.
- Template.properties also support an include setting which makes the element search include another sub-directory before returning to where it found the include. It is possible to put include and goto in the same properties file, where the include will be executed first.
- The traversal of the stack levels with goto and include setting form the template hierarchy for an object type.
- #parse("super") includes the first file of the same name from further down the stack in a pattern or template file.
- $super.method calls code from the next method of the same name found in a different sub-directory stack found in the template hierarchy.
- The JeeWiz standard levels are presented as multiple levels under the resource directory.
- It's possible to define a stack by chaining the levels using the model.properties parent setting, then calling the top (most specific) level using the templateDir property in the ant call. It's also possible to state the stack explicitly by passing in a comma separated list of template directories as the $templateDir. In the latter case, the model.property parent setting will be ignored.
- Chaining meta-models down the stack needs the specification of two new properties addModel and addSpec to make the parent met-model build available to a higher level.
Exercises
- The simple example will be found in %jwhome%\examples\tutorial\05webapp. If you have an older release of JeeWiz without the tutorial, you may have to download it separately from <a href = "./Downloads/05webapp.zip">here.</a> Split it into the 5 level structure shown in the structuring levels section. Some of these may be very thin levels, but don't worry about that. Make sure that the model.properties are correctly defined at each level: the name, the package, the parent level, etc. Think about which level each metamodel class needs to be in, or whether it need to be split and inheritance used. If you've used inheritance, that's a clue that you might need to use template goto as well.
- The standard stack comes with some tests for various levels. Look at the last of the hibernate tests (in jeewiz/tests/hibernate/0040) at the build file. Look at the templateDir definition. This uses the comma separated list mechanism, and picks up an ant variable for the database, which has to be passed in. Note the use of the jwrun syntax and the difference in the way properties are passed in. Change your build.xml to use jwrun syntax and pass in the stack definition as a templateDir list.
- Look at the test's specification jeewiz/tests/hibernate/0040/specification/assembly.xml. Note that the specification uses entity in a similar manner to the attribute group. Follow the inheritance chain (check out the templateDir definition again - use MySQL4_1 as the database) and find where the Application meta-class is defined. Note that the standard build puts meta-class definitions in a directory called specification rather than metaspecification. Which levels have no meta-classes?
What type is ejbJar? If you get stuck on this question check out the bizobject session.properties. Remember that for a list with no defined type, the object type defaults to the list name. If you are still having problems joining the dots (I picked a non-trivial case on purpose) look up what generateAllList does in the Architect's Guide, and see what the difference is between the application.xml and the Application.java files.
Find the metaclass definition of entity in the inheritance hierarchy. What does it extend? Write down its inheritance chain, including the levels, back to BaseBuildComponent.
One of the lists defined in the jar-base.xml specification is used to create an entityList (using generateAllList). Which?
- If you wanted to put the jsp GUI part of your stack onto the hibernate stack, to get transactionality, name three problems would you encounter.
