Roshan Shrestha is the Chief Software Architect at Qualtech Systems, Inc.
He holds a Master Degree in Electrical Engineering from the University
of Connecticut, and has been writing Java programs since JDK 1.0.
Wouldn't it be great if the web application that you wrote to run on a server could be made to run on a regular desktop computer with minimal effort on the user's side? You start thinking about all the obstacles you have to overcome: First of all, the computer may not have JDK or JRE installed. You also need Tomcat to run your servlets. And finally, you need to direct the user to open a browser and navigate to your application's start page. In this article, I am going to show you how you can package your application together with all required components (JRE , Tomcat, and a few other free/open-source components) that the user can just unzip to his/her computer and then run by clicking on an executable file, no batch file required. The application size is small enough to fit on a portable USB drive, so you can even run your program without installing it on a computer! If your application requires a database, no problem - you can embed a pure Java database into your application.
Note: You can download the source code from Resources.
The entire application consists of just one Java source file! Everything else is downloadable from the web.
First, what application is complete without a flashy splash screen:
// display the welcome splash screen
splash = Toolkit.getDefaultToolkit().getImage("images/splash.png");
splashWindow = SplashWindow.splash(splash);
Next, the tray icon is created:
// create the tray icon
WindowsTrayIcon.setWindowsMessageCallback(new WindowsMessageCallback());
Image logo=null;
logo = Toolkit.getDefaultToolkit().getImage("images/icon.png");
trayIcon = new WindowsTrayIcon(logo, 16, 16);
trayIcon.setToolTipText(tooltip);
trayIcon.setVisible(true);
From the tray icon, you may wish to display a few menu items if the user right clicks on it:
// create tray icon menu
TrayIconPopup popup = new TrayIconPopup();
// Menu to open the browser and display the start page
TrayIconPopupSimpleItem item = new TrayIconPopupSimpleItem("&Open");
item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
TomcatLauncher.this.showHomePage();
}
}
);
popup.addMenuItem(item);
For debugging, you may even wish to have a menu to restart Tomcat, and to display its log file:
if(DEBUG)
{// menu for debug version
// Menu to restart the application
item = new TrayIconPopupSimpleItem("&Restart");
item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
TomcatLauncher.this.restart();
}
}
);
popup.addMenuItem(item);
// Menu to display the tomcat logs
item = new TrayIconPopupSimpleItem("&Show log");
item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
TomcatLauncher.this.showLog();
}
}
);
popup.addMenuItem(item);
sepItem = new TrayIconPopupSeparator();
popup.addMenuItem(sepItem);
}
And lets not forget a way for the user to exit the application:
item = new TrayIconPopupSimpleItem("&Exit");
item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
closeWindow();
}
}
);
popup.addMenuItem(item);
To embed Tomcat in your application, the folks at Apache has provided a version of Tomcat just for this purpose, which is a much smaller download. However, I could not get it to work! If someone can make it work, I would be very much interested to obtain the fix. For now, I am using the full-blown Tomcat engine.
Bootstrap bootStrap = new Bootstrap();
bootStrap.init();
bootStrap.start();
System.out.println("Tomcat started");
// display the start page
showHomePage();
// hide the splash window
splashWindow.hide();
We also need to make sure that the user does not start another instance of our application:
if (WindowsTrayIcon.isRunning("Tomcat"))
{
// another instance already running
JOptionPane.showMessageDialog(new Frame(),
error,
title,
JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
The Tomcat output normally is printed on the console. To capture the logs so that it can be displayed
to the user, one option would be to modify Tomcat's server.xml
so that it dumps the logs to a file, and then display that file. Another option, which is used
in this implementation, is to redirect stdout
and stderr to a file:
// redirect the stdout and stdin to create a log file
stdout = new PrintStream(new FileOutputStream(LOG_FILE));
System.setOut ( stdout );
System.setErr ( stdout );
Then, to display the log file, we simply open it in Notepad:
Runtime runtime = Runtime.getRuntime();
runtime.exec("notepad " + LOG_FILE);
The following line of code launches the user's default browser:
edu.stanford.ejalbert.BrowserLauncher.openURL(startPage);
To restart Tomcat, we launch another instance of Tomcat.exe,
and exit from the currently running one:
cmd = "./tomcat.exe";
WindowsTrayIcon.cleanUp();
// start another instance of the program
runtime.exec(cmd);
// shutdown Tomcat
Bootstrap bootStrap = new Bootstrap();
bootStrap.init();
bootStrap.destroy();
// give Tomcat a chance to clean up
Thread.sleep(2500);
//exit from this process
System.exit(0);
We also add a ShutdownHook to handle abrupt shutdown, e.g.,
if the user kills the Java process from the Task Manager.
// handle abrupt shutdown
public void RegisterShutdownHook()
{
this.shutdownThread = new Thread("myhook")
{
public void run()
{
if(readyToExit == false)
{// abnormal exit
cleanUp();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownThread);
}
And that's pretty much the meat of our code. Now we could create a batch file to run our program, but we can do better. Using the library from the good people at JSmooth, we can wrap our code in a native windows executable. The jsmooth project file, and the ant build script to compile the code and create an executable can be downloaded from Resources.
You
can download a fully compiled, ready to run version (although you do
have
to download and copy Tomcat, as well as the JRE) from Resources.
To compile the program, you will need to download and install Apache
ant. Also, download Apache Tomcat and set the variable CATALINA_HOME.
Assuming that the ant binary is in the path, open a Console window,
navigate to the application root directory (where the build.xml
is located), and issue the command ant.
The output files
will get created in the directory called bin.
The main
executable is called tomcat.exe.
All paths are resolved
relative to the executable, so the directory structure needs to be laid
out in a certain manner:
Copy the entire Tomcat directory into the bin
directory,
and rename the tomcat directory (e.g.jakarta-tomcat-5.0.25)
to jakarta-tomcat.
Similarly, copy the JRE folder to the bin
directory, and rename the JRE directory (e.g.j2re1.4.2_06)
to jre.
Your software distribution now is the entire
content inside the bin
directory. Note: You need
not copy and distribute the JRE if the target computer has Java
installed - the application will work without the JRE distribution,
making the size of our application much smaller.
To test our application, double click on tomcat.exe.
you
should see a splash screen for a few seconds, then a browser will
pop-up and display the Tomcat index page. If you look at the right
hand corner of your Taskbar, you should see our application as a small
icon with the Tomcat logo.
Copy the entire directory content to a USB flash-drive, plug it to another computer that has no Java installed, and try running it directly from the USB drive! Note: Ensure that you are using USB 2.0 port - the JRE and Tomcat distribution contain a lot of small files, and it will be slow copying all the files to the USB drive. Also, on a USB 1.0 port, the performance isn't going to be that great. Unfortunately, Windows does not support autorun for USB drives, so to run the application you have to double click on the executable manually.
If things don't work as described, you can create a debug version of tomcat.exe.
To do this, open tomcat.jsmooth, and change these two lines:
Change
<skeletonName>Windowed Wrapper</skeletonName>
to
<skeletonName>Console Wrapper</skeletonName>
and change
<key>Debug</key>
<value>0</value>
to
<key>Debug</key>
<value>1</value>
Recompile the application, then open a console window, and run tomcat.exe.
On the console window, you will see plenty of messages to help you with
debugging.
No full-blown application is complete without a database backend. For
this, use a pure Java database (such as HSQL) that can be embedded. You
can also package a Microsoft Access file. The connection parameters
can be specified as (assuming we name our database as my_database):
driver=org.hsqldb.jdbcDriver
url=jdbc:hsqldb:file:DB/my_database
user=my_user
password=my_password
Note the use of relative path to describe the HSQL data files. Create a
directory called DB under
the root application directory, and copy the
HSQL files there (there will be a minimum of two files: my_database.sql,
and my_database.properties).
This application could use a few enhancements. For one, the start page URL (http://localhost:8080) could be externalized to a property file, so that it can be customized without having to recompile. Similarly, the Tomcat port number is hardcoded to 8080. If this port number is in use, the user should be given the option of changing it to another value. This can be achieved by adding a menu item to the tray icon, and providing a pop-up swing dialog box where the user can type in the a port number. Someone also needs to figure out how to use the embedded version of Tomcat instead of the full-featured version. The application keeps running in the background, even when the user closes the browser. We could make the application exit when the session times out. Another way would be to have an "Exit" or a "Logout" button on the web pages - clicking on this button causes the application to exit (before exiting, the application could redirect the browser to a static "goodbye" page (or display a "goodbye" page and then exit after a few seconds).
If you want to add a desktop shortcut to your application, create menu items on the Windows Start menu, or add an entry to the quick launch bar, you can easily do all that by using Roxes ant task (see Resources).