Saving chart images on the server side

The examples presented so far send an encoded image back to a client browser over a binary output stream, using a javax.servlet.ServletOutputStream object. There are certain circumstances, however, where this approach is not suitable for delivering chart images from servlets. Opening a binary output stream requires that the response content type be set to one of the available image mime types, like "gif/image", "png/image" and "jpeg/image", and in this situation it is not possible to simultaneously send html code back to the requester, since the only expected data type is that defined by one of these mime types.

As an example of a scenario where outputting encoded images over a binary output stream is not appropriate, consider a servlet implementing drill-down on JetChart graphs. A drill-down implementation requires that a servlet encodes a chart into a GIF, JPEG or PNG image and generates an html map of a series data points using the <map> tag. The encoded image has to be sent back over the binary output stream, hence it is necessary to set a mime type as previously described. At the same time, the servlet must output the html map back to the client browser, and the mime type has to be set to "text/html", what is not possible within the same servlet request.

A solution to the problem outlined above is to save the encoded image into a file located in a folder within the web application directory structure and embed in the servlet generated html code a reference to the file location, using the <image> tag. However, there are two shortcomings to this approach:

  1. The file name. It has to be unique, otherwise one client might be served an image that was previously created on demand from another request.

  2. The files folder size. If files are generated with unique names, the folder size increases across time. It is necessary to implement a mechanism to clean up the images folder.

The example below is a servlet that outputs a PNG encoded chart into a file, assigning a unique file name using the current system time in miliseconds and the requester's IP address. The servlet starts a thread to clean up the files folder. The thread awakes every 1 minute and checks the time stamp of each file, and if more than 5 minutes has passed since they were created, they are deleted.

Get the example code
here.
Get the cleaner thread code here.

import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.*;
import com.jinsight.jetchart.*;
import java.io.*;
import java.awt.image.BufferedImage;
import java.util.Locale;

import com.sun.media.jai.codec.*;

public class Example5 extends HttpServlet {
    
    String writeDir;
    String fileExtension;
    
    int expiryTime,checkTime;
    
    FileCheckThread fct;
    
    public void init() {

	// The folder where the png files are saved. Change it accordingly. 
	writeDir="/usr/local/jakarta/webapps/examples/images/";

	// The check time and file expiry time to be passed to the cleaner thread.
	checkTime=1; // 1 minute. Thread awakes every 1 minute.
	expiryTime=5; // 5 minutes. All files that are more than 5 minutes old are deleted.

	fileExtension="png";

	// Creates the cleaner thread and starts it. 
	fct=new FileCheckThread(writeDir,expiryTime,checkTime,fileExtension);
	fct.start();

    }
    
    public void doGet(HttpServletRequest req,HttpServletResponse res) throws IOException,ServletException {
        
        // Sets the response content type.
        res.setContentType("text/html");        

	PrintWriter pw=res.getWriter();
	
        // Creates the chart context.
        Graph graph=new Graph();
	
	graph.setLabels(new String[]{"label1","label2","label3","label4","label5","label6","label7"});
	
	graph.set3DEnabled(true);

        graph.setTitle(new String[]{"The JetChart Library","Saving chart images on the server side"});
	
        // Sets the size of the chart context and enables offscreen graph generation. These two methods must
        // always be invoked when using JetChart with servlets.
        graph.setSize(500,400);
        graph.setOffScreenGraphEnabled(true);

	// Some JetChart classes place calls to the method Graph.getLocale(), which only returns
	// a valid Locale object if the Graph object has been laid out on a container, like a 
	// Frame or JFrame object. If the Graph object has not a parent, an exception is then
	// raised when the Graph.paint(Graphics g) method is invoked to paint chart on the 
	// BufferedImage, telling that Graph must have a parent in order to determine its 
	// locale. If we set the Graph object locale as below, the exception is not raised. 
        graph.setLocale(Locale.getDefault());

        // If a BufferedImage object is used to generate offscreen images, the method below
        // must always be set to true.
        graph.setBufferedImageEnabled(true,BufferedImage.TYPE_INT_RGB);

	// Enables the grid.
	GraphSet graphSet=graph.getGraphSet(0);
	Grid grid=graphSet.getGrid();
	grid.setEnabled(true);
	grid.setColor(Color.gray);

	double[] values={220,180,170,145,140,132,120};
	Color[] colors={Color.red,Color.blue,Color.yellow,Color.green,Color.magenta,Color.orange,Color.cyan};

	// Creates a line series.
	LineSerie ls=new LineSerie(values,"Line series");
	ls.setColor(Color.yellow);

	// Disables series legend
	ls.setLegendEnabled(false);

	// Adds the series to the chart context.
	graph.addSerie(ls);

	// The BufferedImage object dimension has to be equal to the Graph object dimension. 
        BufferedImage bi=new BufferedImage(500,400,BufferedImage.TYPE_INT_RGB);

	Graphics2D g=bi.createGraphics();
	
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

	graph.paint(g);
	
	// Creates an object that encapsulates the common functionalities of PNG encoding.
	// This method is found in the JAI package.
	PNGEncodeParam png=PNGEncodeParam.getDefaultEncodeParam(bi);

	// At this point we create a file to output the png encoded image. The file has to
	// be created in  a  folder  within  the website directory structure, so it can be 
	// accessed from a client browser. This servlet also must have write access to the
	// image folder.
	// To prevent the client browser from getting the  png image from the local cache,
	// we assign  unique  names  to  the  file  created  appending the current time in 
	// miliseconds and the IP address of the remote machine.
	// A drawback to this file naming method  is  that  the  images folder will become
	// larger in size across time, because a new file is created each time a new request 
	// arrives to this servlet. The FileCheckThread class is a thread that implements
	// a mechanism to periodically delete the png files. It is started in the init()
	// method of this servlet.
	String fileName=req.getRemoteAddr()+System.currentTimeMillis()+".png";

	// The png stream is outputted to a file. Change the directory accordingly. 
	FileOutputStream out=new FileOutputStream("/usr/local/jakarta/webapps/examples/images/"+fileName);

	// Gets a PNG encoder.
	ImageEncoder encoder=ImageCodec.createImageEncoder("PNG",out,png);

	// Encodes the BufferedImage object.
	encoder.encode(bi);

        out.close();

	// After creating the png image, sends html code with a reference to the png file in
	// the <image> src tag.

	String html="<html><body bgcolor=white><center>\n";
	// The file name and location is passed to the 'src' attribute of the <image> tag.
	html+="<image src=../images/"+fileName+"></center></body></html>";
	
	// Sends the html stream back to the client browser.
	pw.println(html);
	
	pw.close();

    }

    
}