Sun Java Solaris Communities My SDN Account
 
Article

Server-Side AWT

 
By Joe Bella, June 18, 2002  

Ask most server-side Java technology developers why they haven't become certified yet for the Java platform, and they'll probably tell you that it's because they haven't gotten around to learning AWT (Abstract Window Toolkit) and Swing yet. These developers may be tempted to think that AWT is of little use to them outside of the occasional applet or thick client, yet hidden within this largely ignored toolkit is a wealth of functionality that the server-side programmer can use. In this article, I will show you two examples of useful functionality that can be implemented using AWT in less than an hour.

I know about your hesitance to use AWT. I, too, was one of those developers. For years I had spent my time learning the ins and outs of the javax.servlet branch of the Java tree. I played around with a dozen different frameworks and HTML template schemes, spent sleepless nights worrying about the object-relational impedance mismatch, carefully constructed my three-layer architectures, and delighted in adding color to my UML diagrams. (Okay, maybe I don't get out as much as I should.) In all that that time, I had never even so much as instantiated an AWT class.

DoMouseover() meets javax.servlet

Interestingly enough, my first foray into server-side AWT had nothing to do with graphics. I had been toying with the idea of building a generic event-handling system for one of my projects so that I could asynchronously handle mundane things such as writing to a log file, or sending off a notification email -- tasks that complete independently, allowing the main execution thread to complete without waiting. In my research, I kept running across discussions of the AWT delegation-event model. Being a dedicated object-oriented programmer, I was too lazy to code this myself. Instead I decided to co-opt AWT in my servlet.

First I created an Event Manager class that extends java.awt.Component. The Event Manager is responsible for registering listeners, posting events to the System Event Queue, and passing events to the proper listeners. When an event is thrown using the postEvent(AbstractWebEvent) method, the EventManager puts the event in the system event queue. The system event queue, running as a separate thread, calls back to the processEvent(AWTEvent) method, which passes the event to each listener registered with the addListener(ListernerInterface) method. Each listener is responsible for either handling or ignoring the event. The events themselves ultimately derive from java.awt.event.AWTEvent class. Each event has properties that the appropriate handler can call to perform the task. For example, the WebEmailEvent has from, to, subject, and message body attributes that the WebEmailListener relies on to handle the event.

Figure 1: Event Manager Class Diagram

By utilizing the existing event management framework in AWT, I was able to create a robust event management system for my server-side application without having to write potentially buggy multithreaded code.

Best of all, by having a true event management system, I was able to decouple the code that handles an event from the code that generates the event. Now, depending on my needs, I can, for example, plug in a LogListener that writes to a local rolling log file or a network syslog server, without having to change any code in the application itself.

Images Large and Small

Of course, the obvious use of the AWT libraries is for manipulating graphics. This is where many server-side programmers start to get nervous. But before you run out of the room screaming, let me assure you that there are several useful things that can be done with AWT without having to worry about obscure graphic file formats, or individual pixel manipulation. (The Java platform's robust graphics library will handle these details.) In this example, I will illustrate how you can write a program that scales web images with just three lines of code!

The need for preview or thumbnail versions of an image on a web site can be a thorn in the side for content managers. One low-tech solution tothis problem is to have the user upload multiple versions of every picture. This solution has the advantage of being simple, but has the drawback of being time intensive since an artist has to create two versions of every image.

Another low-tech solution is to set the width and height parameters on the image tag in the HTML. This has the desired effect of creating a scaled-down image, but has several drawbacks. One drawback is that the entire full-size image is sent with each request, instead of a smaller thumbnail image. Another is that the width and height parameters must be set for each individual image to make the image scale proportionately.

For those of you who have felt this pain, AWT has a quick solution (implemented in this example as a servlet) that will dynamically resize a GIF, JPEG, or a PNG file for you.

public class ResizeImageServlet extends HttpServlet
{

    private String imageDir = "";

    public final void init( ServletConfig config ) throws ServletException
    {
        // No initialization necessary
    }

    public final void doGet( HttpServletRequest req, HttpServletResponse res )
        throws ServletException, IOException
    {
        // No difference to us if it's a get or a post.
        this.doPost(req,res);
    }

    public final void doPost( HttpServletRequest req, HttpServletResponse res )
        throws ServletException, IOException
    {
        try
        {
          int targetWidth=0;
          int targetHeight=0;

          // Get a path to the image to resize.
          // ImageIcon is a kluge to make sure the image is fully 
          // loaded before we proceed.
          Image sourceImage = new ImageIcon(Toolkit.getDefaultToolkit().
          getImage(req.getPathTranslated())).getImage();

          // Calculate the target width and height
          float scale = Float.parseFloat(req.getParameter("scale"))/100;
          targetWidth = (int)(sourceImage.getWidth(null)*scale);
          targetHeight = (int)(sourceImage.getHeight(null)*scale);

          BufferedImage resizedImage = this.scaleImage
          (sourceImage,targetWidth,targetHeight);

          // Output the finished image straight to the response as a JPEG!
          res.setContentType("image/jpeg");
          JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder
          (res.getOutputStream());
          encoder.encode(resizedImage);
        }
        catch(Exception e)
        {
            res.sendError(HttpServletResponse.SC_BAD_REQUEST);
        }
    }

    private BufferedImage scaleImage(Image sourceImage, int width, int height)
    {
        ImageFilter filter = new ReplicateScaleFilter(width,height);
        ImageProducer producer = new FilteredImageSource
        (sourceImage.getSource(),filter);
        Image resizedImage = Toolkit.getDefaultToolkit().createImage(producer);

        return this.toBufferedImage(resizedImage);
    }

    private BufferedImage toBufferedImage(Image image)
    {
        image = new ImageIcon(image).getImage();
        BufferedImage bufferedImage = new BufferedImage(image.getWidth(null)
        ,image.getHeight(null),BufferedImage.TYPE_INT_RGB);
        Graphics g = bufferedImage.createGraphics();
        g.setColor(Color.white);
        g.fillRect(0,0,image.getWidth(null),image.getHeight(null));
        g.drawImage(image,0,0,null);
        g.dispose();

        return bufferedImage;
    }
}

The doPost() command is primarily concerned with calculating the desired width and height (based on the scale parameter) and figuring out where the source image file is. I chose to use the request variable's getPathTranslated() method, which retrieves everything from the servlet path to the query string and translates it into an actual system path.

The interesting part of the code is located in the method scaleImage(). The actual image resizing is done with three lines of code. Once we have converted the GIF, JPEG, or PNG file into an AWT Image object, we can apply the ReplicateScaleFilter to resize the image to the desired proportions. The toBufferedImage() method is a bit code that converts our regular Image object into a BufferedImage object that com.sun.image.codec.jpeg.JPEGImageEncoder requires to create a JPEG.

I installed the example servlet on Apache Tomcat 3.2.3 under a context called /graphics and set it to be triggered by the key /resize/* in the web.xml file. I placed my source image (photomain1.jpg) in the /graphics folder under /WEB-INF. The idea was to be able to view the original image by using the context without the servlet trigger. As an example URL, http://localhost:8080/graphics/photomain1.jpg returns my source image untouched by the servlet.

Figure 2: photomain (original image)

By changing the sample URL to http://localhost:8080/graphics/resize/photomain1.jpg?scale=50, I trigger the servlet and request an image that is half the size of the original.

Figure 3: photomain50 (image scaled down 50%)

This servlet can be deployed along with your application and used to generically scale any images on your site. Just place the resize URL in an IMG tag.

All of this, of course, comes with a slight hit to processor utilization. For high-traffic sites, you might want to consider running this code when a content manager uploads an image and writing the generated JPEG to disk so that it can be served statically.

Before you run off and try this on your Solaris system, you should know that any calls to the AWT toolkit in versions of Java 2 SDK before 1.4 require that the machine running the code have a valid display context, namely X Windows. The moment you try to instantiate an AWT class, the getGraphics() method will look for this context. Unfortunately, if your sys admin is anything like mine, X is long gone from your production servers.

If you do not have X on your production machine, I recommend that you install Xvfb on your servers. Xvfb stands for X Virtual Frame Buffer, and it was first included in the X11R6 sources (available from ftp.x.org). In a nutshell, Xvfb pretends to be a full-fledged X server and satisfies AWT's need for one.

Once you have Xvfb set up on your server, you can point your AWT to it by setting the DISPLAY environment variable in your app server's startup script. If possible, I recommend that you install the binary (reference below) instead of trying to compile X from sources. Compiling X is not for the faint of heart.

In the Java 1.4 Platform, Sun made our lives much easier and addressed the lack of headless AWT support. Instead of setting up a dummy X server like Xvfb, just put the parameter "-Djava.awt.headless=true" in your Java invocation line, and that's it! Life is good.

I think one of the truly great things about the Java programming language is the richness and depth of its standard libraries. Server-side programmers can find a lot of useful functionality in the AWT libraries. From ready-made event models to dynamic graphic rendering, the applications for server-side programmers are almost limitless.

Your Java software developer certification might be just around the corner!

Resources

About the Author

Joe Bella has spent the last 10 years as a developer and software architect of several highly visible Internet sites. When he is not losing sleep over object-relational impedance, he is president of Quimbik, Inc., a San Francisco-based web development and hosting company.

Back to Top

Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.