Classic UML Tutorial

Introduction
Installing the sample
Example overview
Setting up the project
Defining Dependencies
Create source folders
Create the model
Defining the metamodel
Log4j configuration
Creating the generator workflow and the first template
Execute the workflow
Looping through the model
Creating a template for JavaBeans
Calling the JavaBean template from Root.xpt
The first generated code
Defining property declarations and accessor methods
Using Extensions
Working with associations
Constraint Checking

Introduction

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.

Installing the sample

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:

Figure 71. Configure oAW-Classic Metamodel

Configure oAW-Classic Metamodel

Example overview

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:

Figure 72. Example Model

Example Model

As a result, we want to create some JavaBean style classes which have properties with getter/setter methods.

Setting up the project

Create a new Java Project called oaw4.demo.classic.uml and select the option to create seperate source and output folders.

Figure 73. Creating the tutorial project

Creating the tutorial project

Afterwards, select from the context menu PDE toolsConvert Projects to Plug-in Projects, 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.

Defining Dependencies

In the new project, there is now a META-INF/MANIFEST.MF. Open it, go to the Dependencies page and add the following dependencies:

Figure 74. Defining plug-in dependencies for oAW Classic

Defining plug-in dependencies for oAW Classic

  • 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 source folders

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.

Figure 75. Creating source folders

Creating source folders

Create the model

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:

  1. Create the stereotype Entity with base class Class .
  2. Create the stereotype Key with base class Property .
  3. Create the stereotype DAO with base class Class .
  4. Create a package structure in your project: oaw4 / demo / classic / uml / entity
  5. Create a class diagramm in the package entity
  6. Create a class Author
    1. Assign the stereotype Entity
    2. Create an attribute 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.
    3. Create an attribute name of type String.
  7. Create the class Book with stereotype Entity . The Key attribute is isbn of type String . The second attribute is the title .
  8. Draw an association between these two classes.
    1. The association end at class Author is named author, is navigable and the multiplicity is 1..*.
    2. The other association end is writtenBook with multiplicity 0..* and is navigable.
  9. Create the class Copy of stereotype Entity
    1. The Key attribute is inventoryNumber of type String.
    2. Add attribute location of type String.
  10. Draw a directed association from Copy to Book
    1. The association end at class Book is named book, is navigable, with multplicity 1.
    2. The opposite end at Copy is unnamed, not navigable and multiplicity 0..*
  11. Create the class Library of stereotype Entity
    1. The Key attribute is id of type String.
    2. The library has a String attribute name
  12. Draw an association from Library to Copy
    1. The association end at class Library is named owner, is navigable, the multiplicity is 1.
    2. The association end at class 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.

Defining the metamodel

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:

Figure 76. Tutorial Metamodel

Tutorial Metamodel

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.

Defining the metaclasses

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 Keys 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 {
}

Metamappings

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>

Log4j configuration

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

Creating the generator workflow and the first template

The workflow script

The next step is to create a workflow script. The workflow has to accomplish the following tasks:

  • Initialize the environment for using the "classic" metamodel
  • Parse the tool-specific xmi file and instantiate the metamodel
  • Initialize all instantiated elements
  • Run the generator to create code
  • Tear-down: Print messages collected through the generator run

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>

Workflow Properties

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

Setting up for use with other UML tools

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

Create the root template

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:

Execute the workflow

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!

Looping through the model

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.

Creating a template for JavaBeans

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.

Calling the JavaBean template from Root.xpt

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.

The first generated code

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 {
}

Defining property declarations and accessor methods

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;
	 }
}

Using Extensions

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.

Declaring functions with the Xtend language

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;
}

Calling Java functions as extensions

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 {
	 ...

Working with associations

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.

Association Extensions

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 AssociationEnds. 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.

Writing a template for associations

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.

Extending the JavaBeans template

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»
}

Generator result

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.

Constraint Checking

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.

Alternatives for implementing constraints

Constraints can be provided in three different ways:

  • Overriding the 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.

  • Using the new Check language.

    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.

Constraints to implement in the example

In our example, we want to implement the following constraint:

  1. Each navigable association end must have a role name.
  2. Unnavigable association ends should have no role name.

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.

Creating the Check file

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.

Checking the model

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.

Testing the constraints

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.