Creating custom templates

This quick tutorial will walk you through the necessary steps to create your own templates for use with the Groovy MDA library.

The groovy MDA library supports three argruments when executing the jar command. Two of them are optional.

 java -jar groovymda.jar [url to model] <output directory> <url to model processor implementation>

In future versions this will mostly likly be changed to support an XML based configuration or maybe use apache commons CLI.

To use your own templates, you can start by creating a Model Processor implementation.

This requires implementing a groovy script that implements a process method.

 void process(Map context) {
    
 }

The recommended approach is to extend the GroovyModelProcessor that comes with the library.

An example working implementation is below:

class MyModelProcessor extends GroovyModelProcessor {

        void process(Map context) {
                // ITERATE THROUGH EACH CLASS IN THE MODEL
                getAllClasses(context.model).each { modelElement ->
                        // ADD THE CURRENT MODEL ELEMENT TO THE CONTEXT
                        context.currentModelElement = modelElement

                        // GET THE FULLY QUALIFIED NAME FOR THE CLASS
                        def fullyQualifiedName = getFullyQualifiedName(context.currentModelElement)

                        // ONLY PROCESS NON JRE CLASSES (java.lang.String does not need to be generated)
                        if (!fullyQualifiedName.startsWith("java") && fullyQualifiedName.size() > 0) {

                                // YOU CAN BIND CLOSURES TO THE CONTEXT TO MAKE THEM ACCESSIBLE TO YOU TEMPLATES
                                context.helloWorld = { aString ->
                                        return "Hello ${aString}!"
                                }       
                                
                                // SET THE TEMPLATE TO USE
                                def templateName = "BasicPojo.gtl"

                                // SET THE OUTPUT FILE NAME FOR THE FULLY QUALIFIED NAME
                                def outputName = "${fullyQualifiedName.replace('.','/')}.java"

                                // PROCESS THE TEMPLATE
                                processTemplate(templateName, outputName, context)

                        }
                }
        }
    
}

The above Groovy script will be saved as MyModelProcessor.groovy.

The default GroovyModelProcessor script that is in the jar has a lot of helper closures that are bound to the context and available to your templates/processor.

The following are the closures that are bound to the context that are available to use in your templates and model processor:

javaToSql(string) - converts camel case to underscore separated
javaType(umlType) - converts a UML data type to java type
firstCharUpper(string) - converts first char to upper case
firstCharLower(string) - converts first char to lower case
getPackageName(modelElement) - gets the package name for the model element
getAttributes(class) - gets the attributes for the class
getAssociationEnds(model, class) - gets the association ends for the specified class
getEndType(associationEnd) - returns end participant type for single value association or a Set or List for multiple (if ordered will retrun List, else Set) 
getEndName(associationEnd) - get the specified name for end participant or creates one based on multiplicity and class name (ex: Class Car with multplicity of many would return "cars")
taggedValues(modelElement) - get tagged values as map for specified model element
isOneToOne(sourceAssociationEnd, targetAssociationEnd) - determine if the source to target is a one to one
isOneToMany(sourceAssociationEnd, targetAssociationEnd) - determine if the source to target is a one to many
isManyToOne(sourceAssociationEnd, targetAssociationEnd) - determine if the source to target is a many to one
isManyToMany(sourceAssociationEnd, targetAssociationEnd) - determine if the source to target is a many to many
isOwner(association, associationEnd) - determines if the end is the owner in the association based on tagged attributes
isCollection(associationEnd) - determine if the association end a collection based on specified multiplicity

In the above MyModelProcessor.groovy example, we are specifying to use a template named BasicPojo.gtl

This template will output simple pojos from model elements and also make use of the hello world closure defined in the custom model processor.

The contents of the BasicPojo.gtl template are below:

<% if (getPackageName(currentModelElement) != "") { %>
package ${getPackageName(currentModelElement)};
<% } %>

/**
 * ${helloWorld(currentModelElement.name)} 
 */
public class ${currentModelElement.name} {

        <%
        getAttributes(currentModelElement).each { attribute ->
                def attributeName = attribute.name
                def attributeType = javaType(attribute.type)
                %>
                private ${attributeType} ${attributeName};
                <%
        }
        %>
   
        <%
        getAssociationEnds(model, currentModelElement).each { sourceEnd ->
                def association = sourceEnd.association
                def targetEnd = association.connection.find { end -> end != sourceEnd }
                if (targetEnd.isNavigable()) {
                        def targetType = getEndType(targetEnd)
                        def targetName = getEndName(targetEnd)
                        %>
                private ${targetType} ${targetName};
                        <%
                }
        } 
        %>
   
        <%
        getAttributes(currentModelElement).each { attribute ->
                def attributeName = attribute.name
                def attributeType = javaType(attribute.type)
                %>

                public ${attributeType} get${firstCharUpper(attributeName)}() {
                        return ${attributeName};
                }

                public void set${firstCharUpper(attributeName)}(${attributeType} ${attributeName}) {
                        this.${attributeName} = ${attributeName};
                }

                <%
        }
        %>
   
        <%
        getAssociationEnds(model, currentModelElement).each { sourceEnd ->
                def association = sourceEnd.association
                def targetEnd = association.connection.find { end -> end != sourceEnd }
                if (targetEnd.isNavigable()) {
                        def sourceName = getEndName(sourceEnd)
                        def targetType = getEndType(targetEnd)
                        def targetName = getEndName(targetEnd)
                        %>

                public ${targetType} get${firstCharUpper(targetName)}() {
                        return ${targetName};
                }

                public void set${firstCharUpper(targetName)}(${targetType} ${targetName}) {
                        this.${targetName} = ${targetName};
                }

                        <%
                
                }
        }
        %>
   
}

Notice the Class documentation section that calls the helloWorld closure that was defined in the MyModelProcessor.groovy script.

Any closures bound to the context map passed to the process method of the model processor will be available to the templates.

After adding both files to a directory containing the groovymda.jar and a uml model we can run the following command.

java -jar groovymda.jar 'jar:file:./addressbook.zargo!/addressbook.xmi' . file:./MyModelProcessor.groovy

Thats basically it. From this you can create templates to generate any type of code from a uml model.

Example of one of the generated Classes is below:

package com.acme.domain;


/**
 * Hello Address! 
 */
public class Address {

        
                private java.lang.String firstName;
                
                private java.lang.String lastName;
                
                private java.lang.String street1;
                
                private java.lang.String street2;
                
                private java.lang.String city;
                
                private java.lang.String phone;
                
   
        
                private com.acme.domain.Country country;
                        
                private com.acme.domain.Region region;
                        
                private com.acme.domain.User user;
                        
   
        

                public java.lang.String getFirstName() {
                        return firstName;
                }

                public void setFirstName(java.lang.String firstName) {
                        this.firstName = firstName;
                }

                

                public java.lang.String getLastName() {
                        return lastName;
                }

                public void setLastName(java.lang.String lastName) {
                        this.lastName = lastName;
                }

                

                public java.lang.String getStreet1() {
                        return street1;
                }

                public void setStreet1(java.lang.String street1) {
                        this.street1 = street1;
                }

                

                public java.lang.String getStreet2() {
                        return street2;
                }

                public void setStreet2(java.lang.String street2) {
                        this.street2 = street2;
                }

                

                public java.lang.String getCity() {
                        return city;
                }

                public void setCity(java.lang.String city) {
                        this.city = city;
                }

                

                public java.lang.String getPhone() {
                        return phone;
                }

                public void setPhone(java.lang.String phone) {
                        this.phone = phone;
                }

                
   
        

                public com.acme.domain.Country getCountry() {
                        return country;
                }

                public void setCountry(com.acme.domain.Country country) {
                        this.country = country;
                }

                        

                public com.acme.domain.Region getRegion() {
                        return region;
                }

                public void setRegion(com.acme.domain.Region region) {
                        this.region = region;
                }

                        

                public com.acme.domain.User getUser() {
                        return user;
                }

                public void setUser(com.acme.domain.User user) {
                        this.user = user;
                }

                        
   
}