MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Java Programming on the Mac Getting Fit for the Holidays

by Daniel H. Steinberg
12/10/2002

In this article, we'll look at interacting with your Java programs remotely by taking advantage of the Apache Web Server that ships with Mac OS X. We'll write a quick CGI script to compile a Java program and then write a slightly more complicated script to process HTML files. Many thanks to Herb Schilling of NASA's Glenn Research Center in Cleveland and O'Reilly's Nat Torkington for their help getting the CGI scripts to do what I want. The example is taken from an upcoming book that I co-wrote with Dan Palmer on Extreme Software Engineering from Prentice Hall.

The Fit framework is another great idea from Ward Cunningham. You can read more about it and download it at fit.c2.com . Before looking at the CGI scripts, I want to talk briefly about the Fit framework. It's such a cool idea and much simpler than the solutions that many of us have been playing with for writing and automatically running acceptance tests. As always, suggestions for future articles and comments can be sent to me at DSteinberg@core.com.

Playing with Toys

At least once a day someone ducks into the office of one of my colleagues and makes some comment about the two Macs on his desk. He teaches computer science at a local university and used to run Windows and Linux but now has a TiBook and an iMac. Once each day he listens as someone derides his choice of a "toy machine."

I ask him what his answer is, and he shrugs and responds, "What's the point?"

"What's the point?" I exclaim. "The point is that your Mac ships with Java, Ruby, Python, and Perl. The point is that you can open up a Terminal window and edit using vi or emacs. You can set up sendmail or use lynx. You can enable the Apache Web server that ships with every Mac by checking a check box. That's the point."

"I usually tell them that," he says, "but then they ask me if they can play the latest version of some game on it like they can on a Windows box."

"Well then," I reply, "which one is the toy machine?"

I've been thinking a lot about toys lately, and I've reconsidered my position. The Mac is a toy. Mac OS X is a totally decked out toy for developers. In the old days I would go to conferences and see the latest technology and know that it would be years until I'd see a Mac version. In fact, I first got into the business of covering Java on the Mac because Apple's releases always lagged so far behind the corresponding Windows release. Sure, Java 1.4 still isn't final, but you can download regular releases of the Developers Preview for free at the ADC site . We'll talk more about why the upcoming Java release is special in a future article.

Last month I went to Seattle for the Ruby developers' conference and for the annual OOPSLA (Object Oriented Programming something something something ) conference and then on to Dallas for the Lonestar Software Symposium. The first thing that struck me at all of these conferences was the growing percentage of Macs that were around. If Macs still represent only about five percent of the market, then they are way over-represented at developers shows these days. Ruby inventor Matz said that he is pleased that Ruby is distributed as part of Jaguar but that no one from Apple ever contacted him. In Dallas, different developers used different IDEs from IDEA and Eclipse to ProjectBuilder and command line tools. At OOPSLA, Martin Fowler mentioned a new framework available for supporting acceptance testing.

What Is Fit?

The Fit framework allows business people to specify what software should do or how it will behave in simple tables. These tables are meant to be embedded in HTML or Wiki pages. The tables are then tied into the application that they are testing by fixtures that extend and use the classes provided in the framework. What makes this framework even cooler is that a customer can run the acceptance tests remotely just by clicking a hyperlink in a browser. The focus of this article will be setting up the CGI scripts to remotely process the HTML pages. In the next section, you'll get a taste of what Fit is all about.

Understanding the Fit Framework

First, let's imagine that you're building a cash register program. If you enter the unit price in pennies and the number of items purchased, then you'd like to verify that the total cost for that particular item is correct. Your table might look something like this: (You can view source if you need an HTML refresher.)

register.CaseDiscountFixture
unitPrice numberPurchased itemTotal()
800 1 800
800 5 4000
800 12 9120
800 17 13120
800 24 18240
800 29 22240
800 1200 912000
4 12 46

For example, in this table the items in all rows except the last one are priced at $8 each. The first row helps you confirm that the price of one item is $8, and the second row helps you confirm that the price of two items is $40. The third row doesn't seem right at first. Your client is trying to verify that a 5 percent discount is given for each set of twelve purchased.

One of the advantages of the Fit framework is that the tables are easy for the clients to create and for the developers to process. In this case, the developers have indicated that the Java class that will be used to process it is called CaseDiscountFixture and that it is inside of the register package. This particular fixture extends the ColumnFixture class that is part of the Fit framework. In this case, each column of the table will map to a different member of the CaseDiscountFixture class. The first two columns will be inputs and correspond to variables, the third column is the expected return value from a method call. The CaseDiscountFixture class will need to contain variables named unitPrice and numberPurchased of type int and a method named itemTotal() that takes no arguments and returns an int .

Here's a look at one possible example of register.CaseDiscountFixture .

package register;

import fit.ColumnFixture;

public class CaseDiscountFixture extends ColumnFixture{
  public int unitPrice;
  public int numberPurchased;

  public int itemTotal(){
    Item item = new Item(unitPrice);
    item.addToOrder(numberPurchased);
    return item.totalItemCost();
  }
}

Notice this creates a new instance of type Item and sends it messages. If you'd like, for now you can replace the body of itemTotal() with this return statement.

 return unitPrice * numberPurchased;

In practice, you would use the first approach, but this second, stubbed out version will allow you to easily experiment with the framework without creating all of the supporting classes.

Learning Unix for Mac OS X

Related Reading

Learning Unix for Mac OS X
By Dave Taylor, Brian Jepson

As a second example, let's look at a table that models how you might control a GUI. Here you'll model pressing buttons, entering information, and checking the results. Continuing the cash register example, this might represent manually entering the price for an item that can't be scanned.

fit.ActionFixture
start register.MiscItemFixture  
press miscButton  
check display Enter Unit Price
enter unitPrice 800
check display 800
press enterButton  
check display Misc Grocery 800
press timesButton  
check display Enter number of items
enter numberOfItems 5
check display 5
press doneButton  
check display 4000
check totalCost 4000

In this case there are four keywords recognized in the fit.ActionFixture class: start , press , enter , and check . The press keyword is used to simulate a button press. In this example you can see that there is a miscButton , enterButton , timesButton , and doneButton . These will correspond to method calls -- not in the ActionFixture class but in the register.MiscItemFixture that is instantiated with the start keyword. The enter keyword also calls methods with names specified in the second column with values passed in the third column. Finally, the check keyword calls the method in the second column and compares the return value with the value in the third column.

Running the Fit Framework Locally

There are four components to running acceptance tests using the Fit framework. First you need HTML files containing one or more tables like the ones you saw in the last section. Second, the classes in fit.jar are used to process the tables and to call the classes, methods, and variables referenced by them. Third, fixtures must be created by developers to implement the methods specified by the tables. Finally, you need the production code itself. Remember, the goal is to produce production code that meets the customer's needs. You should keep your fixtures thin. They should call into the application code that does the real work.

With fit.jar and your fixtures in the classpath you can process HTML files by running fit.FileRunner and passing in the path to the input and output files as arguments. OK, I love being able to say the following. On your Mac, open up a Terminal window, move to an appropriate directory, and use this command.

java -classpath fit.jar:classes fit.FileRunner inputPath outputPath 

If the necessary classes, methods, or variables aren't available, you'll get an error message that looks like this.

Error message.

If the fixture is in place, but some of the returned results don't match the expectation, you'll see something like this:

Results.

In this image you can see the green background for the first two tests indicating that the expected and actual results agree. When they don't agree you'll see a red background along with details. You can see the actual value and the expected value to see what is being returned by your application. I will provide a more detailed example of the Java code being called in a separate article on Fit. For now, let's move on to creating the CGI to process these files remotely.

Running Remotely Using CGI Scripts

If you haven't started up your Apache Web Server, you'll need to carefully follow these directions. (1) Open up your System Preferences, (2) Click on Sharing, and (3) Check the Personal Web Sharing checkbox. That's it. You're running the Apache Web Server. Open up a browser and go to , http://localhost and you'll see a welcome page from Apache.

At this point you should check out Kevin Hemenway's second article on Apache Web-Serving with Mac OS X. The biggest configuration issues will tend to be setting the permissions on the files your running and on the folders containing files you want to run and write to. Create the following script and save it as compile-cgi inside of the /Library/WebServer/CGI-Executables directory.

#!/bin/sh
echo "Contents: text/html"
echo
echo "<h2>hello</h2>"

This is a simple script that should produce HTML output with the single heading "hello." To run it, you point your browser at http://localhost/cgi-bin/compile-cgi . In this case you'll see a message that says you don't have permission to access the file. Open up your Terminal application and navigate inside of /Library/WebServer/CGI-Executables . Change the permissions on compile-cgi by typing chmod 755 compile-cgi . Now you should be able to access compile-cgi with your browser and should see the heading "hello".

In your default setting the cgi-bin directory is /Library/WebServer/CGI-EXECUTABLES , and the root directory for documents is /Library/WebServer/Documents . Now create a directory named Hello inside of /Library/WebServer/Documents . Inside of that, create a Java source file named Hi.java that looks like this:

 public class Hi {
  public static void main(String[] args){
    System.out.println("Hi");
  }
}

Yes, this is HelloWorld. You know from my ONJava article that I hate using HelloWorld as a first program in teaching Java -- but this isn't an article on teaching Java. Change the permissions of Hello.java as before.

Now let's do some work with compile-cgi . We'll change directories to Hello and print out the name of the directory to verify that we're there. We'll then print out the contents of the directory, compile the Java class file and again print out the contents of the directory to verify that the Hello.class file was generated. Here's the new compile-cgi .

#!/bin/sh
echo "Contents: text/html"
echo
cd ../Documents/Hello
pwd
echo      
ls   
echo      
javac Hi.java
echo      
ls

The output is not exactly what we expect. You should see something like this:

/Library/WebServer/Documents/Hello

Hi.java

Hi.java

The file Hi.class was never created. What went wrong? The easiest way to figure out what is going wrong is to look at the error log. You can do this from the Terminal with this command:

 tail -f /private/var/log/httpd/error_log 

You'll see that there was "an error while writing Hi:Hi.class (Permission denied)". This time you need to change the permissions in the Hello directory so that you can write to it. Change directories to /Library/WebServer/Documents, and give Hello all permissions with the command chmod 777 Hello . Now when you point your browser at http://localhost/cgi-bin/compile-cgi , the results will be this:

/Library/WebServer/Documents/Hello

Hi.java

Hi.class
Hi.java

Success. We have compiled a Java source file remotely, not that you'd want to. This example gives you a glimpse of the power of this mechanism. It should also sound warning bells of the possible dangers. Before you actually do this on a publicly accessible machine, you'll want to reconsider the permissions you are setting.

Running the Fit Tests

This time the process is a little different. We want the client to be able to open up an HTML page that shows the acceptance tests and click on a link to fit-cgi . The CGI script should then process the acceptance tests and generate the output HTML page. Finally, the browser should redirect to this newly generated page.

In this example, I have a directory called dozen inside of the /Library/WebServer/Documents directory. Its structure looks like this:

Directory structure.

Create a file called CaseDiscountFitTest.html inside of /Library/WebServer/Documents/dozen/tests/acceptance/testsource . It should contain a hyperlink to http://localhost/cgi-bin/fit-cgi. It should also include the first table we looked at in this article. The corresponding fixture, CaseDiscountFixture will go in the fixtures directory inside of a register subdirectory. The compiled code should be inside of the classes directory. The file fit.jar should be copied to your CGI-Executables directory. Finally, make sure the permissions on the testresults directory have been set so that you can write into it.

With that setup, we can create the code for fit-cgi . Note that in the following listing, each of the indented lines is really a continuation of the previous lines. In your script they should be part of the previous line with the spaces deleted.

#!/bin/sh
java -classpath fit.jar:../Documents/dozen/classes fit.FileRunner  
  ../Documents/dozen/tests/acceptance/testsource/CaseDiscountFitTest.html 
  ../Documents/dozen/tests/acceptance/testresults/CaseDiscountFitTest.html
echo "Location: 
  http://localhost/dozen/tests/acceptance/testresults/CaseDiscountFitTest.html"
echo

The first really long line is of the form

java -classpath (the classpath) (the input file) (the output file)

The first echo line is the redirect to the specified location. Direct your browser to http://localhost/dozen/tests/acceptance/testsource/CaseDiscountFitTest.html . Click on the link to fit-cgi . After a moment, you should be redirected to http://localhost/dozen/tests/acceptance/testresults/CaseDiscountFitTest.html , and the results of running the tests should be displayed. If there are problems, check your error log for more information.

There are lots of problems with that CGI script. The first problem is that all of the paths are hard-coded. This means that every HTML file will need its own CGI script. Also, if I decide to change the location of the dozen folder, I need to make a lot of changes to a lot of scripts.

Here's the plan for the new and improved script. Determine the classpath, location of the input file, and location of the output file from the address of the file that is calling fit-cgi . Process the input file based on this information. Redirect the browser to the output file location.

#!/bin/sh
TEST_HTML=`echo "$HTTP_REFERER"|sed 's/testsource/testresults/'`
TEST_RESULTS=`echo "$DOCUMENT_ROOT/$TEST_HTML"|sed 's/http:\/\/localhost\///'`
TEST_INPUT=`echo "$TEST_RESULTS"|sed 's/testresults/testsource/'`
TEST_CLASSPATH="fit.jar:$TEST_INPUT/../../../../classes"
java -classpath $TEST_CLASSPATH fit.FileRunner $TEST_INPUT $TEST_RESULTS
echo "Location: $TEST_HTML"
echo

Concentrate on the last three lines of this revised script. See how flexible and easy to read these lines are. The values that begin with "$" come from the variables that are set in the first four lines. Let's turn our attention to those. Check out the first line.

TEST_HTML=`echo "$HTTP_REFERER"|sed 's/testsource/testresults/'` 

This looks puzzling. First we set the variable TEST_HTML to the value $HTTP_REFERER . The rest of that line pipes the contents of $HTTP_REFERER to the sed application, which searches for the string testsource and replaces it with the string testresults . This is the location of the output file that will used later to redirect the browser. Let's move on to the next line.

TEST_RESULTS=`echo "$DOCUMENT_ROOT/$TEST_HTML"|sed 's/http:\/\/localhost\///'`

Here, we turn this URL into a path by stripping of the http://localhost from the beginning and adding the path to the document root to the beginning. In our case, this replaces http://localhost with /Library/WebServer/Documents . This result is stored in the TEST_RESULTS variable to be passed into the class fit.FileRunner as a parameter.

TEST_INPUT=`echo "$TEST_RESULTS"|sed 's/testresults/testsource/'`

Next, this TEST_RESULTS variable is processed to replace testresults with testsource . It may seem that we've taken an extra step here. The reason we worked in this order is that once the tests have been run, $HTTP_REFERER can be coming from testresults and not from testsource . By processing in this order we easily accommodate that possibility.

Finally, the classpath is set. The file fit.jar is inside of the same directory as fit-cgi , and we know the relative position from the TEST_INPUT to the class files. You may object that this means that the file structure of a project directory needs to be kept the same. On one hand, that is true. On the other hand, if I change that structure, I only need to make the corresponding change to the files that process the acceptance tests in this one location. This is the weakest line in the script. Feel free to suggest changes in the TalkBack.

Now you should be able to add links to fit-cgi from any of your acceptance tests inside of the testsource directory. On the Fit Wiki, Ward has published his run script. It creates a temporary local file with the contents of the page being processed. He processes that page and then displays the results. The advantage in his approach is that you don't have to have a fixed structure for your projects. Either script can serve as a starting place for your experiments.

Final Thoughts

In this article, I took a look at the newly released Fit framework and introduced you to some of the possibilities available in using CGI on your Mac. There are a lot more elegant ways to accomplish the same result. What if we wrote our scripts in Perl or Python or Ruby? What type of installation nightmares would we need to endure to use those languages? None. Perl, Python, Ruby, and even AppleScript would provide alternative ways to accomplish the same results and they're all built in to Mac OS X.

Seasons Greetings. Enjoy the developers' toy box that is Mac OS X.

Daniel H. Steinberg is the editor for the new series of Mac Developer titles for the Pragmatic Programmers. He writes feature articles for Apple's ADC web site and is a regular contributor to Mac Devcenter. He has presented at Apple's Worldwide Developer Conference, MacWorld, MacHack and other Mac developer conferences.


Read more Java Programming on the Mac columns.

Return to the Mac DevCenter.


Copyright © 2009 O'Reilly Media, Inc.