For those who are not affiliated with UTD, the UTD-specific classes referred to in this document would not be readily available. I believe we can freely share with other educational institutions, but that comes under legal-stuff-I-know-nothing-about-and-that's-good.
Author: Barbara Baughman, Systems Analyst, University of Texas at Dallas, baughman@utdallas.edu
Set up the tomcat lib subdirectory
Create application subdirectories
The web.xml file - Configuring the application
Servlets that use Velocity templates
Templates for every application
Configuring Log4j loggers in the properties file.
Setup application workflow through several servlets
Setup application workflow through function classes.
A blank screen is returned instead of the results of a Velocity template.
Velocity doesn't execute one of my methods or find a reference
The home directory for tomcat is referred to as ${tomcat_home}.
The name of the directory that stores the application files is referred to as app_name.
The home directory for the application files is reffered to as ${app_home}, which is the same as ${tomcat_home}/webapps/app_name.
What permissions should be used
for the files?
All of the files for the ${tomcat_home} subdirectory and everything
underneath it should be owned by the same userid as apache (http). The
group permission should have userids that can read and/or write. There is
no need to make anything world-readable/writeable/executable.
The server.xml file in $tomcat_home/conf contains instructions about setting up the tomcat-managed web applications. It is a good idea to add a <Context> element to the file for every web application, just to make sure the application will be setup as you like. Put them after the line for <Context path="/examples" ...</Context>
<Context path="/jams" docBase="jams" debug="0" reloadable="true" crossContext="false"> <Logger className="org.apache.catalina.logger.FileLogger" prefix="localhost_jams." suffix=".txt" timestamp="true"/> </Context>
The path refers to the path from $tomcat_home/webapps that holds the files related to this application. The docBase under my usual setup is the same as the path without the leading '/'.
Reloadable is true if you want the application to be reloaded when a java class file changes. This is very convenient for a test environment. However, in production it will invalidate all of your current sessions when it reloads. For production environments, this should be false.
The crossContext indicates if the ServletContext for this application will be shared with other tomcat web applications. I make this false, since it is unnecessary for my applications.
The Logger is an optional statement that puts tomcat log messages for startup/shutdown for this application into a separate file. In this case, you can look for it under $tomcat_home/logs/localhost_jams.yyyy-mm-dd.txt.
Tomcat log files
If tomcat doesn't seem to startup correctly, or you application doesn't
seem to startup, you can go to the tomcat logs to view error
messages. See above for an example of setting up a separate log file for
a given web application. Otherwise, the messages are in
localhost_log.yyyy-mm-dd.txt. These log files should be purged
periodically, since they do nothing but get more and more numerous.
Setup the
tomcat shared/lib subdirectory
Under ${tomcat_home}/shared/lib, put the jar files for programs that will
be
needed for all applications. In particular, tomcat expects to find all
oracle drivers and ldap-related jars in this directory. For any given web
application, tomcat will include
in its CLASSPATH the jars in this directory. The files should be owned by the
same userid that will startup tomcat.
If you add new jars to this library, you must restart tomcat to have them included in the CLASSPATH.
Create application
subdirectories
Make the directories/subdirectories for the application. First, create
the subdirectory app_name under ${tomcat_home}/webapps, where
app_name will be the name of your choice. All application
files should go under ${tomcat_home}/webapps/app_name. Be sure that
apache allows the ${app_home} subdirectory to be
readable by a browser.
Under ${app_home}, create the subdirectories images, html, and WEB-INF. The images subdirectory contains image files that may be used in the application. The html subdirectory contains any HTML files that may be used in the application. The WEB-INF subdirectory contains everything else. Of course, you can have other subdirectories depending on how you wish to organize the application. Keep in mind that everything under the WEB-INF subdirectory will not be viewable from a web browser, and everything not under WEB-INF can be viewed from a web browser. Under WEB-INF, create the subdirectories lib, logs, templates, and classes.
The URL http://server.utdallas.edu/app_name/html/htmlfile.html will look for the file ${app_home}/html/htmlfile.html.
The lib subdirectory contains all jars that are needed for this particular application. Include the velocity jar here, log4j jars, and UTD-specific jars. All files in this subdirectory are in the tomcat CLASSPATH for this application only. If you add new jars to this library, you must reload this web application or restart tomcat to have them included in the CLASSPATH. If you put a new version of the jar in this subdirectory, you must reload the application or restart tomcat to have them in the CLASSPATH for this web application.
The logs subdirectory contains application logs.
The classes subdirectory contains java classes that are known in the tomcat CLASSPATH for this web application. It will contain any classes that are specific to this application.
The templates subdirectory will hold the Velocity templates for this application.
The web.xml file - Configuring the
application
At application startup, Tomcat expects to find a file called web.xml in
each application under ${app_home}/WEB-INF (although
it does have a default in ${tomcat_home}/conf).
Put an entry for every servlet
that will be accessed under this application as follows:
<servlet> <servlet-name> MAIN </servlet-name> <servlet-class>MainController</servlet-class> <init-param> <param-name>properties</param-name> <param-value>/WEB-INF/velocity.properties</param-value> </init-param> <load-on-startup>5</logad-on-startup> </servlet>
This tells the web browser information about a particular servlet. It gives the name and then the class in the CLASSPATH that will run. In this case, it is probably the file $app_home/WEB-INF/classes/MainController.class. The load-on-startup tells tomcat to run the init() method when tomcat starts up. The number is a simple sequence number to have multiple servlets in the application start in a particular order.
The example above includes an init-param for the servlet. Optionally,
initializing parameters can also be defined as a context parameter, which
makes it available to all servlets within the application. Parameters
defined under the <servlet> are obtained through the ServletConfig
object. Context parameters are available through the ServletContext
object. During the servlet's init() method call:
getServletConfig().getInitParameter("properties");
to get an init-param under the servlet definition, or
getServletConfig().getServletContext().getInitParameter("properties");
to get a context parameter. Here properties is the param-name.
To set a context parameter, have the following code before the first <servlet>.
<context-param> <param-name>properties</param-name> <param-value>/WEB-INF/app.properties<param-value> <description>Property values for this application </descrption> </context-param>
For every servlet that should be accessible to the user, you must also
give the <servlet-mapping>, telling which URL's will be mapped to a
particular servlet. A servlet that does not interract with the web
application user does not need and should not have a
<servlet-mapping>
<servlet-mapping> <servlet-name>MAIN</servlet-name> <url-pattern>/servlet/MAIN</url-pattern> </servlet-mapping>
The URL pattern refers to the characters that will follow the base URL that gets you to the application itself. For example, if the base application URL is https://netid.utdallas.edu/guam, the the mapping above will take all URL requests to https://netid.utdallas.edu/guam/servlet/MAIN to be processed by the class MainController. The servlets must all be defined in the web.xml file before the first servlet-mapping.
The web.xml file is where you can impose the maximum length of
a user session to be idle before the session information goes away. The
following example sets the session limit to 60 minutes.
<session-config> <session-timeout>60</session-timeout> <session-config>
In addition, it is a good idea to put a welcome file in the application home directory for troubleshooting purposes. If it does nothing but provide a link to the welcome or login screen, that's enough.
<welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list>
This gives the location of template files. If more than one directory is used, separate the list of directories with a comma. Note that the path is relative to the app_home directory because of the way the method startVelocity() below initializes the VelocityEngine.
To use log4j to output the Velocity log file, also add something like
the following line to the application properties file.
runtime.log.logsystem.log4j.category=VELOCITY
Then in the log4j properties file, define the log4j parameters for this category. Currently, Velocity's log4j link uses the deprecated Category class instead of the preferred Logger class.
The easiest way to create a servlet that has a builtin VelocityEngine is to create a class that extends edu.utdallas.ir.servlet.UTDServlet. The following method is used to create a VelocityEngine based on a Properties object created from the properties file. Call edu.utdallas.ir.util.WebAppProperties.getProperties(ServletConfig) to get this Properties object. This method is included in UTDServlet during init().
If you are not affiliated with UTD, then you'll want to create your own web application servlet that has some basic methods and does standard initialization.
private VelocityEngine startVelocity(Properties p) { String m=""; VelocityEngine ve=new VelocityEngine(); //Setup the file resource loader String path=p.getProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, "/templates"); path = getServletContext().getRealPath( path ); if ( path != null) { p.setProperty( RuntimeConstants.FILE_RESOURCE_LOADER_PATH,path); } m=new StringBuffer(64) .append("Velocity templates in ").append(path).toString(); msg.add(m); //Setup the velocity log String vellog=p.getProperty("RuntimeConstants.RUNTIME_LOG", "/WEB-INF/logs/velocity.log"); String log=getServletContext().getRealPath(vellog); if (log!=null) { p.setProperty(RuntimeConstants.RUNTIME_LOG,log); } try { ve.init(p); } catch (Exception e) { m=new StringBuffer(64) .append("Unable to init the VelocityEngine.\n") .append(e).toString(); msg.add(m); working= false; } return ve; }
Here msg is a Vector of startup messages and working is a boolean value that tells whether the servlet was properly initialized. These are private values defined for the class.
Following is a basic method for rendering a template.
protected void renderTemplate(HttpServletResponse res, VelocityContext context, String template) { res.setContentType("text/html"); try { ve.mergeTemplate(template, context, res.getWriter()); } catch (ResourceNotFoundException rnfe) { } catch (ParseErrorException pee) { } catch (MethodInvocationException mie) { } catch (Exception e) { } }Add your own handling of any exceptions. The exception is very seldom thrown (famous last words). If there is a problem, look to velocity.log for the error message. The ve object is the VelocityEngine object owned by the servlet.
Other standard methods that I find helpful are:
protected Vector setServletParameters(Properties p);
This method is called during init(), right after the properties are
obtained from the web application's properties file as designated in
web.xml. The properties file, then, contains all the changeable
parameters that would define the servlet workings. The Vector is a set of
String messages about the value of the parameters as they are picked
up.
protected VelocityContext setInitialContext(Properties p);
This method is called during init(), after the properties are obtained
from the web application's properties file. It loads a VelocityContext
object with objects that you want to make available to all templates, and
which never change. You would do this by creating the template's context
using a statement like:
//Create a VelocityContext for this template rendering from initial
context
VelocityContext ctx=new VelocityContext(initialContext);
Servlets that use Velocity
templates.
There are several servlets already in edu.utdallas.ir.servlet which will
make development of Velocity-based applications much easier. The class
edu.utdallas.ir.servlet.UTDServlet is already mentioned. However, the
servlet you will most often extend, is
edu.utdallas.ir.servlet.UTDAppServlet or
edu.utdallas.ir.servlet.UTDAppServletLogged. See the documentation for
these classes for information about what the class does and decide which
methods you would want to override. The DownloadServlet and
DownloadServletLogged servlets will provide a web application where a user
can download files in a protected directory after authenticating.
When creating classes that will be used as references for the Velocity template, make it easy on the designer. Use lots of get methods and is methods to return data or boolean values respectively. This will make getting references more intuitive. The reference $user.lastname will refer to the method getLastname(). The reference #if ($user.isStudent) will refer to the method isStudent(). Avoid mixing upper case more than once after the get unless you have a clear understanding with the designer. The method getLastName will have to be referenced in the template as $user.lastName or $user.LastName. Velocity is only forgiving about the first capital letter.
All of the classes referenced in a template must be public, and all methods referenced must be public. Only public methods within the class are accessible to Velocity.
Public classes defined within another public class are difficult for Velocity to find. Avoid these.
Avoid making the template designer use methods that are complex. The get methods and is methods are good. Other methods that do not take parameters are also okay but the addition of the required () at the end can be confusing, because now some references need it and some don't. Methods that call for parameters are even more dicey, because the designer may need to know about using the correct java types, etc. This should really be avoided. Whenever possible, run this method in the java servlet and pass the results to the template. Another option is to place a tool in the VelocityContext that simplifies the use of the method. Even for standard methods that you think are simple (size()), it is better to make a simple method for the designer like getsize() to make a reference like $list.size, or to send a VelocityContext string $listsize. Be kind to your designer.
Also, be sure that methods used by the designer cannot throw any kind of exception whatsoever, even NullPointerException or IndexOutOfBoundsException. You don't want the designer to be able to inadvertantly send a javac exception stack to the web user.
Templates for every application
Some templates show up in virtually all of my applications. Thus, templates
with the same function always have the same name to help me with
troubleshooting.
The error.vm template is called when the application fails in some crucial part of initialization.
The noserver.vm template gives a standard message to the user when a request cannot be processed because of a system error. The idea is to avoid sending stack traces to the poor user. Instead, send this screen to the user, and log the stack trace to a log file.
The login.vm template presents a standard login screen.
The welcome.vm template presents the "home page" screen for this application.
The head.vm template presents the standard top of a screen. You can use the Velocity directive #parse("head.vm") at the top of a template file to have this at the top of the screen. Changing the header then only involves changing this file. This is also a good place to setup a standard