Skip to main content

skip to main content

developerWorks  >  Java technology | XML  >

Create VoiceXML pages within a Java Web developer framework, Part 4: Creating VoiceXML libraries in Java

Tools and Techniques for Building Java-based VoiceXML applications

developerWorks
Document options

Document options requiring JavaScript are not displayed

Sample code


Rate this page

Help us improve this content


Level: Intermediate

Brett McLaughlin (brett@newInstance.com), Author and Editor, O'Reilly Media Inc.

11 Jul 2006

With the basics of Java-based VoiceXML applications down pat, you're ready to start coding smarter applications than ever before. Through clever uses of Java™Beans components, servlets, JavaServer Pages (JSPs) technology, and plain old Java objects (POJOs), you can make your application development faster and more streamlined than ever before.

Building an application -- whether it's a VoiceXML application or an enterprise application -- is a monumental task. You've got to write the code, make sure all your requirements are met, keep up with dependencies, handle building, testing, deployment... it's not a simple job. When you're writing wireless applications, you've got an entire additional set of constraints to worry about: smaller memory footprints to deal with, limited resources on your target device (a phone or PDA), and code that has to stay lean and optimized. Even worse, the smallest changes to your application can render it inoperable on certain devices, requiring complicated debugging and (re-)testing.

One of the best ways to eliminate some of these issues is to create a set of proven tools and libraries for use in your VoiceXML applications. For example, if you develop an approach for handling path constants (like /pages/menu.vxml), and test that on multiple devices, then you really don't want to mess with that piece of working code. Of course, if that code is integrated into your application -- just lines of code inside other application-specific lines of code -- you've got a problem: you can't easily reuse your working, tested code on new applications, even if they need the same function.

To avoid this rather annoying problem, you can pull the path-handling code out of your application, wrap it up as a method or class that is generic -- one that can be used by any VoiceXML application -- and voila! You've got a library. That library should work on any VoiceXML application, and you're suddenly able to avoid rewriting and retesting working code. Build up several of these libraries to handle routine and common VoiceXML tasks, and your application development is going to get a lot simpler. That's the focus of this article: making your VoiceXML application development an easier task, by putting everything you can into libraries and reusable pieces of code.

Mastering the basics

The last several articles have already given you a number of tools and techniques that can help in large-scale VoiceXML application development. Many of these were approached in earlier articles from the perspective of a specific technology: JSP technology provided an easy way to compile less; servlets made it easy to read and load VXML files; JavaBeans components are a great way to define and reuse constants. Before moving on to some new tools and techniques, let's take a quick survey of what you should already understand. Be sure each of these approaches to coding VXML apps is familiar to you, as many of the later techniques in this article build on these basics.

Static VXML files

In the first article of this series, you learned how to take your static VXML files and convert them to servlets. One of the easiest ways to accomplish this task is to use a servlet that simply reads in a complete VXML file and outputs it line by line. A servlet that does just this is shown in Listing 1.


Listing 1. Loading a static VXML file


package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class VoiceXMLServlet extends HttpServlet {

  private static final String VXML_FILENAME =
    "simple-voice_recog.xml";

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    String vxmlDir = getServletContext().getInitParameter("vxml-dir");

    BufferedInputStream bis = null;
    ServletOutputStream out = null;

    try {
      // Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Output the VXML file 
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
      }
    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }
}

Add some options

This version of readFile() is rather simplistic, but you could easily spruce it up. You might want to add a version that takes a File as input, instead of just a String; you could simply have the version of readFile() shown in Listing 2 delegate to the version that takes a File. You also might want versions that take a Writer as input instead of an OutputStream, to add more flexibility.

This might not seem much like a reusable component, until you think about how often you reuse a certain portion of a VXML file. For example, you might change the prompts and options available to a user based on what type of phone they have, or on their user credentials, but you end up using the same VXML grammar again and again. In situations like this, you can use a Java function that reads in a VXML file fragment. A method that does just this is shown in Listing 2.


Listing 2. Reading in a VXML fragment


  public static void readFile(String filename, OutputStream out)
    throws IOException {

    BufferedInputStream bis = null;

    try {
      // Load the VXML file
      File vxml = new File(filename);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Output the VXML file 
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
        out.write(readBytes);
      }
    } finally {
      if (bis != null) bis.close();
    }
  }

You can pass a ServletOutputStream into this method, along with a file name, and print out a VXML fragment. Then, if you have VoiceXML grammars, or common VXML that never changes, you can leave it in a VXML fragment file (a file that doesn't have a full VXML document, but, instead, only part of one), and read it into your application as needed. Some handy extensions to this idea are discussed later, but you should always have a simple method for reading in static files when writing VoiceXML applications.

JavaBeans components for path lookups

In the third (and most recent) article of this series, I outlined an approach for using JavaBeans components to store paths in your VoiceXML applications. A bean like that shown in Listing 3 is the central reusable element in this technique.


Listing 3. Using JavaBeans components to store path information

package com.ibm.dw.voicexml;

public class LookupBean implements java.io.Serializable {

  private String identifier;
  private String filename;

  public LookupBean() { }

  public void setIdentifier(String identifier) {
    this.identifier = identifier;
    if (identifier.equals("accountInformation"))
      filename = "/profile/account-information.jsp";
    else if (identifier.equals("mainMenu"))
      filename = "/main-menu.jsp";
    else if (identifier.equals("news"))
      filename = "/news/news.jsp";
    else if (identifier.equals("help"))
      filename = "/help/help.jsp";
    else
      throw new RuntimeException("Invalid Request Target");
  }

  public String getIdentifier() {
    return identifier;
  }

  public String getFilename() {
    return filename;
  }
}

This is pretty straightforward, and mostly involves you thinking through your paths and storing them all in one place -- in the LookupBean.

One handy side effect of this approach worth mentioning is that it will often help you in organizing your VXML files and paths. Look back at Listing 3, the code for LookupBean, and notice how the JSP files used in the application are all lined up vertically. When you look at them like this, it's easy to see that the paths and directory structures are pretty consistent. This will remind you of what files and JSPs pagesyou already have in place, and often also remind you to be consistent. In other words, you want to avoid having one help file in /help/help.jsp, and another in /about/help.jsp. That's easy to do if you never have the two JSP files listed within 100 lines of each other in your code; but by using LookupBean, you'll catch mistakes like this right away.

JSP constants

A slight improvement on LookupBean is to move the file names to constants, defined either in LookupBean, or in another Java class, like Listing 4.


Listing 4. Defining constants in a PathConstants class



package com.ibm.vxml;

public class PathConstants {

  public static final String ACCOUNT_INFORMATION_URL = "/profile/account-information.jsp";
  public static final String MAIN_MENU_URL           = "/main-menu.jsp";
  public static final String NEWS_URL                = "/news/news.jsp";
  public static final String HELP_URL                = "/help/help.jsp";

  // etc...
}

Now, you would change your LookupBean to use these constants, as shown in Listing 5.


Listing 5. LookupBean using the PathConstants class



package com.ibm.dw.voicexml;

public class LookupBean implements java.io.Serializable {

  private String identifier;
  private String filename;

  public LookupBean() { }

  public void setIdentifier(String identifier) {
    this.identifier = identifier;
    if (identifier.equals("accountInformation"))
      filename = PathConstants.ACCOUNT_INFORMATION_URL;
    else if (identifier.equals("mainMenu"))
      filename = PathConstants.MAIN_MENU_URL;
    else if (identifier.equals("news"))
      filename = PathConstants.NEWS_URL;
    else if (identifier.equals("help"))
      filename = PathConstants.HELP_URL;
    else
      throw new RuntimeException("Invalid Request Target");
  }

  public String getIdentifier() {
    return identifier;
  }

  public String getFilename() {
    return filename;
  }
}

This might seem like a minor change, and even somewhat of an annoyance. However, you're probably going to use those path constants in classes other than LookupBean at some point in your application development. By moving the file names into constants in a separate class, you can simply reuse those constants anywhere in your application.



Back to top


Going further with Java classes

Now that you've got the basics under control, let's look a little further into how Java classes can really help out your application development. Remember, you want your VXML light and small -- wireless devices don't want to deal with large files. However, it's easy to write a JSP page or servlet that outputs a lightweight VXML file, but still is full of convoluted and spaghetti code. It might be a small bit of output, but maintaining the code that generates that output is a real pain.

The easiest way to avoid this pitfall is to use Java classes anywhere you can. By moving utility and reusable code into plain old Java classes, your JSP pages and servlets that actually produce VXML remain simple and maintainable. This section looks at a few helpful tools and techniques to accomplish just that, all using simple Java classes.

Encapsulating static VXML

You've already seen that you can take some of your VXML that doesn't change much and stuff it into a static file, and then read that file in as needed in your application. This solves the problem of retyping the same VXML into multiple files (JSP pages or servlets), and of the maintenance nightmare of possibly having the same VXML in multiple files, and updating one set of VXML without updating the others. However, the approach introduces a significant problem of its own.

A little homework

As in the case of the method shown in Listing 2, Listing 6 is ripe with areas for improvement. You can overload the writeFile() method pretty easily, adding additional options for supplying a file name (perhaps using a File or even an InputStream or Writer) with more flexibility, or adding more options for output (such as a Writer in addition to the OutputStream). Adding a few such options will make this class even more useful.

Using this technique, you're incurring the cost of opening, buffering, and reading in a file every time the JSP page or servlet that uses that fragment is accessed. This problem is most obvious if you've got a legal notice, or perhaps part of a standard header or footer, in a static file; every single time you access a page that uses those VXML fragments, the file is read and output. That's not a good thing -- especially when you consider that wireless users are often the most impatient when it comes to content. Nobody wants to wait around on your application; they want to hit a link on their phone, read what they requested, and get moving again.

The easiest way to avoid this problem is to read static content in via a Java class. That's a fairly simple change, as shown in Listing 6.


Listing 6. Reading in files using a Java class


package com.ibm.dw.voicexml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FileLoader {

  private static Map fileList = new HashMap();

  private static void loadFile(String filename) throws IOException {
    if (fileList.containsKey(filename)) {
      // We already have this file loaded
      return;
    } else {
      BufferedInputStream bis = null;
      try {
        StringWriter writer = new StringWriter();
        bis = new BufferedInputStream(
          new FileInputStream(
            new File(filename)));
        int readBytes = 0;
        while ((readBytes = bis.read()) != -1) {
          writer.write(readBytes);
        }
        fileList.put(filename, writer.toString());
      } finally {
        if (bis != null) bis.close();
      }
    }
  }

  public static void writeFile(OutputStream out, String filename)
    throws IOException {

    // Load the file we want to output
    loadFile(filename);

    // Output the file
    String fileContent = (String)fileList.get(filename);
    byte[] data = fileContent.getBytes();
    out.write(data, 0, data.length);
  }
}

This class looks pretty similar to the servlet code shown back in Listing 1 and the utility method in Listing 2, but has some significant differences. First, even though the class reads the file in the same way (in the loadFile() method), it only reads in the file once. After the file has been read, it throws the file name and its contents into a Map, and then never re-reads that file again. This is fairly simple code, and really provides a nice optimization.

Of course, these are all static methods, meaning you don't need to instantiate an instance of FileLoader before using it (you could use a singleton approach, but that seemed overkill for what I'm describing here). You actually want to ensure that developers don't instantiate the class; you want a single map of file names and content in order to get the most optimization possible.

Preloading files

The only downside to using the FileLoader class is that it still doesn't encounter errors until a specific file is requested. That usually occurs when a user is actually accessing your application -- one of the worst times that an error can be located and reported. For example, suppose that you store information about your company in an external file called about.vxml. Because this information is pretty static, you use the FileLoader class to load the file when it's requested, and avoid having to stick this information into a JSP page (this is a good choice; how often does your company's bio change?).

Now suppose that in a routine backup, about.vxml is corrupted. Nobody knows about the problem, but now about.vxml is sitting around on your file server, ready to spew garbage upon anyone who accesses it. Two days later, a potential customer checks out your site using their PDA, and tries to pull up your company's contact information and about page. Suddenly, your application crashes, your user gets upset, and you lose business. Not good!

Perhaps the easiest way to avoid this is to preload files, like about.vxml, that you know are going to be used often. This has two advantages:

  1. It saves load time when users request these pages for the first time. Instead of the first user who accesses a page waiting on it, you can load the pages at application startup time, and now no user gets caught waiting around.
  2. It avoids errors like the one just described happening to users; if there are errors, they are caught and reported at application startup.

It's pretty easy to add this feature to your application. First, you need to change the loadFile() method to be public in your FileLoader class, so you can load a file without having to output it; Listing 7 shows the change you need to make.


Listing 7. Making a small change to FileLoader


package com.ibm.dw.voicexml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FileLoader {

  private static Map fileList = new HashMap();

  public static void loadFile(String filename) throws IOException {
    if (fileList.containsKey(filename)) {
      // We already have this file loaded
      return;
    } else {
      BufferedInputStream bis = null;
      try {
        StringWriter writer = new StringWriter();
        bis = new BufferedInputStream(
          new FileInputStream(
            new File(filename)));
        int readBytes = 0;
        while ((readBytes = bis.read()) != -1) {
          writer.write(readBytes);
        }
        fileList.put(filename, writer.toString());
      } finally {
        if (bis != null) bis.close();
      }
    }
  }

  public static void writeFile(OutputStream out, String filename)
    throws IOException {

    // Load the file we want to output
    loadFile(filename);

    // Output the file
    String fileContent = (String)fileList.get(filename);
    byte[] data = fileContent.getBytes();
    out.write(data, 0, data.length);
  }
}

Now, you can create a servlet or JSP page that simply calls loadFile() a few times, with each file you want to preload, as shown in Listing 8.


Listing 8. Calling the loadFile() method

  FileLoader.loadFile("about.vxml");
  FileLoader.loadFile("help.vxml");
  FileLoader.loadFile("legal.vxml");
  FileLoader.loadFile("header.vxml");
  FileLoader.loadFile("footer.vxml");
  // etc...

You can put this code in several places, but I usually put it in the init() method of a servlet, sort of like in Listing 9.


Listing 9. Loading VXML files at startup in a servlet


// Lots of import statements here, omitted for brevity

public class InitServlet extends HttpServlet {
  public void init(ServlertConfig config) throws ServletException {
    super.init(config);
    try {
      List filesToLoad = MyApplicationConstans.getSomeListSomehow();
      for (Iterator i = filesToLoad.iterator(); i.hasNext(); ) {
        String filename = (String)i.next();
        FileLoader.loadFile(filename);
      }
    } catch (Exception e) {
      throw new ServletException(e);
  }
}

There's nothing too remarkable about this code. The init() method gets a list of files to load, and then iterates through the list, loading each file from that list (writing the code to get this list of files is up to you!), and reporting any errors. If there are problems, you'll find out about them here, rather than when a user accesses your application.

The init() method runs as soon as InitServlet is loaded; however, that only happens when someone accesses the servlet, and that doesn't happen automatically (at least not without some work on your part). To make sure the servlet does load as soon as your application starts up, you should create a new entry for this servlet in your web.xml file:

<servlet>
 <servlet-name>InitServlet</servlet-name>
 <servlet-class>InitServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>

This ensures that the servlet is loaded as soon as your application starts (automatically, now). Your static VXML files are all loaded when your application comes online, before users access it, at a time when you can deal with any problems.



Back to top


Standardizing headers and footers

Another common problem -- resulting in another common technique -- is the outputting of headers and footers in your VXML output. This is also a common problem in the world of HTML and XML, and you'll often see a similar approach to solving those problems as the one outlined in this section.

What's the problem?

To understand this issue a little better, consider the simple VXML file shown in Listing 10 (this file was originally detailed in the first article of this series).


Listing 10. A fairly simple VXML file



<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form id="MainMenu">
    <field name="instrument">
      <prompt>What is your  favorite musical instrument?</prompt>

      <!-- Insert an inline grammar -->
      <grammar type="text/gsl">
        [guitar mandolin dobro (violin fiddle) banjo]
      </grammar>

      <!-- Handle the case when they give no answer -->
      <noinput>
        Did you say something? I didn't hear you.
        <reprompt />
      </noinput>

      <!-- Handle the case when no match is found -->
      <nomatch>
        I suppose that's OK, but it's not on my top five. 
        Want to try again?
        <reprompt />
      </nomatch>
    </field>

    <!-- Handle the various options. -->
    <filled namelist="instrument">
      <if cond="instrument == 'guitar'">
        <prompt>That's right! Hang up and go practice.</prompt>
      <elseif cond="instrument == 'mandolin'" />
        <prompt>Nice... and only four strings to keep in tune.</prompt>
      <elseif cond="instrument == 'dobro'" />
        <prompt>Boy, that's no fun to learn, is it?</prompt>
      <elseif cond="instrument == 'violin'" />
        <prompt>We call that a fiddle, Mr. Fancy Pants.</prompt>
      <elseif cond="instrument == 'fiddle'" />
        <prompt>Does playing classical music on a 
        fiddle make it a violin?</prompt>
      <elseif cond="instrument == 'banjo'" />
        <prompt>Wow, I hope you live alone.</prompt>
      </if>
    </filled>
  </form>
</vxml>

Although it might not seem like much, every single VXML file is going to have the following code before any page-specific content:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">

Likewise, this code is at the end of every VXML file:

</vxml>

This doesn't seem like much in common between files, does it? Consider, however, that this truly is the "lowest common denominator" in terms of what is the same across VXML files. For instance, you might decide that you want options for a user to return to the main menu in every VXML page you serve. That might require a grammar like this:

  <link next="#Menu">
    <grammar type="text/gsl">[menu begin start (start over)]</grammar>
  </link>

Rather than start adding this into each and every VXML page, you could add it to your common set of information for each VXML file:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <link next="#Menu">
    <grammar type="text/gsl">[menu begin start (start over)]</grammar>
  </link>

With even this tiny addition, having a way to standardize the content included at the beginning and end of every VXML file becomes tremendously useful. You cut down on possible errors from changing the header or footer in one file and not another, as well as ensuring that you don't have a single page here or there that doesn't have the "main menu" link (or whatever other common function you want your pages to share).

Prepending and appending content

You can simply use another Java utility class to handle prepending or post-pending standard content. Like you've already seen, it's pretty easy to use Java classes to insert content into your VXML output; this is another case where more of the same works great.

Suppose you just want the standard VXML header:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">

You can use a method like that shown in Listing 11 to handle outputting this header.


Listing 11. Outputting a standard header

public static void printHeader(OutputStream out) throws IOException {
  out.write(VXMLConstants.HEADER_BYTES, 0, VXMLConstants.HEADER_BYTES.length);
}

Then, you might have something like this in your VXMLConstants source file:

public static final String HEADER_STRING =
  "<?xml version="1.0" encoding="UTF-8"?>" +
  "<vxml version="2.1">";
public static final byte[] HEADER_BYTES = HEADER_STRING.getBytes();

There are plenty of ways to get the same effect, and this is just one, but the point is that now you can call printHeader() from your servlets or JSP pages to output a standard header. You can pass in an OutputStream, readily available from JSP pages or servlets, and let the Java class handle outputting standard header text. Then, if you want to change or add VXML to that header, simply make a change to the HEADER_STRING constant in your VXMLConstants class. With one change (and a recompilation), all your VXML files will have the updated header.

It's trivial for you to implement a similar strategy for the footer (I'll leave that to you as an exercise). Like the header, you can start very simply -- perhaps with just a </vxml> element to close out the VXML -- and add common VXML as you need it.

The end result will probably be that every JSP page or servlet starts with printHeader() and ends with printFooter(), at least in the section where VXML is output.

Wrapping content up

While adding these methods is a really helpful technique, it does require a bit of extra discipline on the part of the programmer. You've got to make sure that you match up your header and footer, or you're going to have big problems. Keep in mind that in most cases, you're going to open elements in your header, and then close them in your footer. In the simplest case, this involves the opening and closing of the vxml element. But as you add more common content to the header and footer, you may want to open several nested elements within your header (for instance, you might open a VXML form in the header) and close those elements in your footer.

If you forget to include either the printHeader() or the printFooter() methods, your VXML isn't going to be valid; you'll have elements opened in the header that aren't closed in the footer, or vice versa. The end result is at best strange-looking VXML, and at worst a complete crash of your application. Neither one is going to generate fans and repeat customers -- which means that neither one is acceptable for wireless and VoiceXML development.

The simplest approach -- albeit the most "manual" approach -- is to develop some sort of coding standard. Teach your developers to simply "do the right thing," and be disciplined enough to insert the correct header and footer statements. While this is obviously an approach susceptible to human error, it has the beauty of simplicity.

If you find that approach isn't working, though, you may need to take more heavy-handed approaches. Suppose that you've got some developers that just aren't getting it, and your manager isn't a strong enough personality to take the steps needed to get these developers to do what they should; here, you'll need to come up with a way to ensure the header and footer are used in every file, and take the ability to mess things up out of your developers' hands.

In these cases, you can actually set things up so that developers pass their content to a method that handles the actual VXML output. Listing 12 shows a method that takes a String as input, and handles outputting the String, along with prepending and post-pending your headers and footers.

In these cases, you can actually set things up so that developers pass their content to a method that handles the actual VXML output. Listing 12 shows a method that takes a String as input, and handles outputting the String, along with prepending and post-pending your headers and footers.


Listing 12. Taking control of VXML output


    public static void print(OutputStream out, String vxmlContent)
    throws IOException {

    byte[] data = vxmlContent.getBytes();
    printHeader(out);
    out.write(data, 0, data.length);
    printFooter(out);
  }

This may look simpler than you first expected; the method just takes a stream to send output to, and your VXML in the form of a string, and outputs it. However, it does ensure that headers and footers are always handled.

This may look a little strange, as now your JSP pages and servlets aren't directly outputting VXML, but are, instead, simply passing content to a utility method that handles all the output. However, it's possible for you to take more control over the VXML output; by taking the direct output from your JSP pages and servlets, you can be sure that headers and footers are printed, output streams are closed (really only a problem if you're using servlets or have pretty inexperienced developers), and resources are managed. If you're pulling data from a database, or have developers that really don't take care to handle both headers and footers, this is a good way to handle these problems.

You should really consider this as more of an intermediary measure, though; there may be times when developers intentionally want to leave off the standard header or footer, and this approach makes that technique harder. Instead of just leaving off the header or footer and outputting it manually, you've got to handle the complete VXML output. Again, though, as a corrective step, this is a good technique to have in your VoiceXML toolbox.



Back to top


In conclusion

This article should give you quite a few useful tools and tips to add to your developer's toolbox. Each of these classes can serve an important purpose when you're developing applications, and you really should try and employ at least one of these techniques in each new VoiceXML project you take on.




Back to top


Download

DescriptionNameSizeDownload method
Code samplewi-voicexml4source.zipHTTP
Information about download methods


Resources

Learn

Get products and technologies
  • IBM trial software: Build your next development project with trial software, available for download directly from developerWorks.

  • VoiceXML.com: Sign up for great VoiceXML tools.

  • Voxeo Community Tools : This page is a good starting point for finding VoiceXML-related add-ons and utilities.

  • Apache Struts: Apache Struts is a popular open source framework for developing Web applications, and works with with VXML and JSPs.

  • Shale: You can download Shale from the Apache Jakarta Web site.

  • Apache Tomcat: A great servlet engine, and works perfectly with Shale, as well as Struts.


Discuss


About the author

Photo of Brett McLaughlin

Brett McLaughlin has worked in computers since the Logo days.(Remember the little triangle?) In recent years, he's become one of the most well-known authors and programmers in the Java and XML communities. He's worked for Nextel Communications, implementing complex enterprise systems; at Lutris Technologies, actually writing application servers; and most recently at O'Reilly Media, Inc., where he continues to write and edit books that matter. Brett's upcoming book, Head Rush Ajax, brings the award-winning and innovative Head First approach to Ajax, along with bestselling co-authors, Eric and Beth Freeman. His last book, Java 1.5 Tiger: A Developer's Notebook, was the first book available on the newest version of Java technology and his classic Java and XML remains one of the definitive works on using XML technologies in the Java language.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top