JavaBean
template from
Root.xpt
This example shows the usage of openArchitectureWare 4 with integration of an UML tool that is not based on EMF UML2. In openArchitectureWare 4, we call this "Classic" style, as the underlying metamodel has to be the "Classic" UML metamodel that was introduced by oAW 3. For the example, Magic Draw 11.5 Community Edition is used, but the example can easily be adapted to any supported UML tool. It is strongly recommended to work through this tutorial with MagicDraw in order to minimize environmental problems!
Note that the usage of oAW Classic is not recommended when you have an UML2 tool that is capable of exporting to EMF UML2 format. It is recommended to use the UML2 adapter whenever possible. However, there are still tools in usage that are not based on and do not export to EMF UML2. In this case the tool adapters of oAW Classic are the way to go.
Make sure that you installed the openArchitectureWare feature properly in your Eclipse environment.
openArchitectureWare depends on EMF, so check that you have installed it. If you need further information on oAW installation please look at http://www.openarchitectureware.org/staticpages/index.php/documentation.
Instead of working through this tutorial, you can also install the
packaged example by downloading the
oaw-samples-classic-uml-4.x.x
package. It contains
one Eclipse project, which you have to import into your workspace. To make
the projects compile and run, you may have to define to use the
oAW-Classic Metamodel in the project
properties:
The purpose of this tutorial is to demonstrate the very simplest way to use oAW4 in combination with an (non EMF UML2 capable) UML tool to create code from a model that contains some classes. The project is really simple, so it is the right place to start when you are new to openArchitectureWare 4 and want to use UML tools like MagicDraw, Poseidon, Rational Rose etc.
In this example, we want to generate code from this model:
As a result, we want to create some JavaBean style classes which have properties with getter/setter methods.
Create a new Java Project called
oaw4.demo.classic.uml
and select the option to create
seperate source and output folders.
Afterwards, select from the context menu
→ , since we want to define our dependencies via Eclipse Plug-In dependencies.Alternatively, you could create a Plug-In project instead of these both steps.
In the new project, there is now a
META-INF/MANIFEST.MF
. Open it, go to the Dependencies
page and add the following dependencies:
org.openarchitectureware.classic.umlMetamodel
: The classic UML metamodel classes
org.openarchitectureware.classic.core
: Framework classes for oAW classic
org.openarchitectureware.classic.workflow
: oAW classic workflow components and cartridges
org.openarchitectureware.classic.xmiInstantiator
: Parser component for UML tools
org.openarchitectureware.classic.libraries
: Required 3rd party libraries
org.openarchitectureware.core.xpand2
: The Xpand template engine
org.openarchitectureware.core.expressions
: oAW language Check for defining constraints
Create two folders model
and
templates
in the project root (not in
src
!) and add this folders as classpath folders in
the project properties dialog, Libraries tab. By doing this, the model and
the templates can be found in the classpath of the project without placing
the files in the source folder.
This section explains how to create the model from scratch using MagicDraw 10/11. If you are using another UML tool, this is a guideline to create the model, but the tool can differ in some details. You can skip this section if you have MagicDraw, downloaded the sample project and use the model from the sample project.
Start your UML Tool and create the model from the screenshot above.
You have to create the Stereotypes Entity
and
Key
.
To define the model above follow these steps:
Entity
with base class
Class
.
Key
with base class
Property
.
DAO
with base class
Class
.
oaw4 / demo / classic / uml / entity
entity
Author
Entity
id
of type
String
. Please select for String the
datatype from your model, not from UML Standard
Profile! Assign the stereotype
Key
for this attribute.name
of
type String
.Book
with stereotype
Entity
. The
Key
attribute is
isbn
of type
String
. The second attribute is the
title
.
Author
is named
author
, is navigable and the multiplicity
is 1..*.writtenBook
with multiplicity
0..* and is navigable.Copy
of stereotype
Entity
Key
attribute is
inventoryNumber
of type
String
.location
of type
String
.Copy
to
Book
Book
is named book
, is navigable, with
multplicity 1.Copy
is
unnamed, not navigable and multiplicity
0..*Library
of stereotype
Entity
Key
attribute is
id
of type
String
.String
attribute name
Library
to
Copy
Library
is named
owner
, is navigable, the multiplicity is
1.Book
is named ownedBook
, is navigable and the
multiplicity is 1..*.Save your model packed format in the model folder of your project
and give the file the name
AuthorBookExampleMD11.mdzip
. The tool adapter will
automatically recognize that it is zipped and read the appropriate ZIP
entry.
Create a subfolder model/md11
. From the
profiles
directory of your MagicDraw installation
copy the UML_Standard_Profile.xml
to there.
Models are instances of a Metamodel. In order to get openArchitectureWare to do something useful it needs to know the used metamodel. Using oAW "Classic" the metamodel is implemented by metaclasses. In UML, they are represented in the model by stereotypes.
The recently defined model already uses the stereotypes
DAO
, Entity
and
Key
. Entities are some kind of business objects,
which have some attributes. They are represented in UML as classes.
Exactly one attribute is a special one: a Key
attribute. DAOs
are classes which manage
Entities
. We want to express this relationship by
using a dependency from DAO
to
Entity
in our model.
In UML, this metamodel looks like this:
We have to provide the metaclasses that make up our DSL. The base
metaclasses Class
and
Attribute
are provided by openArchitectureWare
within the package org.openarchitectureware.meta.uml
and its subpackages.
Create a package oaw4.demo.classic.uml.meta
.
This package will contain our metaclasses which realize the UML profile
we want to use. As you can see from the model above we use the
Stereotypes Entity
, Key
and DAO
. For these Stereotypes we need
metaclasses.
Metaclasses are derived from UML metaclasses. oAW4 provides
implementation classes for UML metaclasses. For example, an
Entity
is represented by classes, so, the right
metaclass to extend is Class
, while
Key
s are a specialization from
Attribute
.
In the simplest case, you will only have to create the classes
Entity
, Key
and
DAO
and derive them from the base metaclasses
Class
and Attribute
defined in package
org.openarchitecture.meta.uml.classifier
:
package oaw4.demo.classic.uml.meta; public class Entity extends org.openarchitectureware.meta.uml.classifier.Class { // nothing to do in the simplest case }
package oaw4.demo.classic.uml.meta; public class Key extends org.openarchitectureware.meta.uml.classifier.Attribute { }
package oaw4.demo.classic.uml.meta; public class DAO extends org.openarchitectureware.meta.uml.classifier.Class { }
By now, the generator will not know that the stereotype
<<Entity>>
has to be mapped to the
metaclass oaw4.demo.classic.uml.meta.Entity
. By
default these elements are mapped to their UML base classes, in case of
Entity
this is
Class
.
To map stereotypes to metaclasses a xml file has to be created, called the metamapping file.
Create the file metamappings.xml
in your
source folder:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE MetaMap SYSTEM "http://www.openarchitectureware.org/dtds/metamap.dtd"> <MetaMap> <Mapping> <Map>Entity</Map> <To>oaw4.demo.classic.uml.meta.Entity</To> </Mapping> <Mapping> <Map>DAO</Map> <To>oaw4.demo.classic.uml.meta.DAO</To> </Mapping> <Mapping> <Map>Key</Map> <To>oaw4.demo.classic.uml.meta.Key</To> </Mapping> </MetaMap>
Later when running the generator you would like to see some output
messages. Therefore you have to define a
log4j.properties
. Create this file in the source
folder:
# Set root logger level to INFO and its only appender to A1. log4j.rootLogger=INFO, A1 # A1 is set to be a ConsoleAppender. log4j.appender.A1=org.apache.log4j.ConsoleAppender # A1 uses PatternLayout. log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %m%n
The next step is to create a workflow script. The workflow has to accomplish the following tasks:
For all these tasks, pre-defined workflow-scripts and components can be included in the workflow. The following workflow script does all these tasks and should fit for the first steps. Later on, you may need to customize the workflow script to integrate further components like model modifiers. Therefore, the script itself is not bundled as a cartridge.
<?xml version="1.0" encoding="UTF-8"?> <workflow> <property file="workflow.properties"/> <cartridge file="org/openarchitectureware/workflow/oawclassic/classicstart.oaw"> <metaEnvironmentSlot value="me"/> <instantiatorEnvironmentSlot value="ie"/> </cartridge> <component class="org.openarchitectureware.core.frontends.xmi.workflow.XMIInstantiator"> <instantiatorEnvironmentSlot value="ie"/> <modelFile value="${model.xmi}"/> <xmlMapFile value="${toolMappingFile}"/> <metaMapFile value="${metaMapFile}"/> <toolAdapterClassname value="${toolAdapterClassname}"/> <moduleFile value="${moduleFile}"/> <outputSlot value="model"/> </component> <cartridge file="org/openarchitectureware/workflow/oawclassic/classicinit.oaw"> <metaEnvironmentSlot value="me"/> </cartridge> <component id="dirCleaner" class="org.openarchitectureware.workflow.common.DirectoryCleaner"> <directories value="${srcGenPath}"/> </component> <component id="generator" class="org.openarchitectureware.xpand2.Generator"> <metaModel class="org.openarchitectureware.type.impl.java.JavaMetaModel"> <typeStrategy class="org.openarchitectureware.type.impl.oawclassic.OAWClassicStrategy" convertPropertiesToLowerCase="false"/> </metaModel> <expand value="Root::Root FOREACH me.getElements('Model')"/> <genPath value="${srcGenPath}/"/> <srcPath value="${srcGenPath}/"/> <beautifier class="org.openarchitectureware.xpand2.output.JavaBeautifier"/> <beautifier class="org.openarchitectureware.xpand2.output.XmlBeautifier"/> <fileEncoding value="ISO-8859-1"/> </component> <cartridge file="org/openarchitectureware/workflow/oawclassic/classicfinish.oaw"> <instantiatorEnvironmentSlot value="ie"/> <dumpfile value="${dumpfile}"/> </cartridge> </workflow>
The workflow includes a properties file, in which the concrete
configuration is stored. Create the file
workflow.properties
like this:
# Note: all paths must be found in the classpath! # the metamappings file metaMapFile = metamappings.xml # model.xmi: name of the XMI export # toolMappingFile: tool mapping file to use # toolAdapterClassname: tool adapter implementation # moduleFile: profile files # MagicDraw 10 model.xmi = AuthorBookExampleMD10.mdzip toolMappingFile = magicdraw_xmi21_all.xml toolAdapterClassname = org.openarchitectureware.core.frontends.xmi.toolsupport.uml.magicdraw.MagicDrawAdapter21 moduleFile = magicdraw/md10/UML_Standard_Profile.xml #model.xmi = AuthorBookExampleMD11.mdzip #toolMappingFile = magicdraw_xmi21_all.xml #toolAdapterClassname = org.openarchitectureware.core.frontends.xmi.toolsupport.uml.magicdraw.MagicDrawAdapter21 #moduleFile = magicdraw/md11/UML_Standard_Profile.xml # path to create the generated output to srcGenPath = src-gen # path where the dump file is created dumpfile = bin/dump
This configuration file is designed for use with MagicDraw.
Other UML tools can be easily configured by changing the properties
model.xmi
,
toolAdapterClassname
,
toolMappingFile
and
moduleFile
. The property
moduleFile
specifies additional modules to load
and merge, which is currently only evaluated by the MagicDraw
adapter.
The existing tool adapter classes and mapping files can be found beneath the package
org/openarchitectureware/core/frontends/xmi/toolsupport/uml/<TOOL>
For example, to set up this project for Poseidon 4/5 the appropriate settings are:
toolAdapterClassname = org.openarchitectureware.core.frontends.xmi.toolsupport.uml.poseidon.PoseidonAdapter toolMappingFile = poseidon40_xmi12_all.xml
In the workflow the generator has been configured to start with the this definition:
<expand value="Root::Root FOREACH me.getElements('Model')"/>
This means that the generator will look for a template file
Root.xpt
in the classpath where a definition named
Root
is found for all elements that are selected
by the expression me.getElements('Model')
. In
case of UML models there exists solely one element of type
Model
that will be selected.
Now, create the file Root.xpt in the templates folder. Remember: We have configured our Eclipse project that the folder templates is a classpath folder. You will see that the new file is recognized as an oAW template file, as it has a template file icon:
For the beginning the template will be simple:
«IMPORT org::openarchitectureware::core::meta::core» «DEFINE Root FOR Model» «ENDDEFINE»
This template contains only an empty definition for Model
elements. The namespace of the Model
metaclass
must be known for the generator, so we import the corresponding package.
Otherwise, we would have to fully qualify Model
.
As you could expect, this template does not really do anything
yet.
At this time, your project structure should look like this:
We have not defined anything useful in our template yet. However, we
now execute the workflow to prove that everything is right configured.
Execute the workflow by selecting workflow.oaw
and
select Run As -> oAW Workflow from the context
menu. The output should be like this:
0 INFO ---------------------------------------------------------------------------------- 0 INFO openArchitectureWare v4.3 -- (c) 2005, 2008 openarchitectureware.org and contributors 0 INFO ---------------------------------------------------------------------------------- 0 INFO running workflow: C:/dev/ide/workspace/oaw-v4-projects/oaw4.demo.classic.uml/src/workflow.oaw 0 INFO 540 INFO Starting: workflow org/openarchitectureware/workflow/oawclassic/classicstart.oaw 540 INFO Starting: org.openarchitectureware.workflow.oawclassic.ClassicOAWSetup 561 INFO classic oAW environment is set up; instantiator environment in: ie, meta environment in me 561 INFO Starting: org.openarchitectureware.core.frontends.xmi.workflow.XMIInstantiator 561 INFO Loading XMI from: AuthorBookExampleMD10.mdzip using map: magicdraw_xmi21_all.xml and metamap: metamappings.xml 571 INFO Starting mapping instantiator ... 571 INFO 581 INFO Parsing metamap metamappings.xml ... 721 INFO Parsing of metamap took 0.14s 721 INFO 1692 INFO Parsing design AuthorBookExampleMD10.mdzip ... 1702 INFO Loading model... 2163 INFO Initializing XMI support for XMI version 2.1 2163 INFO Scanning for referenced modules... 2183 INFO Found reference to module 'UML_Standard_Profile.xml' 2193 INFO Model loaded in 0.491s 2193 INFO 2193 INFO Loading modules... 2984 INFO Scanning for referenced modules... 3064 INFO Modules loaded in 0.871s 3064 INFO 3074 INFO Merging... 3114 INFO Modules merged in 0.04s 3114 INFO 3234 INFO Parsed design in 1.542s 3234 INFO 3234 INFO Loading extensions... 3304 INFO Extensions loaded in 0.07s 3304 INFO 3304 INFO Loading design... 3405 INFO Design loaded in 0.101s 3405 INFO 3405 INFO Applying tool specific design modifications... 3415 INFO Design modified in 0.01s 3415 INFO 3415 INFO Instantiating metamodel... 3525 INFO Metamodel instantiated in 0.1s 3525 INFO 3525 INFO Instantiated design in 2.954s 3525 INFO 3525 INFO MetaModel Summary 3525 INFO ----------------- 3525 INFO 3525 INFO 3x Association (org.openarchitectureware.meta.uml.classifier.Association) 3525 INFO 6x AssociationEnd (org.openarchitectureware.meta.uml.classifier.AssociationEnd) 3525 INFO 4x Attribute (org.openarchitectureware.meta.uml.classifier.Attribute) 3525 INFO 1x DAO (oaw4.demo.classic.uml.meta.DAO) 3525 INFO 4x Entity (oaw4.demo.classic.uml.meta.Entity) 3525 INFO 4x Key (oaw4.demo.classic.uml.meta.Key) 3525 INFO 1x Model (org.openarchitectureware.core.meta.core.Model) 3525 INFO 7x Package (org.openarchitectureware.meta.uml.classifier.Package) 3525 INFO 12x PrimitiveType (org.openarchitectureware.meta.uml.classifier.PrimitiveType) 3525 INFO 3525 INFO Starting: workflow org/openarchitectureware/workflow/oawclassic/classicinit.oaw 3525 INFO Starting: org.openarchitectureware.workflow.oawclassic.ModelInitializer 3525 INFO initializing model elements (calling initializeModelElements) 3535 INFO Starting: org.openarchitectureware.workflow.oawclassic.ModelChecker 3535 INFO checking model elements (calling checkConstraints) 3545 INFO Starting: org.openarchitectureware.check.CheckComponent 3765 INFO Starting: dirCleaner [org.openarchitectureware.workflow.common.DirectoryCleaner] 3765 INFO Cleaning C:\dev\ide\workspace\oaw-v4-projects\oaw4.demo.classic.uml\src-gen 3875 INFO Starting: generator [org.openarchitectureware.xpand2.Generator] 3915 INFO Starting: workflow org/openarchitectureware/workflow/oawclassic/classicfinish.oaw 3915 INFO Starting: org.openarchitectureware.workflow.oawclassic.MessageOutput 3915 INFO Starting: org.openarchitectureware.workflow.oawclassic.DumpWriter 3915 INFO writing dump to: bin/dump 3925 INFO workflow completed in 3385ms!
Congratulations! You have just set up the whole environment to get openArchitectureWare running. Now, let us do the interesting stuff!
Later on, we want to do something with the elements contained in our
model. A common pattern in the Root template when using UML models is to
loop through the model to find the elements that should be expanded. A
model is an instance of Namespace
and all elements
directly contained by the model can be accessed through the association
OwnedElement
. The owned elements can be of
various types: Classes, Packages, Datatypes and so on. Packages are also
instances of Namespace
, so they also have a
OwnedElement
association.
We use the feature to recursively resolve any element
contained in the model tree. Now extend the Root.xpt
template file:
«IMPORT org::openarchitectureware::core::meta::core» «IMPORT org::openarchitectureware::meta::uml::classifier» «DEFINE Root FOR Model» «EXPAND Root FOREACH OwnedElement» «ENDDEFINE» «DEFINE Root FOR Package» «EXPAND Root FOREACH OwnedElement» «ENDDEFINE» «DEFINE Root FOR Object»«ENDDEFINE»
Note, that we have added a new IMPORT
statement,
because the metaclass Package
is in package
org.openarchitectureware.meta.uml.classifier
.
First, the definition Root
FOR
Model
will be called by the generator. This will
call the definition named Root
for all elements,
that the Model
instance contains. Look at the last
definition: This is a catcher for all elements that are found while
traversing through the tree that are not of any handled type.
When the model contains Package
instances the
definition Root FOR Package
will be called for
these instances. This is polymorphism at work! When the package contains
subpackages, the definition is called recursively.
As we want to create JavaBean style classes from the entities in our
model, we now want to create the template file for this. Create a new
folder javabeans
within the folder
templates
create the file
JavaBean.xpt
there.
For a first step, this template will simply create a file with the name of the class.
«IMPORT org::openarchitectureware::meta::uml::classifier» «DEFINE BeanClass FOR Class» «FILE NameS+".java"» public class «Name» { } «ENDFILE» «ENDDEFINE»
Note, that we have named the definition
BeanClass
and it is defined for elements of type
Class
. So, this template will not only fit for
elements of type Entity
, but can be used for any
class.
In the FILE
statement, we access the name of the
current element by property NameS
. This is
specific to the oAW classic metamodel. The property
NameS
returns the name of the element as String.
The property Name
itself is of type
Object
for backward compatibility. But in templates
we usually want the name as a String object.
By now, this template is not called. We have to extend the
Root.xpt
template file. Add a definition
Root
defined for Entity
.
We also have to add a new IMPORT
, so that oAW can
resolve Entity
.
«IMPORT oaw4::demo::classic::uml::meta» ... «DEFINE Root FOR Entity» «EXPAND javabeans::JavaBean::BeanClass» «ENDDEFINE»
As you remember, while looping through the model for each element in
the model tree a definition called Root
is
called. Before adding this, we had a definition for Packages, Models, and
all other objects. Now, when evaluating elements of type
Entity
this definition matches. In this definition,
we call the definition named BeanClass
from the
template file JavaBean.xpt
. As the template
JavaBean.xpt
is defined in the package
javabeans
, we also have to qualifiy this namespace.
As an alternative, we could import the namespace
javabeans
. The definition is (implicitely) called for
the current element, which is of course an
Entity
.
Run the generator again (as a shortcut you could type Ctrl+F11). Now, your project should contain the folder
src-gen
, which contains four files:
Author.java
, Book.java
,
Copy.java
and
Library.java
.
Open Author.java
. It does only contain the
class definition, since the template was that simple.
public class Author { }
Now, we want to extend the JavaBean template to create instance variables, property getters and setters. Generating declarations for instance variables as well as their getter and setter methods is a very recurring task for attributes, so we want this in a central template file.
Create the file Attribute.xpt
in folder
javabeans
.
«IMPORT org::openarchitectureware::meta::uml::classifier» «DEFINE PropertyDeclaration FOR Attribute» private «Type.NameS» «NameS»; «ENDDEFINE» «DEFINE Getter FOR Attribute» public «Type.NameS» get«NameS.toFirstUpper()» () { return this.«NameS»; } «ENDDEFINE» «DEFINE Setter FOR Attribute» public void set«NameS.toFirstUpper()» («Type.NameS» «NameS») { this.«NameS» = «NameS»; } «ENDDEFINE»
Of course, we also have to call these templates from our
JavaBean
template. We want to call the templates
PropertyDeclaration
,
Getter
and Setter
for
each attribute a class has. So, open your
JavaBean.xpt
template and extend it like
follows:
«IMPORT org::openarchitectureware::meta::uml::classifier» «DEFINE BeanClass FOR Class» «FILE NameS+".java"» public class «Name» { «EXPAND Attribute::PropertyDeclaration FOREACH Attribute» «EXPAND Attribute::Getter FOREACH Attribute» «EXPAND Attribute::Setter FOREACH Attribute» } «ENDFILE» «ENDDEFINE»
Once again, run the generator. Now, your generated files have properties!
public class Author { private String id; private String name; public String getId() { return this.id; } public String getName() { return this.name; } public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } }
A very powerful feature of openArchitectureWare 4 are extensions. With extensions the Xpand template engine can be extended with functions without the need to modify the metamodel.[11] A very common use of extensions are the use of naming conventions, navigation, computation of package, path and filenames for artifacts etc.
openArchitectureWare extensions are declared in files ending with
.ext
. The declaration of extension functions can be
by means of oAW expressions or by calling Java functions. The latter have
to be declared public
and static
.
We will not cover the syntax of expressions very deep, so if you are interested to get more information look at the reference core reference chapters Xtend and Expressions. Also, in the other tutorials you can see more examples for the usage of expressions.
For our example, we want to use extensions to introduce some naming conventions for instance variables, parameters, and the computation of the package and path for classes.
Now, create a file NamingConventions.ext
in
the templates/javabeans
folder. In this extension
we declare some functions that are helpful for name
conversions:[12]
import org::openarchitectureware::meta::uml; import org::openarchitectureware::meta::uml::classifier; String asParameter (ModelElement elem) : "p"+elem.NameS.toFirstUpper(); String asSetter (ModelElement elem) : "set"+elem.NameS.toFirstUpper(); String asGetter (ModelElement elem) : "get"+elem.NameS.toFirstUpper(); String asInstanceVar (ModelElement elem) : elem.NameS.toFirstLower();
In extension files, import statements for used types are necessary, too. The function declarations are single-lined and can use any expressions, calls of other extension functions included.
Open Attribute.xpt
and make use of these
functions. In template files, the usage of extensions must be declared
by the «EXTENSION»
keyword. The modified
template file will look like this:
«IMPORT org::openarchitectureware::meta::uml::classifier» «EXTENSION javabeans::NamingConventions» «DEFINE PropertyDeclaration FOR Attribute» private «Type.NameS» «asInstanceVar()»; «ENDDEFINE» «DEFINE Getter FOR Attribute» public «Type.NameS» «asGetter()» () { return this.«asInstanceVar()»; } «ENDDEFINE» «DEFINE Setter FOR Attribute» public void «asSetter()» («Type.NameS» «asParameter()») { this.«asInstanceVar()» = «asParameter()»; } «ENDDEFINE»
Run the generator and open Author.java
. The
file looks almost the same, since the replacements for instance
variables, getter and setter method names are equivalent. The only
difference are the parameters for setter methods. They are now prefixed
with the character p
.
public void setId(String pId) { this.id = pId; }
More complex computations are often easier and more understandable
by means of the Java programming language. So, the
Xtend language allows to define functions by
referencing a Java function. The methods that should be called, must be
declared public static
.
In our example, we miss the declaration of packages, and files are written into the root directory. We now want to declare some functions that help us to compute the full package name of classes, their corresponding path and the fully qualified name.
Create a new package
oaw4.demo.classic.uml.extend
and a class
ClassUtil
. In this class we define a function
getPackageName()
that computes the full package
of a Class (remember that we operate on the class of the metamodel named
(org.openarchitectureware.meta.uml.classifier.)Class
,
not java.lang.Class
).
package oaw4.demo.classic.uml.extend; import org.openarchitectureware.meta.uml.classifier.Class; import org.openarchitectureware.meta.uml.classifier.Package; public class ClassUtil { public static String getPackageName (Class cls) { String result = ""; for (Package pck=cls.Package(); pck!=null; pck=pck.SuperPackage()) { result = pck.NameS() + (result.length()>0 ? "."+result : ""); } return result; } }
We declare this function in
NamingConventions.ext
and two more functions that
use it. Add these function declarations:
String packageName (Class cls) : JAVA oaw4.demo.classic.uml.extend.ClassUtil .getPackageName(org.openarchitectureware.meta.uml.classifier.Class); String packagePath (Class cls) : packageName(cls).replaceAll("\\.", "/"); String fqn (Class cls) : packageName(cls).length>0 ? packageName(cls)+"."+cls.NameS : cls.NameS;
The additional functions are used to compute the path to files
based on a class and to get the full qualified name of a class. We want
to make use of these functions to declare the package, compute the
desired file path and to extend from the superclass, if a class has one
(in our example model this is not the case yet). If no superclass
exists, the Bean class should declare to implement the
Serializable
interface.
Open the JavaBean.xpt
template file and
modify it like follows:
«IMPORT org::openarchitectureware::meta::uml::classifier» «IMPORT javabeans» «EXTENSION javabeans::NamingConventions» «DEFINE BeanClass FOR Class» «FILE packagePath()+"/"+NameS+".java"» package «packageName()»; public class «Name» «IF hasSuperClass»extends «SuperClass.fqn()» «ENDIF» implements java.io.Serializable { ...
Once again, run the generator and refresh your
src-gen
folder. Now, the classes are generated in
the expected directory structure:
The generated classes now contain the correct
package
statement:
package oaw4.demo.classic.uml.entity; public class Author implements java.io.Serializable { ...
In the model, we have defined some associations of different kind.
In the next step, we evaluate these associations and create references,
accessor methods and modification methods. As you can see, an m:n
association between Author
and
Book
exists. There also exists an association from
Library
to Copy
with the
multiplicity 1:n. The association from Book
to
Copy
is not navigable. These associations must be
handled differently.
Ordinary associations in UML consist of three elements: Two association ends and one association containing them. From the view of a class, a referenced class is at the opposite end. Normally it must be considered whether the association, i.e. the opposite association end, to the referenced class is navigable.
Also, the templates for associations between classes are very
common, so we define them in a central template file
Association.xpt
in the
templates/javabeans
folder.
We want to keep the usage of the association template for the
calling class template simple and hide this complex stuff about
association ends. So, we define simple entry points for
Class
elements. We also make use of further
extensions to facilitate template development.
The extensions for functions used for associations are put into a
separate file. Create the file
templates/javabeans/Associations.ext
with this
content:
import org::openarchitectureware::meta::uml::classifier; extension javabeans::NamingConventions; String fqn (AssociationEnd ae) : !ae.isMultiple ? ae.Class.fqn() : "java.util.Collection<"+ae.Class.fqn()+">"; String iterator (AssociationEnd ae) : "java.util.Iterator<"+ae.Class.fqn()+">";
As you can see, we will create Java 5 style collections for to-many associations. If you do not want to use generics, you could easily replace or modify the extension file and all associations will change to your style.
We see in this extension file, that other extensions can be
referenced using the extension
keyword. In this
case, we need the function fqn()
that was
defined for classes. For associations, another function
fqn()
is defined, but now for
AssociationEnd
s. For to-one associations, the
fully qualified name of the associated class is returned, for to-many
references, we return a generic collection for that class. In the
template code, classes and association ends will both use the
fqn()
function to print out the referenced
type.
Now, it is time to write the template for associations. Create the
file Association.xpt
. Now fill in the content. We
will explain some statements right after.
«IMPORT org::openarchitectureware::meta::uml::classifier» «EXTENSION javabeans::NamingConventions» «EXTENSION javabeans::Associations» «DEFINE ReferenceVariables FOR Class» «FOREACH AssociationEnd.Opposite.select(ae|ae.isNavigable) AS ae» private «ae.fqn()» «ae.asInstanceVar()»; «ENDFOREACH» «ENDDEFINE» «DEFINE AccessorMethods FOR Class» «EXPAND ToOneAccessorMethods FOREACH AssociationEnd.Opposite.select(ae|!ae.isMultiple && ae.isNavigable)» «EXPAND ToManyAccessorMethods FOREACH AssociationEnd.Opposite.select(ae|ae.isMultiple && ae.isNavigable)» «ENDDEFINE» «DEFINE ToOneAccessorMethods FOR AssociationEnd» public void «asSetter()» («Class.fqn()» «asParameter()») { this.«asInstanceVar()» = «asParameter()»; } public «Class.fqn()» «asGetter()» () { return this.«asInstanceVar()»; } «ENDDEFINE» «DEFINE ToManyAccessorMethods FOR AssociationEnd» public void add«NameS.toFirstUpper()» («Class.fqn()» «asParameter()») { this.«asInstanceVar()».add(«asParameter()»); } public void remove«NameS.toFirstUpper()» («Class.fqn()» «asParameter()») { this.«asInstanceVar()».remove(«asParameter()»); } public «iterator()» «asGetter()» () { return this.«asInstanceVar()».iterator(); } «ENDDEFINE»
At first, we see that this template uses both extensions,
NamingConventions
and
Associations
.
Next, a template ReferenceVariables
is
declared. The template makes use of a FOREACH
loop of a different kind than we saw before. One more specific thing
here is the statement
«FOREACH AssociationEnd.Opposite.select(ae|ae.isNavigable) ...
This returns all navigable opposite end of each association end a class has.
The body of the loop declares a reference variable for an
association. For both alternatives (to-one and to-many), the function
fqn()
(the one defined for association ends) is
called to determine the right type. In our example, this should result
in the following both declarations:
// to-one association (Copy.java) private oaw4.demo.classic.uml.entity.Library owner; // to-many association (Author.java) private java.util.Collection<oaw4.demo.classic.uml.entity.Book> writtenBook;
Both types are now expressed by this statement – simple, isn't it?
private «ae.fqn()» «ae.asInstanceVar()»;
The next template definition is also interesting. The definition
AccessorMethods FOR Class
dispatches to the
definitions ToOneAccessorMethods
or
ToManyAccessorMethods
, depending on the
cardinality of the association. To distinguish both types, we make use
of the select expression which is defined for expression. The
statement
«EXPAND ToOneAccessorMethods FOREACH AssociationEnd.Opposite.select(ae|!ae.isMultiple && ae.isNavigable)»
means that the definition
ToOneAccessorMethods
should be expanded for
each opposite association end which is not to-many and navigable. So for
unnavigable associations no accessor method will be created.
The definitions for the accessor methods have nothing new, so we do not explain them in detail now.
Now, we want to use the new templates from the JavaBeans template
so that the classes get instance variables for referenced classes and
appropriate accessor methods. Modify the template file
JavaBeans.xpt
by adding these two statements to the
class definition:
public class «Name» «IF hasSuperClass»extends «SuperClass.fqn()» «ENDIF»{ «EXPAND Attribute::PropertyDeclaration FOREACH Attribute» «EXPAND Attribute::Getter FOREACH Attribute» «EXPAND Attribute::Setter FOREACH Attribute» «EXPAND Association::ReferenceVariables» «EXPAND Association::AccessorMethods» }
Run the generator again. After successful generation, open
Book.java
. This file should have the following
contents:
package oaw4.demo.classic.uml.entity; public class Book implements java.io.Serializable { private String isbn; private String title; private java.util.Collection<oaw4.demo.classic.uml.entity.Author> author; public String getIsbn () { return this.isbn; } public String getTitle () { return this.title; } public void setIsbn (String pIsbn) { this.isbn = pIsbn; } public void setTitle (String pTitle) { this.title = pTitle; } public void addAuthor (oaw4.demo.classic.uml.entity.Author pAuthor) { this.author.add(pAuthor); } public void removeAuthor (oaw4.demo.classic.uml.entity.Author pAuthor) { this.author.remove(pAuthor); } public java.util.Iterator<oaw4.demo.classic.uml.entity.Author> getAuthor () { return this.author.iterator(); } }
In this file, we have a to-many association named
author
to entity Author
. No
code exists for the association to the Copy
class, since this association is not navigable.
An example for to-one associations can be seen in the
Copy
class:
public class Copy implements java.io.Serializable { ... private oaw4.demo.classic.uml.entity.Library owner; ... public void setOwner (oaw4.demo.classic.uml.entity.Library pOwner) { this.owner = pOwner; } public oaw4.demo.classic.uml.entity.Library getOwner () { return this.owner; } ... }
The template code is really small, but now you could only have to
model classes and their associations and you get the right JavaBeans
code from the model. Our JavaBeans template is not specific for
entities, you could use it to generate JavaBeans code for just any
modelled class. But we only call this template for classes stereotyped
with <<Entity>>
for now. You remember
that the model contains another class <<DAO>>
LibraryDAO
for which no code is generated yet.
It is very important that a generator produces correct output. Therefore, the input information must be valid, i.e. the model must be consistent. To prove this, the metamodel is enriched by constraints that check the consistence of the model.
Constraints can be provided in three different ways:
checkConstraints()
method of the metaclasses.
The checkConstraints()
method is
only available for metaclasses based on the "classic" metamodel.
It is not recommended to use this alternative. This alternative is
for backward compatibility; in oAW3 metaclasses usually used this
method to check model constraints.
oAW 4 has a new language named Check that can be used to check model constraints. In this example, we will focus on this method. You may want to read the chapter Check language in the core reference for more information.
We want to implement constraint checks by using the
Check language. The syntax for check files is
similar to those for Extensions, as it uses also the oAW Expressions
framework. Check files have the file extension .chk
and have be on the classpath in order to be found.
In our example, we want to implement the following constraint:
Constraint 1 must be fulfilled, otherwise an error message should be printed and the generation process should not be started. If constraint 2 is not fulfilled a warn message should be printed, but the generation process should not be stopped.
As the constraints that should be implemented are specific for
associations, the check file should be named
AssociationChecks.chk
. Create this file in
templates/javabeans
.
import org::openarchitectureware::meta::uml::classifier; context AssociationEnd ERROR Class.NameS+"->"+Opposite.Class.NameS+": Navigable association ends must have a role name" : isNavigable ? !isUnnamed : true; context AssociationEnd WARNING Class.NameS+"->"+Opposite.Class.NameS+": Not navigable association ends should have no role name": isNavigable ? true : isUnnamed;
The syntax is rather simple. Both constraints should be checked
for the metaclass AssociationEnd
, so the
appropriate namespace has to be imported. The expression following the
colon is the constraint. If it is not fulfilled, the message is printed.
When error messages are printed, the workflow will be
interrupted.
To execute the constraint checks, the generator workflow has to be
extended. The constraint check is configured by a workflow component
CheckComponent
. Insert this code into
workflow.oaw
, right between the cartridge
classicinit.oaw
and the
dirCleaner
component.
<component class="org.openarchitectureware.check.CheckComponent"> <metaModel class="org.openarchitectureware.type.impl.java.JavaMetaModel"> <typeStrategy class="org.openarchitectureware.type.impl.oawclassic.OAWClassicStrategy" convertPropertiesToLowerCase="false"/> </metaModel> <checkFile value="javabeans::AssociationChecks"/> <expression value="me.getElements('ModelElement')"/> <abortOnError value="true"/> </component>
Also, the constraint checker has to know which metamodel it should
use. It is the same configuration as for the Generator
component.[13]The checkFile
property specifies the
qualified name of the check file.
Finally, the expression
property defines for
which elements to constraints should be applied. In our case, we select
all elements in the
MetaEnvironment
. However, for our special case it
would be satisfactory to select only all elements of type
AssociationEnd
. For larger projects, it could be
of advantage to select the right subset of elements for improving
performance.
Start the generator. The generator should run without complaining, because all constraints are fulfilled in the example model for now.
Now open the model; we will change the model in a way that both constraints are not fulfilled.
First, edit the association between Book
and Author
. Take the association end named
author
and remove the name. Set the
navigable
flag to false
for the
opposite end named writtenBooks
.
Save the model and re-run the generator. The generator now produces the expected messages and stops execution, because there is one constraint error and one warning.
4316 INFO Starting: org.openarchitectureware.check.CheckComponent 4667 ERROR Workflow interrupted. Reason: Errors during validation. 4667 WARN Book->Author: Not navigable association ends should have no role name [<<AssociationEnd>> writtenBook] 4667 ERROR Author->Book: Navigable association ends must have a role name [<<AssociationEnd>> ]
[11] With openArchitectureWare3 it was needed to code functionality of the metamodel in metaclasses
[12] The functions in the Extension file could be defined for type
Attribute
instead of
ModelElement
, but we want to use these
functions for other types later on, too.
[13] It is possible to declare the used metamodel once and reference it the second time.