Ajax programming with the Java Scripting API

An excerpt from 'Beginning Java SE 6 Platform: From novice to professional'

Jeff Friesen's Beginning Java SE 6 Platform: From Novice to Professional is out now from Apress. In this article Jeff introduces you to his new book by presenting excerpts from its chapter on the Java Scripting API (JSR 223). You'll learn some of the fundamentals of the Java Scripting API, such as how to obtain a script engine, get the script engine to evaluate scripts, and communicate with scripts via script variables. You'll also have the chance to apply what you've learned to a project involving the XMLHttpRequest object used in Ajax programming.

Java SE 6 officially arrived on December 11, 2006. Shortly thereafter, I wrote Beginning Java SE 6 Platform: From Novice to Professional for Apress. The book consists of 10 chapters and five appendices that explore many new and improved features in Java SE 6. The final appendix previews several features that will likely appear in Java SE 7.

Chapter 9 focuses on the Java SE 6 Scripting API, which enables Java programs to interact with scripts written in Ruby, PHP, Groovy, JavaScript, and other scripting languages. This API was developed under JSR 223: Scripting for the Java Platform; it consists of six interfaces, five regular classes, and one exception class that are stored in the javax.script package.

This article, an excerpt from Chapter 9, gets you started with the Java Scripting API. You'll learn how to obtain a script engine (a software component that evaluates scripts), how to get the script engine to evaluate scripts, and how to communicate with scripts via script variables.

I also present new material not found in the book, giving you a chance to apply what you've learned in a project that bridges the gap between Java and Ajax (Asynchronous Javascript and XML) programming. I present a Swing application that uses the Java Scripting API and XMLHttpRequest object to obtain instantaneous weather data, which the application's GUI displays.

Obtaining a script engine

Prior to performing other scripting tasks, a Java program must obtain an appropriate script engine. A script engine exists as an instance of a class that implements the ScriptEngine interface or extends the AbstractScriptEngine class. The program begins this task by creating an instance of the ScriptEngineManager class via one of these constructors:

  • The public ScriptEngineManager() constructor works with the calling thread's context classloader if one is available, or the bootstrap classloader otherwise, and a discovery mechanism to locate ScriptEngineFactory providers.
  • The public ScriptEngineManager(ClassLoader loader) constructor works with the specified classloader and the discovery mechanism to locate ScriptEngineFactory providers. Passing null to loader is equivalent to calling the former constructor.

The program uses the ScriptEngineManager instance to obtain a list of factories via this class's public List<ScriptEngineFactory> getEngineFactories() method. For each factory, ScriptEngineFactory methods, such as String getEngineName(), return metadata describing the factory's script engine. Listing 1 presents an application that demonstrates most of the metadata methods.

Listing 1. EnumerateScriptEngines.java

// EnumerateScriptEngines.java

import java.util.*;

import javax.script.*;

public class EnumerateScriptEngines
{
   public static void main (String [] args)
   {
      ScriptEngineManager manager = new ScriptEngineManager ();

      List<ScriptEngineFactory> factories = manager.getEngineFactories ();
      for (ScriptEngineFactory factory: factories)
      {
           System.out.println ("Engine name (full): "+
                               factory.getEngineName ());
           System.out.println ("Engine version: "+
                               factory.getEngineVersion ());
           System.out.println ("Supported extensions:");
           List<String> extensions = factory.getExtensions ();
           for (String extension: extensions)
                System.out.println ("  "+extension);
           System.out.println ("Language name: "+
                               factory.getLanguageName ());
           System.out.println ("Language version: "+
                               factory.getLanguageVersion ());
           System.out.println ("Supported MIME types:");
           List<String> mimetypes = factory.getMimeTypes ();
           for (String mimetype: mimetypes)
                System.out.println ("  "+mimetype);
           System.out.println ("Supported short names:");
           List<String> shortnames = factory.getNames ();
           for (String shortname: shortnames)
                System.out.println ("  "+shortname);
           System.out.println ();
      }
   }
}

Assuming that no additional script engines have been installed, you should observe the following output when you run this application against Java SE 6:

Engine name (full): Mozilla Rhino
Engine version: 1.6 release 2
Supported extensions:
  js
Language name: ECMAScript
Language version: 1.6
Supported MIME types:
  application/javascript
  application/ecmascript
  text/javascript
  text/ecmascript
Supported short names:
  js
  rhino
  JavaScript
  javascript
  ECMAScript
  ecmascript

The output reveals that an engine can have both a full name (Mozilla Rhino) and multiple short names (rhino, for example). The short name is more useful than the full name, as you will see It also shows that an engine can be associated with multiple extensions and multiple MIME types, and that the engine is associated with a scripting language.

After determining the appropriate script engine, the program can invoke ScriptEngineFactory's ScriptEngine getScriptEngine() method to return an instance of the script engine associated with the factory. Although new script engines are usually returned, a factory implementation is free to pool, reuse, or share implementations. The following code fragment shows how to accomplish this task:

if (factory.getLanguageName ().equals ("ECMAScript"))
{
    engine = factory.getScriptEngine ();
    break;
}

Think of the code fragment as being part of Listing 1's for (ScriptEngineFactory factory: factories) loop; assume that the ScriptEngine variable engine already exists. If the scripting language hosted by the factory is ECMAScript (language version does not matter in this example), a script engine is obtained from the factory and the loop is terminated.

Because the previous approach to obtaining a script engine is cumbersome, ScriptEngineManager provides three convenience methods that take on this burden, listed in Table 1. These methods let you obtain a script engine based on file extension (possibly obtained via a dialog-selected script file), MIME type (possibly returned from a server), and short name (possibly chosen from a menu).

Table 1. ScriptEngineManager convenience methods for obtaining a script engine

MethodDescription
public ScriptEngine getEngineByExtension(String extension)Creates and returns a script engine that corresponds to the given extension If a script engine is not available, this method returns null. A NullPointerException is thrown if null is passed as extension.
public ScriptEngine getEngineByMimeType(String mimeType)Creates and returns a script engine that corresponds to the given MIME type If a script engine is not available, this method returns null. A NullPointerException is thrown if null is passed as mimeType.
public ScriptEngine getEngineByName(String shortName)Creates and returns a script engine that corresponds to the given short name. If a script engine is not available, this method returns null. A NullPointerException is thrown if null is passed as shortName.

Listing 2 presents an application that invokes getEngineByExtension(), getEngineByMimeType(), and getEngineByName() to obtain a Rhino script engine instance. Behind the scenes, these methods take care of enumerating factories and invoking ScriptEngineFactory's getScriptEngine() method to create the script engine.

Listing 2. ObtainScriptEngine.java

// ObtainScriptEngine.java

import javax.script.*;

public class ObtainScriptEngine
{
   public static void main (String [] args)
   {
      ScriptEngineManager manager = new ScriptEngineManager ();

      ScriptEngine engine1 = manager.getEngineByExtension ("js");
      System.out.println (engine1);

      ScriptEngine engine2 =
        manager.getEngineByMimeType ("application/javascript");
      System.out.println (engine2);

      ScriptEngine engine3 = manager.getEngineByName ("rhino");
      System.out.println (engine3);
   }
}

After compiling ObtainScriptEngine.java, running the application generates output that is similar to the following, indicating that different script engine instances are returned:

com.sun.script.javascript.RhinoScriptEngine@1f14ceb
com.sun.script.javascript.RhinoScriptEngine@f0eed6
com.sun.script.javascript.RhinoScriptEngine@691f36


Once a script engine has been obtained (via ScriptEngineFactory's getScriptEngine() method or one of ScriptEngineManager's three convenience methods), a program can access the engine's factory via ScriptEngine's convenient ScriptEngineFactory getFactory() method. The program can also invoke various ScriptEngine methods to evaluate scripts.

Evaluating scripts

After obtaining a script engine, a Java program can work with ScriptEngine's six overloaded eval() methods to evaluate scripts. Each method throws a ScriptException if there is a problem with the script. Assuming successful script evaluation, an eval() method returns the script's result as some kind of Object, or null if the script does not return a value.

The simplest of the eval() methods are Object eval(String script) and Object eval(Reader reader). The former method is invoked to evaluate a script expressed as a String; the latter method is invoked to read a script from some other source (such as a file) and evaluate the script. Each method throws a NullPointerException if its argument is null. Listing 3 demonstrates these methods.

Listing 3. FuncEvaluator.java

// FuncEvaluator.java

import java.io.*;

import javax.script.*;

public class FuncEvaluator
{
   public static void main (String [] args)
   {
      if (args.length != 2)
      {
          System.err.println ("usage: java FuncEvaluator scriptfile "+
                              "script-exp");
          return;
      }

      ScriptEngineManager manager = new ScriptEngineManager ();
      ScriptEngine engine = manager.getEngineByName ("rhino");

      try
      {
          System.out.println (engine.eval (new FileReader (args [0])));
          System.out.println (engine.eval (args [1]));
      }
      catch (ScriptException se)
      {
          System.err.println (se.getMessage ());
      }
      catch (IOException ioe)
      {
          System.err.println (ioe.getMessage ());
      }
   }
}

FuncEvaluator is designed to evaluate the functions in a Rhino-based script file via eval(Reader reader). It also uses eval(String script) to evaluate an expression that invokes one of the functions. Both the script file and script expression are passed to FuncEvaluator as command-line arguments. Listing 4 presents a sample script file.

Listing 4. stats.js

function combinations (n, r)
{
   return fact (n)/(fact (r)*fact (n-r))
}

function fact (n)
{
   if (n == 0)
       return 1;
   else
       return n*fact (n-1);
}

The stats.js file presents combinations(n, r) and fact(n) functions as part of a statistics package. The combinations(n, r) function works with the factorial function to calculate and return the number of different combinations of n items taken r items at a time. For example, how many different poker hands in five-card draw poker (where five cards are dealt to each player) can be dealt from a full card deck?

Invoke java FuncEvaluator stats.js combinations(52,5) to discover the answer. After outputting null on the first line (to indicate that stats.js does not return a value), FuncEvaluator outputs 2598960.0 on the line below The Double value returned from combinations(52,5) indicates that there are 2,598,960 possible poker hands.

Communicating with scripts via script variables

Previously, you learned that eval() can return a script's result as an object. Additionally, the Scripting API lets Java programs pass objects to scripts via script variables, and obtain script variable values as objects. ScriptEngine provides void put(String key, Object value) and Object get(String key) methods for these tasks. Both methods throw NullPointerException if key is null, IllegalArgumentException if key is the empty string, and ClassCastException if key is not a String. Listing 5's application demonstrates put() and get().

Listing 5. MonthlyPayment.java

// MonthlyPayment.java

import javax.script.*;

public class MonthlyPayment
{
   public static void main (String [] args)
   {
      ScriptEngineManager manager = new ScriptEngineManager ();
      ScriptEngine engine = manager.getEngineByExtension ("js");

      // Script variables intrate, principal, and months must be defined (via
      // the put() method) prior to evaluating this script.

      String calcMonthlyPaymentScript = 
         "intrate = intrate/1200.0;"+
         "payment = principal*intrate*(Math.pow (1+intrate, months)/"+
         "                            (Math.pow (1+intrate,months)-1));";

      try
      {
          engine.put ("principal", 20000.0);
          System.out.println ("Principal = "+engine.get ("principal"));
          engine.put ("intrate", 6.0);
          System.out.println ("Interest Rate = "+engine.get ("intrate")+"%");
          engine.put ("months", 360);
          System.out.println ("Months = "+engine.get ("months"));
          engine.eval (calcMonthlyPaymentScript);
          System.out.printf ("Monthly Payment = %.2f\n",
                             engine.get ("payment"));
      }
      catch (ScriptException se)
      {
          System.err.println (se.getMessage ());
      }
   }
}

MonthlyPayment calculates the monthly payment on a loan via the formula MP = P*I*(1+I)N/(1+I)N-1, where MP is the monthly payment, P is the principal, I is the interest rate divided by 1200, and N is the number of monthly periods to amortize the loan. Running this application with P set to 20000, I set to 6%, and N set to 360 results in this output:

Principal = 20000.0
Interest Rate = 6.0%
Months = 360
Monthly Payment = 119.91

The script depends on the existence of script variables principal, intrate, and months. These variables (with their object values) are introduced to the script via the put() method -- 20000.0 and 6.0 are boxed into Doubles; 360 is boxed into an Integer. The calculation result is stored in the payment script variable. get() returns this Double's value to Java.

Working with XMLHttpRequest

The XMLHttpRequest object is an important part of Ajax, which consists of several technologies that are collectively employed to create interactive Web applications. When a user requests a change in a Web page, this object exchanges a small amount of data with the server (behind the scenes). Because an entire Web page is not reloaded, Web applications appear to be as responsive as desktop applications.

You can use XMLHttpRequest with the Java Scripting API to create interesting Swing applications. For example, these technologies can be used to create Swing mashups (smashups) that combine data from multiple Web services. In this section, I present a Weather application that uses XMLHttpRequest with the Java Scripting API to obtain and display weather data from Yahoo! Weather. Its GUI appears in Figure 1.

Figure 1. The Weather application presents the current weather conditions for a given zip code

The Weather application's GUI consists of a text field for entering a zip code, and a label for presenting some weather data (including an icon image) associated with that zip code. Type the zip code and press Enter. A few seconds later, you should see the weather data (or an error message if you specify an incorrect zip code).

Listing 6 presents the Weather application's source code.

Listing 6. Weather.java

// Weather.java

import java.awt.*;
import java.awt.event.*;

import java.io.*;

import javax.script.*;

import javax.swing.*;
import javax.swing.border.*;

public class Weather extends JFrame
{
   static ScriptEngine engine;

   public Weather ()
   {
      super ("Weather");
      setDefaultCloseOperation (EXIT_ON_CLOSE);

      // Create a gradient panel in which to present the GUI.

      GPanel pnl = new GPanel ();
      pnl.setLayout (new BorderLayout ());

      // Build input panel for entering a five-digit zip code.

      JPanel pnlInput = new JPanel ();
      Border inner = BorderFactory.createEtchedBorder ();
      Border outer = BorderFactory.createEmptyBorder (10, 10, 10, 10);
      pnlInput.setBorder (BorderFactory.createCompoundBorder (outer, inner));
      pnlInput.setOpaque (false);
      pnlInput.add (new JLabel ("Zip code:"));
      final JTextField txtZipCode = new JTextField (5);
      pnlInput.add (txtZipCode);
      pnl.add (pnlInput, BorderLayout.NORTH);

      // Create and register an action listener with the zip code field. In
      // response to the user pressing Enter, validate this field's content,
      // obtain an input stream to weather.js, make the zip code available to
      // this file's script, and evaluate the script.

      ActionListener al;
      al = new ActionListener ()
           {
               public void actionPerformed (ActionEvent ae)
               {
                  try
                  {
                      String zipcode = txtZipCode.getText ();
                      if (zipcode.length () != 5)
                          throw new NumberFormatException ("five digits " +
                                                           "expected");
                      Integer.parseInt (zipcode);

                      InputStream is;
                      is = Weather.class.getResourceAsStream ("weather.js");
                      if (is == null)
                      {
                          JOptionPane.showMessageDialog (Weather.this,
                                                         "weather.js missing");
                          return;
                      }

                      engine.put ("zipcode", zipcode);

                      InputStreamReader isr = new InputStreamReader (is);
                      engine.eval (isr);
                  }
                  catch (NumberFormatException nfe)
                  {
                      JOptionPane.showMessageDialog (Weather.this,
                                                     "Bad input: " +
                                                     nfe.getMessage ());
                  }
                  catch (ScriptException se)
                  {
                      JOptionPane.showMessageDialog (Weather.this,
                                                     se.getMessage ());
                  }
               }
           };
      txtZipCode.addActionListener (al);

      // Build output label for displaying the weather.

      final JLabel lblWeather = new JLabel ();
      lblWeather.setPreferredSize (new Dimension (300, 200));
      lblWeather.setHorizontalAlignment (JLabel.CENTER);
      inner = BorderFactory.createEtchedBorder ();
      outer = BorderFactory.createEmptyBorder (10, 10, 10, 10);
      lblWeather.setBorder (BorderFactory.createCompoundBorder (outer, inner));
      pnl.add (lblWeather, BorderLayout.CENTER);

      getContentPane ().add (pnl);

      // Make the output label available to the weather.js script.

      engine.put ("lblWeather", lblWeather);

      pack ();
      setResizable (false);
      setLocationRelativeTo (null); // center on the screen
      setVisible (true);
   }

   public static void main (String [] args)
   {
      // Obtain the rhino script engine.

      ScriptEngineManager manager = new ScriptEngineManager ();
      engine = manager.getEngineByName ("rhino");

      // The following if statement is a safety check in the unlikely case
      // that the rhino script engine cannot be found.

      if (engine == null)
      {
          System.err.println ("rhino script engine not available");
          return;
      }

      // Obtain an input stream to ajax.js and evaluate this file's script.

      try
      {
         InputStream is = Weather.class.getResourceAsStream ("ajax.js");
         if (is == null)
         {
             System.err.println ("ajax.js missing");
             return;
         }
         InputStreamReader isr = new InputStreamReader (is);
         engine.eval (isr);
      }
      catch (ScriptException se)
      {
         System.err.println (se);
         return;
      }

      // Create the GUI on the event-dispatching thread.

      Runnable r;
      r = new Runnable ()
          {
              public void run ()
              {
                 new Weather ();
              }
          };
      EventQueue.invokeLater (r);
   }
}

class GPanel extends JPanel
{
   private GradientPaint gp;

   @Override
   public void paintComponent (Graphics g)
   {
      if (gp == null)
          gp = new GradientPaint (0, 0, Color.cyan, 0, getHeight (),
                                  Color.yellow);

      // Paint a nice gradient background with cyan at the top and yellow at
      // the bottom.

      ((Graphics2D) g).setPaint (gp);
      g.fillRect (0, 0, getWidth (), getHeight ());

   }
}

Inside the Weather application

The most interesting parts of Listing 6 involve the Java Scripting API. For starters, the main() method in Listing 6 obtains the Rhino script engine via manager.getEngineByName ("rhino"). It then uses this script engine to load a modified version of the Java Scripting Project's ajax.js script file and evaluate its script, which defines the XMLHttpRequest object.

XMLHttpRequest is accessed by the script stored in weather.js. When the user presses Enter, txtZipCode's action listener receives an action event. After making the entered zip code available to weather.js via the zipcode script variable, the listener loads weather.js and evaluates its script, which accesses zipcode.

Two fixes to the ajax.js script
Although you can obtain ajax.js from the Java Scripting Project, I've included a slightly modified version of this file with the rest of the article's code packet. My modified script does the following:
  • Replaces case 'responseTex' with case 'responseText'. XMLHttpRequest includes a responseText property -- not a property named responseTex.
  • Relocates this._status = this._connection.responseCode; this._statusText = this._connection.responseMessage from the end of function sendRequest() to the start of function readResponse().
The relocation allows weather.js to be evaluated on the event-dispatching thread without slowing down this thread. Without the relocation, the event-dispatching thread is delayed until this._connection.responseCode completes.

The weather.js JavaScript source code is presented in Listing 7.

Listing 7. weather.js

// weather.js

// The event-dispatching thread is currently running.

// Create the XMLHttpRequest object.

var xmlHttp = new XMLHttpRequest ();

// Assign a function to the object's onreadystatechange property. The function 
// extracts content from a loaded RSS file and assigns the content to a Java
// program's weather output label. As an exercise, include the file's forecast
// data, which is stored in a pair of <yweather:forecast> elements.

xmlHttp.onreadystatechange = function ()
{
   // The event-dispatching thread is not running here.

   // If document is in the LOADED state...

   if (xmlHttp.readyState == 4)
   {
       // Start building the result. A JLabel requires its text to be begin
       // with the <html> tag when HTML content is to be rendered.

       var result = "<html><center>";

       // Reference the responseXML object.

       var XMLObj = xmlHttp.responseXML;

       // Extract the document's first description element.

       var desc = XMLObj.getElementsByTagName ("description").item (0)
                        .getChildNodes ().getFirstChild ().getNodeValue ();

       result = result + desc + "<br>";

       // If the description reveals no error (an invalid zip code indicates
       // an error)...

       if (desc.indexOf ("Error") == -1)
       {
           // Extract the units indicator (F, Fahrenheit - C, Celsius).

           var units = XMLObj.getElementsByTagName ("yweather:units").item (0);
           var attr = units.getAttributes ();

           var temperature = "";

           for (var i = 0; i < attr.getLength (); i++)
                if (attr.item (i).getNodeName () == "temperature")
                    temperature = attr.item (i).getNodeValue ();

           // Extract the weather conditions.

           var cond = XMLObj.getElementsByTagName ("yweather:condition")
                            .item (0);
           attr = cond.getAttributes ();

           var text = ""; // Cloudy, Fair, and so on.
           var code = ""; // Code for GIF file containing conditions image.
           var temp = ""; // Temperature
           var date = ""; // Date conditions are based on.

           for (var i = 0; i < attr.getLength (); i++)
                if (attr.item (i).getNodeName () == "text")
                    text = attr.item (i).getNodeValue ();
                else
                if (attr.item (i).getNodeName () == "code")
                    code = attr.item (i).getNodeValue ();
                else
                if (attr.item (i).getNodeName () == "temp")
                    temp = attr.item (i).getNodeValue ();
                else
                if (attr.item (i).getNodeName () == "date")
                    date = attr.item (i).getNodeValue ();

           result = result + "As of: " + date + "<br><br>";

           // To prevent the event-dispatching thread (which is not running at
           // this point) from being delayed due to downloading the conditions
           // image (via the <img> tag in the JLabel component's text), the
           // image file is cached to the hard drive. file.exists() returns
           // false if the file is not cached.

           // NOTE: For brevity, exception handling has been omitted. However,
           // you might consider adding exception handling in the if statement
           // below (as an exercise).

           var file = new java.io.File (code + ".gif");
           if (!file.exists ())
           {
               var url;
               url = new java.net.URL ("http://l.yimg.com/us.yimg.com/i/us/we/52/"
                     + code + ".gif");
               var is = url.openStream ();
               var fos = new java.io.FileOutputStream (code + ".gif");
               var ch;
               while ((ch = is.read ()) != -1)
                  fos.write (ch);
               fos.close ();
           }

           var path = new java.io.File ("").getAbsolutePath ();
           result = result + "<table border=1 bgcolor=#ffffff><tr><td>" +
                    "<img src=\"file:///" + path + "/" + code + ".gif\">";
           result = result + "</td></tr></table><br>";
           result = result + "Current Conditions: ";
           result = result + text + ", " + temp + " " + temperature;
       }

       result = result + "</center></html>";

       // The output label component must be accessed on the event-dispatching
       // thread.

       var r = new java.lang.Runnable ()
               {
                   run: function ()
                   {
                       // The event-dispatching thread is currently running
                       // Assign the weather result to this label.

                      lblWeather.setText (result);
                   }
               };
       java.awt.EventQueue.invokeLater (r);
   }
}

// Get an RSS document containing weather information for the specified zip
// code. Asynchronous mode is used to prevent the event-dispatching thread
// from being delayed.

xmlHttp.open ("GET", "http://weather.yahooapis.com/forecastrss?p="+zipcode,
              true);
xmlHttp.send (null);


This script creates an XMLHttpRequest object and then assigns a function to this object's onreadystatechange property. This function extracts weather data from an XML-based RSS document after the document loads. The object's open() and send() functions are then invoked to get the document identified by the zipcode script variable's value from Yahoo! Weather.

Weather data is extracted by invoking various Java DOM methods via XMLHttpRequest's responseXML property. The extracted data consists of the content of the document's first description element and various attributes of its yweather:units and yweather:condition elements. Listing 8 illustrates these elements.

Listing 8. Snippet of an XML-based RSS document from Yahoo! Weather

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" 
     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
 <channel>
 ...
 <description>Yahoo! Weather for Miami, FL</description>

 ...
 <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />
 ...
 <yweather:condition text="Mostly Cloudy" code="28" temp="82" date="Wed, 31 Oct 2007 12:15 pm EDT" />
 ...
</channel>
</rss><!-- p2.weather.re3.yahoo.com compressed/chunked Wed Oct 31 09:38:59 PDT 2007 -->
Trouble in Yahoo! Weather
I've noticed a small problem with the yweather:condition element's text attribute. Every now and then, this attribute is empty. In other words, it consists of an empty string. I'm pretty sure that my code is not responsible; I believe that the problem originates with Yahoo! Weather.

The extracted weather data is assembled into a result string. (The data includes an image that is framed in a table on a white background; not all images look nice when placed on a gradient background.) This string is then assigned, on the event-dispatching thread, to the JLabel component identified by the lblWeather script variable. This script variable was created in the application's constructor.

Playing with the Weather app

If you like, you can build and run the Weather application. Unzip the article's code archive and set the current directory to the unzipped Weather directory (which contains Weather.java, ajax.js, and weather.js). Compile the Java source code via javac Weather.java. To run the application, specify java Weather. You should see the GUI previously revealed in Figure 1.

You can easily distribute this application as a JAR file. Create a manifest.mf file that contains Main-Class: Weather, and invoke jar cfm Weather.jar manifest.mf *.class *.js to build the JAR. (The JavaScript files can be included in the JAR because Weather.java accesses them via getResourceAsStream().) Invoke java -jar Weather.jar to run the jarred application.

All-Java version of XMLHttpRequest
Although you can work with XMLHttpRequest in the context of ajax.js and the Java Scripting API, you can alternatively take advantage of an all-Java version of this object. You will find the Java version in the org.jdesktop.http.async package, which is part of the SwingLabs SwingX-WS project.

In conclusion

In this article I've introduced some of the fundamentals of the Java Scripting API and shown how you can use it in an Ajax programming example involving a Swing-based mashup. My book, Beginning Java SE 6 Platform: From Novice to Professional contains much more coverage of the Java Scripting API, including a project that introduces JavaScript to the JEditorPane component. It also explores Java SE 6 features related to annotations, JDBC, Web services, Swing programming, internationalization, security, application monitoring and management, networking, and more.

If you like what you've learned in this article, feel free to check out the book. In fact, I'll send a free copy of my book to the first three people who correctly answer the following question:

In Weather.java, why did I specify engine = manager.getEngineByName ("rhino"); instead of engine = manager.getEngineByExtension ("js"); to obtain a script engine?

Email your answer to jeff@javajeff.mb.ca before December 15. This offer is good in the United States and Canada only!

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out http://www.javajeff.mb.ca to discover all of his published Java articles and more.

Learn more about this topic