In Turbine, we have excellent integration with the template tool Velocity, we call this VelocitySite building. The reason why we want to wrap Turbine around Velocity instead of using it on its own is to provide a completely MVC model for building web applications where the framework has control over the authentication, security, connection pool, etc and Velocity is simply used as the View portion of the MVC model. Turbine is responsible for helping you manage all the different templates as well so that you can easily construct a site that both designers and engineers can work together on (that is our primary goal!). The reason why this is good is that it will help you design and build web applications that have more functionality and less duplication of code since Turbine is fully re-usable.
Knowledge of how Velocity works and what a Velocity Context object is are required for understanding this documentation. This documentation also assumes that you are using a Servlet API 2.2 and higher servlet engine such as Tomcat because we are now targeting towards using WAR archives. Although, this should work with older servlet engines as well...it just may be slightly harder for you to setup. You should also be familiar with the rest of the Turbine documentation that is referenced on the index page.
Here is a brief description of the way that the system works: all requests are passed through the Turbine Servlet. The servlet is then responsible for brokering the request, building up the Context object and then calling Velocity's template engine to process your template document and return the results. Pretty simple. Now, lets move on to some more detailed instructions...:-)
Lets start off with a simple Velocity Screen to get things rolling so you can see how powerful things are. You should compile this class into your WEB-INF/classes directory.
Note: In the examples below, you should replace com.yourcompany.app with the correct value for your application.
package com.yourcompany.app.modules.screens; // Velocity Stuff import org.apache.velocity.context.Context; // Turbine Stuff import org.apache.turbine.util.RunData; import org.apache.turbine.modules.screens.VelocityScreen; public class HelloWorld extends VelocityScreen { public void doBuildTemplate( RunData data, Context context ) throws Exception { // the context object has already been setup for you! context.put ("hello", "this is a test..."); } }
Ok, as you can see, you do not even need to return a value from your method! You simply stuff objects into the Velocity Context and that is it! Next, you will want to create a .vm template of the same name as your class: HelloWorld.vm...within that template you will put the BODY portion of your page. In other words, there is no reason to put the header/footer into this file since that will be built seperately (more documentation on that futher down...). You should save this document in your templates/screens/ directory.
You will also need to modify your TurbineResources.properties file to tell Turbine where to look for your new screen class.
module.packages=org.apache.turbine.modules, com.yourcompany.app.modules
<p> <font color="red"> $hello of the emergency broadcast station. </font> </p>
When you request a URL like this:
http://www.server.com/servlet/Turbine/template/HelloWorld.vm
What that will do is cause the system to first execute the HelloWold.java class (if it exists) and then it will call Velocity's template engine directly and execute the HelloWorld.vm template and then return the results back to you.
NOTE: Turbine capitalizes the first letter in the class file name before looking for the matching class in the classpath. This allows you to follow (somewhat) normal class naming guidelines. For example:
index.vm and Index.vm both map to Index.class roleeditor.vm maps to Roleeditor.class role_editor.vm maps to Role_editor.class
The result will be a fully formed HTML document with the $hello replaced with the value: "this is a test...". If you want to make the URI above shorter or easier to read, you could simply re-write things using mod_rewrite. For example, by removing the "/servlet/Turbine/template" portion of the URI.
As you can see, this is much easier than doing servlet after servlet because it removes a lot of the setup and other things that could potentially go wrong. It also makes the individual template files easier to manage and more re-usable because there isn't any surrounding page layout mixed in with the page.
If you want to do something that is more complicated than this that is reflected across all of your Screens, then you should write a class that is a subclass of VelocityScreen and then put your specific code in there. For example, if you wanted a security method to be called automaticially without having to normally call super() to get it from an overridden method in the superclass, you could have something like this (semi-pseudo untested code):
package com.yourcompany.app.modules.screens; // Velocity Stuff import org.apache.velocity.context.Context; // Turbine Stuff import org.apache.turbine.util.RunData; import org.apache.turbine.modules.screens.VelocityScreen; public abstract class SecureScreen extends VelocityScreen { protected boolean doCheckSecurity(RunData data) throws Exception { if (data.isSecure()) return true; else return false; } /* * override the doBuild() method of the * TemplateScreen to always check security first */ public void doBuild(RunData data, Context context) throws Exception { if ( !this.doCheckSecurity(data)) { // set the template and screen to be different or do // some other short circuit code here } doBuildTemplate(data, context); return super.buildTemplate(data, context); } }
What this would do is override the doBuild() method in TemplateScreen (which is the base class that VelocityScreen subclasses from) and have it always do a security check before displaying the content. The benefit of this is that all you need to do to add security to your screen is to simply subclass this class instead of VelocityScreen. There is also no need to remember to call super() since the doBuild() method will be called for you automatically by Turbine. You can start to see how Turbine is simply an extension of the servlet framework itself. For example, the Servlet Engine will call HttpServlet's service() method for you, just like Turbine will call your doBuild() method for you. Tight integration with tools like Velocity just make things even cleaner. :-)
One other feature in this system is that if you are not building up a Context object that is specific for your screen, then you do not need to create a Java class to match the template file. You simply create the .vm template and put it in a directory and call it with the same URI described above. This is really useful when you have a static site of content that you are converting to be dynamic and you only want to do small portions of it at any one time. You can also do the opposite of this which is to only create a Java class file and no template file. You can do this by simply overriding the doBuild() method in TemplateScreen and returning your results directly instead of attempting to build a template. Later, when you want to add a template and remove the HTML code, you can change the doBuild() into a doBuildTemplate(), build the Context object up and you are done. Cool!
Here is an example of having templates in subdirectories and making links to each of them.
Create a directory structure like this:
WEB-INF/templates/screens/admin/ Put index.vm into the screens/ directory. Put UserAdmin.vm into the screens/admin/ directory.
In index.vm, if you want to link to UserAdmin.vm, you would add something like this to the HTML:
<a href="$link.setPage("admin,UserAdmin.vm")">User Admin Screen</a>
As you can see above, I used a "," instead of a "/". The above will create a fully formed URI with the session information encoded into the link if the \ client browser has cookies turned off.
If you really want to use a slash instead of the comma when using the $link tool, you will need to make a change to your TurbineResources.properties file. You need to change the value of tool.request.link from org.apache.turbine.util.template.TemplateLink to org.apache.turbine.util.template.TemplateLinkWithSlash.
You will incur a slight performance hit since this class will replace the slash for with a comma for you. The URL that is build as a result will be identical.
Now that you know how to create simple Screens, you are probably wondering where the layout and navigation portions of the page come from and how you control that. If you were not wondering that, then shame on you. :-) Essentially, it is the same exact procedure as before except you subclass VelocitySiteLayout and VelocitySiteNavigation instead. Again, it is possible to not have Java class files always match up with the template and in most cases, you probably won't need to have a user defined Context in the Layout.
Below is an example Layout. It will be searched for in the templates/layouts directory structure and also takes advantage of the same template path lookup code as described below:
#if($data.Message) $data.Message #end <table width="100%"> <tr> <td>$navigation.setTemplate("/default_top.vm")</td> </tr> <tr> <td>$screen_placeholder</td> </tr> <tr> <td>$navigation.setTemplate("/default_bottom.vm")</td> </tr> </table>
The variable $screen_placeholder is important here because that is where the output from your Screen will be placed. VelocitySiteLayout is responsible for taking care of this. The other variables are for including your Navigations into the system. The benefit of all of this is that it enforces the View portion of the MVC model because the "body" and "navigation" portions of your page simply becomes a variable that you can plug into any number of Layouts and in any location. If you want to write code so that the Layout is determined by a variable in the database or in the HTTP request or whever, all you need to do is write your own VelocitySiteLayout and override some of the methods in there to determine the layout based on your own conditions instead of the system default conditions.
Since everything is keyed off the template variable, if data.getParameters().getString("template") returns /about_us/directions/driving.vm, the search for the Screen class is as follows (in order):
If the template variable does not exist, then VelocityScreen will be executed and templates/screens/index.vm will be executed. If index.vm is not found or if the template is invalid or Velocity execution throws an exception of any reason, then templates/screens/error.vm will be executed.
For the Layouts and Navigations, the following paths will be searched in the layouts and navigations template subdirectories (in order):
Actions happen when you have an action parameter defined in the URI. For example:
In this case, what happens is that the class UpdateWorld class (located in your WEB-INF/classes/com/yourcompany/app/modules/actions/ directory) is executed first before anything. Then your HelloWorld class (located in your WEB-INF/classes/com/yourcompany/app/modules/screens/ directory) is executed. Then your template (for screen/navigation/layout) HelloWorld.vm is executed. The point of an action is that it should perform some sort of "action" on your system. Usually, this means storing some information from a POST request into a database or sending email or something of that nature. Actions themselves do not return results, but may set a message with data.setMessage(). They can also short circuit the system by changing the Template and Screen to be executed. You might want to do that if there is an error with the form data and you want to re-display the same page again. Unlike most of the rest of the modules (ie: Screens, Navigations, Layouts), there is no corresponding Velocity template file for an Action.
package com.yourcompany.app.modules.actions; // Velocity Stuff import org.apache.velocity.context.Context; // Turbine Stuff import org.apache.turbine.util.RunData; import org.apache.turbine.modules.actions.VelocityAction; public class AddUser extends VelocityAction { public void doPerform( RunData data, Context context ) throws Exception { if ( data.getParameters().getString("username",null) == null) { data.setMessage("Username does not exist"); setTemplate ( data, "AddUser.vm" ); return; } // store user info into database data.setMessage("Information stored!"); setTemplate( data, "MainMenu.vm"); // stuff something into the Velocity Context context.put ("variable", "foo"); } }
In the very basic example Action above, a check is performed to make sure that the form data contained a "username" variable. If there is no data, the template is changed back to the "AddUser" template and the processing is stopped with an error message that can be easily displayed in the template. If the processing finishes, then the MainMenu.vm template will be shown and the message can be displayed.
There is also a new feature of Turbine that the VelocitySiteAction takes advantage of, it is called Action Event. Please click the link and read more documentation about it. This is an excellent way to write your actions because now they are entirely event driven based on which button was clicked in the HTML form.
Frames are easily achieved with Velocity, the frameset tag being implemented at the screen level. The default setting for the layout.default directive in the TurbineResources.properties is the VelocityECSLayout class. The VelocityECSLayout wraps the Screen in Body tags. For frames this needs to be removed. The layout.default needs to be changed to;
layout.default=VelocityOnlyLayout
The layout Velocity template needs to point to the screen_placeholder. As an example modify the Default.vm in the /WEB-INF/templates/layouts/;
$screen_placeholder
The screen_placeholder marker will load and process the template marked as the template.homepage in the TurbineResources.properties. To create frames this is the template which will need to contain the frameset tag. Edit /WEB-INF/templates/screens/Index.vm;
$page.setTitle("Frames example"); <frameset> <frame src="$link.getPage("FrameTop.vm")" /> <frame src="http://www.something.com/" /> </frameset>
Create a FrameTop.vm template in the /WEB-INF/templates/screens directory. The top frame will load this template. The reference to something.com requires an internet connection and is shown as an example, anything can be linked to the lower frame, another Velocity Screen for instance.
## Example FrameTop.vm <p><b>The FrameTop Velocity Template</b>
The VelocityService can support many paths for the Velocity Templates to be loaded and read from. This is specified in the TurbineResources.properties file;
services.VelocityService.file.resource.loader.path = /templates,/my-templates,/more-tempates
The multiple paths are comma delimited and are specified from the Servlet Engines context.