Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

M2T-JET-FAQ/How do I run a JET template from Java?

< M2T-JET-FAQ
Revision as of 09:04, 17 September 2009 by F.hackenberger.chello.at (Talk | contribs) (Directly instantiating a JET template)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Question

JET launch configurations run a JET 'transformation' (which executes a bunch of templates, and persists the template results in the workspace). But, how do I execute individual JET templates from a Java, and consume the template results from a Java program?

Answer

A number of mechanisms are possible, depending on your requirements, but they all require you to do the following three steps:

  1. create a JET2Context object to encapsulate your input and track execution errors
  2. create a JET2Writer instance to record your template results
  3. load and execute the template

Creating a JET2Context object

JET2Context objects are normally constructed as part of launching a JET transformation. Unfortunately, constructing one for direct template execution is a little tricky. The following static method does all the 'right' things.

/**
 * Create a JET context suitable individual template execution
 * @param modelRoot the model root (corresponds to XPath /). May be <code>null</code>
 * @param variables predefined JET variables. May be <code>null</code>.
 * @return the JET execution context
 */
public static JET2Context createJETContext(Object modelRoot,
        final Map<String, ?> variables) {
 
    Map<String, Object> copiedVariables = new HashMap<String, Object>(
        variables != null ? variables : 
        Collections.<String, Object> emptyMap());
 
    // ensure that c:iterate can set the XPath context object
    copiedVariables.put("org.eclipse.jet.taglib.control.iterateSetsContext", Boolean.TRUE);
 
    final JET2Context context = new JET2Context(modelRoot, copiedVariables);
 
    // this statement has the side effect of initializing tag handling
    TransformContextExtender.getInstance(context);
    return context;}

Note that the method defines the variable org.eclipse.jet.taglib.control.iterateSetsContext. This a new variable defined with the release of JET 1.0.0 - it enables the JET c:iterate tag to set the XPath context object to the object of the current iteration (thus allowing for shorter XPath expressions). Setting the variable in versions earlier than 1.0.0 has no effect.

A word about the JET2Context 'source'

When you create a JET2Context, you pass it an object that represents the 'source' to the templates. In practical terms this object gets mapped to the initial '/' in XPath expressions. If you are navigating ECore based documents, typical practice is to pass in an EMF Resource object. If you are navigating XML documents, pass in a org.w3c.dom.Document object. Or, you you have no intention of using absolute XPath objects in your template, you can simply pass null as the source object. In that case, you will want to pass set one or more variables that will provide the necessary parameters for your template.

Creating a JET writer

Creating a JET2Writer instance is much simpler. Most of the type, you should be content with BodyContentWriter, which merely buffer's the template contents. Here is a utility method that constructs a writer:

/**
 * Create a writer suitable for individual template execution.
 * Use {@link BufferedJET2Writer#getContent()} to retrieve the writer's contents
 * @return a JET writer
 */
public static BufferedJET2Writer createJETWriter() {
    return new BodyContentWriter();
}

Note that the method returns BufferedJET2Writer (an interface). This interface provides the very useful getContents() method, that allows you to retrieve the writer's contents. You can also use setContexts(String) to completely replace the existing contents. In particular, setContents("") may be useful.

Loading a JET template

There are three possibilities for running a JET template

  1. Directly instantiate the Java class created by the JET compiler
  2. Directly instantiate the 'template loader' class created by the JET compiler, and use that to load the template by it's path
  3. Use JET API to load the JET transformation plug-in, and provide you with a template loader.

Each method is successively more indirect. With that indirection, you get some benefits:

  • Method 2 allows you to refer to JET templates by the template file name, rather than know what Java class was produced by the JET compiler
  • Method 3 allows you to refer to a JET plug-in. This removes the need to know anything about Java classes generated by the JET compiler.
  • Method 3 allows you to take advantage of JET's ability to dynamically load JET projects from the workspace.
  • Method 3 allows you to parametrize which JET transform is actually loaded. Clients could then create JET projects that 'extend/override' a base implementation of the templates you provide. Clients can then add alternative implementations to your templates.

Directly instantiating a JET template

If you take this route, it is suggested that you explicitly specify the Java package and class name the compiler will generate in the <%@jet%> directive:

<%@jet class="MyTemplateClass" package="my.template.pkg"%>
... rest of your template ...

Then, to create a template instance, you simply do:

JET2Template template = new MyTemplateClass();
template.generate(context, out);

Note that, in order for another plug-in to 'see' your template classes, you may have to open the Plug-in Manifest editor, and, on the Runtime tab, export the package containing template.

If you use features like the <java:import> tag, you have to finalise the write before you use the string. Otherwise you imports will not be in the file. Usually the finalisation is done by the framework, so we have to do it ourselves. Implement the helper method below and then call

finalizeContent(writer, null, null);

after generating the template.

/**
 * @see org.eclipse.jet.taglib.workspace.ActionsUtil
 */
private static final void finalizeContent(JET2Writer writer, Object committedObject, String existingContent)
		throws JET2TagException {
	final IWriterListener[] eventListeners = writer.getEventListeners();
	for (int i = 0; i < eventListeners.length; i++) {
		if (existingContent != null && eventListeners[i] instanceof IWriterListenerExtension) {
			((IWriterListenerExtension) eventListeners[i])
					.finalizeContent(writer, committedObject, existingContent);
		} else {
			eventListeners[i].finalizeContent(writer, committedObject);
		}
	}
}

Directly instantiating a template loader

The JET compiler creates a class that is responsible for loading templates in the project. The default name of the class is _jet_transformation, but its package name will vary. If you want to use this approach, it is strongly recommended that you modify the JET projects' plugin.xml to specify a name that you will remember.

Here is a typical JET project's plugin.xml. Modify the templateLoaderClass attribute to the fully qualified name of the loader you want to use:

<plugin>
   <extension
         id=""
         name=""
         point="org.eclipse.jet.transform">
      <transform 
            startTemplate="templates/main.jet" 
            templateLoaderClass="my.jet.compiled.MyTemplateLoader" 
            enableEmbeddedExpressions="true"
            >
         <description></description>
         <tagLibraries>
            <importLibrary id="org.eclipse.jet.controlTags" usePrefix="c" autoImport="true"/>
            <importLibrary id="org.eclipse.jet.javaTags" usePrefix="java" autoImport="true"/>
            <importLibrary id="org.eclipse.jet.formatTags" usePrefix="f" autoImport="true"/>
            <importLibrary id="org.eclipse.jet.workspaceTags" usePrefix="ws" autoImport="false"/>
         </tagLibraries>
      </transform>
   </extension>
</plugin>

While you are in the Plug-in Manifest editor, make sure that the package containing the loader class is properly exported (see the Runtime tab).

Then, to load your template, you would code:

JET2TemplateLoader loader = new MyTemplateLoader();
// Load the template 'templates/myStuff.jet'
JET2Template template = loader.getTemplate("templates/myStuff.jet");
if(template != null) {
    template.generate(context, out);
}

Use JET2 API's to load a JET project

This method, while the most complex is also the most flexible. You do not need to explicitly code class names for JET templates or the template loader, nor do you need to modify the JET project's Runtime exports. You need to know the plug-in of the JET project and the project relative path of the JET template, then you write a bit of code like this:

try {
    // use templates from 'my.jet' project
    JET2TemplateManager.run(new String[] {"my.jet"}, new JET2TemplateManager.ITemplateOperation() {
        public void run(ITemplateRunner templateRunner) {
            // Run the template 'templates/myStuf.jet'
            templateRunner.generate("templates/myStuff.jet", context, out);
        }
    });
} catch (BundleException e) {
    // handle failure to load plugin 'my.jet'", e);
}

The JET2TemplateManager takes care of finding the listing plug-in (or plug-ins), and loading them, if they are not loaded. Once the ITemplateOperation completes, the template manager will clean up appropriately.

Note that although this above example only runs one template in the template operation, it is certainly possible to run many templates.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.