Skip to content

TeaServlet Tutorial

jappy edited this page Jan 25, 2012 · 35 revisions

NOTICE: This page is being rewritten or discarded. This page is currently orphaned.

Preface

The TeaServlet is a framework that sits on top of the Servlet API and creates a separation between data and presentation. The TeaServlet requires a servlet container to run in.

This tutorial is designed to guide new users through a few tasks that will lead to an understanding of the TeaServlet. We will look at how to build applications that work with it. We will also explore the Tea template language.

The tutorial is meant to supplement the other documents that are available on the TeaServlet and Tea. For TeaServlet installation, see the TeaServlet User Manual.

The tutorial presumes that you have an understanding of Java programming and an understanding of servlets. If you don’t, refer to Appendix A: Servlet Resources for a list of servlet resources.

1. Getting Started

1.2 Installation

The first thing you need to do is download the TeaServlet. Go to the TeaServlet page at https://github.com/teatrove/teatrove/ and download the latest version.

The TeaServlet's download instructions; requirements, installation instructions, and administration page descriptions are contained in the TeaServlet User Manual.

Once the TeaServlet is installed in the servlet container, the administration pages are available. You should ensure these are configured and working properly - as shown in the user manual, before you continue with this tutorial.

2. Creating A Tea Template

2.1 Tea Template Tutorial

The following steps describe how to create a simple Tea Template. You will become familiar with the Tea syntax and practice using some of the functions available in the Tea Template language.

2.1.1 Template Declaration

<% template index()

The <% marks the beginning of a code region. Code must be inside a code region to be compiled as code. Anything outside the code region is interpreted as a String and printed as text on the html page. To close a code region, use %>.

In the above example, index is the name of the template. The parentheses hold any parameters that the template may take and your variable name for each parameter. In the above example, there are no parameters needed.

Below is an example of a template declaration for a template that takes parameters:

<% template story(Integer id, String page)

The above template declaration can take an Integer parameter named id and a String parameter named page. If this template is called in a browser, it would have its parameters appended to the end of the template name as name-value pairs. For example, http://espn.go.com/nba/story?id=5&page=clubhouse

2.1.2 Comments

Comments are not read as code – they are used to describe what your code is doing. You should use comments generously. It should be easy to scan the comments in your template and know exactly what your template does.

Tea uses // for a single-line comment and /* to open and */ to close a multiple-line comment. // THIS IS A SINGLE LINE COMMENT Below is a multiple-line comment. The asterisks to the left of the lines of text between the /* and */ are not necessary, but help make the comment readable. The top of your template should have a section of comments like below which includes the name and relative path of the template, a copyright statement, author name, create date, and a description of what the template does.

/*
 * index.tea 
 * Author: Joe Schmoe
 * Date: 5/10/2011
 * This template displays the index.
 */

When in doubt, add a comment.

2.1.3 Statements

It is not necessary to use semicolons to end your statements, but they can be used. Both of the below statements are acceptable.

currentDate()
currentDate();

2.1.4 Practice #1

Write a simple template that displays the current date.

  1. Open a new text document using a text editor.
  2. Start your template by creating a template declaration. You can name your template whatever you want. It will take no parameters.
  3. Next, add the multiple line comment chunk at the top of the template that includes the name and relative path of the template, a copyright statement, author name, create date, and a description of what the template does.
  4. Below that, add the function currentDate()
  5. Add a single line comment above the currentDate() function that says something like // DISPLAY DATE
  6. Save your template to the template location that was set up. Save your file with a .tea extension.
  7. Go to the template compile page and compile your template. Any compilation errors will show up on that page.
  8. View your new page. The current date should appear in a format similar to this: 'June 4, 2002 11:55:02 AM PDT'
  9. At the top of your template, add the function 'dateFormat("h:mm a, MM dd yyyy”)'.
  10. Save, compile, and view your changes.

2.1.5 Expression Statement

To print text or html to the web page, you only need to put the text in quotes, within a code region. Either double or single quotes (also known as “tick marks”) will work.

"This text will appear on the web page."
'This will too.'

If the text or html is not inside a code region, you do not need to use quotes.

%>
This text will appear on the web page.
<%

Be aware that if you are using single quotes and your String has internal single quotes or if you are using double quotes and your String has internal double quotes, you will need to escape the internal quote marks. Use a backslash to escape characters.

Bad:

'That\\'s going to cause a problem.'

Good:

'That\\'s not a problem now that the apostrophe is escaped.'

Bad:

"He said, "These internal quotes are a problem.""

Good:

"He said, \\"These internal quotes are no problem when they are escaped.\\""

2.1.6 Variable Assignment

A Tea variable’s type can be inferred based on assignment. A single equal sign '=' is used to assign variables.

text = "Hello" // Assign the string literal “Hello” to text 
result = 20 // Assign the integer literal 20 to result
amount = result + 20 // Assign the calculated sum of result and 20 to amount

2.1.7 Arithmetic

Tea can perform arithmetic on both integers and floating-point numbers. Supported operations are addition, subtraction, multiplication, division, division remainder, and negation. The operators are +, -, *, /, %, and -, respectively.

2.1.8 Accessing Properties

To access properties from an object use a dot (.) followed by the property name. For example, to access the day property from the currentDate(), use this syntax:

currentDate().day

2.1.9 Practice #2

Add html formatting around the date.

  1. To print text or html within a code region, enclose the html in tick marks. For example, '<html><head></head><body>\n'
  2. You can use the newline character \n to create a line break in the html that is produced when the template executes.
  3. If you want to add html to the same line as your variable, you should use the ampersand to separate the code and the variable. For example, '<font size=2 face="Verdana, Geneva, Arial"><b>' & currentDate() & '</b></font>'
  4. Add variable assignment to your template. At the top of your template (after the multi- line code chunk), create a variable called date and assign it the value of currentDate().
  5. Now change the line that writes out currentDate() to use the variable date instead.
  6. Access the month property from the date variable and display it on a new line.
  7. Save, compile, and view your changes.
  8. Add a String parameter name to your template declaration and access and display the name variable on your page.
  9. Save, compile, and view your changes. Pass in your first name as the value for the name parameter when you call your template.

2.1.10 If Statement

Use == to compare equality of two values, != to check for inequality, > for greater than, and < for less than, <= for less than or equals, and >= for greater than or equals.

if (name == "mike") {
    // MIKE IS WELCOME
    'Welcome!'
} else {
    // ALL OTHERS ARE NOT 'Go away'
}

// CHECK COUNT VALUE AND DISPLAY APPROPRIATE TEXT
if (count == 1) {
    'you\\'re at the top`
} else if (count == 2) {
    'you\\'re in the top two' 
} else if (count > 10) {
    'slowpoke!'
} else {
    'you\\'re in the running'
}

2.1.11 Practice #3

  1. Add another dateFormat function to your template below the line that displays the date.
  2. This time format the date like this: dateFormat("yyyyMMdd")
  3. Now create an if statement that compares your date variable to a string that represents today’s date.

For example:

if (date == "20020604") { 
    'today<br>'
}
  1. Now add an else statement that will execute if the first comparison isn’t true. If the else executes, display 'some other day<br>'
  2. Save, compile, and view changes.

2.1.12 Logical Operators

You can use and, or, and not as logical operators in conjunction with relational tests.

if (dogName != "Wiley" and dogBreed != "Aussie") {
   // NO OTHER DOGS ALLOWED
   'You aren’t my dog!'
} else { 
   'Welcome, Wiley'
}

2.1.13 Looping

The foreach statement is the loop control statement in Tea. The foreach statement iterates through the values of an array, a collection, or a range of values.

// LOOP THROUGH THE ARRAY NAMED LIST AND DISPLAY EACH ITEM 
foreach (item in list) {
   item
}
// LOOP THROUGH THE ARRAY NAMED LIST IN REVERSE AND DISPLAY EACH ITEM 
foreach (item in list reverse) {
    item
}
// LOOP THROUGH ARRAY OF NUMBERS ONE THROUGH TEN AND DISPLAY EACH NUMBER 
foreach (number in 1..10) {
    number
}

2.1.14 Break Statement

The break statement can be used to exit a loop.

// FOREACH THROUGH AN ARRAY OF NUMBERS ONE THROUGH TEN AND DISPLAY EACH NUMBER
// UNLESS THE NUMBER IS GREATER THAN FOUR 
foreach (number in 1..10){
    number 
    if (number > 4) {
        break
    }
}

2.1.15 Practice #4

  1. Add a loop to your template.
  2. Loop through numbers 1 through 10 in reverse and display each number followed by a '<br>'.
  3. Save, compile, and view your changes.
  4. Play around with variables, if else statements, and loops.

2.1.16 Array Creation

Arrays can either be defined in a template, passed into a template, or returned from a function call. Tea has two types of arrays.

2.1.17 Indexed Arrays

To create an indexed array in Tea, use the following syntax:

// AN INDEXED ARRAY CONTAINING VOWELS
vowels = #(“a”, “e”, “i”, “o”, “u”)

Items in an indexed array can be accessed using array property syntax. For example:

// ACCESS THE FOURTH ITEM IN THE ARRAY
letter = vowels[3]

Indexed arrays can also be foreached through. For example:

// FOREACH THROUGH VOWEL ARRAY AND PRINT EACH VOWEL IN ARRAY 
foreach (letter in vowels) {
    letter & '<br>'
}

2.1.18 Associative Arrays (a.k.a. Hash Tables)

Hash tables are used when you want to create an array of paired items. To create a hash table, use the following syntax:

// ASSOCIATIVE ARRAY OF ZIP CODES AND CITY NAMES 
zipHash = ##(
    "98125", "North Seattle, WA",
    "98225", "Bellingham, WA", 
    "54859", "Minong, WI", 
    "98104", "Downtown Seattle, WA"
)

Items in an associative array can be accessed using this syntax:

// ACCESS CITY MATCHED WITH ZIP 
city = zipHash["54859"]

2.1.19 Practice #5

  1. Create an indexed array in your template.
  2. Access and display the third item in the array.
  3. Foreach through the same array and display each item followed by a '<br>'
  4. Create an associative array in your template.
  5. Access and display one item in the second pair in your associative array.

2.1.20 String Functions

Tea has a variety of functions to manipulate Strings. We will practice using a few string functions here. For a complete list of Tea functions, see the Tea Template Language.

// APPEND ONE STRING TO ANOTHER STRING 
firstString = "fish" 
secondString = firstString & "monger"

// CONVERT ALL LETTERS IN THIS STRING TO UPPERCASE 
firstString = "title" 
capString = toUpperCase (firstString)

// SET A NEW VARIABLE EQUAL TO THE FIRST THREE LETTERS OF THIS STRING 
firstString = "Monday" 
shorterString = substring(firstString, 0, 2)

// IF STRING ENDS WITH THE WORD “DAY”, CREATE A SUBSTRING OF THE STRING, REMOVING THE WORD “DAY” 
firstString = "Tuesday" 
if (endsWith(firstString, "day")) {
    substring(firstString, 0, firstString.length-3)
}

// FIND THE INDEX FOR THE NUMBER 1 IN THE STRING 
// IF THERE IS NO NUMBER 1 IN THE STRING THE FUNCTION RETURNS -1 

firstString = "Welcome1" 
if (findFirst(firstString, 1) != 1) {
    "First one"
}

2.1.21 Practice #6

  1. In your template, set a variable equal to a string consisting of at least two words (for example, your first and last name).
  2. Using the findFirst function, find the index of the first space character “ “ in your string.
  3. Using the substring function, make a new string variable that removes the first word of your first string.
  4. Save, compile, and view your changes.

2.1.22 Adding New Functions

TeaServlet can be extended by having Java engineers create new functions. Many sites have their own specialized functions that are created by their engineering group. For instance, ESPN.com has functions such as getGame(gameId), which takes a gameId as a parameter and returns a Game object.

2.1.23 Sub-Templates

Sub templates allow complex templates to be managed in pieces. Additionally, sub-templates may be re-used, being called by many different templates. For instance, the ESPN front page may be produced from a single template that calls a header template, a left sidebar template, a main feature template, a right side template, and a footer template. The footer template may be called by many other templates.

The two main reasons for using sub-templates are 1) to be able to reuse code and 2) to simplify a template by breaking it down into modular pieces.

Reusing code makes it easy to update the site by changing only one template instead of many. Below are some examples of reusing code:

  • A footer template that writes out the footer for every page on the site.
  • A search template, which creates the search box for every page on the site.
  • A features template, which formats the features box for several pages. In other cases, a template may be very long and complex, and therefore difficult to read. If all the code for some complex pages were on one page, it can make it very difficult to troubleshoot the template. Instead, breaking the template down into several sub-templates may make the template easier to understand and troubleshoot.

For example:

  • A template like the ESPN front page has many different sections and hundreds of lines of code. It is clearer if the main template calls sub-templates for each different section on the page.

Note: Your templates should contain comments that make it clear what the sub-templates do. Otherwise, it can end up like a wild goose chase as you run from template to template trying to fix a problem. A sub-template is called by using the call function and the template name. For example, call name()

2.1.24 Practice #7

  1. Take out the part of your code you just added in Practice #6 and create a new sub- template.
  2. Call that sub-template from your original template.
  3. Save, compile, and view your changes.

2.1.25 Practice #8

Use the Tea Template Language manual as a resource for this practice.

  1. Incorporate other functions into your practice template.
  2. Save, compile, and view your changes.
  3. Go back over your template and be sure you have commented your code well.
  4. Bring back any questions, comments, or problems you encounter.

3. SAMPLE TEA TEMPLATE CODE

3.1 Tea Template Code Check

You can use the Tea Template code below to compare to the code you write in your practices above. There is often more than one way to write Tea Template code to get the desired result. The code below demonstrates good practice.

3.1.1 Practice #9

<% template today()
/*
 * today.tea 
 *
 * This template displays the current date.
 *
 */

// FORMAT DATE TO SHOW HOURS, MONTH, DAY AND YEAR 
dateFormat("h:mm a, MMM d yyyy")

// DISPLAY DATE 
currentDate()

3.1.2 Practice #10

// FORMAT DATE TO SHOW HOURS, MONTH, DAY AND YEAR 
dateFormat("h:mm a, MMM d yyyy")

// SET DATE EQUAL TO CURRENT DATE
date=currentDate()

// BEGIN HTML
'<html><head></head><body>\n'

// DISPLAY DATE
'<font size=2 face=Verdana, Geneva, Arial><b>' & date & '</b></font><br>'

// DISPLAY MONTH
'<font size=2 face=Verdana, Geneva, Arial><b>' & date.month & '</b></font><br>'

// DISPLAY NAME PASSED INTO TEMPLATE '<font size=2 face=Verdana, Geneva, Arial><b>' & name & '</b></font><br>'

3.1.3 Practice #11

// FORMAT DATE
dateFormat("yyyyMMdd")

// COMPARE TODAY'S DATE WITH DATE STRING
if (date == "20020613") {
    '<br>today'
} else {
   '<br>some other day'
}

3.1.4 Practice #12

// LOOP THROUGH ARRAY, DISPLAY EACH NUMBER ON ITS OWN LINE 
foreach (number in 1..10 reverse) {
    number & '<br>'
}

3.1.5 Practice #13

// LOOP THROUGH ARRAY, DISPLAY EACH NUMBER ON ITS OWN LINE
foreach (number in 1..10 reverse) {
    number & '<br>'
}

// INDEXED ARRAY OF FRIENDS' NAMES
friendArray = #("Mike", "Mari", "Liz", "Linda")

// DISPLAY THIRD FRIEND IN ARRAY 
friendArray[2] & '<br>'

// DISPLAY EACH FRIEND
foreach (friend in friendArray) {
    friend & '<br>'
}

// HASH TABLE OF PET TYPE AND NAME
petArray= ##(
    "fish", "Goldie",
    "dog", "Wiley",
    "flea", "Jumpy",
    "cat", "Hank"
)

// DISPLAY DOG NAME
petArray["dog"] & "<br>"

3.1.6 Practice #14

// FIRST AND LAST NAME
name = "Jodi Wade"

// FIND FIRST SPACE
spaceIndex = findFirst(name, " ")

// CREATE NEW NAME STRING BY REMOVING FIRST WORD IN STRING 
newName = substring (name, spaceIndex)

// PRINT NEW NAME 
newName

3.1.7 Practice #15

Main template:

// CALL NAME.TEA TO DISPLAY NAME
call name()

New sub-template:

<% template name()
/* 
 * name.tea 
 *
 * This template is called by date.tea and displays last name.
 *
 */
// FIRST AND LAST NAME
name = "Jodi Wade"

// FIND FIRST SPACE
spaceIndex = findFirst(name, " ")

// CREATE NEW NAME STRING BY REMOVING FIRST WORD IN STRING
newName = substring (name, spaceIndex)

// PRINT NEW NAME
newName

4. Creating A TeaServlet Application

Once TeaServlet is installed, the next step is to create a TeaServlet application.

4.1 Hello World

The following steps describe how to create a simple "Hello World" application. The application will display a "Hello" greeting to the user based upon the user’s name passed in the URL.

For example, hitting the URL http://.../HelloWorld?name=Bob will display "Hello Bob".

4.1.1 The Tea Template

The first step is to create a Tea template. You should be fairly comfortable with the Tea Template language after going through the tutorial in Section 2 of this document: Creating a Tea Template. Create a new template that looks like this:

<% template HelloWorld() 
   yourName = getName()
%> Hello <% yourName %>

Observe that the second line calls the function getName. The contents of this function will be defined further on. The results of the function are stored in a variable called yourName, and then output to the web page on the last line, just after the word "Hello".

Save this new template as "HelloWorld.tea" in the directory specified for the templates.path property in the properties file. If you save the file elsewhere, the TeaServlet will not be able to find your template. For more information about writing Tea templates, refer to the Tea User Manual.

4.1.2 The TeaServlet Application

The second step is to create your TeaServlet application class. An application is similar to a servlet in that it has an init method and a destroy method. Instead of having a doGet (or service) method, it has a getContextType method and a createContext method.

The application allows us to add a group of functions to the TeaServlet. All of the functions are available to the Tea templates.

You need to create the application class and call it "HelloWorldApplication". This is how the class will look:

package sample;

import org.teatrove.teaservlet.*;
import javax.servlet.ServletException;

public class HelloWorldApplication implements Application {

    // initialize the application 
    public void init(ApplicationConfig config) throws ServletException { }

    // destroy the application 
    public void destroy() { }

    // return the class that contains the functions 
    public Class getContextType() {
        return HelloWorldContext.class;
    }

    // return an instance of the functions class 
    public Object createContext(ApplicationRequest request, ApplicationResponse response) { 
        return new HelloWorldContext(request);
    }

The init method and destroy method are empty in this example. You can ignore these methods for now. The important methods are getContextType and createContext.

The getContextType method tells the TeaServlet which class contains our functions. The class that contains the functions is called the context. In our example, a class called "HelloWorldContext" is our context. The createContext method is used to create an instance of the context class. This instance must be of the same class that your getContextType method returns, HelloWorldContext.

The createContext method is very similar to the doGet method of a standard servlet. Both of these methods are passed a request and a response object. We can use the request object to get information about the user’s request.

4.1.3 The Context Class

Once you have an application, the next step is to build a context class. This is how the context class will look:

package sample;

import javax.servlet.http.*;

public class HelloWorldContext { 
    HttpServletRequest mRequest;

    public HelloWorldContext(HttpServletRequest request) {
        // save the user’s request object 
        mRequest = request;
    }

    public String getName() {
        // get the "name" parameter from the URL String 
        name = mRequest.getParameter("name"); 
        if (name == null) {
            name = "World";
            return name;
        }
    }
}

The HelloWorldContext class contains all the functions that you want to be available to the Tea templates. In this case, there is only one function that you are making available, the getName function. The getName function needs to get information from the user’s request. To handle this, you are passing the request into the constructor of this class.

The getName method tries to get the "name" parameter from the URL. If it doesn’t exist, it returns the default value of "World".

4.1.4 Running The HelloWorld Sample

The last step before running HelloWorld is to add the HelloWorld application in the TeaServlet properties file. Find the applications branch in the TeaServlet properties file and add the class as shown below:

applications {
    "HelloWorld" {
        class = sample.HelloWorldApplication
    }
}

Other applications such as the administration application for the TeaServlet will also be in the applications branch. Do not remove the administration application or you won’t be able to access the administration pages.

Once the TeaServlet properties file is saved, the TeaServlet will load the HelloWorld application class when it starts up. If your servlet container supports servlet reloads then reload the TeaServlet. If not, restart your servlet container. HelloWorld is now ready to run.

To run HelloWorld, go to the administration pages (if needed, refer to the TeaServlet User Manual) and click on the Applications link. The HelloWorldApplication is listed there. Click on the Templates link and you will see the HelloWorld template listed. Click on the HelloWorld template. The template will execute and you will see "Hello World". Adding a name parameter to the end of a URL, such as "HelloWorld?name=Bob", will cause the template to display "Hello Bob" instead.

If you see the display as expected, you have correctly created a TeaServlet application with functions that can be called from a template.

Because this web application cleanly separates logic from presentation, more templates can be added that use the getName function without changing any Java code. Each new template is simply a new presentation of the same data.

4.1.5 Adding a Second Template

To demonstrate multiple templates, we will create a "BonjourWorld" template. (Bonjour is how you say "hello" in French.) Create a template that looks like this:

<% template BonjourWorld() 
    yourName = getName()%> Bonjour <%yourName%>

Once the template is appropriately saved, go back to the administration pages and click on the Templates link. Click on the Reload Changes button. The administration page will indicate that the TeaServlet compiled and loaded the BonjourWorld template. Hit the URL http://.../BonjourWorld?name=Pierre and you will see: "Bonjour Pierre".

You have created a new template and made it available to your users without stopping the TeaServlet and without any loss of page serving. Templates can be reloaded while the TeaServlet is still running and serving your web site. New templates can be added and existing templates can be modified all while the web site is live and serving users.

5. UNDERSTANDING THE TEASERVLET

5.1 Application Life Cycle

The life cycle of a TeaServlet application includes calls to four methods: the init method, the getContextType method, the createContext method, and the destroy method.

5.1.1 Init Method

The init method of the application is called after the application instance is constructed. Note that the init method of the application looks very similar to the init method of a servlet. The only difference between these init methods is the TeaServlet application uses the ApplicationConfig class instead of the Servlet API’s ServletConfig class. Since the ApplicationConfig class extends the ServletConfig class, the TeaServlet application's init method can do everything a servlet’s init method can.

Use the application init method to initialize your application. You can read initialization parameters in the init method or initialize global resources. If the application accesses a database, create a database connection pool in the application method so a new connection to the database does not have to be created for every request.

5.1.2 GetContextType Method

The getContextType method is called after the init method exits. This method tells the TeaServlet which context class your application will be providing.

5.1.3 CreateContext Method

The createContext method is called for every request to a template, even if your application is not used by the template. For this reason, minimize the amount of work in the method and leave the logic within the functions in your context.

The createContext method is very similar to the doGet method of a servlet. Both methods take a request and a response object. The ApplicationRequest extends the Servlet API’s HttpServletRequest. This means that any standard servlet request method can be called on the ApplicationRequest. Likewise, the ApplicationResponse extends the Servlet API’s HttpServletResponse. Therefore, you have access to everything that you have in a regular servlet.

If multiple requests are made to the TeaServlet, it is possible for multiple threads to be in this method simultaneously. For this reason, it is very important that code in the createContext method is thread-safe, also recommended for a doGet method.

5.1.4 DestroyMethod

The application's destroy method is used to free resources that have been allocated. This method will be called whenever the servlet container calls the TeaServlet’s destroy method. However,

the destroy method cannot always be relied upon. For example, if the servlet container crashes, the TeaServlet’s destroy method will not be invoked.

5.2 Comparing TeaServlet Applications To Servlets

There is little difference between building a TeaServlet application and creating a standard servlet. The primary difference is the separation that the TeaServlet enforces between data and presentation. The results of this separation are explained in the following sections.

5.2.1 The HelloWorld Sample

The HelloWorld sample, described earlier in the document, could easily be written as a servlet. It would look something like this:

package sample;

import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorldServlet extends HttpServlet {

    public void init(ServletConfig config) throws ServletException {}

    public void destroy() {}

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
	response.setContentType("text/html");
	PrintWriter out = response.getWriter();
	String yourName = getName(request);
	out.println("Hello " + yourName);	
    }

    public String getName(HttpRequest request) {
	// get the "name" parameter from the URL
	String name = request.getParameter("name");
	if (name == null) {
	    name = "World";
        }
        return name;
    }
}

5.2.2 InitAndDestroyMethods

The init and destroy methods in the two HelloWorld examples are identical except for the parameters. Although the methods in the HelloWorld examples are not doing anything, they would be implemented the same way in both the TeaServlet application and the servlet if there were some initialization code.

5.2.3 DoGet And GetName Methods

The doGet method in the servlet is comparable to the createContext method in the TeaServlet application. The primary differences are between methods in the TeaServlet application and the servlet are with these two methods.

In the servlet’s doGet method, the data is retrieved from the getName function and the presentation is sent to the PrintWriter’s output stream. The getName method in the servlet is passed in the user’s request. In the applications createContext method, an instance of the HelloWorldContext class is returned. The getName method is defined in the context and the user’s request is passed into the context instead of the getName method.

With the exception of those differences, the getName method is the same as the createContext method. The createContext method and the context only define the logic piece. The presentation is completely separate and resides in the Tea template file.

5.2.4 Summary

Creating a TeaServlet application is similar to creating a servlet because the TeaServlet is only a thin layer on top of the Servlet API. Due to this similarity, converting a servlet to a TeaServlet application is easily accomplished in most cases.

The main difference between the TeaServlet application and a servlet is the separation between data and presentation. The TeaServlet’s main goal is to enforce a separation between data and presentation so that is why the differences are in that area only.

The following table shows how functionality in a servlet directly maps to functionality in a TeaServlet application:

Functionality Servlet TeaServlet application
Initialization HttpServlet’s init method Application’s init method
Clean up HttpServlet’s destroy method Application’s destroy method
Business logic HttpServlet’s doGet method and data functions Application’s createContext method and data functions in context class
Presentation logic HttpServlet’s doGet method Tea template

5.2.5 Why Use the TeaServlet?

With so many similarities and so little difference, users may wonder why they should use the TeaServlet instead of a standard servlet. The main benefit to using the TeaServlet is that you don’t have to change your Java code every time you want to change a view or make a new view of the existing data.

The HelloWorld sample for the TeaServlet illustrates this capability. By adding the BonjourWorld template, we were able to create an additional view on top of the existing functionality.

In contrast, the HelloWorld sample servlet is more difficult. To say "Bonjour Pierre" as we did earlier with the TeaServlet, you would have to modify your Java code and include a flag that specified which view you wanted.

6. SAMPLE APPLICATIONS

The following sections illustrate additional sample applications.

6.1 The News Sample

You will start by creating the NewsApplication. This application will return local news story objects based on the location of news that is requested. For this sample, you will read the news stories from the properties file.

6.1.1 The NewsApplication Class

Create the NewsApplication class. It should look like this:

package sample;

import org.teatrove.teaservlet.*;

import java.util.*;
import javax.servlet.ServletException;

public class NewsApplication implements Application {

    // the application config
    private ApplicationConfig mConfig;
 
    // a list of local news stories
    private List mNewsStories;

    // initialize the application
    public void init(ApplicationConfig config) throws ServletException {
	// save the application config
        mConfig = config;

	// create the news stories list
	mNewsStories = new Vector()

	// load the news stories from the properites file
	loadNewsStories();
    }

    // destroy the application
    public void destroy() {}

    // return the class that contains the functions
    public Class getContextType() {
	return NewsContext.class;
    }

    // return an instance of the functions class
    public Object createContext(ApplicationRequest request,
                            ApplicationResponse response) {
	return new NewsContext(mNewsStories);
    }

    // load the news stories from the properties file
    public void loadNewsStories() {
	// get the number of news stories
        int newsstories = 0;
	String parameter = mConfig.getInitParameter("newsstories");
        if (parameter == null) {
	    mConfig.getServletContext().log(
                "Error: newsstories property is missing " +
                "from the properties file");
        } else {
            try {
                newsstories = Integer.parseInt(parameter);
            } catch (NumberFormatException e) {
 	        mConfig.getServletContext().log(
                    "Error: newsstories property is not " +
                    "a valid number");
            }
        }

	// read each news story
        for (int story = 1; story <= newsstories; story++) {
	    String prefix = "newsstory" + story + ".";
	    String location = mConfig.getInitParameter(prefix + "location");
	    String headline = mConfig.getInitParameter(prefix + "headline");
	    String body = mConfig.getInitParameter(prefix + "body");

	    // create news story object and add to list of stories
	    NewsStory newsstory = new NewsStory(location, headline, body);
	    mNewsStories.add(newsstory);
        }
    }
}

The NewsApplication has two data members: the application config and the news stories. These are initialized in the init method. The init method then calls the loadNewsStories function. The loadNewsStories function loads the news stories from the properties file. If you were creating a real application, you would probably be reading these from a database or an Enterprise JavaBeans container instead.

6.1.2 TheNewsStoryClass

Notice the loadNewsStories function uses a NewsStory class. Create that class now. It will look like this:

package sample;

import org.teatrove.teaservlet.*;

public class NewsStory {

    // the location of this news story
    private String mLocation;

    // the headline of this news story
    private String mHeadline;

    // the body of this news story
    private String mBody;

    // constructor for the NewsStory object
    public NewsStory(String location, String headline, String body) {	
	mLocation = location;
	mHeadline = headline;
	mBody = body;
    }

    // get the location of this news story
    public String getLocation() {
	return mLocation;
    }

    // get the headline of this news story
    public String getHeadline() {
	return mHeadline;
    }

    // get the body of this news story
    public String getBody() {
	return mBody;
    }
}

The NewsStory class is a basic JavaBean. It has some data members and some accessor methods to get at the data.

6.1.3 The NewsContext Class

The NewsContext class is the last class needed before this sample will run. This is how the NewsContext class will look:

package sample;

import org.teatrove.teaservlet.*;

import java.util.*;

public class NewsContext {
    // a list of local news stories
    private List mNewsStories;
	
    // constructor for the NewsContext object
    public NewsContext(List newsStories) {
	mNewsStories = newsStories;
    }

    // get list of stories for the location
    public NewsStory[] getNewsStoriesByLocation(String location) {
	List stories = new Vector();
	for (int count = 0; mNewsStories.size(); count++) {
	    NewsStory story = (NewsStory) mNewsStory.get(count);
	    if ((location == null) || (location.equals(story.getLocation()) {
		stories.add(story);
            }
        }

        // convert the list to an array
        return (NewsStory[]) stories.toArray(new NewsStory[stories.size()]);
    }

    // get list of stories for all locations
    public NewsStory[] getNewsStories() {	
        return getNewsStoriesByLocation(null);
    }
}

The list of news stories is passed into the NewsContext class. These stories are saved in a data member. You have created two functions that are available to be called by the Tea templates, the getNewsStoriesByLocation function and the getNewsStories function. The getNewsStoriesByLocation function only returns stories that match the specified location.

6.1.4 Setting Up The Properties File

Once you compile your classes, you are ready to setup the NewsApplication in the properties file. Initialization parameters need to be added under the "applications" branch. It will look something like this:

applications {	
    "MyNewsSample" {

	## class file of the application
	class = sample.NewsApplication

	## initialization parameters for the application
	init {
	    ## number of news stories
            newsstories = 3

	    ## first news story
            newsstory1 {
                location = seattle
                headline = Seattle Mariners Win World Series
                body = The Mariners beat the NY Yankees 4-2.
            }

	    ## second news story
	    newsstory2 {
                location = seattle
                headline = Space Needle Falls!
                body = Seattle’s Space Needle fell today /
                    during a small earthquake.
            }

	    ## third news story
            newsstory3 {
                location = losangeles
                headline = Detective Fired From LAPD
                body = The LAPD fired Detective Smith today.
            }
        }
    }
}

The initialization parameters setup here are the same ones that the loadNewsStories method will use. The "newsstories" parameter specifies how many news stories there will be. Each news story is then specified. Instead of embedding the location, headline, and body parameters in the newsstory1 section, you could specify them like this:

## first news story 
newsstory1.location = seattle 
newsstory1.headline = Seattle Mariners Win World Series 
newsstory1.body = The Mariners beat the NY Yankees 4-2.

Properties can be specified either way. The TeaServlet supports either syntax in the properties file.

6.1.5 The Tea Template

Once the Java coding is finished and the properties file is setup, you need to create a Tea template that uses your functions.

Start up Kettle and create a new project. Go to the Project menu and select Properties. On the Context Classes tab, you will specify your context. Click on New and enter the class name of sample.NewsContext. Make sure that the context class is in the class path that is specified on the ClassPath tab.

If you view the Tea Function Browser in Kettle, you will see the NewsContext. You will also see the functions that you can call in your template. These functions can also be viewed from the TeaServlet administration pages by clicking on the Functions link.

In Kettle, you should setup all the contexts that are displayed on the Applications link of the TeaServlet administration pages. This will allow you to access any of the functions in your templates.

Let’s create a template now:

<% template NewsPage(String location)

if (location == null) { 
    // get all news stories
    newsStories = getNewsStories()
} else {
    // get local news stories only 
    newsStories = getNewsStoriesByLocation(location)
} 
%>

<b>Today’s News:</b> <hr>
<% foreach (story in newsStories) { %> 
    <i><% story.headline %></i><br>
    <% story.body %><br>
<% } %>
That’s all folks!

Compile your template in Kettle to make sure there are no errors. Then load the template into the TeaServlet by clicking Reload Changes on the Template link in the administration pages. You can view your template by hitting http://.../NewsPage in your browser.

Let’s take a closer look at this template.

Notice the "<%" and "%>" tags throughout the template. These tags specify the start and end of code regions. Anything outside the code block is outputted as text and is referred to as a text region. Anything inside a code region is executed.

The top of every template starts with the template declaration. You must have this as the first line in your template.

Parameters can be passed to templates. In this case, we are getting the "location" parameter from the URL.

  • Viewing http://.../NewsPage?location=seattle will set the location parameter to "seattle". View that URL and you will see the Seattle news.
  • View http://.../NewsPage?location=losangeles to see the Los Angeles news. The foreach loop is the only loop allowed in Tea. This allows you to loop over an array or collection of objects.

Create a second template now that only shows the Seattle news. It will look like this:

<% template SeattleNews() 
call NewsPage("seattle")
%>

Here you are calling your NewsPage template to do the work. In this case, you are using the NewsPage template like a function. This "function" outputs the news for the selected location. You will find that making presentation functions in templates is a good way to reuse presentation logic. Calling other templates is also a useful way to break up a template into smaller parts. You could write the SeattleNews template like this:

<% template SeattleNews2()
// get seattle news stories only 
newsStories = getNewsStoriesByLocation("seattle")
%>
<b>Today’s News:</b>
<hr>
<% foreach (story in newsStories) { %>
     <i><% story.headline %></i><br>
     <% story.body %><br>
<% } %> 
That’s all folks!

This SeattleNews2 template would produce the same results as the SeattleNews template. The difference is obvious. You have had to do a lot more work in the second template. If you decide to change the "That's all folks!" message to "The End", you would have to change this in your NewsPage template as well as the SeattleNews2 template. Using the SeattleNews template, the change to the NewsPage template would be all that is required.

For more information about writing Tea templates, see the Tea User Manual.

6.1.6 Changes To The NewsApplication

The NewsApplication creates a new instance of the context for every request. Since the context does not have any request-based data, you could change the application to be slightly more efficient. You would change your class to look like this:

public class NewsApplication implements Application {
    ...

    // the context instance
    private NewsContext mContext;

    // initialize the application
    public void init(ApplicationConfig config) throws ServletException {
	...
	// create an instance of the context
	mContext = new NewsContext(mNewsStories);
    }

    // return an instance of the functions class
    public Object createContext(ApplicationRequest request,
                                ApplicationResponse response) {
	return mContext;
    }
}

Note that the HelloWorldApplication class can’t be changed to work like this because it uses request-based data. Each user needs a new copy of the context because the "name" parameter is being accessed from the request.

6.1.7 Changing The Data Source

Because the application logic and the presentation logic are separate, you can change your application implementation at any time without having to change your presentation logic in the templates.

Suppose that you have found a better source for your news stories and you can access these stories from an XML file. You would modify your application to work like this:

package sample;

import org.teatrove.teaservlet.*;

import java.io.*;
import javax.servlet.*;

public class NewsApplication implements Application {

    // the news file
    private File mNewsFile;
 
    // initialize the application
    public void init(ApplicationConfig config) throws ServletException {
	// make sure the xmlfile parameter is specified
	String filename = config.getInitParameter("xmlfile");
        if (filename == null) {
	    throw new ServletException("Missing xmlfile property");
        }

        // make sure the file exists
        mNewsFile = new File(filename);
        if (!mNewsFile.exists()) {
	    throw new ServletException("File doesn't exist: " + filename);
        }
    }

    // destroy the application
    public void destroy() {}

    // return the class that contains the functions
    public Class getContextType() {
	return NewsContext.class;
    }

    // return an instance of the functions class
    public Object createContext(ApplicationRequest request,
                            ApplicationResponse response) {
	return new NewsContext(mNewsFile);
    }
}

In this new NewsApplication class, you are throwing a ServletException if an error occurs. This allows the servlet container to handle the error for you.

You also need to re-implement the NewsContext class. It will look like this:

package sample;

import org.teatrove.teaservlet.*;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import org.jdom.*;
import org.jdom.input.SAXBuilder;

public class NewsContext {

    // the news file
    private File mNewsFile;
	
    // constructor for the NewsContext object
    public NewsContext(File newsFile) {
 	mNewsFile = newsFile;
    }

    // get list of stories for the location
    public NewsStories[] getNewsStoriesByLocation(String location) {

	// create list to store selected stories in
        ArrayList stories = new ArrayList(children.size());

        // open the file and find the root element
        FileInputStream file = new FileInputStream(mNewsFile);
	Element root = new SAXBuilder().build(file).getRootElement();
	
	// iterate through the nodes of the xml tree
        List children = root.getChildren();
        Iterator iter = children.iterator();
        while (iter.hasNext()) {
	    Element child = (Element)iter.next();
	    if ("news".equals(child.getName())) {
		try {
		    String locale = child.getAttribute("location);
		     if ((location == null) || (location.equals(locale))) {
			 String headline = child.getAttribute("headline");
			 String body = child.getAttribute("body");
			 NewsStory story = new NewsStory(locale, headline, body);
			 stories.add(story);
                     }
                 } catch (Exception e) {}
            }
        }

        return (NewsStories[])stories.toArray(new NewsStories[stories.size()]);
    }

    // get list of stories for all locations
    public NewsStories[] getNewsStories() {	
        return getNewsStoriesByLocation(null);
    }
}

Most of the work is performed in the NewsContext class. For every request to the getNewsStories or getNewsStoriesByLocation functions, the XML file is being parsed.

The JDOM library is used to parse the XML file. You will need to download the library before you can get this sample to compile. The library is available from http://www.jdom.org.

You will have to create a sample news file. It will look like this:

<?xml version="1.0" encoding="ISO-8859-1"?>
<newsfile>
    <news location="seattle"> 
        <headline>Seattle Mariners Win World Series</headline> 
        <body>The Mariners beat the NY Yankees 4-2.</story>
    </news>
    <news location="seattle">
         <headline>Space Needle Falls!</headline>
         <body>Seattle’s Space Needle fell today during a small earthquake.</story>
    </news>
    <news location="losangeles">
         <headline>Detective Fired From LAPD</headline>
         <body>The LAPD fired Detective Smith today.</story>
    </news>
</newsfile>

Now you are ready to run this application in the TeaServlet. Since you didn't change the names of your functions, there are no changes required to the Tea templates. Your previous Tea templates will work fine. Hit the templates a few times and then add some more news items to the XML file. Your additional news items will be displayed by the templates as soon as the XML file is saved.

Clone this wiki locally