The primary plugin of the AndroMDA framework, cartridges provide the ability to process model elements that have specified stereotypes (i.e. <<Entity>>, <<Enumeration>>, etc.) or model elements that meet certain conditions inferred by the model (all Actors that have a dependency to a <<Service>> for example). Cartridges process these model elements using template files defined within the cartridge descriptor.
This page describes the internal structure of an AndroMDA cartridge. After reading it, you will probably find it very easy to write your own cartridge or customize an existing cartridge for your needs.
If you want to use existing cartridge(s) for generation (i.e. the BPM4Struts Cartridge, etc.), simply place the cartridges on your classpath; using the AndroMDA Maven Plugin this is VERY simple, you just add the AndroMDA maven plugin and cartridge(s) as dependencies to your project.xml file and then run the plugin goal andromda:run, that's it!
<dependencies> ... <dependency> <groupId>${project.groupId}</groupId> <artifactId>maven-andromda-plugin</artifactId> <version>3.0</version> <type>plugin</type> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>andromda-bpm4struts-cartridge</artifactId> <version>3.0</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>andromda-hibernate-cartridge</artifactId> <version>3.0</version> </dependency> ... </dependencies>
Sometimes you may want to override some of the resources a cartridge uses without touching the internals of the cartridge: i.e. you want to add some more stuff to a JSP-page that the BPM4Struts Cartridge generates. Easy to do: just specify a mergeLocation when declaring the cartridge-dependency and put the replacement-files in that directory (if you generated your project using the Maven andromdapp:generate plugin you will need to do that in <your-project>/mda/conf/andromda.xml):
... <namespace name="bpm4struts"> <properties> ... <property name="mergeLocation">${maven.src.dir}/cartridge/custom</property> ... </properties> </namespace> ...
This way the cartridge will try to load resources (templates, images, etc.) from ${maven.src.dir}/cartridge/custom prior to loading them from the cartridge-jar.
When loading resources from the merge location you will need to respect the same directory structure as in the cartridge jar. This means that when the cartridge contains templates/SomeTemplate.vsl and your merge location is ${maven.src.dir}/cartridge/custom you will need to copy your new template in ${maven.src.dir}/cartridge/custom/templates/SomeTemplate.vsl, like this you can override all cartridge resources.
Sometimes you may want to merge (or replace) things in a cartridge: this can be done in templates, within the cartridge.xml, or even within the metafacades.xml. For example within the BPM4Struts cartridge, the web.xml (or struts-config.xml) may not contain everything you need (i.e. you may need to add a new <servlet/> and <servlet-mapping/>, some <context-param/>, or even <error-page/> definitions).
This is easy to do: just specify a mergeMappingsUri namepace property when declaring the cartridge-dependency and create a new mappings file in which to map what you want merged.
... <namespace name="bpm4struts"> ... <property name="mergeMappingsUri">file:${maven.conf.dir}/mappings/Bpm4StrutsMergeMappings.xml</property> ... </namespace> ...
For example, this fragment below (from a mappings file) says we want to replace anything found matching <!-- error-page merge-point --> (defined in the <to/> definition) with the <error-page/> fragments (found in the from <from/> definition):
<mappings name="Bpm4StrutsMergeMappings"> ... <mapping> <from><![CDATA[<!-- error-page merge-point -->]]></from> <to> <![CDATA[ <error-page> <error-code>404</error-code> <location>/index.jsp</location> </error-page> <error-page> <error-code>408</error-code> <location>/index.jsp</location> </error-page> <error-page> <error-code>400</error-code> <location>/index.jsp</location> </error-page> ]]> </to> </mapping> ... </mappings>
In addition to the ability of merging literal strings into merge-points (the contents of the <to/> element). It's also possible to merge the entire contents of one or more files into a merge-point. For example, this fragment below (from a mappings file) says we want to replace anything matching # custom-messages merge-point with the combined contents of the files ../../../web/conf/resources/custom-resources1.properties and ../../../web/conf/resources/custom-resources2.properties (please take note of the nested <path/> elements, these tell AndroMDA to treat the contents of the to element as the concatenated contents of the files defined by the paths).
<mappings name="Bpm4StrutsMergeMappings"> ... <mapping> <from><![CDATA[# custom-messages merge-point]]></from> <to> <path>../../../web/conf/resources/custom-resources1.properties</path> <path>../../../web/conf/resources/custom-resources2.properties</path> </to> </mapping> ... </mappings>
If you wanted to add resources (i.e. a new template, image, static file, etc) to a cartridge without modifying the contents of the cartridge, you could use the mergeMappingsUri in combination with the mergeLocation. You would use the mergeMappingsUri to define the new template in a cartridge, and use the mergeLocation to specify the actual template.
An AndroMDA cartridge is a resource (directory or jar file) on the classpath that consists of several items:
File | Contents | Required |
---|---|---|
/META-INF/andromda/namespace.xml | Because the cartridge ia a namespace component it MUST be registered within a namespace descriptor. This descriptor is what allows the cartridge's namespace to be "discovered" on the classpath. This namespace descriptor also registers the cartridge component within the AndroMDA core. | Yes |
/**/*.class | There could be a couple uses for this directory, placing cartridge specific metafacade classes, and/or other support classes (such as templateObjects | No |
/META-INF/andromda/cartridge.xml | Declarative cartridge descriptor (see below). | Yes |
/META-INF/andromda/metafacades.xml | Metafacades descriptor. This is used to specify metafacades for the underlying metamodel (UML 1.4, etc.). | No |
/templates/**/*.vsl (or any template extension depending on your cartridge template engine). | Templates that tell the cartridge how to format the generated code. | Yes |
All items except the descriptor and the templates are optional. If you want to see a complete cartridge, have a look inside the andromda-bpm4struts-cartridge cartridge.
The AndroMDA core uses this descriptor to discover the capabilities of a cartridge: the supported metafacades, stereotypes, outlets, templates, property references, etc. The cartridge descriptor must reside in the META-INF/andromda subdirectory and must be named cartridge.xml.
Let's have a look at part of a typical cartridge descriptor:
<cartridge> <templateEngine> <!-- library of macros used in template engine --> <macrolibrary name="templates/EJB.vm" /> </templateEngine> <!-- define the template objects that are made availble to the template --> <templateObject name="str" className="org.andromda.utils.StringUtilsHelper"/> <templateObject name="transform" className="org.andromda.cartridges.ejb.EJBScriptHelper"> <property reference="viewType"/> </templateObject> <resource path="templates/*.properties" outlet="entity-beans" overwrite="true"> </resource> <template path="templates/EntityBean.vsl" outputPattern="{0}/{1}Bean.java" outlet="entity-beans" overwrite="true"> <modelElements variable="entity"> <modelElement> <type name="org.andromda.cartridges.ejb.metafacades.EJBEntity"/> </modelElement> </modelElements> </template> <template path="templates/EntityLocalIntf.vsl" outputPattern="{0}/{1}.java" outlet="entity-beans" overwrite="true"> <modelElements variable="entity"> <modelElement> <type name="org.andromda.cartridges.ejb.metafacades.EJBEntity"/> </modelElement> </modelElements> </template> <template path="templates/EntityHome.vsl" outputPattern="{0}/{1}LocalHome.java" outlet="entity-beans" overwrite="true"> <modelElements variable="entity"> <modelElement> <type name="org.andromda.cartridges.ejb.metafacades.EJBEntity"/> </modelElement> </modelElements> </template> <template path="templates/EntityBeanImpl.vsl" outputPattern="{0}/{1}BeanImpl.java" outlet="entity-impls" overwrite="false"> <modelElements variable="entity"> <modelElement> <type name="org.andromda.cartridges.ejb.metafacades.EJBEntity"/> </modelElement> </modelElements> </template> ... </cartridge>
Note the usage of the "{0}/" pattern. This will cause the target java file to be generated into "com/mycompany/test" if it was in the package "com.mycompany.test".
The <cartridge/> element is the root of the cartridge descriptor and also gives a name to the cartridge.
Attribute | Description | Required? |
---|---|---|
name | Specifies the name of the cartridge. | Yes |
The <property/> element is used to specify property references for a cartridge. The values of these references are defined within the AndroMDA Configuration namespace elements. For example, if you have the property reference securityEnabled defined (i.e. <property reference="securityEnabled"/>), then the framework will expect the client to define a namespace property named securityEnabled when this cartridge is run. This property (and it's value) will then be made available to the template during processing.
Attribute | Description | Required? |
---|---|---|
reference | Specifies the name of the reference. This is the name of a property that must be defined in the namespace descriptor and is the name of a variable that will be made available to the template during processing. | Yes |
The <templateObject/> element is used to specify helper objects within your cartridge. These must have a default constructor and must be a class available to your cartridge (either internally or externally).
You can also define property references within your template objects. In order to do this, you'll need to make sure you have a java bean setter matching each reference name. For example:
<templateObject name="hibernateUtils" className="org.andromda.cartridges.hibernate.HibernateUtils"> <property reference="hibernateVersion"/> </templateObject>
Attribute | Description | Required? |
---|---|---|
name | Specifies the name of the template object. This is the variable name under which the template object will be made available to your template. | Yes |
className | The fully qualified class name of the template object. | Yes |
The <template/> element is used to describe the template that will be used to generate source code.
Attribute | Description | Required? |
---|---|---|
path | Specifies the path (relative to the cartridge root) for a template (*.vsl) file to use for code generation. | Yes |
outputPattern |
Specifies a pattern in
java.text.MessageFormat syntax.
You can use this pattern to tell AndroMDA how to construct
output file names. The pattern can consist of any ordinary
printable characters as well as some predefined placeholders
for things that AndroMDA already knows about: {0} stands for the package directory of the class. {1} stands for the class name. See example above. |
Yes |
outlet | Specifies the logical name of the outlet where the cartridge will write the output files caused by this template. | Yes |
overwrite | Specifies whether the files already specified by the outlet attribute should be overwritten when AndroMDA runs the next time. | Yes |
generateEmptyFiles | Specifies whether files should be generated even if the template did not produce any output. This can be used by the cartridge developer to decide if a certain file should be generated based on the information in the model. Note: If this property is set to "false", the template produces no output, overwrite is set to "true", and an existing file is found (probably generated by a previous run), then this file is removed. | No, default is false. |
outputToSingleFile | Specifies whether you want to output all aggregated model elements to a single file. This is useful for example if you wanted to generated a SQL script that had tables for all entities in your model. This also allows you to group more than one type of model element into multiple collection variables which are then made available to your templates. | No, default is false. |
outputOnEmptyElements | This attribute only makes sense when outputToSingleFile is set to true. Indicates that when there are no elements aggregated (i.e. an empty collection), whether or not the file should still be output. | No, default is true. |
The <modelElements/> element is nested directly within the <template/> element, it is used to instruct each <template/> on which model elements it should process. The <modelElements/> encloses the <modelElement/>.
Attribute | Description | Required? |
---|---|---|
variable | The variable which is made available to the template (i.e. a value of entity would make $entity available in a velocity template). | No |
The <modelElement/> is nested directly within the <modelElements/>, it is used to within the <modelElements/> to instruct each <template/> on which model elements it should process. For example, using this element you can specify both stereotypes and or metafacade types (and optionally the metafacade's properties).
Attribute | Description | Required? |
---|---|---|
stereotype | Defines the name of the stereotype the model element must have in order to be made available to a template. | No, because a nested <type/> may be used instead. |
variable |
The variable which is made available to the template. This would only make sense to use
if you set the outputToSingleFile attribute to true, normally
the variable from the <modelElements/> attribute is sufficient for
your templates. In combination with the outputToSingleFile attribute
this would allow you to group model elements together and make them available to the template as collections.
The below example makes all model elements stereotyped with <<Entity>> and <<Service>>
as $entityEjbs and $sessionEjbs respectively (if you're using velocity for your
templating language). If you take a look at the enclosing <modelElements/>, you'll notice
the ejbs as the variable value, this will make ALL
entities and services available as $ejbs in a velocity template (since
outputToSingleFile is set to true).
... <template path="templates/ejb-jar.xml.vsl" outputPattern="ejb-jar.xml" outlet="ejb-jar-descriptor" overwrite="true" outputToSingleFile="true"> <modelElements variable="ejbs"> <modelElement stereotype="Entity" variable="entityEjbs"/> <modelElement stereotype="Service" variable="sessionEjbs"/> </modelElements> </template> ... |
No |
The <type/> element is nested directly within a <modelElement/>, it is used to specify in what metafacade type the meta model element MUST be wrapped in order to be made available to a <template/>. For example, this could make each metafacade of type org.andromda.cartridges.webservice.metafacades.WebService available to a template:
... <template path="templates/webservice/wsdl/wrapped-wsdl.vsl" outputPattern="{0}/{1}.wsdl" outlet="wsdls" overwrite="true"> <modelElements variable="service"> <modelElement> <type name="org.andromda.cartridges.webservice.metafacades.WebService"/> </modelElement> </modelElements> </template> ...
Attribute | Description | Required? |
---|---|---|
name | The name of the metafacade type. (i.e. org.andromda.core.uml.ClassifierFacade, org.andromda.cartridges.webservice.metafacades.WebService, etc). | No, the stereotype attribute could be used instead. |
There are optional nested <property/> elements that may be placed directly within a <type/>. These properties are used for two purposes:
Here's an example of the first and most common use, the following would make each model element with a metafacade of type org.andromda.core.uml.ClassifierFacade with the property enumeration equal to true available to a template:
... <modelElements variable="enumeration"> <modelElement> <type name="org.andromda.metafacades.uml.ClassifierFacade"> <property name="enumeration">true</property> </type> </modelElement> </modelElements> ...
Here's example of the second use (note that the property still must be valid in order to be made available to the template, in the case of this example that would mean the navigableConnectingEnds collection has at least one or more elements). The following would process a template for each associationEnd in the collection of navigableConnectingEnds on the entity. It will also make the entity variable (in addition to the associationEnd variable) available to each processed template. This is useful in cases where you have a property on your metafacade that is something other than one or more metafacade instances; doing something like this allows you to process those related objects within a template.
... <modelElements variable="entity"> <modelElement> <type name="org.andromda.metafacades.uml.Entity"> <property name="navigibleConnectingEnds" variable="associationEnd"/> </type> </modelElement> </modelElements> ...
Another VERY useful application of these properties, is to determine when and when not to output a file (for example you may only want to output a WSDL for a web service that has at least one exposed operation), so in that case you'd map a nested property checking for the exposed operations like the example directly below:
... <modelElements variable="services"> <modelElement> <type name="org.andromda.cartridges.webservice.metafacades.WebService"> <property name="exposedOperations"/> </type> </modelElement> </modelElements> ...
Its important to note that you are NOT required to specify a property's value when using the nested <property/>, if you do not specify the value attribute, then the properties needs to be valid, meaning the following things: it's non-null, it has one or more elements if its a collection type property, or has a value of true if a boolean type.
Attribute | Description | Required? |
---|---|---|
name | The name of the property to check the existence of (there must be a valid java bean accessor on the metafacade). | Yes |
The <resource/> element is used to process files inside the cartridge without passing them through any template engine. They will simply be copied.
Attribute | Description | Required? |
---|---|---|
path | Specifies the path (relative to the cartridge root) for a resource file to be copied to an outlet. You can use the asterisk wildcard to match multiple characters at once, similar to Ant filesets. The matching will start relatively to the root of the cartridge, similar to the values used in the path attribute of the <template/> tag. | Yes |
outputPattern |
Specifies a pattern in
java.text.MessageFormat syntax.
You can use this pattern to tell AndroMDA how to construct
the resource output file names. The pattern can consist of any ordinary
printable characters as well as some predefined placeholders
for things that AndroMDA already knows about: {0} stands for the name of the resource file contained within your cartridge. See example below. |
Yes. |
outlet | Specifies the logical name of the outlet where the cartridge will copy this resource. | Yes |
overwrite | Specifies whether the files already specified by the outlet attribute should be overwritten when AndroMDA runs the next time. | Yes |
required | Specifies whether or not the resource is required during processing, if set to false, AndroMDA will ignore the fact that no outlet property has been defined in a namespace when running AndroMDA. Otherwise if its true, warnings will be issued notifying a cartridge user that they must define a property matching the outlet name. | No, default is true |
This example would copy all .jar files to the location defined by the resource outlet within the lib directory.
<resource path="**/*.jar" outputPattern="lib/{0}" outlet="resources" overwrite="false"/>