How to create a web-application using Tomcat, Turbine, Torque and MySQL
What is this all about?
=======================
In this document, I describe how to get a "web-application" working that
has a common web-interface for web-browsers, uses Java-technology for
the business logic and has a MySQL-backend to hold the data.
Though I use the subnet FreeSoftware-project "CodexRerum" I created
as the example, the most important thing I wanted to achieve with this
document is to describe, how the different tools used can be set up and
got to work with each other properly:
- 'Tomcat' is used as the Java servlet container and thus actually hosts the
"Java application".
- The 'Turbine'-framework is used to properly implement the Model-View-Controller
(MVC) architecture. Similar to what XML does for general data, the MVC makes
it possible to keep the user interface (View) separated from the business
logic and stored data (Model).
- 'Torque' makes use of an SQL-database really easy for Java programmers.
Instead of having to code SQL-statements directly, you can manipulate the database
contents via common Java-objects.
- 'Velocity' is used as a simple scripting language and processes
data on the user-interface-side.
- 'MySQL', as usual, works in the dark and cares for the data it hosts.
For all these projects, *lots* of documentation is available on their websites
(including Turbine's "TDK", which is a quasi-all-in-one-package).
Nevertheless, I did not find a brief "How to get it working by doing it yourself"
description, which is why I started one myself.
So, if you want to know how to set up such an application step by step to get
it running eventually, but if you don't want to spend a week or so on all the
documentations of the different projects, this document's for you.
Note that there are lot's of things about this document that can be
enhanced. So, feedback is always welcome!
Note too that this document is *not* intended to be a replacement but rather an
*add-on* to the official documentation of the miscellaneous projects. So, read
the "officials"!
Many thanks to Peter Riegersperger for helping me a lot with all this!
Oh, and, yes, you're right, I still need to layout this page a little bit. ;)
(c) Copyright Markus Amersdorfer < markus(dott)amersdorfer(att)subnet(dott)at >
How to get Torque working:
==========================
0. Torque-3.1 uses Maven instead of Ant.
As we don't wanna have to fiddle 'round with Maven too at the moment (having
to find out how to get Torque AND Tomcat AND Turbine to work is enough for me),
let's stick with Torque-3.0.2 (which uses our well-known and beloved Ant) ...
1. Extract torque-3.0.2.tar.gz and use torque-3.0.2/ for this project only
(which makes it easy as we don't have to use different config-files):
~/CodexRerum/torque-3.0.2/
2. The Torque-config-files:
$ cat ~/CodexRerum/torque-3.0.2/build.properties
torque.project = codexrerum
torque.database = mysql
torque.targetPackage = at.subnet.codexrerum.om
torque.database.url = jdbc:mysql://localhost:3306/codexrerum
torque.database.driver = org.gjt.mm.mysql.Driver
torque.database.host = localhost
$ cat ~/CodexRerum/torque-3.0.2/Torque.properties
torque.applicationRoot = .
log4j.category.org.apache.torque = ALL, org.apache.torque
log4j.appender.org.apache.torque = org.apache.log4j.FileAppender
log4j.appender.org.apache.torque.file = ${torque.applicationRoot}/logs/torque.log
log4j.appender.org.apache.torque.layout = org.apache.log4j.PatternLayout
log4j.appender.org.apache.torque.layout.conversionPattern = %d [%t] %-5p %c - %m%n
log4j.appender.org.apache.torque.append = false
torque.database.default=codexrerum
torque.database.codexrerum.adapter=mysql
torque.dsfactory.codexrerum.factory=org.apache.torque.dsfactory.TorqueDataSourceFactory
torque.dsfactory.codexrerum.pool.defaultMaxConnections=10
torque.dsfactory.codexrerum.pool.maxExpiryTime=3600
torque.dsfactory.codexrerum.pool.connectionWaitTimeout=10
torque.dsfactory.codexrerum.connection.driver = org.gjt.mm.mysql.Driver
torque.dsfactory.codexrerum.connection.url = jdbc:mysql://localhost:3306/codexrerum
torque.dsfactory.codexrerum.connection.user = codexrerum
torque.dsfactory.codexrerum.connection.password = password-for-codexrerum-user
torque.idbroker.cleverquantity=true
torque.manager.useCache = true
Here's my ~/CodexRerum/torque-3.0.2/schema/codexrerum-schema.xml.
(You should right-click and choose "save link as...", as otherwise your
Mozilla-browser might try to render the XML-file and you'll see nothing...)
3. Run ant to have the sql-commands as well as the java-source-code created:
~/CodexRerum/$ ant -f build-torque.xml sql
~/CodexRerum/$ ant -f build-torque.xml om
~/CodexRerum/torque-3.0.2/src/sql/codexrerum-schema.sql holds the SQL-commands.
~/CodexRerum/torque-3.0.2/src/java/at/subnet/codexrerum/om/ holds the Java-class-hierarchy.
4. As the mysql-root-user, manually create the "codexrerum" database and "SOURCE" the
.sql-file to have the tables created:
$ mysql -u root -p
> CREATE DATABASE codexrerum;
> GRANT ALL ON codexrerum.* TO codexrerum'%' IDENTIFIED BY 'codex-rerum-password';
> USE codexrerum;
> SOURCE ~/CodexRerum/torque-3.0.2/src/sql/codexrerum-schema.sql
Check that the tables have been created properly:
> SHOW TABLES;
Note:
If, at some point in your development process, you learn that you have
to redesign your database (e.g. you need to add another column to a table,
something like that), you can easily adapt the -schema.xml file and re-run
the the ant-commands above.
BUT: If you check the -schema.sql file, you'll see that it holds "DROP TABLE"
commands and thus all your previously will be *deleted* if you simply SOURCE
the new -schema.sql file. If you don't want to do this, mind to adapt the
file or even adapt the database manually to reflect the changes.
Furthermore, you will also have to update the .om-package with the new
.java files. BUT: The only files you should copy to your e.g. Eclipse
project directory are "Base*.java" and "map/*". Do *not* copy .om-package
classes that do not start with "Base...", cause these are the ones that
hold your own business-logic and will loose already performed changes to
these files if you overwrite them.
4a. Check that you can log in from your machine to your MySQL-server:
$ mysql -u codexrerum -p
Enter password: codex-rerum-password
I developed this application on a Debian Sarge pre-release version with
"mysql-server 4.0.21-3" and was wondering why I couldn't login though
I provided the correct password.
I finally figured out that the server's default list of mysql-users
included the two entries "Host='localhost', User=''" and another with
my machine's name 'thistle' instead of 'localhost'. Note that for both
entries the User-field was an empty string.
Now run 'mysql -u codexrerum -p -h localhost' (with "-h localhost"
being the default even if you don't provide this command-line option).
Remember the GRANT command for codexrerum: it set the Host-field to '%'
and thus did not restrict the user to any specific host.
The mysql-daemon new checks against its "user"-table in its
"mysql"-database and apparently matches the "Host='localhost', User=''"
entry in precedence over the "Host='%', User='codexrerum'" one.
So, if you can't connect from your own machine to your mysql-server (running
on this machine too), you can do one of the following:
- Specify a host on your GRANT option, e.g. "localhost".
The disadvantage is that you might have to add two user-entries, one
for localhost and one for "thistle" (replace this with your machine's
name), as Turbine e.g. connects via "thistle", while Torque rather uses
"localhost".
(Note: I did not verify this in detail, but you get the idea!)
- Delete the two user-entries from database "mysql", table "user", where
the "User"-column has the empty string set -- and enable your
'%'-Host-setting this way:
> DELETE FROM user WHERE Host="localhost" AND User="";
> DELETE FROM user WHERE Host="thistle" AND User="";
> flush privileges;
5. In Eclipse, create a new project "CodexRerum".
$ mkdir $ECLIPSE_WORKSPACE/CodexRerum/conf/
$ mkdir $ECLIPSE_WORKSPACE/CodexRerum/src/
$ mkdir $ECLIPSE_WORKSPACE/CodexRerum/lib/
$ mkdir $ECLIPSE_WORKSPACE/CodexRerum/torque-lib/
$ cp ~/CodexRerum/torque-3.0.2/Torque.properties $ECLIPSE_WORKSPACE/CodexRerum/conf/
$ cp -r ~/CodexRerum/torque-3.0.2/src/java/at/ ~/eclipse_workspace/CodexRerum/src/
$ cp ~/CodexRerum/torque-3.0.2/lib/*jar $ECLIPSE_WORKSPACE/CodexRerum/torque-lib/
Note: Concerning torque-lib/xml-apis-2.0.2.jar, see additional information
below when explaining how to get Turbine and Tomcat working!
Now download the Connector/J from mysql.com and save "mysql-connector-java-3.0.15-ga-bin.jar"
(or similar) to $ECLIPSE_WORKSPACE/CodexRerum/lib/.
$ cp -r ~/CodexRerum/torque-3.0.2/src/java/at/ $ECLIPSE_WORKSPACE/CodexRerum/src/
Add the jar-files to the build-path of the Eclipse-project so that
Eclipse knows about the libraries.
( Project-Properties -- Java Build Path -- Libraries -- Add Jars )
Set the "Default output folder" to "/CodexRerum/classes".
( Project-Properties -- Java Build Path )
Set "CodexRerum/src" as the source folder.
( Project-Properties -- Java Build Path -- Source )
[ If you wanna get rid of Eclipse's problem-descriptions about imports
that are never used, simply right-click your src/-directory and run
Source -- Organize Imports. :) ]
[ Mind to not change the Base*.java classes, they hold the "Torque-logic".
Your business-logic goes into the classes *not* starting with "Base"! ]
6. Test.java:
import org.apache.torque.Torque;
import at.subnet.codexrerum.om.Location;
public class Test {
public static void main(String[] args) throws Exception {
Torque.init("conf/Torque.properties");
Location loc = new Location();
loc.setName("Telefon");
loc.save();
loc.setDescription("Am Tisch");
loc.save();
}
}
Running this application from Eclipse directly should add a row to
the codexrerum-DB, table "Location" -- check this out:
$ mysql -u root -p
> use codexrerum;
> SELECT * FROM Location;
If the entry shows up, you have a working Torque-App! :)
-------------------------------------------------------------------------------
How to get Tomcat working:
==========================
Using Tomcat 5.0.28:
- Download and explode the tarball.
- Run '/opt/tomcat/bin/catalina.sh run' (or wherever you saved it to).
- Use your browser and go to "http://localhost:8080/".
You can try e.g. the "Servlet Examples".
- Done already. :)
Note:
If you wanna use "Tomcat Administration" or "Tomcat Manager", you'll
have to define the corresponding users first as is described on Tomcat's
Welcome-page.
-------------------------------------------------------------------------------
How to get Turbine working together with Tomcat:
================================================
The Turbine-libraries used in this document are based on the Turbine-TDK
version 2.2-b3.
I also tried using "turbine-2.3.jar" instead of the TDK's version,
but when using the newer one, Tomcat can not properly initialize our
web-application. So, though this is not the latest version, let's stick
with TDK's turbine-2.2-b3.jar and get everything up and running...
1. The TDK:
Download and explode "tdk-2.2-b3.tar.gz".
In $TDK/build.properties, add a line similar to the following:
tdk.home = /home/max/download/turbine/tdk-2.2-b3
Now, in /home/max/download/turbine/tdk-2.2-b3/, run:
$ ant
After a successful build, you'll have a webapps/newapp/ directory.
"newapp" is the TDK's sample application, which we'll use scripts
and libraries from...
2. Install the libraries:
$ mkdir $ECLIPSE_WORKSPACE/codexrerum/turbine-lib/
$ mkdir $ECLIPSE_WORKSPACE/codexrerum/tomcat-lib/
From the TDK's sample application "newapp", copy the libraries that
are not already covered by those in lib/ and torque-lib/ to
$ECLIPSE_WORKSPACE/CodexRerum/turbine-lib/.
These are:
bcel.jar
cactus-1.2.jar
cactus-ant-1.2.jar
commons-email-0.1-dev.jar
commons-util-1.0-rc2-dev.jar
dom4j-1.4-dev.jar
ecs-1.4.1.jar
flux-2.2.jar
fulcrum-3.0-b2-dev.jar
httpunit-1.3.jar
mail-1.2.jar
oro-2.0.6.jar
turbine-2.2-b3.jar
velocity-dvsl-0.43.jar
xalan-2.1.0.jar
xmlParserAPIs-2.0.2.jar [note: see additional information below]
xmlrpc-1.1.jar
Note the following:
- The newapp-libs can be found in $TDK/webapps/newapp/WEB-INF/lib/ .
(If anything, the libraries from our previously downloaded Torque
(see sections above) are the newer ones and should be kept in
precedence over the older ones from the TDK.)
- The TDK's "hsqldb-1.7.0.jar" is not copied, as we most definitely
won't need it!
- TDK's "mm.mysql-2.0.13-bin.jar" is not needed either, as we already have
the newer mysql-connector in lib/. (The new version is needed for correct
handling of German umlauts and such things...)
Another special case:
- TDK's "servlet-2.2.jar" and "servlet.jar" are copied to yet another
library directory: $ECLIPSE_WORKSPACE/CodexRerum/tomcat-lib/
The reason for *this* is that we might need these two libraries
during the development process (so it should be available to Eclipse),
but Tomcat would complain about them if they are present in the
finally deployed web-application. Putting them into yet another
library directory allows us to simply not include them in the
Ant-tasks we will define later to easily deploy our web-application
(which, for the local development, will simply copy the class-files
and all libraries but the ones in tomcat-lib/).
Yet another important thing to do:
- Now that you have (nearly) everything in place, you have to move two
of the libraries again: move "torque-lib/xml-apis-2.0.2.jar" and
"turbine-lib/xmlParserAPIs-2.0.2.jar" into the "tomcat-lib/"
directory.
If you don't do that, they will be deployed into the Tomcat-webapp's
lib-directory. Unfortunately, the web-application's classloader will
then load these libraries and "overwrite" the default ones, resulting
in JSPs not working anymore in the webapp.
To finalise the inclusion of the libraries, mind to add them to Eclipse's
"Java build path" (including the changed location of the Torque-library
'xml-apis-2.0.2.jar').
3. Turbine configuration:
$ cp $TDK/webapps/newapp/WEB-INF/conf/TurbineResources.template \
~/eclipse_workspace/CodexRerum/conf/TurbineResources.properties
For the time being, simply adapt just one line:
module.packages=at.subnet.codexrerum.web.modules,org.apache.turbine.flux.modules
Further changes to tweak Turbine would be advisable, though.
4. Add a new package: at.subnet.codexrerum.web.modules.screens
In this package, create the following "Index.java", which will
initially serve as our "Hello World" sample application.
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/screens/Index.java
*/
package at.subnet.codexrerum.web.modules.screens;
import org.apache.turbine.modules.screens.VelocityScreen;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;
/* Note the base class "VelocityScreen". */
public class Index extends VelocityScreen {
protected void doBuildTemplate(RunData data, Context context)
throws Exception {
/*
* "HelloVar" is made avaible to Index.vm (which as you might realise
* has the same name as this class -- apart from the file-extensions, of
* course).
*/
context.put("HelloVar", "Hello Turbine World!");
}
}
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/screens/Index.java
*/
In order to have this code executed, we first have to set up our
Tomcat environment properly...
5. Tomcat hosting our "CodexRerum" application.
Some basics first:
CodexRerum/ is the directory that holds everything of our web-app.
CodexRerum/WEB-INF/ holds the classes, libraries and config-files.
The content of the WEB-INF directory is never made available to
the users accessing CodexRerum via Tomcat.
Common html-code or similar things go to CodexRerum/. You can create
a normal directory-structure in there.
CodexRerum/templates/ holds some velocity macros. In our case, this
directory too is *not* served to clients access the web-app via Tomcat,
but we need to make sure about this by adapting the configuration
manually in CodexRerum/WEB-INF/web.xml (see below).
So, create the directories:
$ mkdir -p /opt/tomcat/webapps/CodexRerum/WEB-INF/classes/
$ mkdir /opt/tomcat/webapps/CodexRerum/WEB-INF/conf/
$ mkdir /opt/tomcat/webapps/CodexRerum/WEB-INF/lib/
$ mkdir -p /opt/tomcat/webapps/CodexRerum/templates/app/
Now fill these directories ... first, the config-files:
$ cp $ECLIPSE_WORKSPACE/CodexRerum/conf/Torque.properties \
/opt/tomcat/webapps/CodexRerum/WEB-INF/conf/
$ cp $ECLIPSE_WORKSPACE/CodexRerum/conf/TurbineResources.properties \
/opt/tomcat/webapps/CodexRerum/WEB-INF/conf/
Now, copy the libraries:
$ cp $ECLIPSE_WORKSPACE/CodexRerum/lib/* \
/opt/tomcat/webapps/CodexRerum/WEB-INF/lib/
$ cp $ECLIPSE_WORKSPACE/CodexRerum/torque-lib/* \
/opt/tomcat/webapps/CodexRerum/WEB-INF/lib/
$ cp $ECLIPSE_WORKSPACE/CodexRerum/turbine-lib/* \
/opt/tomcat/webapps/CodexRerum/WEB-INF/lib/
Some useful Velocity macros:
$ cp -r $TDK/webapps/newapp/templates/app/* \
/opt/tomcat/webapps/CodexRerum/templates/app/
Based on TDK-newapp's web.xml, create the following
/opt/tomcat/webapps/CodexRerum/WEB-INF/web.xml:
<!--
/opt/tomcat/webapps/CodexRerum/WEB-INF/web.xml
-->
<web-app>
<servlet>
<servlet-name>CodexRerum</servlet-name>
<servlet-class>org.apache.turbine.Turbine</servlet-class>
<init-param>
<param-name>applicationRoot</param-name>
<param-value>webContext</param-value>
</init-param>
<init-param>
<param-name>properties</param-name>
<param-value>WEB-INF/conf/TurbineResources.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Initialise CodexRerum (incl. log4j) -->
<servlet>
<servlet-name>initialisation</servlet-name>
<servlet-class>at.subnet.codexrerum.web.Initialisation</servlet-class>
<init-param>
<param-name>log4j-configuration</param-name>
<param-value>WEB-INF/conf/log4j.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- This is needed for mapping requests to the application: -->
<servlet-mapping>
<servlet-name>CodexRerum</servlet-name>
<url-pattern>servlet/CodexRerum/*</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>templates</web-resource-name>
<url-pattern>templates/*</url-pattern>
</web-resource-collection>
<web-resource-collection>
<web-resource-name>logs</web-resource-name>
<url-pattern>logs/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Templates</realm-name>
</login-config>
</web-app>
<!--
/opt/tomcat/webapps/CodexRerum/WEB-INF/web.xml
-->
(Note that the order in which the different options of the file are stated
should be compatible to Tomcat 4, which seems to be kind of picky thereby,
resulting in SAXParsersExceptions and such things.)
Next, add some settings to /opt/tomcat/conf/server.xml that e.g. make
our web-app be reloaded automatically as soon as its class-files
are exchanged for newer ones -- without having to start Tomcat anew.
(Very useful for the development process :) ...)
This code block is placed at the end of the <Host name="localhost" ...>
section:
<!--
Add to /opt/tomcat/conf/server.xml
-->
<!-- [...] -->
<Context path="/CodexRerum" docBase="CodexRerum" debug="0" reloadable="true">
<Resources className="org.apache.naming.resources.FileDirContext"
allowLinking="true"/>
<Logger className="org.apache.catalina.logger.FileLogger"
prefix="localhost_CodexRerum_log." suffix=".txt"
timestamp="true"/>
</Context>
</Host>
<!-- [...] -->
<!--
Add to /opt/tomcat/conf/server.xml
-->
6. Before we have Tomcat run our Java application, let's just see if
it serves our web-app.
Create the file /opt/tomcat/webapps/CodexRerum/index.html that simply
holds the words "Hello CodexRerum!".
Now, start Tomcat by running:
# /opt/tomcat/bin/catalina.sh run
Wait until it says something like "INFO: Server startup in 12463 ms",
then browse to http://localhost:8080/CodexRerum/ .
If it greets you according to your index.html, proceed.
Else, debug what's going wrong and fix it.
(*ah*, how I dislike such statements in Howto's ;) ...)
7. Next, we eventually want to copy all the class-files over to the
Tomcat web-app directory:
$ cp -r $ECLIPSE_WORKSPACE/CodexRerum/classes/at/ \
/opt/tomcat/webapps/CodexRerum/classes/
And now is one of those moments -- after all this work, sit back
and relax for a moment.
...
If you feel comfortable enough, point your web-browser to
http://localhost:8080/CodexRerum/servlet/CodexRerum/template/Index.vm
and see what it returns. There are two possible outcomes:
1 - BAD: "HTTP Status 404", or sth. like that ... but this won't happen,
will it? ;)
2 - GOOD: A rather empty and somewhat broken looking page with some menu on
the left side appears.
Great -- something's working! :)
So, what's happening here?
As soon as you point your web-browser to the URL shown above -- besides
lots of other things of course -- Tomcat executes your "Index" class,
and then executes the Velocity based template with the same name as our
"Index" class: Index.vm, to be found as
/opt/tomcat/webapps/CodexRerum/templates/app/screens/Index.vm.
Remember we copied the whole templates/app/-directory over from the
TDK's newapp-application?
So, as the macros in there are definitely designed for 'newapp', it
makes sense that everything looks a little broken.
What you can do now is to replace the Index.vm with your own one,
holding just a single line of code:
## /opt/tomcat/webapps/CodexRerum/templates/app/screens/Index.vm
## The following line accesses Index.java's "HelloVar":
$HelloVar
Now, again browse to
http://localhost:8080/CodexRerum/servlet/CodexRerum/template/Index.vm
and you should see the content of Index.java's "assignment" to 'HelloVar',
accessed via a Velocity macro.
Note:
If, instead of "Hello Turbine World!", you see the string "$HelloVar",
then your Index-class is most probably not executed. Check these things:
- Your "Index" class "extends VelocityScreen", as is shown in the
Java code above.
- Eclipse built the class-file according to your changes (done when using
its "Project -- Build Automatically" option).
- You copied the .class-file over to the Tomcat-webapp.
- All file-permissions are correct (= everything's readable).
- You reloaded Tomcat (which you don't have to do if you used the setting
in /opt/tomcat/conf/server.xml to enable auto-reloading, see above).
Note:
If you read Velocity's User-Guide, you'll see that Velocity by default
prints the variable name like "$HelloVar" if the corresponding variable
is not set (so that's why you might get this output if your overall
system is not set up correctly).
Note:
The other HTML output you see besides your "Hello Turbine World!" is
based on the quite self-explanetory files
CodexRerum/templates/app/navigations/(DefaultTop|DefaultBottom|Menu).vm,
which are automatically included in the process of generating the final
HTML code.
Similarly, CodexRerum/templates/app/layouts/Default.vm is used as the
layout template, as there is no distinct Index.vm in this layouts-
directory.
-------------------------------------------------------------------------------
Integrate Torque-based database-access into Tomcat/Turbine:
===========================================================
0. Now that we got our Tomcat up and running and have it serve our Turbine
application, let's integrate some Torque stuff and use our database
(which still has an entry in the Location-table from our previous
Torque-tests...).
1. What we want is to show all locations in the Location-table.
So, let's first change the well-known Index.java a little bit,
actually adding one line of code only:
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/screens/Index.java
*/
package at.subnet.codexrerum.web.modules.screens;
import org.apache.turbine.modules.screens.VelocityScreen;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;
public class Index extends VelocityScreen {
protected void doBuildTemplate(RunData data, Context context)
throws Exception {
/* Let's keep something that already works just fine ... :) */
context.put("HelloVar", "Hello Turbine World!");
/*
* "Locations" also is made available to Index.vm. Though this of
* course depends on what getAll() returns, in our example it is
* a List of all locations stored in our MySQL-database.
*/
context.put("Locations", LocationPeer.getAll());
}
}
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/screens/Index.java
*/
2. Next, add the "getAll()" method to "LocationPeer.java" in the
package at.subnet.codexrerum.om (with 'Location' in 'LocationPeer.java'
of course not by chance matching the corresponding SQL-table's name
'Location').
(Note: Do *not* change the classes which's names start with "Base...",
these are Torque-logic! You own code goes into classes such as
LocationPeer.java!)
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/om/LocationPeer.java
*/
package at.subnet.codexrerum.om;
import java.util.List;
import org.apache.torque.TorqueException;
import org.apache.torque.util.Criteria;
public class LocationPeer
extends at.subnet.codexrerum.om.BaseLocationPeer
{
public static List getAll() throws TorqueException {
Criteria crit = new Criteria();
/* If you wanted the list to be ordered by Name,
* simply set the corresponding criteria accordingly:
*/
//crit.addAscendingOrderByColumn(LocationPeer.NAME);
return LocationPeer.doSelect(crit);
}
}
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/om/LocationPeer.java
*/
3. The only two thing left to do are:
- Copy the new class-files over to the Tomcat-webapp-directory.
(Note: Tomcat needs a few seconds from the time you copied the new
class files into the webapp's classes/ directory until it has
automatically reloaded them.)
- Have the Velocity macro show the results, so here's the new version:
##
## /opt/tomcat/webapps/CodexRerum/templates/app/screens/Index.vm
##
## The following line accesses Index.java's "HelloVar":
<h1>
$HelloVar
</h1>
<p>
<strong>Known locations are:</strong>
</p>
#foreach( $location in $Locations )
<p>
Name: $location.getName()<br>
Description: $location.getDescription()
</p>
#end
##
## /opt/tomcat/webapps/CodexRerum/templates/app/screens/Index.vm
##
If you now reload the website, you should see all SQL-entries
in the MySQL database "codexrerum", table "Location".
Congratulations! :)
4. Now, as the final section in this HOWTO, let's add functionality
to add new locations.
To do this, we need Turbine's "actions" ... so here we go:
Create the new package "at.subnet.codexrerum.web.modules.actions"
hosting the following class "LocationAction.java":
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/actions/LocationAction.java
*/
package at.subnet.codexrerum.web.modules.actions;
import org.apache.turbine.modules.actions.VelocityAction;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;
import at.subnet.codexrerum.om.Location;
public class LocationAction extends VelocityAction {
/**
* Fallback-method that is executed if the requested action
* (something like doCreate()) is not defined.
*/
public void doPerform(RunData data, Context context) {
/* Do something here */
}
/* See http://jakarta.apache.org/turbine/turbine-2.3/howto/action-event-howto.html
* Naming convention is to have these methods start with "do",
* followed by a capital letter (in this case "C"), followed
* by lower-case letters only (resulting in method names such as
* doMaxsupercreatemethod(), which does NOT use the usual CamelCase).
*/
/**
* Creates a new Location entry.
*/
public void doCreate(RunData data, Context context) throws Exception {
String name = data.getParameters().getString("name", null);
String description = data.getParameters().getString("description", null);
if (name != null) {
Location loc = new Location();
loc.setName(name);
loc.setDescription(description);
loc.save();
} else {
throw new Exception("The location's name must not be empty!");
}
}
}
/*
* $ECLIPSE_WORKSPACE/src/at/subnet/codexrerum/web/modules/actions/LocationAction.java
*/
Now, add the corresponding HTML-form to Index.vm:
##
## ../CodexRerum/templates/app/screens/Index.vm
##
## [...]
<p>
<strong>Add a new location:</strong>
</p>
<p>
<form method="post" action="$link.setPage("Index.vm").setAction("LocationAction")">
Name: <input type="text" name="name"><br>
Description: <input type="text" name="description"><br>
<input type="submit" name="eventSubmit_doCreate" value="Create this location">
</form>
</p>
##
## ../CodexRerum/templates/app/screens/Index.vm
##
Finally, copy the new class file to the webapp-directory,
reload the website and create new locations!
Congratulations again!
:)
Oh, and before forget this beloved sentence:
Adding the functionality to delete and modify database entries is
left as an exercise for the reader.
Take care -- and have fun with all this!
-- APPENDIX -------------------------------------------------------------------
Some IMHO interesting information
=================================
Concerning Peers and Criteria
-----------------------------
The Peers-Howto clears some things up:
http://db.apache.org/torque/torque-302/peers-howto.html
"You use a tool called Torque to generate Peer classes for you. There
are 4 classes for each table. Base<table-name>Peer, Base<table-name>,
<table-name>Peer and Base<table-name>.
The Base* classes contains all the functionality and should not be
change. The other two classes are empty and this is where your
application business logic goes. If you regenerate with torque only the
Base* classes changes. This allows you to change the schema, but still
keep your existing code."
"Peer Classes"
"Everything in Peers resolve around Peer classes. A Peer class has a one-to-one
mapping to a Database table. You use each table's associated Peer class to do
operations on that table. Peer classes can be generated for you automatically.
Peer classes have static methods only, so you would never create objects of
Peer classes. It is not necessary to have objects on this level because of the
one-to-one mapping with a table. Peer methods are thread safe."
Velocity
--------
*) Turbine's Context Howto explains well, how to use Turbine and Velocity with
each other, with detailed information about "Context", "RunData" similar
constructs.
*) If you want to use "/opt/tomcat/webapps/CodexRerum/templates/app/GlobalMacros.vm"
to define velocity macros (in C-speak you would probably say "functions"),
each time you change something concerning the structure of your macro, you
will have to reload Tomcat anew.
(There might a workaround I'm not aware of, but I'm not aware of one ;) ...)
(Check the Velocity User-Guide, section "Velocimacros" for more information
on how to create and use macros ...)
*) The only available loop-construct in Velocity is "#foreach", e.g.:
#foreach( $item in $list )
item ${velocityCount}/$list.size(): $item
#end
A typical for-loop such as "for (int i = 0; i < 5; i++)" can be implemented
as follows:
#foreach( $i in [1..5] )
loop $i
#end
*) Velocity seems to use a global namespace for its variables. This makes a
recursive invocation of macros nearly impossible.
*) In order to get a feeling about all this, you can include the following code
in e.g. the Index.vm and see what it does:
#*****************************************************************************#
##
## Some self-explanetory vm-Code ...
##
<h1>
Misc Velocity-Stuff
</h1>
<p>
<em>Defined and undefined references:</em><br>
HelloVar: "$HelloVar"<br>
Quiet HelloVar: "$!HelloVar"<br>
Unreferenced: "$Unreferenced"<br>
Quiet Unreferenced: "$!Unreferenced"
</p>
<p>
<em>Escaping:</em><br>
## The following line defines $email in this template:
#set( $email = "foo" )
$email<br>
\$email<br>
\\$email<br>
\\\$email
</p>
<p>
## Assignment to an undefined reference:
<em>Assignment to an undefined reference:</em><br>
#set( $foo = '$moon is an undefined reference, thus the value of $foo is not assigned to it' )
$moon = $foo
</p>
<p>
Mind that '$foo.User("john")' is the same as '$foo.getUser("john")'.
</p>
<p>
Velocity's numeric comparisons are constrained to Integers -
anything else will evaluate to false. The only exception to this is equality '==',
where Velocity requires that the objects on each side of the '=='
is of the same class.<br>
</p>
<p>
The <code>'\#include'</code> script element simply includes other files,
but the content is not interpreted.<br>
The <code>'\#parse'</code> script element can be used for VTL code to be included
and interpreted.
</p>
<p>
The <code>'\#stop'</code> script element allows the template designer to
stop the execution of the template engine and return. This is useful
for debugging purposes.
</p>
<p>
There is a range operator <code>[n..m]</code>:<br>
<code>\#foreach( \$foo in [1..5] ) \$foo \#end</code><br>
results in:<br>
<code>#foreach( $foo in [1..5] ) $foo #end</code><br>
Note that the range operator only produces the array when used in conjunction with
<code>\#set</code> and <code>\#foreach</code> directives, as demonstrated in this example:
[1..3]
</p>