Monday, January 30, 2006

Enabling JSP in Tomcat

Sometimes you just want to enable a directory on your tomcat server to be able to run JSP pages without actually installing a WAR or EAR file. Here are the essential steps to enable a directory to execute JSP files.

For this example, I'm setting it up on a SuSE Linux system so your directory structure may vary.

  1. Create a JSP directory in your tomcat webapps directory. In this case, the default webapps directory for Tomcat is /srv/www/tomcat5/base/webapps/ so I make a jsp directory there.

  2. Under the jsp directory I create the basic webapp directory structure and create a directory named /srv/www/tomcat5/base/webapps/jsp/WEB-INF

  3. In the WEB-INF directory, I create a basic deployment descriptor named web.xml which looks like:

    <?xml version="1.0" encoding="ISO-8859-1"?>
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    <display-name>My JSPs<display-name>

  4. In the tomcat configuration directory /etc/tomcat5/base/Catalina/localhost add the context path for the JSPs in a file named jsp.xml that looks like:

    <Context path="/jsp" docBase="jsp"
    debug="0" reloadable="true" />

  5. Restart Tomcat (rctomcat5 restart)

  6. Create some sample JSP file in the jsp directory (/srv/www/tomcat5/base/webapps/jsp such as:

    <title>Hello World</title>
    <h1><%= "Hello World" %><h1>

  7. Make sure it's working via http://localhost:8080/jsp/index.jsp

  8. I run Tomcat under Apache via mod_jk so that I can utilize the SSL layer of Apache. So, to make the JSP transparently accessible from Apache, I update the /etc/apache2/conf.d/jk.conf and add the following lines:

    # Add the jsp directory
    Alias /jsp "/srv/www/tomcat5/base/webapps/jsp"
    <Directory "/srv/www/tomcat5/base/webapps/jsp">
    Options Indexes FollowSymLinks
    allow from all
    JkMount /jsp ajp13
    JkMount /jsp/* ajp13
    <Location "/jsp/WEB-INF/">
    AllowOverride None
    deny from all

  9. After restarting Apache (rcapache2 restart) I can then reference the JSPs directly from Apache, such as http://localhost/jsp/index.jsp

Multimedia setup for SuSE 10

This was a very useful resource for setting up multimedia capabilities for SuSE 10:

Additionally, adding some of the extra Yast software sources from or is useful.

Current Earth image wallpaper

When I'm using Windows, I use WeatherPulse to display the current satelite picture on my desktop (the "GOES E on Color" option). I'm very happy with it and wanted something similar on my SuSE Linux KDE desktop. To do that, I used XPlanet. Below are the setup steps I followed:

  1. Install xplanet RPM if not already installed.

  2. I made a .xplanet directory in my home directory in which I copied the config file from /usr/share/xplanet/config

  3. In the [earth] section of the config file, I added the lines:


  4. To get the dynamic cloud map, I downloaded the Perl script from to my .xplanet directory. Note: you may wish to change this line in the script to suite your preferences:

    my $MaxDownloadFrequencyHours = 1;

  5. I then added this line to my crontab

    0 * * * * cd /home/username/.xplanet; perl > /dev/null

    to update the cloud information once an hour

  6. To get the topography and enahanced coloring maps in my config, they can be purchased from J.H.T.'s Planetary Pixel Emporium (though a Google search for earthmap4k may reveal other sources).

  7. If you want to track a satelite, such as the Hubble telescope or International Space Station (ISS), download the lastest tracking data with either:

    wget -O science.tle

    for scientific satelites (Hubble) or:

    wget -O stations.tle

    for space stations (ISS). In the resulting science.tle file, you will see a section with the header "HST" (Hubble space telescope), place those lines in a file named hubble.tle which will then look like:

    1 20580U 90037B 06008.34262361 .00000587 00000-0 33114-4 0 5098
    2 20580 28.4688 11.8178 0003636 285.4875 74.5312 14.99943058661070

    If you also want to track the IST, also add the "ISS (ZARYA)" lines from the stations.tle file. These lines will look something like:

    1 25544U 98067A 06009.46434028 .00013978 00000-0 10043-3 0 1177
    2 25544 51.6445 283.5661 0010748 133.7206 217.3373 15.73896465408147

    Then, create a file named hubble in your .xplanet directory that contains:

    20580 "" image=hubble.png transparent={0,0,0} trail={orbit,-10,0,5} color=green

    for tracking the Hubble and/or:

    25544 "" image=iss.png transparent={0,0,0} trail={orbit,-10,0,5} color=green

    for tracking the space station. Finally, add this line to the [earth] clause of the config:


  8. Finally, set it up in KDE. right click on the background.

  9. Select Configure Desktop.

  10. Click on Background in the left hand panel.

  11. In the Background group box, I have No picture selected.

  12. Click on Advanced Options.

  13. I have Use the following program for drawing the background checked.

  14. Select XPlanet in the list.

  15. Click on Modify.

  16. My Command entry is:

    xplanet --config /home/username/.xplanet/config
    --geometry %xx%y --num_times 1 --output %f.jpg
    --latitude 27.96 --longitude -82.48 --radius 60 &&
    mv %f.jpg %f

    (the latitude and longitude is for where I live -- adjust to suit)

  17. My Preview cmd entry is the same as above.

  18. I have my Refresh time set to 5 minutes if I'm tracking satellites, and 30 minutes if not.

  19. Enjoy a dynamic updating picture of the earth such as:

Update - December 19, 2007

First off, I want to point out that XPlanet is an option for Windows as well. See XPlanet on Microsoft Windows. You can use the same Perl script to download the latest cloud cover image using ActivePerl. When you run XPlanet use the -fork option to have it periodically update the background.

Secondly, the instructions above were for KDE, but if you use Gnome, here are the instructions to get XPlanet to work with Gnome2.

Wednesday, January 25, 2006

Encoding email addresses

Here is a handy online tool I use to encode email addresses on my web
pages so that spammers won't find it via automated searches.

Using XML in Ajax (with PHP)

In support of my earlier post about using the XMLHttpRequest object for XML, here is a nice article demonstrating the use of the responseXML attribute of the XMLHttpRequest with PHP. Here is another.

Also, just for reference here are a few nice sites discussing developments in AJAX:
AJAX blog
AJAX Matters
ajax info

As well as some good usage guideline articles:
Ajax Mistakes
XMLHttpRequest Usability Guidelines

Detecting email problems

This is just a little tip for detecting email issues with your web
hosting provider. Myself and some friends of mine were experiencing
email loss with our hosting provider. So, in order to provide some
imperial data we set up a couple of automated processes to send
ourselves email every 5 minutes. We set up one process on the
provider's server which sent to an email account in question on the
provider's server, as well as an external control account. Then we set
up another process on another server outside of the hosting provider's
realm to send email to the same email account in question on the hosting
provider and the control account. The external email account was our
control set to monitor that all the email messages were actually

The setup (done on both the hosted server and the secondary server):

Make a test message such as:

Subject: 5min Test from [servername]
From: Automated Test <>

Test email.

The header information in the test message is useful for adding filters
to your email client. The blank line is necessary after the header
lines in order for sendmail to recognize it as a header.

We then set up a simple cron process

*/5 * * * * /usr/sbin/sendmail -i, < testemail.txt

This will send out the message every 5 minutes (every minute divisible
by 5). Then it is just a matter of scanning the email received for
missing emails.
In the end, we were able to detect email loss as well as delayed email.
The hosting provider was then able to make some adjustments to stop the
loss. During peek load times, some email still gets lost for a hour or
two, but does eventually show up.

Monday, January 23, 2006

Ajax in practice

In my research of using AJAX, I've run across plenty of nice examples. Most of the examples consist of the basics of building and using an XMLHttpRequest such as:

function createRequestObject() {
var ro;
var browser = navigator.appName;
if(browser == "Microsoft Internet Explorer"){
ro = new ActiveXObject("Microsoft.XMLHTTP");
ro = new XMLHttpRequest();
return ro;
var http = createRequestObject();

The use that XMLHttpRequest object to submit the request to the back-end server such as:

function sendRequest(action) {'get', 'ajaxHandler.php?action='+action);
http.onreadystatechange = handleResponse;

And finally some function to update the browser page based on the result:

function handleResponse() {
/* The XMLHttpRequest object has a property called readyState with several states:
0: Uninitialized
1: Loading
2: Loaded
3: Interactive
4: Finished
if(http.readyState == 4){
var response = http.responseText;
document.getElementById('divName').innerHTML = response;

That's all well and good, but is seems like a misuse of an XMLHttpRequest to send a plain text (or HTML) response. We have a perfect opportunity to separate presentation from data and everyone seems to mash them together on the back-end instead of letting the browser handle the rendering. At least the example here demonstrates using the responseXML property of the XMLHttpRequest object instead of only the text response which is nice.

One of the worst abuses that I've encountered so far seems to b e the Google Suggest implementation. In my past research of AJAX I ran across this excellent piece by Chris Justus about how Google used AJAX to implement Google Suggest. I must say, I'm disappointed with how the folks at Google did that. As Chris demonstrates, they actually return a JavaScript snippet which it then invoked by the browser to add the search suggestions.

Sample returned value:

sendRPCDone(frameElement, "fast bug",
new Array("fast bug track", "fast bugs", "fast bug", "fast bugtrack"),
new Array("793,000 results", "2,040,000 results", "6,000,000 results", "7,910 results"),
new Array(""));

The actual sendRPCDone method is previously loaded in the web page, but they instruct the browser to invoke it was it gets the response. Obviously it works, and is very fast, but the data just seems too bound to the implementation. Why not just return the data in a format that can be used in this page, and possibly other functions? Something like:

<search>fast bug</search>
<value>fast bug track</value>
<value>fast bugs</value>
<value>fast bug</value>
<value>fast bugtrack</value>

Friday, January 20, 2006


I ran across this link to a paper based on my Master's thesis. My graduate advisor, Dr. David Workman, edited my paper and presented it at conference a couple years ago.

He has apparently turned it into the topic of a whole course which I became aware of when some of his students contacted me about the source code that was used in the program this paper discusses. Here is a link to the course information. The full text of the above mentioned paper is available from this course page.

It's nice to know that your work still has life after you graduate!

Changing FeedCreator.class.php for Podcasting

As I mentioned in my Podcasting HowTo, I used Kai Blankenhorn's FeedCreator.class.php as the basis of my podcasting feed. In this posting I will detail my additions to his code to support podcasting and iTunes.

You may download the final file here

First, at line 31, I updated the change log:

added support for iTunes tags
added support for podcast (added enclosure)
made the Item category be an Array as you are allowed to specify
multiple category elements in the RSS spec

Then, at what becomes line 173 (after the changelog changes), I added
these classes:

* Version string.
define("FEEDCREATOR_VERSION", "FeedCreator 1.7.2-iTunes");
* An Enclosure is a part of an Item
* @author Steven Pothoven <>
* @since 1.7.2-podcast
class Enclosure {
* Attributes of an enclosure
var $url, $length, $type = "audio/mpeg";
* iTunes extensions to RSS 2.0
* @author Steven Pothoven <>
* @since 1.7.2-iTunes
class iTunes {
* This tag can only be populated using iTunes specific categories.
var $category, $subcategory;
* This tag should be used to note whether or not your Podcast contains explicit material.
* There are 2 possible values for this tag: Yes or No
var $explicit;
* At the Channel level, this tag is a short description that provides general information about the Podcast. It will appear next to your Podcast as users browse through listings of Podcasts
* At the Item level, this tag is a short description that provides specific information for each episode.
* Limited to 255 characters or less, plain text, no HTML
var $subtitle;
* At the Channel level, this tag is a long description that will appear next to your Podcast cover art when a user selects your Podcast.
* At the Item level, this tag is a long description that will be displayed in an expanded window when users click on an episode.
* Limited to 4000 characters or less, plain text, no HTML
var $summary;
* At the Channel level this tag contains the name of the person or company that is most widely attributed to publishing the Podcast and will be displayed immediately underneath the title of the Podcast.
* If applicable, at the item level, this tag can contain information about the person(s) featured on a specific episode.
var $author;
* This tag is for informational purposes only and will allow users to know the duration prior to download
* The tag is formatted: HH:MM:SS
var $duration;
* This tag allows users to search on text keywords
* Limited to 255 characters or less, plain text, no HTML, words must be separated by spaces
var $keywords;
* This tag contains the e-mail address that will be used to contact the owner of the Podcast for communication specifically about their Podcast on iTunes.
* Required element specifying the email address of the owner.
var $owner_email;
* Optional element specifying the name of the owner.
var $owner_name;
* This tag specifies the artwork for the Channel and Item(s). This artwork can be larger than the maximum allowed by RSS.
* Preferred size: 300 x 300 at 72 dpi
* Minimum size: 170 pixels x 170 pixels square at 72 dpi
* Format: JPG, PNG, uncompressed
var $image;
* This tag is used to block a podcast or an episode within a podcast from being posted to iTunes. Only use this tag when you want a podcast or an episode to appear within the iTunes podcast directory.
var $block;

In the FeedItem class I added these lines at the new line 283:

* Support for attachments
var $enclosure;
* Support for iTunes
var $itunes;

In the _setFormat function, I added a new case at the new line 491:

case "PODCAST":
$this->_feed = new PodcastCreator();

In the createFeed function, I added the iTunes extentions at line

/* iTunes add iTunes specific tags */
if ($this->itunes!="") {
if ($this->itunes->category!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->itunes->category)."\">\n";
if ($this->itunes->subcategory!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->itunes->subcategory)."\"/>\n";
$feed.= " </itunes:category>\n";
if ($this->itunes->explicit!="") {
$feed.= " <itunes:explicit>".$this->itunes->explicit."</itunes:explicit>\n";
if ($this->itunes->subtitle!="") {
$feed.= " <itunes:subtitle>".htmlspecialchars($this->itunes->subtitle)."</itunes:subtitle>\n";
if ($this->itunes->summary!="") {
$feed.= " <itunes:summary>".htmlspecialchars($this->itunes->summary)."</itunes:summary>\n";
if ($this->itunes->author!="") {
$feed.= " <itunes:author>".htmlspecialchars($this->itunes->author)."</itunes:author>\n";
if ($this->itunes->keywords!="") {
$feed.= " <itunes:keywords>".htmlspecialchars($this->itunes->keywords)."</itunes:keywords>\n";
if ($this->itunes->owner_email!="") {
$feed.= " <itunes:owner>\n";
$feed.= " <itunes:email>".$this->itunes->owner_email."</itunes:email>\n";
if ($this->itunes->owner_name!="") {
$feed.= " <itunes:name>".$this->itunes->owner_name."</itunes:name>\n";
$feed.= " </itunes:owner>\n";
if ($this->itunes->image!="") {
$feed.= " <itunes:link rel=\"image\" type=\"image/jpeg\" href=\"".$this->itunes->image."\">[image]</itunes:link>\n";

and additional iTunes and podcast extensions at line 1154:

/* podcasts add the enclosure element */
if ($this->items[$i]->enclosure!="") {
$feed.= " <enclosure url=\"".str_replace(" ", "%20", htmlspecialchars($this->items[$i]->enclosure->url)). "\" length=\"".htmlspecialchars($this->items[$i]->enclosure->length). "\" type=\"".htmlspecialchars($this->items[$i]->enclosure->type). "\"/>\n";
/* iTunes add iTunes specific tags */
if ($this->items[$i]->itunes!="") {
if ($this->items[$i]->itunes->category!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->items[$i]->itunes->category)."\">\n";
if ($this->items[$i]->itunes->subcategory!="") {
$feed.= " <itunes:category text=\"".htmlspecialchars($this->items[$i]->itunes->subcategory)."\"/>\n";
$feed.= " </itunes:category>\n";
if ($this->items[$i]->itunes->explicit!="") {
$feed.= " <itunes:explicit>".$this->items[$i]->itunes->explicit."</itunes:explicit>\n";
if ($this->items[$i]->itunes->subtitle!="") {
$feed.= " <itunes:subtitle>".htmlspecialchars($this->items[$i]->itunes->subtitle)."</itunes:subtitle>\n";
if ($this->items[$i]->itunes->summary!="") {
$feed.= " <itunes:summary>".htmlspecialchars($this->items[$i]->itunes->summary)."</itunes:summary>\n";
if ($this->items[$i]->itunes->author!="") {
$feed.= " <itunes:author>".htmlspecialchars($this->items[$i]->itunes->author)."</itunes:author>\n";
if ($this->items[$i]->itunes->keywords!="") {
$feed.= " <itunes:keywords>".htmlspecialchars($this->items[$i]->itunes->keywords)."</itunes:keywords>\n";
if ($this->items[$i]->itunes->duration!="") {
$feed.= " <itunes:duration>".$this->items[$i]->itunes->duration."</itunes:duration>\n";
if ($this->items[$i]->itunes->image!="") {
$feed.= " <itunes:link rel=\"image\" type=\"image/jpeg\" href=\"".$this->items[$i]->itunes->image."\">[image]</itunes:link>\n";

Finally, I added the PodcastCreator class at line 1235:

* PodcastCreator is a FeedCreator that implements Podcast
* @see
* @since 1.7.2-podcast
* @author Steven Pothoven <>
class PodcastCreator extends RSSCreator20 {
function PodcastCreator() {

Podcasting HowTo

As I mentioned in my last post, I added podcasting to our church web site. Today I thought I'd share how I did that for those interested in doing something similar.

To start with, I place all the sermon MP3 files into a separate directory. The MP3 files themselves adhere to the naming standard of:


Where the date is in YYMMDD format. So, this past weeks sermons file is named:

060115_John Keen_Mark 1;1-8.mp3

This makes it very easy add sermons to the list by just uploading the file. Note: the passage uses a ';' to delineate chapter and verse instead of a ':', this is because a ':' is not allowed in a filename on some systems (Windows). The code will correct that when displayed.

I started with a simple table to be shown on the web page. Shortly after that I thought an RSS feed of the contents would be handy so people could be motified of new sermons without visiting the web page. The RSS feed lead to the podcast feed, as they are nearly the same thing.

They all essentially do the same thing, so I'll skip right to the podcast version. However, I'll share one little tidbit from the web page version. From the table on the web page, you can either stream the MP3, or download the file. Streaming an MP3 file from your server doesn't take any special software. I wrote a simple PHP script which I name m3u.php which allows me to stream any MP3 file. The contents of the m3u.php looks like:

header("Content-Type: audio/x-mpegurl");
print '' . $_GET['filename'];

So, in my table code, invoke it as (in PHP):

<a href="sermons/m3u.php?filename=' . $filename . '">

To make the podcast feed, I started with Kai Blankenhorn's FeedCreator.class.php. It worked very well for the RSS feed, but it doesn't support the podcast extensions. So, I updated it to add the podcast and iTunes extensions to RSS. If you're interested, I'll be happy to provide my updated version. I'll summarize the changes in a separate post so I don't make this post too long.

Now, I'll walk through my podcast generating PHP class. First, I need a function to list all the files in my mp3 directory:

// Function list_dir will list all the files in a given directory
function list_dir($dirname)
static $result_array = array();
while ($file=readdir($handle)) {
// ignore the current and parent directory references ('.' and '..')
if (0 == strpos($file, '.')) {
closedir ($handle);
return $result_array;

I then set up the common header portion of the podcast feed:

$rss = new UniversalFeedCreator();
// if cache is less than 12 hours (43200 seconds) old, use it
// this is because the feed only changes once a week to begin with, and this
// script does a lot of ID3 tag manipulation which can take a while
$rss->useCached("PODCAST", "sermons/podcast.xml", 43200);
$rss->title = "Seminole PCA Sermons";
$rss->description = "Recent sermons from the pulpit of Seminole Presbyterian Church";
$rss->language = "en-us";
$rss->link = "";
$rss->webmaster = "";
$rss->docs = "";
$rss->syndicationURL = "".$PHP_SELF;
$rss->category = "Religion";

$image = new FeedImage();
$image->title = "Seminole PCA, Tampa, FL";
$image->url = "";
$image->link = "";
$image->description = "Feed provided by Click to visit.";
$image->width = 144;
$image->height = 204;
$rss->image = $image;

$itunes = new iTunes();
$itunes->category = "Religion & Spirituality";
$itunes->subcategory = "Christianity";
$itunes->subtitle = $rss->description;
$itunes->owner_email = "";
$itunes->owner_name = "Seminole Presbyterian Church";
$itunes->author = "Seminole Presbyterian Church";
$itunes->image = "";
$rss->itunes = $itunes;

Now, I loop through my list of mp3 files to build the contents of the podcast feed:

// PHP code to dynamically generate RSS feed
// Start of code -------
// get a list of the "mp3" directory
// reverse sort the array (puts newest sermon first)
rsort ($array);
reset ($array);

// for each file in the filelist, generate the appropriate HTML table code
foreach ($array as $filename) {
// break apart the filename into the 3 data fields; date, speaker,
// and subject using file naming convention:
// YYMMDD_Speaker_Passage.mp3
$tok = strtok($filename, "_");
$unixdate = mktime(0, 0, 0, substr($tok, 2, 2), substr($tok, 4, 2), substr($tok, 0, 2));
$sermondate=date("F d, Y", $unixdate);

$chapter = strtok($passage, ";");
$verse = strtok(".");
if ($verse !== false) {
$passage = $chapter . ':' . $verse;
} else {
$passage = $chapter;

at this point in my PHP code, I also use the getid3 PHP library to ensure that all the ID3 tags are set correctly in the mp3 file. This is not important for actually generating the podcast feed, so I'll omit it here.

$item = new FeedItem();
$item->title = $speaker . ' on ' . $passage;
$item->link = "" . $mp3dir . "/" . $filename;
$item->description = $sermondate . ' - ' . $speaker . ' expounds on ' . $passage;
$item->date = $unixdate;
$item->source = "";
$item->author = $speaker;
$item->enclosure = new Enclosure();
$item->enclosure->url = "" . $mp3dir . "/" . $filename;
$item->enclosure->length= $id3['filesize'];
$item->category[] = $itunes->category;

$item->itunes = new iTunes();
$item->itunes->category = $itunes->category;
$item->itunes->subcategory = $itunes->subcategory;
$item->itunes->keywords = "Seminole Presbyterian Church PCA Reformed Christian Sermon Tampa Florida FL";
$item->itunes->summary = $item->description;
$item->itunes->author = $item->author;
$item->itunes->duration = $id3['playtime_string'];
$item->itunes->image = "";
$rss->addItem( $item);

Finally, I save feed for caching purposes and return it:

$rss->saveFeed("PODCAST", "sermons/podcast.xml");

The resulting podcast XML can be seen here.

Once that's working, go register you feed at the various podcast directories such as:
iPassage (if you podcasting sermons)
Podcast Alley
iTunes (HowTo)

I published my feed in iPodder,, and Podcast Alley and was automatically propagated to other directories including the iTunes Store.

For reference, here is the official Apple iTunes/Podcasting specification.

Thursday, January 19, 2006


One of the things I do outside of my job is maintain the website for my church. I attend Seminole Presbyterian Church ( On that site I added podcasting for our sermons and was recently interviewed by a reporter at the St. Petersburg Times regarding the podcasting. You can read the article here or from the archive here.

I will be redesigning that page soon and will probably be incorporating some AJAX techniques, so I will be adding posts regarding AJAX soon.

About my blog

Hello, my name is Steven Pothoven, welcome to my blog!

I am a software architect / software engineer for IBM. I have a Master's degree in Computer Science and have been working at IBM since 1995. My specialty is in object-oriented development using Java (J2EE) and I am also a IBM Certified Solution Developer for XML and Related Technologies. The natural progression of my career has moved me toward SOA (Service Oriented Architecture) as both Java and XML are key technologies.

This blog will primarily focus on various computer technologies as I happened to be researching various topics, or reading interesting articles.