Skip to content

Next Steps : Creating Web Applications

nicholashagen edited this page Jan 19, 2012 · 13 revisions

This guide will help walkthrough the creation of a new web application built on Tea and TeaServlet. This guide is meant as an introduction and leaves more detailed explanations for the User Guide. The introduction will step through creating the directory structure, optionally setting up Maven, configuring the TeaServlet, creating a Tea application, writing Tea templates, and finally packaging and running the application. However, the easiest way to get started is to copy an existing application, such as the examples. See the Getting Started guide for more information.

Project Setup

The first step to creating an application is to setup the project. This guide will assume you are using Maven. If you are not using Maven, you can skip these sections and jump to the next section. You will just need to consult your build system documentation on project setup. Also, the Tea utilities to precompile templates are currently based on Maven.

Directory Setup

You should create the following directory structure to start:

 .
 |-- pom.xml
 `-- src
     `-- main
         |-- java
         |   `-- com
         |       `-- example
         |           `-- projects
         |               `-- GreetingApplication.java
         |               `-- GreetingContext.java
         |-- resources
         |   `-- images
         |       `-- sampleimage.jpg
         |-- templates
         |   ` index.tea
         |   ` greeting.tea
         `-- webapp
             |-- scripts
             |   `-- application.js
             `-- WEB-INF
                `-- teaservlet.xml
                `-- web.xml

The src/main/java directory is used for Java source files including custom applications, contexts, and plugins. The src/main/templates is for including the templates of the project.

Maven Setup

As with any Maven project, you will need a valid pom.xml. The POM file is typically a WAR application that includes dependencies on the tea libraries. The following dependency is typically the only dependency you need:

<dependency>
    <groupId>org.teatrove</groupId>
    <artifactId>teaadmin</artifactId>
    <version>4.1.1-SNAPSHOT</version>
</dependency>

For more information as well as sample POM xml files, see the example applications.

Web Application Setup

The first step in setting up a web application in any typical servlet-based web application is to create a web.xml within the web application's WEB-INF directory. The maven standard is to create the web.xml in src/main/webapp/WEB-INF/web.xml. See the examples for a sample file.

The TeaServlet must be installed in the web.xml to serve Tea templates. You may also provide initialization parameters to configure the servlet.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5">
    <servlet>
        <servlet-name>TeaServlet</servlet-name>
        <servlet-class>org.teatrove.teaservlet.TeaServlet</servlet-class>
        <init-param>
            <param-name>properties.file</param-name>
            <param-value>/WEB-INF/${environment:DEV}/teaservlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>TeaServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

This example references the TeasServlet configuration file teaservlet.xml. It also allows you to use substitution variables to map the location. Substitution variables by default include any system properties or environment properties. In this example, if you define the system property 'environment' (ie: -Denvironment=PROD), then it will use that to look for the associated file (ie: /WEB-INF/PROD/teaservlet.xml). If the variable does not exist, it will default to DEV. You may also use substitution variables within the TeaServlet configuration file. You can even include other substitution variables by specifying a substitution.file init param. For more information on substitution files and other methods of configuration, see the TeaServlet User Manual.

TeaServlet Setup

The TeaServlet configuration file defines the configuration for the TeaServlet. By default, if not specified, it defaults to /WEB-INF/teaservlet.xml or /WEB-INF/teaservlet.properties. The configuration file supports two formats (XML and enhanced properties). The enhanced properties format is similar to the standard Java properties format, but supports a more enhanced version that nests properties. For example, the following are equivalent

application.myapp.name=test

application {
    myapp {
        name = test
    }
}

The following is a standard XML based configuration file.

<teaservlet>
    <template>
        <default>index</default>
    </template>

    <admin.key>secret</admin.key>
    <admin.value>password</admin.value>

    <plugins></plugins>

    <applications>
    </applications>
</teaservlet>

The template section configures where the TeaServlet may alternatively load Tea templates from that are not precompiled and where the resulting compiled classes are stored, if any. Tea can use both precompiled templates and non-precompiled templates. Templates are automatically precompiled in Maven builds if they exist in the src/main/tea directory. Otherwise, you can configure TeaServlet to retrieve templates from other sources (including HTTP locations through the Setting up a Template Server). TeaServlet provides an administrative interface for selectively compiling the templates. The templates can also be compiled into class files and stored. This is useful since restarts will automatically pick up the compiled class file rather than re-compiling.

The admin section configures the TeaServlet administrative console. This typically includes the secret key and password that is required to access the console. The console is accessible via http://[server]:[port]/[context]/system/teaservlet/Admin?[admin-key]=[admin-password]. For more information, see the TeaServlet User Guide.

The plugin section provides the ability to load custom plugins into TeaServlet. Plugins can be accessed by applications and allows access to the TeaServlet system for providing increased functionality. Example plugins include custom data sources such as JDBC plugins. For more information see the TeaServlet User Guide.

The final section, applications, provides the ability to install applications for use with Tea. Applications expose context classes and methods that Tea templates can directly invoke. Applications, in a sense, act as helper methods or controllers providing increased functionality to Tea templates. For more information, see the TeaServlet User Guide.

Application Classes

Now that Tea is configured, the next step is to create custom application classes to provide enhanced functionality to templates. For this tutorial, we will create a simple GreetingApplication that provides the ability to return custom greetings. The first step is to create an application class that implements Application. The Application interface exposes four methods: init, destroy, getContextType, and getContext. The init and destroy methods are lifecycle methods that are invoked when Tea starts up and is shutdown. The init method also provides an ApplicationConfig instance that provides access to the specific application configuration as well as access to the plugins in the environment. This allows an application to lookup a plugin or access other data within a plugin.

The context-based methods provide information on the actual context class being exposed by the Application. The getContextType method should return a class instance that refers to the type of class being returned from getContext. Generally, getContextType returns an interface and getContext returns an implementation of that interface. However, they could both return the same class. There are typically two types of applications and contexts. One is a stateful context in which a context has state that is shared by a single request for the duration of that request. The other is a stateless context that has no state and may be used by multiple requests concurrently. To use a stateful context, you should create and return a new instance of the context per request to getContext. You may also pass the request/response parameters if applicable. To create a stateless context, create a single instance of the context, typically during the init method, and return that same instance on subsequent invocations of getCotext. Stateless contexts offer the best performance and lowest garbage collection and should be typically used unless there is a specific use case for per-request handling.

As an example, walkthrough the creation of a GreetingApplication that provides a getGreeting method to a template. First, let's create our simple POJO context class for GreetingContext. This will expose the public methods for Tea. For this example, we expose a single getGreeting method that returns a custom greeting.

public class GreetingContext {
    private String greeting;

    public GreetingContext(String greeting) {
        this.greeting = greeting;
    }

    public String getGreeting() { return this.greeting; }
}

Now, create a GreetingApplication class that implements Application. The application class will be responsible for returning the GreetingContext context. First, in the init method we will create an instance of the GreetingContext. The context expects a greeting to be passed as a parameter. We will make that parameter customizable by looking up the value from the application initialization properties (more on that in a bit).

import org.teatrove.teaservlet.Application;
import org.teatrove.teaservlet.ApplicationConfig;

public class GreetingApplication implements Application {
    private GreetingContext context;

    public void init(ApplicationConfig config) {
        // get the greeting init param, defaulting to Hello
        String greeting = config.getProperties().getString("greeting", "Hello");
        this.context = new GreetingContext(greeting);
    }

    public void destroy() { 
        this.context = null;
    }

    public Class<?> getContextType() {
        return GreetingContext.class;
    }

    public Object getContext() {
        return this.context;
    }
}

We now have a complete application and context that can be installed into Tea. Although this is a very simple example, we could easily modify this to lookup a custom plugin or data source from the ApplicationConfig in the init method and then lookup the custom greeting accordingly.

To make the application and its context's methods available to Tea, the application must be specified in the TeaServlet configuration within the applications block including any custom intialization properties. Tea maps the properties as simple key/value pairs (ie: message=Hola).

<GreetingApplication>
    <class>GreetingApplication</class>
    <init>
        <message>Hola</message>
    </init>
</GreetingApplication>

The name of the GreetingApplication parent element is just used to name the application and does not need to match the name of the class. It is purely arbitrary and only used when fully-qualifying method invocations in Tea (ie: GreetingApplication.getMessage()) to avoid method naming conflicts.

This is an example of a stateless application that returns the same context for every request. Since these types of applications are extremely common, Tea provides a default application so that you only need to create a context class.

<GreetingApplication>
    <class>org.teatrove.teaapps.apps.DefaultApplication</class>
    <init>
        <contextClass>GreetingContext</contextClass>
        <message>Hola</message>
    </init>
</GreetingApplication>

However, you may notice that you have no access to the init parameters in the context class to lookup the message parameter. Tea provides access to these properties if the context implements the Context interface. In this case, the context has to provide a default constructor and implement the init method that takes a ContextConfig.

import org.teatrove.teaapps.Context;
import org.teatrove.teaapps.ContextConfig;

public class GreetingContext implements Context {
    private String greeting;

    public GreetingContext() { }

    public void init(ContextConfig config) {
        this.greeting = config.getProperties().getString("greeting", "Hello");
    }

    public String getGreeting() { return this.greeting; }
}

This is the preferred way of exposing contexts unless there is a special need to provide per request/response handling in an application. For more information on applications and contexts, see the TeaServlet User Guide.

The final way that classes can interact with Tea is by exposing public static methods. Tea may invoke any publicly static method by fully-qualifying the class. To avoid fully-qualifying the package, you may also use the automatic imports feature to import the packages. See the TeaServlet User Guide for more information on imports.

Templates

TeaServlet is based around Tea templates, similar to PHP or JSP. Tea templates have a filename ending in ".tea". The template begins with a definition block and then includes a mixture of Tea code and HTML code. The following is a simple example of a template. You can include this template in the src/main/templates directory.

<% template index()

'Hello World'

Templates are composed of Tea code surrounded by <% and %> code blocks. Any code outside of the <% and %> is plain text or HTML and printed to the output as is. Going back to our example of printing a custom greeting, we now utilize the GreetingApplication:

<% template greeting(String name) %>

<html>
<head><title>Greeting</title></head>
<body>
<%
    greeting = getGreeting()
    greeting ' ' (name ?: 'World')
%>
</body>
</html>

One of the features of Tea is that it will automatically set request parameters that match the named parameters of the definition. It will also convert the data types accordingly so you can use primitives or wrappers accordingly. In this particular case, any request parameter named 'name' will be set as the 'name' variable.

This example, however, includes the frame of the page. This would need to be replicated on every page which is inefficient. Tea supports this by allowing a template to call other templates (ie: server side includes). Templates may also include content as part of the call. The following template acts as a layout template:

<% template frame(Map params) { ... }

title = params['title'] ?: 'Greeting'

%>
<html>
<head><title><% title %></title></head>
<body>
    <div class="header">header</div>
    <div class="body">
        <% ... %>
    </div>
    <div class="footer">footer</div>
</body>
</html>

The <% ... %> is used to include the body of the calling template as denoted by the { ... } in the template definition. This will simplify our greeting template:

<% template greeting(String name)

call frame(##('title' : 'Greeting')) {
    greeting = getGreeting()
    greeting ' ' (name ?: 'World')
}

For more information on Tea and the Tea syntax see the User Guide.

Building the Application

If using Maven, the easiest way to build the application is to run mvn package which will generate a WAR file. The WAR file can be deployed to an application server of choice. Alternative, you can run the command with the embedded Jetty server via mvn jetty:run.

Once the application is running, you can access the greeting template at http://[host]:[port]/[context]/greeting?name=User. You can also access the Administration interface via http://[host]:[port]/[context]/system/teaservlet/Admin?[secret]=[key]