for example:
Map <String, Locale > currencyCodeToLocaleMapping = new HashMap<String, Locale>();
for( Iterator <Entry <String, Locale>> item = currencyCodeToLocaleMapping.entrySet().iterator(); item.hasNext(); ) {
Entry<String, Locale> theEntry = item.next();
//more stuff
}
Saturday, January 10, 2009
Wednesday, January 7, 2009
Run Setup steps for remote profiling on Sun Sparc box
1. Install JProfiler :
on the Sun Sparc box, say, /local/apps/jprofiler. Also you need to install JProfiler on your local box.
2. Create a JProfiler session on your local box
2.1 Start JProfiler on your local box and you will see a Quickstart wizard.
If you do not see this wizard go to Help | Show Quickstart Dialog (or click Shift - F1)
2.2 Select the radio button labled "An application server, locally or remotely", click Next
2.3 Select "Generic Application Server" from the dropdown, and Next
2.4 Check "On a remote computer" and select "Solaris sparc 32/64" from the dropdown. Click Next
2.5 Enter the server name (URL like: server1.yourcompany.com), then click Next
2.6 Enter the jprofiler install directory path on the Solaris box (like '/local/apps/jprofiler') then click Next
2.7 On next page, select:
JVM Vendor: Sun
Version: 1.5.0
Mode: hotspot
2.8 Click Next and enter port number 8082 (this may differ per Solaris box, 9130 appears to be a good alternate)
2.9 check "Don't wait, start immediately" and click Next
2.10 Enter the '/local/apps/jprofiler' on the top text box and click Next
2.11 On this dialog box, you will see an instruction. You only need to take a note on the port=XXXX and id=YYY. Make sure the port and id match with the ones in resin.conf.jprofiler that you will create below. Go to the next step.
2.12 Click Next, check "No, I will start the session later" and click Finish
3. On Sun server box
3.1. Create a new shell script to run the server:
LD_LIBRARY_PATH="/local/apps/jprofiler/bin/solaris-sparc"
export LD_LIBRARY_PATH
3.2 Add a new configuration file to run to be used by the above shell script
add following two lines to the beginning of JVM section:
-agentlib:jprofilerti=port=8082,nowait,id=115,config=/local/apps/jprofiler/config/config.xml
-Xbootclasspath/a:/local/apps/jprofiler/bin/agent.jar
Change the value of the id to match the session id you created above
The port number may be different, depending on the availability on the box (9130 seems to be a good alternate port to use). Remember, it must match what you created in your JProfiler session above.
Remove lines following lines if they exist:
-Xdebug
-Xnoagent
3.3 Copy the config.xml file from your local box to /local/apps/jprofiler/config
3.4 Start the app by running on the Solaris box
4. Start JProfiler session that was created a few steps back
Go to Session | Start Center
Find your session on the Open Session tab
Click Start
On the Session Startup screen click OK and you will attach the profiler to the Resin instance.
on the Sun Sparc box, say, /local/apps/jprofiler. Also you need to install JProfiler on your local box.
2. Create a JProfiler session on your local box
2.1 Start JProfiler on your local box and you will see a Quickstart wizard.
If you do not see this wizard go to Help | Show Quickstart Dialog (or click Shift - F1)
2.2 Select the radio button labled "An application server, locally or remotely", click Next
2.3 Select "Generic Application Server" from the dropdown, and Next
2.4 Check "On a remote computer" and select "Solaris sparc 32/64" from the dropdown. Click Next
2.5 Enter the server name (URL like: server1.yourcompany.com), then click Next
2.6 Enter the jprofiler install directory path on the Solaris box (like '/local/apps/jprofiler') then click Next
2.7 On next page, select:
JVM Vendor: Sun
Version: 1.5.0
Mode: hotspot
2.8 Click Next and enter port number 8082 (this may differ per Solaris box, 9130 appears to be a good alternate)
2.9 check "Don't wait, start immediately" and click Next
2.10 Enter the '/local/apps/jprofiler' on the top text box and click Next
2.11 On this dialog box, you will see an instruction. You only need to take a note on the port=XXXX and id=YYY. Make sure the port and id match with the ones in resin.conf.jprofiler that you will create below. Go to the next step.
2.12 Click Next, check "No, I will start the session later" and click Finish
3. On Sun server box
3.1. Create a new shell script to run the server:
LD_LIBRARY_PATH="/local/apps/jprofiler/bin/solaris-sparc"
export LD_LIBRARY_PATH
3.2 Add a new configuration file to run to be used by the above shell script
add following two lines to the beginning of JVM section:
Change the value of the id to match the session id you created above
The port number may be different, depending on the availability on the box (9130 seems to be a good alternate port to use). Remember, it must match what you created in your JProfiler session above.
Remove lines following lines if they exist:
3.3 Copy the config.xml file from your local box to /local/apps/jprofiler/config
3.4 Start the app by running on the Solaris box
4. Start JProfiler session that was created a few steps back
Go to Session | Start Center
Find your session on the Open Session tab
Click Start
On the Session Startup screen click OK and you will attach the profiler to the Resin instance.
Run JProfiler On ATG Dynamo - part 1, local window box
Create a JProfiler session
1. Start JProfiler and you will see a Quickstart wizard.
If you do not see this wizard go to Help | Show Quickstart Dialog (or click Shift - F1)
2. Select the radio button labeled "An application server, locally or remotely" and click Next
3. Select 'Generic Application Server' from the dropdown and click Next
4. Check "on this computer" if you profiling on your local box and click Next
5. On next page, select:
JVM Vendor: Sun
Version: 1.4.2
Mode: hotspot
6. Click Next twice to get to the "wait for JProfiler GUI" screen
7. Check "Don't wait, start immediately" and click Next twice
8. On this page, it say "The created session has been named XXXXXX". Take a note of the session name as you will need to look it up to start it and get the session id. Go to the next step.
9. Also on the same page you will see the two sets of arguments that need to be added after the java command line. Copy or take a note of those also.
10. Check "No, I will start the session later" and click Finish
Modify postEnvironment.bat(C:\ATG\ATG7.0\home\localconfig\postEnvironment.bat)
1. Copy and NOT delete, the config.xml from the D:\Documents and Settings\yourid\.jprofiler5\config.xml to a new path where ATG can read (it cannot read directories which have spaces) it and do the same with the agent.jar. For example if you see below i added it to a new folder under D:\. So now ATG will read the config files from this new location and JProfiler from the original location(which is D:\Documents and Settings\yourid\.jprofiler5\config.xml).
2. Add the following lines at the end of all lines in the postEnvironment.bat. These are similar to the ones which you have saved in the above steps except the location of the config.xml and agent.jar have to be modified since ATG cannot read if the path contains a space such as "Documents and Setttings"
set JAVA_ARGS=%JAVA_ARGS% -Xrunjprofiler:port=8849,nowait,id=107,config=D:\jprofilerconfig\config.xml
set JAVA_ARGS=%JAVA_ARGS% -Xbootclasspath/a:D:\jprofilerconfig\agent.jar
To find the session id:
Go to JProfiler and select Session | Start Session.
Find the new session that you created in above (look for the name that you noted when it created the session).
Click on the new session under the Open Session tab and then click the Edit icon.
The session id will be appear after the session name.
The config.xml file is located in your documents and settings directory in the .jprofiler directory (so, something like: D:\Documents and Settings\yourid\.jprofiler5\config.xml where you should change HuangX to your user name).
If you needed to specify a different port for JProfiler to listen to during the configuration of the session above, you should change the port number here too.
3. Remove lines the following lines if they exist in postEnvironment.bat:
-Xdebug
-Xnoagent
4. Save the changes to the postEnvironment.bat and rebuild the app.
Start ATG Dynamo with your normal startDyanmo.bat file
1. Start JProfiler and you will see a Quickstart wizard.
If you do not see this wizard go to Help | Show Quickstart Dialog (or click Shift - F1)
2. Select the radio button labeled "An application server, locally or remotely" and click Next
3. Select 'Generic Application Server' from the dropdown and click Next
4. Check "on this computer" if you profiling on your local box and click Next
5. On next page, select:
JVM Vendor: Sun
Version: 1.4.2
Mode: hotspot
6. Click Next twice to get to the "wait for JProfiler GUI" screen
7. Check "Don't wait, start immediately" and click Next twice
8. On this page, it say "The created session has been named XXXXXX". Take a note of the session name as you will need to look it up to start it and get the session id. Go to the next step.
9. Also on the same page you will see the two sets of arguments that need to be added after the java command line. Copy or take a note of those also.
10. Check "No, I will start the session later" and click Finish
Modify postEnvironment.bat(C:\ATG\ATG7.0\home\localconfig\postEnvironment.bat)
1. Copy and NOT delete, the config.xml from the D:\Documents and Settings\yourid\.jprofiler5\config.xml to a new path where ATG can read (it cannot read directories which have spaces) it and do the same with the agent.jar. For example if you see below i added it to a new folder under D:\. So now ATG will read the config files from this new location and JProfiler from the original location(which is D:\Documents and Settings\yourid\.jprofiler5\config.xml).
2. Add the following lines at the end of all lines in the postEnvironment.bat. These are similar to the ones which you have saved in the above steps except the location of the config.xml and agent.jar have to be modified since ATG cannot read if the path contains a space such as "Documents and Setttings"
set JAVA_ARGS=%JAVA_ARGS% -Xrunjprofiler:port=8849,nowait,id=107,config=D:\jprofilerconfig\config.xml
set JAVA_ARGS=%JAVA_ARGS% -Xbootclasspath/a:D:\jprofilerconfig\agent.jar
To find the session id:
Go to JProfiler and select Session | Start Session.
Find the new session that you created in above (look for the name that you noted when it created the session).
Click on the new session under the Open Session tab and then click the Edit icon.
The session id will be appear after the session name.
The config.xml file is located in your documents and settings directory in the .jprofiler directory (so, something like: D:\Documents and Settings\yourid\.jprofiler5\config.xml where you should change HuangX to your user name).
If you needed to specify a different port for JProfiler to listen to during the configuration of the session above, you should change the port number here too.
3. Remove lines the following lines if they exist in postEnvironment.bat:
4. Save the changes to the postEnvironment.bat and rebuild the app.
Start ATG Dynamo with your normal startDyanmo.bat file
Monday, January 5, 2009
Using Velocity Engine To Process Template and Sending email
Summary: This document will describe a way of how to use velocity as standard alone application to send email with the features of internalization. A simple example is given to send email in English and Chinese.
Key words: Velocity, Resource Bundle, format currency, format date, UTF-8, Apache email.
This example is about car reservation. The car rental company sent email to the customer after the customer made a reservation.
Reservation Data
Java bean data objects:
1. Guest records customer’s first name, last name and email address
2. Car presents type of car of rental, FORD, TOYOTA etc.
3. Reservation encapsulates confirmation number, a guest, a car, charge, days of rental and start rental date.

Test
TestVelocity is used to construct test data and call template process and then send email.
public class TestVelocity
{
private static Logger sLogger = Logger.getLogger( TestVelocity.class.getName() );
/**
* email host server
* You got to change it to your email host.
*/
private final static String EMAIL_HOST = "your.email.host";
/**
* Process Velocity template and send email
* @param locale
* @throws Throwable
*/
private void processVelocityAndSendEmail(Locale locale)
throws Throwable
{
sLogger.info( "processVelocityAndSendEmail" );
//prepare data for template
Reservation reservation = constructCarReservatoin();
if (sLogger.isDebugEnabled()) {
sLogger.debug("\n");
sLogger.debug( reservation.toString() );
}
//1. get email body
ReservationTemplateProcessor resTemplateProcessor = new ReservationTemplateProcessor();
String body = resTemplateProcessor.processReservationTemplate( reservation, locale );
//2. send email
String subject = "Welcome to rent Ford's car";
String fromAddress = "rent.car@ford.com";
String toAddress = reservation.getGuest().getEmail();
if (sLogger.isDebugEnabled()) {
sLogger.debug("\n");
sLogger.debug( "body=" + body );
}
EmailUtils emailUtils = new EmailUtils();
emailUtils.setHostName( EMAIL_HOST );
emailUtils.sendEmail( fromAddress, toAddress, subject, body );
}
/**
* process
* @param args
* @throws Throwable
*/
private void process(String[] args)
throws Throwable
{
String arg1="en";
if (args.length>0) {
arg1 = args[0];
}
Locale locale = null;
if ("zh".equalsIgnoreCase( arg1 )) {
locale = new Locale("zh", "CN");
}
else {
locale = new Locale("en", "US");
}
sLogger.info( "language="+arg1);
// 1. test velocity
processVelocityAndSendEmail(locale);
}
public static void main( String[] args )
{
try {
String log4j = System.getProperty( "log4j.configuration", "log4j.properties" );
sLogger.info( "log4j="+log4j);
PropertyConfigurator.configure(log4j);
sLogger.info("Entering application.");
new TestVelocity().process(args);
} catch( Throwable e ) {
e.printStackTrace();
}
}
/**
* Construct fake data for testing
* @return
*/
private Reservation constructCarReservatoin()
{
Reservation reservation = new Reservation();
reservation.setConfirmationNumber( "124457788" );
Guest guest = new Guest();
guest.setFirstName( "Bob" );
guest.setLastName( "Park" );
guest.setEmail( "michael.wang@ichotelsgroup.com" );
reservation.setGuest( guest );
Car car = new Car();
car.setId( Car.FORD );
reservation.setCar( car );
reservation.setCharge( 123.56 );
reservation.setRentDays( 2 );
Date today = new Date();
reservation.setStartDate( today );
return reservation;
}
}
TestVelocity initializes log4j, constructs data through constructCarReservatoin() method, takes language value from command argument and call constructCarReservatoin(Locale locale) to process template to get body of email and send email.
Note: private final static String EMAIL_HOST = "your.email.host";
The your.email.host needs to be your real email server in order to send email.
Reservation Process
ReservationTemplateProcessor is created to decouple velocity service from test client TestVelocity,java. It sets default resource bundle path and velocity template location.
The method of processReservationTemplate does two things:
1. takes reservation and locale objects to fill TemplateRequest with default resource bundle path, locale, template location, reservation and comments.
2. call TemplateProcessorFactory to get VelocityTempateProcess instance and invoke velocity process by passing templateReuqest value.
Notes:
1. So far, there is not velocity code involved.
2. The default resource bundle path set here is for convenience, in template you can use different resource bundle.
Template Process
Template process relies on velocity template engine to process reservation template with reservation object.
The constructor of VelocityTemplateProcessor loads velocity properties from classpath and initialized Velocity Engine.
public VelocityTemplateProcessor()
{
InputStream iStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(VELOCITY_PROPERTIES );
Properties props = new Properties();
if ( iStream == null ) {
throw new TemplateProcessorException(
"Could not find velocity configuration file: velocity/velocity.properties in class path" );
}
try {
props.load( iStream );
mVelocityEngine = new VelocityEngine();
mVelocityEngine.init( props );
} catch( Throwable e ) {
throw new TemplateProcessorException( "could not initilize ", e );
}
}
The method of createContext method creates instance of TemplateContext and save local, default resource bundle path to it.
Additionally, it sets local, default resource bundle path and encoding type to VelocityContext.
The encoding type is defined in velocity properties file to VelocityContext.
Note:
1. TemplateContext is created and saved to velocityContext. The purpose of doing this is for the call back from template to display resource bundle content, currency format, decimal format, date and time format etc.
2. Velocity Macro is defined in velocity configuration file and is loaded to VelocityEngine also.
private VelocityContext createContext( TemplateRequest templateRequest )
throws TemplateProcessorException
{
try {
VelocityContext context = new VelocityContext();
/*---------------------------------------------------
* add common setting here ...
*---------------------------------------------------*/
Locale locale = (Locale)templateRequest.getMap().get(
TemplateConstant.TMPL_LOCALE );
TemplateContext templateContext = new TemplateContext(locale);
String defaultResourceBundlePath = (String)templateRequest.getMap().get(
TemplateConstant.TMPL_DEFAULT_RESOUCE_BUNDLE_PATH );
templateContext.setDefaultResourceBundlePath( defaultResourceBundlePath );
context.put( TemplateConstant.TMPL_CONTEXT_REFERENCE, templateContext );
context.put( TemplateConstant.TMPL_LOCALE, locale );
context.put(
TemplateConstant.TMPL_DEFAULT_RESOUCE_BUNDLE_PATH, defaultResourceBundlePath );
// adding output encoding type: TMPL_OUTPUT_ENCODING
context.put(
TemplateConstant.TMPL_OUTPUT_ENCODING,
mVelocityEngine.getProperty( "output.encoding" ) );
return context;
} catch( Throwable e ) {
throw new TemplateProcessorException( "Could not createContext "
+ templateRequest.toString(), e );
}
}
The method of process takes templateRequest object and create Template object.
The values of templateRequest are passed to VelocityContext.
Note: As configuration file defines input.encoding=UTF-8, so we do not need to do anything in this method for processing UTF-8.
public String process( TemplateRequest templateRequest )
throws TemplateProcessorException
{
sLogger.debug( "Start template processing" );
try {
Template template = mVelocityEngine.getTemplate( templateRequest.getPath() );
VelocityContext context = createContext( templateRequest );
// 1. assign client value to velocity context
Map vMap = templateRequest.getMap();
String key;
Object value;
if ( vMap != null ) {
Iterator it = vMap.entrySet().iterator();
while ( it.hasNext() ) {
Map.Entry entry = (Map.Entry)it.next();
key = (String)entry.getKey();
value = entry.getValue();
context.put( key, value );
}
}
// 2. bind with context and return String Writer
StringWriter writer = new StringWriter();
template.merge( context, writer );
writer.close();
return writer.getBuffer().toString();
} catch( Throwable e ) {
throw new TemplateProcessorException( "Could not process template "
+ " request is " + templateRequest.toString(), e );
}
}
Email Template
Velocity does not care the file extension, we use html so that we can view the layout easily.
vm_macro.html is macro file and load as velocity init. It defines many useful macros for formatting, retrieving resource bundle etc.
#*
* vm_macro.vm
* include all common macro for vm template.
*
*#
#*-- define macro to retrieve --*#
#*-----------------------------------------------------------------
* define resource content macro
* #vm_content("email_new_reservation_html.sectionheader.reservationresources" )
*
* #set( $resourceBundle = "com.ihg.dec.framework.uiServices.i18n.resources.jsp.common.reservation.JSPResources" )
* #vm_content1($resourceBundle "email_new_reservation_html.sectionheader.reservationresources" )
* #vm_content2($resourceBundle $locale "email_modified_reservation.resmail.sectionheader.yourresmod" )
*------------------------------------------------------------------*#
#macro(vm_content $key)${tmplContext.getResourceBundleContentWithKey( $key)}#end
#macro(vm_content1 $resourceBundle $key)${tmplContext.getResourceBundleContentPathKey( $resourceBundle, $key)}#end
#macro(vm_content2 $resourceBundle $locale $key)${tmplContext.getResourceBundleContent( $resourceBundle, $locale, $key)}#end
#*-----------------------------------------------------------------
* define resource content macro with one argument
* #vm_content_arg1("email_new_reservation_html.sectionheader.reservationresources", $arg1)
*------------------------------------------------------------------*#
#macro(vm_content_arg1 $key $arg1)${tmplContext.getResourceBundleContentArg1( $key, $arg1)}#end
#macro(vm_content1_arg1 $resourceBundle $key $arg1)${tmplContext.getResourceBundleContent1Arg1( $resourceBundle, $key, $arg1)}#end
#macro(vm_content2_arg1 $resourceBundle $locale $key $arg1)${tmplContext.getResourceBundleContent2Arg1( $resourceBundle, $locale, $key, $arg1)}#end
#*-----------------------------------------------------------------
* define resource content macro with two arguments
* #vm_content_arg2("email_new_reservation_html.sectionheader.reservationresources" $arg1 $arg2)
*------------------------------------------------------------------*#
#macro(vm_content_arg2 $key $arg1 $arg2)${tmplContext.getResourceBundleContentArg2( $key, $arg1, $arg2)}#end
#macro(vm_content1_arg2 $resourceBundle $key $arg1 $arg2)${tmplContext.getResourceBundleContent1Arg2( $resourceBundle, $key, $arg1, $arg2)}#end
#macro(vm_content2_arg2 $resourceBundle $locale $key $arg1 $arg2)${tmplContext.getResourceBundleContent2Arg2( $resourceBundle, $locale, $key, $arg1, $arg2)}#end
#*-----------------------------------------------------------------
* define resource content macro with three arguments
* #vm_content_arg3("email_new_reservation_html.sectionheader.reservationresources" $arg1 $arg2 $arg3)
*------------------------------------------------------------------*#
#macro(vm_content_arg3 $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContentArg3( $key, $arg1, $arg2, $arg3)}#end
#macro(vm_content1_arg3 $resourceBundle $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContent1Arg3( $resourceBundle, $key, $arg1, $arg2, $arg3)}#end
#macro(vm_content2_arg3 $resourceBundle $locale $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContent2Arg3( $resourceBundle, $locale, $key, $arg1, $arg2, $arg3)}#end
#*-----------------------------------------------------------------
* define currency format
* #vm_currency($myres.floatAmount "USD")
*-----------------------------------------------------------------*#
#macro(vm_currency $amount)${tmplContext.formatCurrencyWithDefaultLocale($amount)}#end
#*-----------------------------------------------------------------
* define date format: long, short etc
* #vm_date(${myres.checkOutDate})
*-----------------------------------------------------------------*#
#macro(vm_date $date)${tmplContext.formatDate($date)}#end
#*-----------------------------------------------------------------
* define time format
*-----------------------------------------------------------------*#
#macro(vm_time $time)${tmplContext.formatTime($time)}#end
#*-----------------------------------------------------------------
* text line break
*----------------------------------------------------------------*#
#macro(vm_textLineBreak)
#end
reservation_html_confirmation.html is the template for email body:
<html dir="ltr">
<head>
<META http-equiv=Content-Type content="text/html; charset=${outputencoding}">
<title> #vm_content("car.rental") </title>
</head>
<body>
#set( $carResources = "com.vm.i18n.car.JSPResources" )
<table border="0" cellSpacing=2 cellPadding=0 width=650>
<tr>
<td colspan="2">
<b>#vm_content("welcome").
#vm_content_arg1("confnum " ${reservation.confirmationNumber})
</b>
</td>
</tr>
#set ( $guest = $reservation.guest)
<tr>
<td>#vm_content("firstname"): </td> <td> ${guest.firstName}</td>
</tr>
<tr>
<td> #vm_content("lastname") :</td> <td>${guest.lastName} </td>
</tr>
<tr>
<td> #vm_content("total.price") : </td> <td>#vm_currency(${reservation.charge}) </td>
</tr>
<tr>
<td> #vm_content("rental.day") :</td> <td> ${reservation.rentDays}</td>
</tr>
<tr>
<td> #vm_content1($carResources "car.brand") </td> <td> #vm_content1($carResources "car.brand.${reservation.car.id}") </td>
</tr>
<tr>
<td> #vm_content("start.date")</td> <td>#vm_date(${reservation.startDate}) </td>
</tr>
</table>
reservation_html_confirmation.html template utilizes VTL language and vm_macro.html marco.
#vm_content("firstname") calls
#macro(vm_content $key) ${tmplContext.getResourceBundleContentWithKey( $key)}#end in vm_macro.html
In turn, TemplateContext. getResourceBundleContentWithKey(String key) is getting called to retrieve content for the key with default resource bundle path.
#set( $carResources = "com.vm.i18n.car.JSPResources" ) defines alternative resource bundle.
#vm_content1($carResources "car.brand.${reservation.car.id}") is the usage of using it.
#vm_currency(${reservation.charge}) calls
#macro(vm_currency $amount)${tmplContext.formatCurrencyWithDefaultLocale($amount)}#end
In turn, TemplateContext. formatCurrencyWithDefaultLocale(double amount) is called and format currency with the locale we passed in through createContext we talked before.
Sending Email
EmailUtils
Once we got email body, next thing we need to do is to send email.
Apache email package is applied here.
public void sendEmail(String fromAddress,
String toAddress,
String subject,
String body) throws EmailException {
HtmlEmail email = new HtmlEmail();
//email.setHostName("mail.myserver.com");
email.setHostName(mHostName);
email.addTo(toAddress);
email.setFrom(fromAddress, "Ford Rent Company");
email.setSubject(subject);
email.setCharset( "UTF-8" );
//set the html message
email.setHtmlMsg(body);
//send the email
email.send();
}
By assigning UTF-8 to charset email.setCharset( "UTF-8" );
, the email will be delivered as UTF-8.
The End
This article and sample example were intentionally omit many details regarding Velocity, please refer Velocity document for detail syntax.
Java version: 1.5
Build/Execute
In velocity\script directory
1. clean
clean.bat
2. set email host
Replace EMAIL_HOST = "your.email.host" from TestVelocity.java with your email host
3. build
build.bat
4. run: default for en_US
run.bat [lang_Region]
for example
run.bat zh
run.bat en
run.bat
The Result
For English version: run.bat en
Welcome to use our car rental service. Your Confirmation Number: 124457788
First Name: Bob
Last Name : Park
Total Price : $123.56
Rental Days : 2
Brand FORD
Start Date September 21, 2006
For Chinese version: run.bat zh
欢迎使用我们的汽车租赁业物. 您的确认号码是: 124457788
名字: Bob
姓名 : Park
租赁费 : ¥123.56
租赁天数 : 2
车品牌 福特
开始日期 2006年9月21日
Source code: click here to download source code.
Key words: Velocity, Resource Bundle, format currency, format date, UTF-8, Apache email.
This example is about car reservation. The car rental company sent email to the customer after the customer made a reservation.
Reservation Data
Java bean data objects:
1. Guest records customer’s first name, last name and email address
2. Car presents type of car of rental, FORD, TOYOTA etc.
3. Reservation encapsulates confirmation number, a guest, a car, charge, days of rental and start rental date.

Test
TestVelocity is used to construct test data and call template process and then send email.
public class TestVelocity
{
private static Logger sLogger = Logger.getLogger( TestVelocity.class.getName() );
/**
* email host server
* You got to change it to your email host.
*/
private final static String EMAIL_HOST = "your.email.host";
/**
* Process Velocity template and send email
* @param locale
* @throws Throwable
*/
private void processVelocityAndSendEmail(Locale locale)
throws Throwable
{
sLogger.info( "processVelocityAndSendEmail" );
//prepare data for template
Reservation reservation = constructCarReservatoin();
if (sLogger.isDebugEnabled()) {
sLogger.debug("\n");
sLogger.debug( reservation.toString() );
}
//1. get email body
ReservationTemplateProcessor resTemplateProcessor = new ReservationTemplateProcessor();
String body = resTemplateProcessor.processReservationTemplate( reservation, locale );
//2. send email
String subject = "Welcome to rent Ford's car";
String fromAddress = "rent.car@ford.com";
String toAddress = reservation.getGuest().getEmail();
if (sLogger.isDebugEnabled()) {
sLogger.debug("\n");
sLogger.debug( "body=" + body );
}
EmailUtils emailUtils = new EmailUtils();
emailUtils.setHostName( EMAIL_HOST );
emailUtils.sendEmail( fromAddress, toAddress, subject, body );
}
/**
* process
* @param args
* @throws Throwable
*/
private void process(String[] args)
throws Throwable
{
String arg1="en";
if (args.length>0) {
arg1 = args[0];
}
Locale locale = null;
if ("zh".equalsIgnoreCase( arg1 )) {
locale = new Locale("zh", "CN");
}
else {
locale = new Locale("en", "US");
}
sLogger.info( "language="+arg1);
// 1. test velocity
processVelocityAndSendEmail(locale);
}
public static void main( String[] args )
{
try {
String log4j = System.getProperty( "log4j.configuration", "log4j.properties" );
sLogger.info( "log4j="+log4j);
PropertyConfigurator.configure(log4j);
sLogger.info("Entering application.");
new TestVelocity().process(args);
} catch( Throwable e ) {
e.printStackTrace();
}
}
/**
* Construct fake data for testing
* @return
*/
private Reservation constructCarReservatoin()
{
Reservation reservation = new Reservation();
reservation.setConfirmationNumber( "124457788" );
Guest guest = new Guest();
guest.setFirstName( "Bob" );
guest.setLastName( "Park" );
guest.setEmail( "michael.wang@ichotelsgroup.com" );
reservation.setGuest( guest );
Car car = new Car();
car.setId( Car.FORD );
reservation.setCar( car );
reservation.setCharge( 123.56 );
reservation.setRentDays( 2 );
Date today = new Date();
reservation.setStartDate( today );
return reservation;
}
}
TestVelocity initializes log4j, constructs data through constructCarReservatoin() method, takes language value from command argument and call constructCarReservatoin(Locale locale) to process template to get body of email and send email.
Note: private final static String EMAIL_HOST = "your.email.host";
The your.email.host needs to be your real email server in order to send email.
Reservation Process
ReservationTemplateProcessor is created to decouple velocity service from test client TestVelocity,java. It sets default resource bundle path and velocity template location.
The method of processReservationTemplate does two things:
1. takes reservation and locale objects to fill TemplateRequest with default resource bundle path, locale, template location, reservation and comments.
2. call TemplateProcessorFactory to get VelocityTempateProcess instance and invoke velocity process by passing templateReuqest value.
Notes:
1. So far, there is not velocity code involved.
2. The default resource bundle path set here is for convenience, in template you can use different resource bundle.
Template Process
Template process relies on velocity template engine to process reservation template with reservation object.
The constructor of VelocityTemplateProcessor loads velocity properties from classpath and initialized Velocity Engine.
public VelocityTemplateProcessor()
{
InputStream iStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(VELOCITY_PROPERTIES );
Properties props = new Properties();
if ( iStream == null ) {
throw new TemplateProcessorException(
"Could not find velocity configuration file: velocity/velocity.properties in class path" );
}
try {
props.load( iStream );
mVelocityEngine = new VelocityEngine();
mVelocityEngine.init( props );
} catch( Throwable e ) {
throw new TemplateProcessorException( "could not initilize ", e );
}
}
The method of createContext method creates instance of TemplateContext and save local, default resource bundle path to it.
Additionally, it sets local, default resource bundle path and encoding type to VelocityContext.
The encoding type is defined in velocity properties file to VelocityContext.
Note:
1. TemplateContext is created and saved to velocityContext. The purpose of doing this is for the call back from template to display resource bundle content, currency format, decimal format, date and time format etc.
2. Velocity Macro is defined in velocity configuration file and is loaded to VelocityEngine also.
private VelocityContext createContext( TemplateRequest templateRequest )
throws TemplateProcessorException
{
try {
VelocityContext context = new VelocityContext();
/*---------------------------------------------------
* add common setting here ...
*---------------------------------------------------*/
Locale locale = (Locale)templateRequest.getMap().get(
TemplateConstant.TMPL_LOCALE );
TemplateContext templateContext = new TemplateContext(locale);
String defaultResourceBundlePath = (String)templateRequest.getMap().get(
TemplateConstant.TMPL_DEFAULT_RESOUCE_BUNDLE_PATH );
templateContext.setDefaultResourceBundlePath( defaultResourceBundlePath );
context.put( TemplateConstant.TMPL_CONTEXT_REFERENCE, templateContext );
context.put( TemplateConstant.TMPL_LOCALE, locale );
context.put(
TemplateConstant.TMPL_DEFAULT_RESOUCE_BUNDLE_PATH, defaultResourceBundlePath );
// adding output encoding type: TMPL_OUTPUT_ENCODING
context.put(
TemplateConstant.TMPL_OUTPUT_ENCODING,
mVelocityEngine.getProperty( "output.encoding" ) );
return context;
} catch( Throwable e ) {
throw new TemplateProcessorException( "Could not createContext "
+ templateRequest.toString(), e );
}
}
The method of process takes templateRequest object and create Template object.
The values of templateRequest are passed to VelocityContext.
Note: As configuration file defines input.encoding=UTF-8, so we do not need to do anything in this method for processing UTF-8.
public String process( TemplateRequest templateRequest )
throws TemplateProcessorException
{
sLogger.debug( "Start template processing" );
try {
Template template = mVelocityEngine.getTemplate( templateRequest.getPath() );
VelocityContext context = createContext( templateRequest );
// 1. assign client value to velocity context
Map vMap = templateRequest.getMap();
String key;
Object value;
if ( vMap != null ) {
Iterator it = vMap.entrySet().iterator();
while ( it.hasNext() ) {
Map.Entry entry = (Map.Entry)it.next();
key = (String)entry.getKey();
value = entry.getValue();
context.put( key, value );
}
}
// 2. bind with context and return String Writer
StringWriter writer = new StringWriter();
template.merge( context, writer );
writer.close();
return writer.getBuffer().toString();
} catch( Throwable e ) {
throw new TemplateProcessorException( "Could not process template "
+ " request is " + templateRequest.toString(), e );
}
}
Email Template
Velocity does not care the file extension, we use html so that we can view the layout easily.
vm_macro.html is macro file and load as velocity init. It defines many useful macros for formatting, retrieving resource bundle etc.
#*
* vm_macro.vm
* include all common macro for vm template.
*
*#
#*-- define macro to retrieve --*#
#*-----------------------------------------------------------------
* define resource content macro
* #vm_content("email_new_reservation_html.sectionheader.reservationresources" )
*
* #set( $resourceBundle = "com.ihg.dec.framework.uiServices.i18n.resources.jsp.common.reservation.JSPResources" )
* #vm_content1($resourceBundle "email_new_reservation_html.sectionheader.reservationresources" )
* #vm_content2($resourceBundle $locale "email_modified_reservation.resmail.sectionheader.yourresmod" )
*------------------------------------------------------------------*#
#macro(vm_content $key)${tmplContext.getResourceBundleContentWithKey( $key)}#end
#macro(vm_content1 $resourceBundle $key)${tmplContext.getResourceBundleContentPathKey( $resourceBundle, $key)}#end
#macro(vm_content2 $resourceBundle $locale $key)${tmplContext.getResourceBundleContent( $resourceBundle, $locale, $key)}#end
#*-----------------------------------------------------------------
* define resource content macro with one argument
* #vm_content_arg1("email_new_reservation_html.sectionheader.reservationresources", $arg1)
*------------------------------------------------------------------*#
#macro(vm_content_arg1 $key $arg1)${tmplContext.getResourceBundleContentArg1( $key, $arg1)}#end
#macro(vm_content1_arg1 $resourceBundle $key $arg1)${tmplContext.getResourceBundleContent1Arg1( $resourceBundle, $key, $arg1)}#end
#macro(vm_content2_arg1 $resourceBundle $locale $key $arg1)${tmplContext.getResourceBundleContent2Arg1( $resourceBundle, $locale, $key, $arg1)}#end
#*-----------------------------------------------------------------
* define resource content macro with two arguments
* #vm_content_arg2("email_new_reservation_html.sectionheader.reservationresources" $arg1 $arg2)
*------------------------------------------------------------------*#
#macro(vm_content_arg2 $key $arg1 $arg2)${tmplContext.getResourceBundleContentArg2( $key, $arg1, $arg2)}#end
#macro(vm_content1_arg2 $resourceBundle $key $arg1 $arg2)${tmplContext.getResourceBundleContent1Arg2( $resourceBundle, $key, $arg1, $arg2)}#end
#macro(vm_content2_arg2 $resourceBundle $locale $key $arg1 $arg2)${tmplContext.getResourceBundleContent2Arg2( $resourceBundle, $locale, $key, $arg1, $arg2)}#end
#*-----------------------------------------------------------------
* define resource content macro with three arguments
* #vm_content_arg3("email_new_reservation_html.sectionheader.reservationresources" $arg1 $arg2 $arg3)
*------------------------------------------------------------------*#
#macro(vm_content_arg3 $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContentArg3( $key, $arg1, $arg2, $arg3)}#end
#macro(vm_content1_arg3 $resourceBundle $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContent1Arg3( $resourceBundle, $key, $arg1, $arg2, $arg3)}#end
#macro(vm_content2_arg3 $resourceBundle $locale $key $arg1 $arg2 $arg3)${tmplContext.getResourceBundleContent2Arg3( $resourceBundle, $locale, $key, $arg1, $arg2, $arg3)}#end
#*-----------------------------------------------------------------
* define currency format
* #vm_currency($myres.floatAmount "USD")
*-----------------------------------------------------------------*#
#macro(vm_currency $amount)${tmplContext.formatCurrencyWithDefaultLocale($amount)}#end
#*-----------------------------------------------------------------
* define date format: long, short etc
* #vm_date(${myres.checkOutDate})
*-----------------------------------------------------------------*#
#macro(vm_date $date)${tmplContext.formatDate($date)}#end
#*-----------------------------------------------------------------
* define time format
*-----------------------------------------------------------------*#
#macro(vm_time $time)${tmplContext.formatTime($time)}#end
#*-----------------------------------------------------------------
* text line break
*----------------------------------------------------------------*#
#macro(vm_textLineBreak)
#end
reservation_html_confirmation.html is the template for email body:
<html dir="ltr">
<head>
<META http-equiv=Content-Type content="text/html; charset=${outputencoding}">
<title> #vm_content("car.rental") </title>
</head>
<body>
#set( $carResources = "com.vm.i18n.car.JSPResources" )
<table border="0" cellSpacing=2 cellPadding=0 width=650>
<tr>
<td colspan="2">
<b>#vm_content("welcome").
#vm_content_arg1("confnum " ${reservation.confirmationNumber})
</b>
</td>
</tr>
#set ( $guest = $reservation.guest)
<tr>
<td>#vm_content("firstname"): </td> <td> ${guest.firstName}</td>
</tr>
<tr>
<td> #vm_content("lastname") :</td> <td>${guest.lastName} </td>
</tr>
<tr>
<td> #vm_content("total.price") : </td> <td>#vm_currency(${reservation.charge}) </td>
</tr>
<tr>
<td> #vm_content("rental.day") :</td> <td> ${reservation.rentDays}</td>
</tr>
<tr>
<td> #vm_content1($carResources "car.brand") </td> <td> #vm_content1($carResources "car.brand.${reservation.car.id}") </td>
</tr>
<tr>
<td> #vm_content("start.date")</td> <td>#vm_date(${reservation.startDate}) </td>
</tr>
</table>
reservation_html_confirmation.html template utilizes VTL language and vm_macro.html marco.
#vm_content("firstname") calls
#macro(vm_content $key) ${tmplContext.getResourceBundleContentWithKey( $key)}#end in vm_macro.html
In turn, TemplateContext. getResourceBundleContentWithKey(String key) is getting called to retrieve content for the key with default resource bundle path.
#set( $carResources = "com.vm.i18n.car.JSPResources" ) defines alternative resource bundle.
#vm_content1($carResources "car.brand.${reservation.car.id}") is the usage of using it.
#vm_currency(${reservation.charge}) calls
#macro(vm_currency $amount)${tmplContext.formatCurrencyWithDefaultLocale($amount)}#end
In turn, TemplateContext. formatCurrencyWithDefaultLocale(double amount) is called and format currency with the locale we passed in through createContext we talked before.
Sending Email
EmailUtils
Once we got email body, next thing we need to do is to send email.
Apache email package is applied here.
public void sendEmail(String fromAddress,
String toAddress,
String subject,
String body) throws EmailException {
HtmlEmail email = new HtmlEmail();
//email.setHostName("mail.myserver.com");
email.setHostName(mHostName);
email.addTo(toAddress);
email.setFrom(fromAddress, "Ford Rent Company");
email.setSubject(subject);
email.setCharset( "UTF-8" );
//set the html message
email.setHtmlMsg(body);
//send the email
email.send();
}
By assigning UTF-8 to charset email.setCharset( "UTF-8" );
, the email will be delivered as UTF-8.
The End
This article and sample example were intentionally omit many details regarding Velocity, please refer Velocity document for detail syntax.
Java version: 1.5
Build/Execute
In velocity\script directory
1. clean
clean.bat
2. set email host
Replace EMAIL_HOST = "your.email.host" from TestVelocity.java with your email host
3. build
build.bat
4. run: default for en_US
run.bat [lang_Region]
for example
run.bat zh
run.bat en
run.bat
The Result
For English version: run.bat en
Welcome to use our car rental service. Your Confirmation Number: 124457788
First Name: Bob
Last Name : Park
Total Price : $123.56
Rental Days : 2
Brand FORD
Start Date September 21, 2006
For Chinese version: run.bat zh
欢迎使用我们的汽车租赁业物. 您的确认号码是: 124457788
名字: Bob
姓名 : Park
租赁费 : ¥123.56
租赁天数 : 2
车品牌 福特
开始日期 2006年9月21日
Source code: click here to download source code.
Subscribe to:
Comments (Atom)