November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 1
Hacking Velocity – ApacheCon 2004
• What is Velocity
• Velocity Tools
• Custom Directives
• Custom Resourceloaders
• Custom Introspector
• Adding Event Handlers
• Modifying Velocity Syntax
Presenter Contact Info:
Will Glass-Husain
Menlo Park, California USA
+ 1 415 440-7500
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 2
What is Velocity?
• A templating engine that can generate any type of text output,
including HTML, email, SQL, or Java source code.
• Based on a "pull" approach in which a text file includes
"References" to data objects that are pulled from a provided
"Context".
$Date
Dear $Contact.FirstName $Contact.LastName,
Thank you for your recent purchase of
$Order.ProductName. The cost of the item was
$Order.TotalCost#if($Order.Tax > 0) including
a tax of $Order.TaxRate%#end.
Sincerely,
Biggest and Best Book Merchants, Inc.November 17, 2004
Dear Jennifer Glass,
Thank you for your recent purchase of Alice
in Wonderland, Annotated Edition. The cost
of the item was $29.95.
Sincerely,
Biggest and Best Book Merchants, Inc.
Order
Contact
Date
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 3
Basic Velocity Concepts
• References
– References objects from the Velocity context. Starts with a "$".
– Can have Properties or Methods
– Common practice is to provide two types: data objects (with
properties) and "tools" (with methods).
– Examples:• $contact.FirstName
• $format("$#,##0.00",$order.price)
• Directives
– Control statements. Starts with a "#"
– Example block directive:• #if ($columncount > 0)
display a table
#end
– Example line directive:• #include('header.vm')
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 4
How To Use Velocity
VelocityEngine ve = new VelocityEngine();
ve.setProperty("file.resource.loader.path","templates");
ve.init();
Context c = new VelocityContext();
c.add("order",order);
c.add("contact",contact);
Template t1 = ve.getTemplate("inputfile.vm");
StringWriter writer = new StringWriter();
t1.merge(c,writer);
String result = writer.toString();
Load a Template From a File
VelocityEngine ve = new VelocityEngine();
ve.init();
Context c = new VelocityContext().
c.add("order",order);
c.add("contact",contact);
StringWriter writer = new StringWriter();
ve.evaluate(c,writer,"velocity",inputstring);
String result = writer.toString()
Evaluate a Template stored as a String
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 5
Improve Productivity with Velocity-Tools
• GenericTools
– GenericTools is a group of reuseable and documented tools that can be added to a Velocity context.
A tool is simply a class which can perform various tasks when made available to the Velocity engine.
Most tools are optimized for use with an automatically managed toolbox (see VelocityViewServlet).
• VelocityView
– VelocityView contains a standalone servlet (VelocityViewServlet) which can render templates for web
applications.
– Also included is a Toolbox Manager which can automatically make "view tools" and data available to
the templates. Any class with public methods can be used as a tool in the template.
– VelocityLayoutServlet is an extension of the basic VelocityViewServlet that can render screen content
into common layout templates.
• VelocityStruts
– VelocityStruts is a set of tools for using the Velocity template engine as the view layer for a web
application built upon the Jakarta Struts framework.
– This work leverages the VelocityViewServlet and additional tools which make it easy to integrate the
Velocity with Struts. Several example hybrid applications are included.
from the web site:
http://jakarta.apache.org/velocity/tools/index.html
VelocityTools is a collection of Velocity subprojects with a common goal of creating tools and
infrastructure for building both web and non-web applications using the Velocity template
engine.
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 6
Example Tools
• DateTool
• IteratorTool
• MathTool
• NumberTool
• RenderTool
• AbstractSearchTool
• CookieTool
• ImportTool
• LinkTool
• ParameterParser
• ViewRenderTool
Generic Tools VelocityView (web) Tools
Context c = new VelocityContext();c.add("date",new DateTool());
Template t1 = ve.getTemplate("inputfile.vm");StringWriter writer = new StringWriter();t1.merge(c,writer);String result = writer.toString();
Using a Tool
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 7
VelocityViewServlet: Serve Velocity Pages Over the Web
• Quick and easy way to build a web app.
• Serves Velocity pages just like HTM or JSP files.
• Tools are automatically placed in the Velocity context
according to "toolbox.xml" config file. Excerpt:
<tool> <key>math</key> <scope>application</scope> <class>org.apache.velocity.tools.generic.MathTool</class></tool>
Note: VelocityViewServlet replaces deprecated VelocityServlet distributed with Velocity project
• Any tool that has a scope of session or request is passed session
and/or request info when it is initialized. (if the tool implements
ViewContext).
• VelocityViewServlet can be subclassed to add additionalfunctionality. Example (in distribution): VelocityLayoutServlet,which uses a 2 pass render in order to apply a common layout toall web pages.
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 8
VelocityViewServlet: Configuration Example (web.xml)
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Example application</display-name>
<!-- Define Velocity template compiler -->
<servlet>
<servlet-name>velocity</servlet-name>
<servlet-class>org.apache.velocity.tools.view.servlet.VelocityViewServlet
</servlet-class>
<init-param>
<param-name>org.apache.velocity.toolbox</param-name>
<param-value>/WEB-INF/toolbox.xml</param-value>
</init-param>
<init-param>
<param-name>org.apache.velocity.properties</param-name>
<param-value>/WEB-INF/velocity.properties</param-value>
</init-param>
</servlet>
<!-- Map *.vm files to Velocity -->
<servlet-mapping>
<servlet-name>velocity</servlet-name>
<url-pattern>*.vm</url-pattern>
</servlet-mapping>
</web-app>
WEB-INF/web.xml
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 9
VelocityViewServlet: Configuration Example (toolbox.xml)
<?xml version="1.0"?>
<toolbox>
<tool>
<key>date</key>
<scope>application</scope>
<class>org.apache.velocity.tools.generic.DateTool</class>
</tool>
<tool>
<key>params</key>
<scope>request</scope>
<class>org.apache.velocity.tools.view.tools.ParameterParser</class>
</tool>
<tool>
<key>cookie</key>
<scope>request</scope>
<class>org.apache.velocity.tools.view.tools.CookieTool</class>
</tool>
<data type="number">
<key>app_version</key>
<value>1.5</value>
</data>
<data type="string">
<key>app_name</key>
<value>A Carroll Compendium</value>
</data>
</toolbox>
WEB-INF/toolbox.xml
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 10
VelocityViewServlet: Configuration Example (velocity.properties)
# all optional
velocimacro.library = /WEB-INF/VM_global_library.vm
velocimacro.permissions.allow.inline = true
velocimacro.permissions.allow.inline.to.replace.global = false
velocimacro.permissions.allow.inline.local.scope = false
velocimacro.context.localscope = false
WEB-INF/velocity.properties
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 11
VelocityViewStruts: An Alternative To JSP
• ActionMessagesTool
• ErrorsTool
• FormTool
• MessageTool
VelocityStruts Tools
• StrutsLinkTool
• SecureLinkTool
• TilesTool
• ValidatorTool
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 12
Controlling Velocity Template Generation with a Custom Directive
• Consider – can the problem you are trying to solve be done
more simply with a Tool or VelocityMacro?
• If not, Velocity allows you to code your own custom directives by
doing the following:
– Create a class that extends
org.apache.velocity.runtime.directive.Directive
– Add the following to velocity.properties
userdirective = yourdirectiveclass
• Review the directives in the velocity source code for good
examples. For example, review
org.apache.velocity.runtime.directive.ForEach
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 13
Custom Directive Example: Generate Endnotes
#endNotes
All in the golden afternoon
(*1:In these prefatory verses Carroll recalls that "golden afternoon" in 1862 when heand his friend the Reverend Robinson Duckworth (then a fellow of Trinity College,Oxford, later canon of Westminster) took the three charming Liddell sisters on a rowingexpedition up the Thames. *)
Full leisurely we glide;For both our oars, with little skill, By little arms are plied,While little hands make vain pretence Our wanderings to guide.
(* 2:Note how this stanza puns three times with the word "little." "Liddle" waspronounced to rhyme with "fiddle." *)#end
Usage:
All in the golden afternoon
Full leisurely we glide;For both our oars, with little skill, By little arms are plied,While little hands make vain pretence Our wanderings to guide.
NOTES
1: In these prefatory verses Carroll recalls that "golden afternoon" in 1862 when he andhis friend the Reverend Robinson Duckworth (then a fellow of Trinity College, Oxford,later canon of Westminster) took the three charming Liddell sisters on a rowingexpedition up the Thames.
2:Note how this stanza puns three times with the word "little." "Liddle" was pronouncedto rhyme with "fiddle."
Results:
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 14
Custom Directive Example: Code Excerpt
public class EndNoteDirective extends Directive {
public String getName() { return "endNotes"; }
public int getType() { return BLOCK; }
public boolean render(InternalContextAdapter context, java.io.Writer writer,Node node)
throws ResourceNotFoundException,ParseErrorException,MethodInvocationException
{
// first, parse the entire block and retrieve the content
StringWriter internalwriter = new StringWriter();
// the node's children are the arguments and the body.
// here there should be only one child, since there are no arguments
node.jjtGetChild(0).render(context, internalwriter);
String sourcecontent = internalwriter.toString();
internalwriter.close();
// missing code creates new "finalcontent" based on "sourcecontent"
writer.write(finalcontent.toString());
return true;
}
}
Full code listings available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 15
Custom Directive Example: Configuration
## Load templates from classpath
resource.loader = class
class.resource.loader.class =
org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
## Specify the user directive
userdirective=com.jlamp.directive.EndNoteDirective
## Log messages displayed with log4j
runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log.logsystem.log4j.category = com.jlamp.velocity
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 16
Custom ResourceLoaders
• A ResourceLoader is a mechanism to load a template. Velocity
provides these built in ResourceLoaders:
– ClasspathResourceLoader
– DataSourceResourceLoader
– FileResourceLoader
– JarResourceLoader
• To implement your own resource loader, extend
org.apache.velocity.runtime.resource.loader.ResourceLoader
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 17
Custom ResourceLoader Example: Excerpt (FTP resource loader)
public class FtpResourceLoader extends ResourceLoader {
String username; String password; String hostname; String rootDirectory;
public void init (ExtendedProperties configuration)
{ username = configuration.getString("username");
password = configuration.getString("password");
hostname = configuration.getString("host");
rootDirectory = configuration.getString("root");
}
public long getLastModified (Resource resource)
{ String name = resource.getName();
if (name.startsWith("/")) name = name.substring(1);
return getFileModificationDate(username,password,hostname,rootDirectory + name).getTime();
}
public boolean isSourceModified (Resource resource)
{
long date = getLastModified(resource);
return (date == 0) || (date != resource.getLastModified());
}
public InputStream getResourceStream (String source) throws ResourceNotFoundException
{
if (source.startsWith("/")) source = source.substring(1);
InputStream i = getFileStream(username,password,hostname,rootDirectory + source);
if (i == null)
throw new ResourceNotFoundException(source);
else
return i;
}
}
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 18
Custom ResourceLoader Example: Configuration
## Load templates from FTP site
resource.loader = ftp
ftp.resource.loader.class = com.jlamp.resourceloader.FtpResourceLoader
ftp.resource.loader.username = templateuser
ftp.resource.loader.password = apricots
ftp.resource.loader.host = www.jlamp.com
ftp.resource.loader.root = /app/templates
## Log messages displayed with log4j
runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log.logsystem.log4j.category = com.jlamp.velocity
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 19
Adding an Event Handler
• Velocity provides a mechanism to intercept and modify parsingoperation.
• Currently, three event handlers available:
– ReferenceInsertionEventHandler: Modify the text that gets insertedfrom a reference (e.g. $date)
– NullSetEventHandler: Prevents logging of null #set operations
– MethodExceptionEventHandler: Intercept a method thrown by atool in the context.
• Event handlers are instantiated for each page that is parsed,and are "attached" to the context before the template ismerged:
EventCartridge ec = new EventCartridge();ec.addEventHandler(new CustomReferenceInsertionHandler());ec.attachToContext( context );
// now merge the template
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 20
Event Handler Example: Escape XML Entities (excerpt)
/**
* Escape all XML Entities in the reference insertion. Specifically, the following
* conversions are performed:
* <DL>
* <DT>&</DT><DD>&amp;</DD>
* <DT><</DT><DD>&lt;</DD>
* <DT>></DT><DD>&gt;</DD>
* <DT>"</DT><DD>&quot;</DD>
* </DL>
*/
public class EscapeXMLEntities implements ReferenceInsertionEventHandler {
/**
* Escape the XML entities for all inserted references.
*/
public Object referenceInsert(String reference, Object value) {
String val = value.toString();
return escapeText(val);
}
}
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 21
Custom Introspector
• The introspector is used to discover and execute reference
properties and method calls.
• The build in introspector calls get/set methods when accessing
properties, and does all method calls.
• Possible custom introspectors:
– Access public fields directly instead of calling get/set methods
– Prevent access of "dangerous" methods by untrusted template
designer such as getClassLoader() and newInstance.
(dirty little secret – any class can be instantiated in a Velocity
Template).
• A custom introspector is fairly complex to implement. The built-in
introspector includes 9 classes in two packages.
• Introspector needs to implement
org.apache.velocity.util.introspection.Uberspect
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 22
An Evil Velocity Template
Today's date: $today
Date class: $today.Class
## Let's do some mischief and instantiate an arbitrary class
#set ($fileclass = $today.Class.forName("java.io.File"))
#set ($stringclass = $today.Class.forName("java.lang.String"))
#set ($paramlist = [".."])
#set ($paramarray = $paramlist.toArray())
#set ($constructors = $fileclass.Constructors)
## Call the constructor java.io.File(String)
#foreach ($c in $constructors)
#if ($c.toString() == "public java.io.File(java.lang.String)")
#set ($file = $c.newInstance($paramarray))
$file
#end
#end
## Now, get the File list. Think about what else you could do!
## Bad, Bad, Bad!
#set ($files = $file.list())
App Directory contents:
#foreach ($f in $files)
$f
#end
Please! Use this knowledge to further the cause of the good, not of those with evil intent.
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 23
An Evil Velocity Template: Result (standard introspector)
Today's date: Mon Oct 18 01:26:06 PDT 2004
Date class: class java.util.Date
..
App Directory contents:
bin
build
classes_src
java
lib
results
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 24
An Evil Velocity Template: Result (custom introspector)
Today's date: Mon Oct 18 01:33:35 PDT 2004
Date class: class java.util.Date
App Directory contents:
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 25
Custom Introspector: Code Excerpt
/**
* Property getter
*/
public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
throws Exception
{
if (!checkObjectExecutePermission(obj,null))
{
rlog.warn ("Cannot retrieve get method from object of class " +
obj.getClass().getName() +
" due to execute security restrictions. ["
+ i.getLine() + "," + i.getColumn() + "]"
+ " in template " + i.getTemplateName()
+ ".");
return null;
}
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 26
Custom Introspector Example: Configuration
## Custom introspector
runtime.introspector.uberspect = com.jlamp.introspection.CustomIntrospector
## Load templates from classpath
resource.loader = class
class.resource.loader.class =
org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
## Log messages displayed with log4j
runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
runtime.log.logsystem.log4j.category = com.jlamp.velocity
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 27
Modifying Velocity Syntax
• Velocity Template Language (VTL) is specified in a grammar file
designed to be used with JavaCC (javacc.dev.java.net).
• Change the language syntax by editing Parser.jjt.
• Run jjtree and javacc to generate new Parser.java files
• Add new capabilities by creating new Node files and copying
into the package org.apache.velocity.runtime.parser.node
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 28
Modifying Velocity Syntax: Adding #{else}
• Problem: Adding text without a space in #if statement is difficult.
• Bad syntax:#if($condition)text1#elsetext2#end
• Use block comments #* *# to split #else and text (unreadable)#if($condition)text1#else#**#text2#end
• Proposed syntax – use optional brackets around directive#if($condition)text1#{else}text2#end
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 29
Modifying Velocity Syntax: Old Parser.jjt (excerpt)
<PRE_DIRECTIVE>
TOKEN :
{
<END: "end" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? >
{
inDirective = false;
stateStackPop();
}
| <IF_DIRECTIVE: "if">
{
SwitchTo(DIRECTIVE);
}
| <ELSEIF_DIRECTIVE: "elseif">
{
SwitchTo(DIRECTIVE);
}
| <ELSE_DIRECTIVE: "else" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? >
{
inDirective = false;
stateStackPop();
}
| <STOP_DIRECTIVE: "stop">
{
matchedToken.kind = EOF;
fileDepth = 0;
}
}
Full modification available as patch:
http://nagoya.apache.org/bugzilla/show_bug.cgi?id=7189
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 30
Modifying Velocity Syntax: Old Parser.jjt (excerpt)
<PRE_DIRECTIVE>
TOKEN :
{
<END: ( "end" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? )
| ("{end}" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? ) >
{
inDirective = false;
stateStackPop();
}
| <IF_DIRECTIVE: "if" | "{if}">
{
SwitchTo(DIRECTIVE);
}
| <ELSEIF_DIRECTIVE: "elseif" | "{elseif}">
{
SwitchTo(DIRECTIVE);
}
| <ELSE_DIRECTIVE:
( "else" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? )
| ( "{else}" ( ( " " | "\t" )* ( "\n" | "\r" | "\r\n" ) )? ) > {
inDirective = false;
stateStackPop();
}
| <STOP_DIRECTIVE: "stop" | "{stop}" > {
matchedToken.kind = EOF;
fileDepth = 0;
} }
Complete sample app available at:
http://jlamp.com/apachecon2004/
November 17, 2004 (c) 2004 Will Glass-Husain. Non-commercial redistribution permitted freely. 31
Modifying Velocity Syntax: Summary of Steps
1. Download project "jakarta-velocity" from Jakarta CVS
2. Modify "Parser.jjt"
3. Execute shell script "build":
4. If necessary (new functionality added), add new node files topackage org.apache.velocity.runtime.parser.node.
#!/bin/sh
echo "Running JJTree ..."
jjtree Parser.jjt
echo "Running JavaCC ..."
javacc Parser.jj
# Remove the generated nodes as they are now
# in a package of their own.
rm -f AST*
rm -f Node.java
rm -f SimpleNode.java
rm -f ParserVisitor.java