Introduction
In this introduction, it will be shown how to generate code using the Torque generator. Maven 2 is used as a build tool (although the example can also be run using plain java), a properties file is used as input for generation and velocity is used as template language. In the following section, the necessary steps to set up such a project are described; and detailed by a simple example: We have a properties file and want to create a java enum which contains the keys as constants. The sample files can be found in the src/test/site/gettingStarted directory of the torque-generator sources.
In our example, from the source
torque.sample.property = sampleValue torque.some.other.property = someOtherValue
package org.apache.torque.generator.example.gettingstarted; /** * Contains all keys in the property file. */ public enum PropertyKeys { /** Key for torque.sample.property */ TORQUE_SAMPLE_PROPERTY("torque.sample.property"), /** Key for torque.some.other.property */ TORQUE_SOME_OTHER_PROPERTY("torque.some.other.property"); /** The property key. */ private String key; /** * Constructor. * * @param key the key of the property. */ private PropertyKeys(String key) { this.key = key; } /** * Returns the property key. * * @return the property key. */ public String getKey() { return key; } @Override public String toString() { return key; } }
Project setup
Create a root folder for your project. In the root folder, create a src/main/torque-gen folder. This is where all the files for the torque generator go. In the torque-gen folder, create a "conf" subdirectory (this is where the main configuration goes), a "templates" subdirectory (for the velocity templates), a "outlets" subdirectory (for the definition of the outlets) and a "src" directory (for the source files, they can be anywhere in the project but if you do not know a better place for them, torque-gen/src is a reasonable default).
Creating a sample source
As a starting point, create the source file which keeps the information you need to generate the output (to MDA addicts, this is known as the "Metamodel instance". There is no explicit metamodel needed, it is defined implicitly by the templates.). In our example, the source will be the property file listed above.
This file will be used to feed the generation process. Name this file "propertiesData.properties" and put it into the src subdirectory of the torque-gen directory.
Internally, the Torque generator uses a representation similar to a xml file to represent the source: Elements make up a tree or graph structure, and each element has a set of named attributes. The internal representation of our property source looks like
<properties> <entry key="torque.sample.property">sampleValue</entry> <entry key="torque.some.other.property">someOtherValue</entry> </properties>
Other input file formats (currently xml) and input types (currently JDBC metadata) are also supported.
Creating the templates
Create the templates which produce the generated code in the "templates" subdirectory of the "torque-gen" folder. Use at least one template per file you want to generate; it is better to use one template per logical unit (this improves reusability and readability). A good idea is to create some sample output and start putting in control structures (variables, loops, includes) as necessary.
In our desired output above, we can identify the following logical units:
- The enum class frame
- The constants for the single properties
- The fields of the enum
- The methods of the enum
The template for the enum's class frame, classFrame.vm, contains the class outer frame plus mergepoints for the constants, fields and methods of the enum:
package org.apache.torque.generator.example.gettingstarted; /** * Contains all keys in the property file. */ public enum PropertyKeys { $torqueGen.mergepoint("constants")## $torqueGen.mergepoint("fields")## $torqueGen.mergepoint("methods")## }
The interesting part in the template is the call of the mergepoint method of the torqueGen Object. The torqueGen object is placed into the velocity context by the torque generator and contains methods for defining mergepoints and accessing the source, options and variables. See its javadoc for the available methods.
The template constant.vm, which outputs a constant for each property,
is also interesting because we need to access information from the
source file. For outputting simple data, this is simple:
The velocity construct "${key}" accesses the object named "key"
in the velocity context. The "key" object is put in the velocity
context by the torque generator, which by default puts all attributes
of the current source element into the velocity context.
For the name of the constant, the key would also be used, but it needs
to be processed in some way (capitalized, underscores added).
For this processing we will use a built-in java generator, which
will be plugged into the mergepoint "constantName".
Finally, we need a comma after each definition, except for the last
definition, where we need a semicolon. To decide whether to render the
comma or the semicolon, we fetch the current source element from
torgueGen (${torqueGen.getSourceElement()) and ask it whether it
has following siblings (i.e. source elements with the same parent
and the same name) (see
the SourceElement javadoc
for available methods on a source element).
/** Key for ${key} */ $torqueGen.mergepoint("constantName")("${key}")#if(${torqueGen.getSourceElement().hasFollowingSibling()}),#else;#end
There is nothing new in the other templates:
keyField.vm outputs the key field of the enum:
/** The property key. */ private String key;
There are three methods; each has its own template:
constructor.vm renders the constructor method:
/** * Constructor. * * @param key the key of the property. */ private PropertyKeys(String key) { this.key = key; }
getKey.vm renders the constructor method:
/** * Returns the property key. * * @return the property key. */ public String getKey() { return key; }
and toString.vm creates the toString method:
@Override public String toString() { return key; }
You might ask yourself why we create so many templates and not use a single large template (which is, of course, also possible). The reason is that smaller templates can be re-used more easily, but even more important, they can be re-plugged more easily. For example, if you want to create an enum class without the toString() method, you do not need to change a single template, but just the way they are plugged together (see below).
Creating the control file
The control file contains information about all the files you want to create. It is a XML file named "control.xml" and resides in the conf directory. Create this file, and for each file or set of files in the output, create a "output" tag in the root "control" tag. An output needs a name by which it can be identified. Each output needs a filename, which can be either fixed (then you can use the "file" attribute of the "output" tag), or which is generated (in which case you would use a "filenameGenerator" tag inside the "output" tag.
Each "output" tag needs a "source" tag which tells the torque generator which source files to use. If you want to create more than one output file per "output" tag and source file, use the "elements" attribute of the "source" tag to select the root elements for the different output files.
Also, each output tag needs a "outlet" tag which contains the name of the outlet to invoke on the root elements. For defining outlets, see the next section.
In our example, we want to create only one file, thus we have a simple control file:
<?xml version="1.0" encoding="UTF-8"?> <control xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://db.apache.org/torque/4.0/generator/configuration http://db.apache.org/torque/4.0/generator/configuration.xsd" xmlns="http://db.apache.org/torque/4.0/generator/configuration" loglevel="debug"> <output name="propertyKeys" file="org/apache/torque/generator/example/gettingstarted/PropertyKeys.java"> <source xsi:type="fileSource"> <include>propertiesData.properties</include> </source> <outlet name="classFrame"/> </output> </control>
The single "output" tag in this file contains the following definitions: Its name is "propertyKeys", the output is written to the file "org/apache/torque/gf/example/propertyenum/PropertyKeys.java", the source is read from "propertiesData.properties" in the src directory, and the outlet named "classFrame" (defined below) will be the master outlet for the output.
Configuring the outlets
Now that we have created the templates and defined our input and output, we must plug the templates into each other. The Torque generator encapsulates each template in a concept named "outlet". Each outlet acts on a source element and produces a String output from it. Also, an outlet can define so-called "mergepoints" where it can call insert the output of other outlets. To define the outlets, create one or more xml file in the "outlets" subdirectory ending with the suffix ".xml". Into each file, put a root element "outlets".
You need at least one outlet per template (you might have more than one if you want to fill the mergepoints differently). For each outlet, create a "outlet" tag in the "outlets" root element. Set the "xsi:type" attribute to "velocityOutlet", the "path" attribute to the path to the template relative to the "templates" subdirectory, and give it an unique name using the "name" attribute. For each mergepoint you want to fill in the outlet, create a "mergepoint" tag in the "outlet" tag with the "name" attribute set to the name of the mergepoint in the template. Define the action you want to execute (e.g. for calling another generator, use the apply action, or for looping through several elements, use the traverse-all action).
In our example, we define the following outlets in the file "outlets/enumOutlets.xml":
<?xml version="1.0" encoding="UTF-8"?> <outlets xmlns="http://db.apache.org/torque/4.0/generator/configuration" xsi:schemaLocation="http://db.apache.org/torque/4.0/generator/configuration http://db.apache.org/torque/4.0/generator/configuration.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <outlet name="classFrame" xsi:type="velocityOutlet" path="classFrame.vm"> <mergepoint name="constants"> <action xsi:type="traverseAllAction" element="entry" outlet="constant"/> </mergepoint> <mergepoint name="fields"> <action xsi:type="applyAction" outlet="keyField"/> <action xsi:type="applyAction" outlet="newline"/> </mergepoint> <mergepoint name="methods"> <action xsi:type="applyAction" outlet="constructor"/> <action xsi:type="applyAction" outlet="newline"/> <action xsi:type="applyAction" outlet="getKey"/> <action xsi:type="applyAction" outlet="newline"/> <action xsi:type="applyAction" outlet="toString"/> </mergepoint> </outlet> <outlet name="constant" xsi:type="velocityOutlet" path="constant.vm"> <mergepoint name="constantName"> <action xsi:type="applyAction" outlet="constantName"/> </mergepoint> </outlet> <outlet name="constantName" xsi:type="javaOutlet" class="org.apache.torque.generator.outlet.java.ConstantNameOutlet"> <inputSourceElement>.</inputSourceElement> <sourceElementAttribute>key</sourceElementAttribute> </outlet> <outlet name="keyField" xsi:type="velocityOutlet" path="keyField.vm"/> <outlet name="constructor" xsi:type="velocityOutlet" path="constructor.vm"/> <outlet name="getKey" xsi:type="velocityOutlet" path="getKey.vm"/> <outlet name="toString" xsi:type="velocityOutlet" path="toString.vm"/> <outlet name="newline" xsi:type="javaOutlet" class="org.apache.torque.generator.outlet.java.NewlineOutlet"/> </outlets>
So, e.g. in the "classFrame" outlet, the three mergepoints "constants", "fields" and "methods" are filled. In the "constants" mergepoint, the outlet "constant" is invoked for each "entry" child element of the current source element. The output is then concatenated and filled into the mergepoint. In the "fields" and "methods" mergepoints, a bunch of outlets is invoked on the current source element ("properties"), the output is concatenated for each mergepoint and inserted into the mergepoint.
The "constantName" and the "newline" outlets are a special case. The "constantName" outlet generates the name of a java constant from an input (e.g. converting to upper case, inserting underscores.) The "newline" outlet simply prints a newline (\n) each time it is called, and is used to beautify the output. Both outlets are java outlets which are provided by the Torque generator and are referenced by their class name. The "constantName" outlet needs some additional configuration to know from where to read its input.
Generating the source
There are currently two ways to run the generation process: Either the generation can be integrated into a Maven 3 build process, or a simple java program can be used.
Using the Torque Maven plugin
To hook the generation process into your Maven build, you need to install Maven. If you are new to Maven, read the Maven documentation. Then, add the following section to your pom.xml:
<build> <plugins> <plugin> <groupId>org.apache.torque</groupId> <artifactId>torque-maven-plugin</artifactId> <version>4.0</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <goals> <goal>generate</goal> </goals> <configuration> <!-- The torque generator files are provided in a project directory --> <packaging>directory</packaging> </configuration> </execution> </executions> </plugin> </plugins> </build>
Then, run "mvn generate-sources" in your project root dir, and the generated sources should show up in the target/generated-sources directory of your project.
Using plain java
As an alternative to Maven, you can also run the generation process using plain java. For this, you can use the following small program:
package org.apache.torque.generator.example.gettingstarted; import java.io.File; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.torque.generator.configuration.UnitDescriptor; import org.apache.torque.generator.configuration.paths.DefaultTorqueGeneratorPaths; import org.apache.torque.generator.configuration.paths.Maven2DirectoryProjectPaths; import org.apache.torque.generator.control.Controller; public class GettingStarted { public static void main(String[] argv) throws Exception { File target = new File("target/gettingStarted"); FileUtils.deleteDirectory(target); Controller controller = new Controller(); List<UnitDescriptor> unitDescriptors = new ArrayList<UnitDescriptor>(); unitDescriptors.add(new UnitDescriptor( UnitDescriptor.Packaging.DIRECTORY, new Maven2DirectoryProjectPaths( new File(".")), new DefaultTorqueGeneratorPaths())); controller.run(unitDescriptors); } }
Add all the dependencies of the torquegenerator into the classpath, and start this java program in the root directory of your project. The generated source will show up in the target/generated-sources subdirectory.