Sun Java Solaris Communities My SDN Account
 
JavaServer Faces Technology

 
API Specifications
BluePrints
Documentation
FAQs
Ajax
 
Forums
Bug Database
IRC Chat
 
 

Guidelines for Designing Reusable Custom Components Using JavaServer Faces Technology

Introduction

JavaServer Faces technology is a user interface component framework for building Java Web applications. It is designed to significantly ease the burden of writing and maintaining applications that run on a Java application server and render their UIs back to a target client. A JavaServer Faces user interface component is the basic building block for creating user interfaces. If a component uses no proprietary API's, it can be reused over and over again in a number of applications, making it much easier to develop applications and improve developer productivity.

In the JavaServer Faces programming model applications can be constructed by simply assembling the user interface components for a particular request or response. Java Server Faces technology provides a number of concrete user interface component implementation classes that cover most of the common requirements. Component writers, application developers, tool providers and implementors of JavaServer Faces technology can create reusable components by extending the standard component API. This article gives you some basic guidelines for designing custom components using ChartComponent as an example.

Key Functionalities of a JavaServer Faces Component

A JavaServer Faces component assumes the following responsiblilites.

  • Converting the internal representation of the component’s properties and attributes into the appropriate markup language for pages being rendered . This process is called encoding. (See the encodeEnd method in Listing 1)
  • Converting the properties of an incoming request—parameters, headers, and  cookies—into the corresponding properties and attributes of the component. This process is called decoding.
  • Queueing events to effect state changes in one or more components.
  • Supporting validation checks on the syntax and semantics of the representation of this component on an incoming request as well as conversion into the internal form that is appropriate for this component.
  • Saving and restoring component state across requests.
When Do you Need to Create a Custom Component  ?

Depending on your role, there could be different circumstances under which you would want to create a custom component. If you are component writer or a tool vendor, you want to offer complex components to your users that are not part of the standard. If you are an application developer, you might have to a create a custom component to add or modify some functionality of a standard component. Typically you would not create a custom component if you need to change the visual representation or decode behavior of a standard component . This is because a Java Server Faces component can optionally delegate the responsibility of decoding and encoding to a renderer, so that applications can deal with components in a manner that is predominantly independent of how the component will appear to the user. For example, the UISelectOne component is presented as a Listbox by the ListBoxRenderer while it is rendered as a set of radio buttons by RadioRenderer. If you do not have to customize the decoding and encoding behavior of your component for a particular client device or for a localized application user, you can  follow the direct implementation model where a component decodes and encodes itself as the ChartComponent (See Listing 1) does. Thats why the rendererType attribute is set to null when the component is instantiated. Following are some of the scenarios in which you would create a custom component.
  • When the component needs to support a new type of event. Every standard component supports a specific type of event for type safety.  For example if you designing a TabbedPane, whenever a new tab is selected, the component would want to fire a  TabSelected event. Extending UICommand component would not serve the purpose since it only deals with ActionEvent.
  • When you need to override the way a standard component does decoding, conversion, validation, or saving and restoring state across requests. You might have a situation where you do not want the state of the component to be persisted across requests or the component might have additional attributes that the standard component doesn't. For example ChartComponent (See Listing 1) supports attributes like height, width, and orientation, among others.  The superclass, UIOutput does not support these attributes. 
  • If the component you are designing has a behavior that is not covered by any of the standard components. 
How Do You Decide What Standard Component to Extend?

  Once you have decided that you need to create a custom component, you have to decide what standard component to extend so that you can inherit as much of the standard behavior as possible. Some factors that could help you arrive at a decision are:
  • If your component is a display only component like the ChartComponent (see Listing 1), you can extend the UIOutput component.
  • If your component is an input type component, you might further consider the following:
    • If it accepts simple text as input, then you can extend the UIInput component.
    • If the user can choose one of many or many of many, you can extend UISelectOne or UISelectMany component respectively.    
  • If your component is a type of control, when activated by the user, triggers an application-specific command or action, you can extend UICommand.
  • If your component is a container that nests other components, you can extend UIPanel.
  • If your component is expected to exhibit more than one type of behavior, you can choose to extend a standard class and implement one of the behavorial interfaces provides by JavaServer Faces. For example if you are designing a component that accepts input as well as deals with actions, you can choose to extend UIInput and implement ActionSource. For more details on these interfaces and concrete component classes, please refer to the JavaServer Faces Specification or the JavaServer Faces technology sections of the J2EE tutorial.
  • If none of the above satisfies your requirement, you can extend the UIComponentBase abstract class. This class provides useful default implementations of nearly every UIComponent method, allowing the component writer to focus on the unique characteristics of a particular UIComponent implementation.
Steps Involved in Creating a Custom Component

     
The following sections describe in detail the various steps involved in developing and using a custom component in your application from the perspective of a chart component. You can use them as basic guidelines to build your own custom components.
Writing The Custom Component Classes

       
To render a chart, we designed two custom components: ChartComponent, to represent the entire chart and ChartItemComponent to represent a single column of the chart, which in turn gets synthesized into a ChartItem model bean.

ChartComponent (see Listing 1)
    The first component is the ChartComponentwhich comprises all the details necessary to render a chart. It is a display only component which is why it extends UIOutput.  It supports additional attributes (in addition to the attributes such as value it inherits from UIOutput) to define properties of chart like width, height, type, orientation etc, so that it can used to display a bar or pie type of chart. For a complete list of atributes, see Listing 1. Since this component is display only, it doesn't override the default validation, conversion or decoding behavior. 

     One of the interesting aspects of ChartComponent is the way it does encoding (see Listing1 encodeEnd method). It delegates the  responsibility of writing out the image to ChartServlet (See Listing 7). The encodeEnd method of this component renders an HTML image tag with the src attribute pointing to url of ChartServlet, passing in necessary parameters to create a dynamic image. This is the output of the encodeEnd method

<img src="/jsf-components/ChartServlet?height=300&amp;width=300&amp;
orientation=vertical&amp;type=bar&amp;title=Employee Number By Department&amp;
xlabel=Departments&amp;ylabel=Employees"
/>

ChartComponent uses the following logic to make the data available to ChartServlet. (see Listing 1, placeChartDataInScope method). If the value returned by a value binding expression associated with the value property is non-null, it must contain an array of ChartItem (see Listing 3) beans, each bean representing a single column of the chart. If the value property is null, an array of ChartItem is synthesized by creating a ChartItem instance for every ChartItemComponent child of ChartComponent. This array is placed in session scope so that the ChartServlet can access the data at the time of rendering.
   
Since the attributes of the chart have to be peristed across requests, ChartComponent implements the saveState and restoreState methods. (see Listing 1). If the state of the component you are designing doesn't have to be persisted, you can skip this step.

ChartItemComponent (see Listing 2)
    The second component extends the abstract class UIComponentBase and must be nested inside a ChartComponent. Each ChartItemComponent causes the addition of a ChartItem instance when the parent component (ChartComponent) synthesizes the chart data during the encodeEnd method. It doesn't override any default behavior, since it doesn't have any visual representation or other functionalities. It merely exists  to encapsulate the individual column data of the chart whether it is set programatically or through JSP tags as explained in the tag handler sections below.

ChartItem (see Listing 3)
    This bean represents a single column of  the chart with properties label, value and color.
 
Registering Custom Components with the JavaServer Faces Framework
    
Every custom component  should be registered with the JavaServer Faces framework via the application configuration file so that it can participate in various phases of the lifecycle as standard components do.  The following code shows how ChartComponent and ChartItemComponent are registered in the application configuration file, faces-config.xml

<component>
    <component-type>Chart</component-type>
    <component-class>components.components.ChartComponent</component-class>
    <component-extension>
      <component-family>Chart</component-family>
    </component-extension>
  </component>
 
  <component>
    <component-type>ChartItem</component-type>
 <component-class>components.components.ChartItemComponent</component-class>
     <component-extension>
      <component-family>ChartItem</component-family>
    </component-extensio
n>
  </component>



Writing the Tag Handler Classes
You will need a custom tag to reference the chart component from the page.  In a typical JavaServer Faces application, the tag handler class drives the rendering of its associated component.  As with any JavaServer Faces tag handler class these tag handler classes:
  • Extend from javax.faces.webapp.UIComponentTag.
  • Include a getRendererType method, which returns the type of renderer that is used - the chart component does not use a renderer, so this method returns null.  The rendering is actually done in a servlet (see ChartServlet).
  • Include a getComponentType method, which returns the type of the component associated with the tag.  As the tag is processed by the JavaServer Pages Engine, this method is used to create the associated component.
  • Include a setProperties method, which sets the component attribute values.
The chart component consists of two custom tags - chart and chartItem

ChartTag
The chart tag allows the page author to set attributes that affect the chart component's visual characteristics, such as the type of chart (bar chart or pie chart), and the size of the chart.  This tag's getComponentType method returns the name of the associated component, ChartComponent.  Refer to Listing 4 for the complete ChartTag source code.

ChartItemTag
The chartItem tag allows the page author to set a single measurable item for the chart.  Each chart item consists of a label, value and color.  One or more chartItem tags must be nested within a chart tag.  This tag's getComponentType method returns the name of the associated component, ChartItemComponent.  Refer to Listing 5 for the complete ChartItemTag source code.

What's In A JavaServer Faces Tag Handler Class?

Let's take a look at ChartTag (Listing 4)  to see how JavaServer Faces tag handler classes are implemented.  You will see that ChartTag extends UIComponentTag.  UIComponentTag is the base class for all JavaServer Faces tags that do not process their tag bodies.  It supports jsp.tagext.Tag functionality and it performs many other JavaServer Faces related functions like finding the component associated with the current tag (and creating one if necessary), setting up a reponse writer for generating output during rendering and calling through to the underlying component's rendering methods.   

In general, a JavaServer Faces tag handler class contains the following code sections:
  • getComponentType / getRendererType methods
  • tag handler instance variables / set methods for the instance variables
  • setProperties method
  • release method
The getComponentType method returns a string identifying the type of the component associated with this tag -- in this case, "ChartComponent".  The getRendererType method returns a string identifying the type of the renderer that would be used by the underlying component.  The ChartComponent does not have a renderer (a servlet does the rendering), so this method returns null.  The tag handler's instance variables are set from the page's tag attributes using the setter methods. 

The setProperties method is used to set the underlying component attributes with the tag attribute values.  A tag attribute value can be specified as a string literal, or it can be specified using a value binding expression, which can be used to get the value of the tag attribute from a bean in your application.  It is specified using the special characters: '#', '{', '}' .  For example:   width="#{chartBean.width}"  means get the value of the width attribute from a bean in your application named chartBean.  That bean must have implemented a getWidth accessor method.  Here is a code snipet from the ChartTag.setProperties method:

                      Table 1: ChartTag setProperties Method
    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        ChartComponent chart = (ChartComponent) component;
      
        if (width != null) {
            if (isValueReference(width)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(width);
                chart.setValueBinding("width", vb);
            } else {
                chart.setWidth(width);
            }
        }
       .
       .
       .


The call to super.setProperties means that the UIComponentTag super class sets two additional component attributes: rendered and rendererType.  The rendered attribute is a boolean value that indicates whether the component is to be rendered.  Our tag could have specified a tag attribute value rendered="false".  The rendererType attribute is set using the tag's getRendererType method.  The setting of all the component attributes from tag attributes follow the logic as you see for the width attribute.  Assuming the width attribute has a value, a check is done to see if that value is a value binding expression using the isValueReference method from the UIComponentTag super class.  If the value is a value binding expression, then we create and store a ValueBinding instance on the ChartComponent to be evaluated later.  Otherwise, we just set the string literal value on the component.

The release method is used to release any resources allocated during the execution of the tag handler.  For the ChartTag, we first call the UIComponentTag.release method which will release any resources set there.  Then we release any resources allocated in ChartTag.

  Table 2: ChartTag release Method
public void release() {
        super.release();
        width = null;
        height = null;
        orientation = null;
    }

Defining The Tags in the Tag Library Descriptor (TLD)
Since JavaServer Faces tags are JavaServer Pages custom tags, they must be defined in a Tag Library Descriptor (TLD) file.  The TLD file is an XML document that describes all custom tags used in an application, and it is used by the web container to validate the tags in the application.  For the chart component, the TLD file will contain entries for the chart and chartItem tags.  As with any JavaServer Pages tag definition, each of the tag entries will contain an overall description of the tag, as well as attribute definitions and descriptions for each tag. 

Registering The Backing Beans Used by the Application
In JavaServer Faces technology, a custom component can use model information, and that model information  can be represented in a backing bean.  A backing bean is a JavaBean that contains methods and data that is used in your application.  In JavaServer Faces technology, a backing bean is instantiated and stored in a specified scope using the Managed Bean Creation Facility.  This facility is configured in the application configuration resource file using managed-bean XML elements to define each bean.  This file is processed at application startup time, which means that the objects declared in it are available to the entire application before any pages are accessed.  The chart component has a backing bean called ChartBean (Listing 6).  The ChartBean class is registered in a JavaServer Faces configuration file, faces-config.xml:

Table 3: Managed Bean Definition For ChartBean
<faces-config>
   .
   .
   .
  <managed-bean>
    <managed-bean-name>ChartBean</managed-bean-name>
    <managed-bean-class>demo.model.ChartBean</managed-bean-class>
    <managed-bean-scope>session</managed-bean-scope>
  </managed-bean>
   .
   .
   .
</faces-config.xml>


As you can see, you can register model beans by specifying a name for the bean, a fully qualified class path of the bean's location, and the scope of the bean's existence in the your application.  Once you have your bean registered, you can access it from your pages by specifying the name, in this case, ChartBean.

Using the Components in a JSP Page
In JavaServer Pages (JSP), you can reference the chart component tags you've defined in the TLD.

Table 4: Chart Component In JSP
<d:chart width="#{ChartBean.width}" height="#{ChartBean.height}" type="Bar" orientation="horizontal"
         title="Employee Number By Department" xlabel="Employees" ylabel="Departments">
         <d:chartItem itemLabel="Eng" itemValue="20" itemColor="red" />
         <d:chartItem itemLabel="Mktg" itemValue="150" itemColor="green" />
         <d:chartItem itemLabel="Sales" itemValue="30" itemColor="blue" />
         <d:chartItem itemLabel="R&D" itemValue="25" itemColor="orange" />
         <d:chartItem itemLabel="HR" itemValue="15" itemColor="cyan" />
</d:chart>


The first thing to notice is that the width and height values of the chart component are extracted from the ChartBean backing bean, using the value binding expression syntax.  When these expressions are evaluated, the ChartBean.getWidth and ChartBean.getHeight methods will be invoked to get the values into the page.  The next thing to notice is the nested chartItem tags.  Each one of these tags defines a measurable value for the chart.  In this case, it would be represented graphically, as a bar in the chart.

Tools Support for Third-Party Custom Components Based on JavaServer Faces Technology:

The JavaServer Faces Technology specification, version 1.0 does not formally describe how third party custom components can be exposed in tools, such as how they can dragged and dropped on to a page and how properties are configured via property sheets, but there are a couple of things that components writers could do.

  • Provide a fully fleshed out config file describing all the attributes and properties of your components and renderers (like it is done in standard-html-renderkit.xml). This is what tools will be importing for metadata about your components.
  • When you are using the standard ResponseWriter APIs during encoding, be sure to pass the appropriate component in the third argument, so that tools can match it.
Some of the third party JavaServer Faces component vendors include Crystal Reports, ILog JViews, and ESRI.

Conclusion

Now that the JavaServer Faces technology specification has been released, there will be more developers creating custom JavaServer Faces components.  The main advantage of creating a JavaServer Faces custom component is that the component follows an open standard.  JavaServer Faces components are represented as Java classes that follow the design patterns outlined in the JavaBeans Specification.  Therefore, new and existing tools that facilitate JavaBean development can be leveraged to create new JavaServer Faces components.  The JavaServer Faces architecture also facilitates the creation of components without requiring any knowledge of the rendering technology involved.  For example, using this approach, the same custom component may be rendered as HTML, as well as WML.

About the authors:

Roger Kitain is currently the co-specifiaction lead for JavaServer Faces. Roger has extensively been involved with server side web technologies
and products since 1997, including the early development efforts of the Sun One Identity Server and Software Download Center.

Jayashri Visvanathan is currently the Reference Implementation lead for JavaServer Faces. Jayashri has also contributed to various client and server side web technologies including Mozilla, WebTop Registry Server and HotJava Browser.

Code Listings

Listing 1 : ChartComponent
/*
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *

 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *   
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

package components.components;

import javax.faces.context.FacesContext;
import javax.faces.component.UIOutput;
import javax.faces.el.ValueBinding;
import javax.faces.component.UIComponent;
import javax.faces.context.ResponseWriter;
import javax.faces.component.UIViewRoot;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;

import components.model.ChartItem;


/**
 * <p>{@link ChartComponent} is a JavaServer Faces component that renders
 * a given set of data as a bar or pie chart.</p>
 */

public class ChartComponent extends UIOutput {

    /**
     * <p>The standard component type for this component.</p>
     */
    public static final String COMPONENT_TYPE = "Chart";


    /**
     * <p>The standard component family for this component.</p>
     */
    public static final String COMPONENT_FAMILY = "Chart";
   
    /**
     * <p>Name of the servlet that renders the image.</p>
     */
    public static final String CHART_SERVLET_NAME = "ChartServlet";
   
    // ------------------------------------------------------ Instance Variables
    private String width = null;
    private String height = null;
    private String orientation = null;
    private String type = null;
    private String title = null;
    private String xlabel = null;
    private String ylabel = null;
   
    // --------------------------------------------------------------Constructors

    public ChartComponent() {
        super();
        setRendererType(null);
    }

   
    // -------------------------------------------------------------- Properties
    /**
     * <p>Return the width of the chart</p>
     */
    public String getWidth() {
        if (null != this.width) {
            return this.width;
        }
        ValueBinding _vb = getValueBinding("width");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }


    /**
     * <p>Set the width of the chart</p>
     *
     * @param width The new width of the chart
     */
    public void setWidth(String width) {
        this.width = width;
       
    }
   
    /**
     * <p>Return the height of the chart</p>
     */
    public String getHeight() {
        if (null != this.height) {
            return this.height;
        }
        ValueBinding _vb = getValueBinding("height");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }


    /**
     * <p>Set the height of the chart</p>
     *
     * @param height The new height of the chart
     */
    public void setHeight(String height) {
        this.height = height;
    }
   
    /**
     * <p>Return the orientation of the chart</p>
     */
    public String getOrientation() {
        if (null != this.orientation) {
            return this.orientation;
        }
        ValueBinding _vb = getValueBinding("orientation");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }


    /**
     * <p>Set the orientation of the chart</p>
     *
     * @param orientation The new orientation of the chart
     */
    public void setOrientation(String orientation) {
        this.orientation = orientation;
    }
   
    /**
     * <p>Return the type of the chart</p>
     */
    public String getType() {
        if (null != this.type) {
            return this.type;
        }
        ValueBinding _vb = getValueBinding("type");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }


    /**
     * <p>Set the type of the chart</p>
     *
     * @param type The new type of the chart
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * <p>Return the title of the chart</p>
     */
    public String getTitle() {
        if (null != this.title) {
            return this.title;
        }
        ValueBinding _vb = getValueBinding("title");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }

    /**
     * <p>Set the title of the chart</p>
     *
     * @param title The new title of the chart
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * <p>Return the x axis label of the chart</p>
     */
    public String getXlabel() {
        if (null != this.xlabel) {
            return this.xlabel;
        }
        ValueBinding _vb = getValueBinding("xlabel");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }

    /**
     * <p>Set the x axis label of the chart</p>
     *
     * @param xlabel The new x axis label of the chart
     */
    public void setXlabel(String xlabel) {
        this.xlabel = xlabel;
    }

    /**
     * <p>Return the y axis label of the chart</p>
     */
    public String getYlabel() {
        if (null != this.ylabel) {
            return this.ylabel;
        }
        ValueBinding _vb = getValueBinding("ylabel");
        if (_vb != null) {
            return (java.lang.String) _vb.getValue(getFacesContext());
        } else {
            return null;
        }
    }

    /**
     * <p>Set the y axis label of the chart</p>
     *
     * @param ylabel The new y axis label of the chart
     */
    public void setYlabel(String ylabel) {
        this.ylabel = ylabel;
    }

    /**
     * <p>Return the component family for this component.</p>
     */
    public String getFamily() {

        return (COMPONENT_FAMILY);

    }
  
    // ----------------------------------------------------- StateHolder Methods
    /**
     * <p>Return the state to be saved for this component.</p>
     *
     * @param context <code>FacesContext</code> for the current request
     */
    public Object saveState(FacesContext context) {
        Object values[] = new Object[8];
        values[0] = super.saveState(context);
        values[1] = width;
        values[2] = height;
        values[3] = orientation;
        values[4] = type;
        values[5] = title;
        values[6] = xlabel;
        values[7] = ylabel;
        return (values);
    }


    /**
     * <p>Restore the state for this component.</p>
     *
     * @param context <code>FacesContext</code> for the current request
     * @param state   State to be restored
     *
     * @throws IOException if an input/output error occurs
     */
    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        width = (String) values[1];
        height = (String) values[2];
        orientation = (String) values[3];
        type = (String) values[4];
        title = (String) values[5];
        xlabel = (String) values[6];
        ylabel = (String) values[7];
    }
   
    public void encodeEnd(FacesContext context) throws IOException {
        placeChartDataInScope(context);
        // render an image that would initiate a request to a URL pointing
        // back into the webapp passing in whatever parameters are needed to
        // create the dynamic image.
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("img", this);
        writeIdAttributeIfNecessary(context, writer, this);
        writer.writeAttribute("src", src(context, this), "value");
        writer.endElement("img");
    }
   
    // ----------------------------------------------------- Private Methods
   
    protected void writeIdAttributeIfNecessary(FacesContext context,
                                               ResponseWriter writer,
                                               UIComponent component) {
        String id;
        if ((id = component.getId()) != null &&
            !id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX)) {
            try {
                writer.writeAttribute("id", component.getClientId(context),
                                      "id");
            } catch (IOException e) {
               /* if (log.isDebugEnabled()) {
                    log.debug("Can't write ID attribute" + e.getMessage());
                } */
            }
        }
    }
   
    private String src(FacesContext context, UIComponent component) {
        String contextPath = context.getExternalContext().getRequestContextPath();
        StringBuffer result = new StringBuffer(contextPath);
        result.append("/");
        result.append(CHART_SERVLET_NAME);
       
        // append parameters to be passed to be servlet
        // ChartServlet will use clientId as the attribute name to get the chart
        // data from session.
        result.append("?chartId=");
        result.append(getClientId(context));
        result.append("&");
       
        result.append("height=");
        if ( getHeight() != null ) {
            result.append(getHeight());
        }
        result.append("&");
       
        result.append("width=");
        if ( getWidth() != null ) {
            result.append(getWidth());
        }
        result.append("&");
    
        result.append("orientation=");
        if ( getOrientation() != null ) {
            result.append(getOrientation());
        }
        result.append("&");
       
        result.append("type=");
        if ( type != null ) {
            result.append(type);
        }
        result.append("&");

        result.append("title=");
        if ( title != null ) {
            result.append(title);
        }
        result.append("&");

        result.append("xlabel=");
        if ( xlabel != null ) {
            result.append(xlabel);
        }
        result.append("&");
     
        result.append("ylabel=");
        if ( ylabel != null ) {
            result.append(ylabel);
        }

        return (result.toString());
     }
   
    /** Place the appropriate data for chart in session scope, so that
     * it will be there when the separate request for the image is
     * processed by the chart servlet. This servlet is responsible for
     * writing out the chart as an image into the respone stream.
     */
    protected void placeChartDataInScope(FacesContext context) {
        int i = 0;
        ChartItem[] chartItems = null;
        // if there is a value attribute set on the bean, data for the chart is
        // retrieved from the bean. If not, we build an array of ChartItem
        // using the children of this component.
        chartItems = (ChartItem[]) getValue();
        if (chartItems == null || chartItems.length == 0 ) {
            chartItems = new ChartItem[getChildCount()];
            Iterator kids = this.getChildren().iterator();
            while ( kids.hasNext()) {
                UIComponent kid = (UIComponent) kids.next();
                if (kid instanceof ChartItemComponent) {
                    ChartItemComponent ci = (ChartItemComponent) kid;
                    ChartItem item = (ChartItem) ci.getValue();
                    if (item == null) {
                        int itemVal =
                            (new Integer((String)ci.getItemValue())).intValue();
                        item = new ChartItem(ci.getItemLabel(),itemVal,
                            ci.getItemColor());
                    }
                    chartItems[i] = item;
                    ++i;
                }
            }
        }
        // store the chart data against the clientId in session.
        Map sessionMap =
            getFacesContext().getExternalContext().getSessionMap();
        sessionMap.put(getClientId(context), chartItems);
    }
   
}


Listing 2: ChartItemComponent

/*
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package components.components;


import java.io.IOException;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.component.UIComponentBase;


/**
 * <p><strong>ChartItemComponent</strong> is a component that may be nested
 * inside a {@link ChartComponent}, and causes the addition of a
 * {@link DataItem} instance to the list of available options for the
 * parent component.  The contents of the
 * {@link ChartItem} can be specified in one of the following ways:</p>
 * <ul>
 * <li>The <code>value</code> attribute's value is an instance of
 *     {@link ChartItem}.</li>
 * <li>The associated {@link ValueBinding} points at a model data
 *     item of type {@link ChartItem}.</li>
 * <li>A new {@link ChartItem} instance is synthesized from the values
 *     of the <code>itemLabel</code>, <code>color</code>, <code>value</code></li>
 * </ul>
 */

public class ChartItemComponent extends UIComponentBase {


    // ------------------------------------------------------ Manifest Constants


    /**
     * <p>The standard component type for this component.</p>
     */
    public static final String COMPONENT_TYPE = "ChartItem";


    /**
     * <p>The standard component family for this component.</p>
     */
    public static final String COMPONENT_FAMILY = "ChartItem";


    // ------------------------------------------------------------ Constructors


    /**
     * <p>Create a new {@link UISelectItem} instance with default property
     * values.</p>
     */
    public ChartItemComponent() {

        super();
        setRendererType(null);

    }


    // ------------------------------------------------------ Instance Variables


    private String itemLabel = null;
    private String itemColor = null;
    private Object itemValue = null;
    private Object value = null;


    // -------------------------------------------------------------- Properties


    public String getFamily() {

        return (COMPONENT_FAMILY);

    }


    /**
     * <p>Return the label for this chart item.</p>
     */
    public String getItemLabel() {

    if (this.itemLabel != null) {
        return (this.itemLabel);
    }
    ValueBinding vb = getValueBinding("itemLabel");
    if (vb != null) {
        return ((String) vb.getValue(getFacesContext()));
    } else {
        return (null);
    }

    }


    /**
     * <p>Set the label for this chart item.</p>
     *
     * @param label The new label
     */
    public void setItemLabel(String label) {

        this.itemLabel = label;

    }

    /**
     * <p>Return the color for this chart item.</p>
     */
    public String getItemColor() {

    if (this.itemColor != null) {
        return (this.itemColor);
    }
    ValueBinding vb = getValueBinding("itemColor");
    if (vb != null) {
        return ((String) vb.getValue(getFacesContext()));
    } else {
        return (null);
    }

    }


    /**
     * <p>Set the color for this chart item.</p>
     *
     * @param color The new color
     */
    public void setItemColor(String color) {

        this.itemColor = color;

    }
   
    /**
     * <p>Return the server value for this selection item.</p>
     */
    public Object getItemValue() {

    if (this.itemValue != null) {
        return (this.itemValue);
    }
    ValueBinding vb = getValueBinding("itemValue");
    if (vb != null) {
        return ((Integer) (vb.getValue(getFacesContext())));
    } else {
        return null;
    }
    }
   
    /**
     * <p>Set the server value for this selection item.</p>
     *
     * @param itemValue The new server value
     */
    public void setItemValue(Object itemValue) {

        this.itemValue = itemValue;

    }

    /**
     * <p>Returns the <code>value</code> of this chart item</p>
     */
    public Object getValue() {

    if (this.value != null) {
        return (this.value);
    }
    ValueBinding vb = getValueBinding("value");
    if (vb != null) {
        return (vb.getValue(getFacesContext()));
    } else {

        return (null);
    }
    }


    /**
     * <p>Sets the <code>value</code> property of this chart item</p>
     *
     * @param value the new value
     */
    public void setValue(Object value) {

        this.value = value;

  
  }


    // ----------------------------------------------------- StateHolder Methods
    public Object saveState(FacesContext context) {
        Object values[] = new Object[5];
        values[0] = super.saveState(context);
        values[1] = itemLabel;
        values[2] = itemColor;
        values[3] = itemValue;
        values[4] = value;
        return (values);
    }

    public void restoreState(FacesContext context, Object state) {
        Object values[] = (Object[]) state;
        super.restoreState(context, values[0]);
        itemLabel = (String) values[1];
        itemColor = (String) values[2];
        itemValue = values[3];
        value = values[4];
    }
}


Listing 3: ChartItem

/*
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

package components.model;

/**
 * This class represents an individual graphable item for the chart conmponent.
 */
public class ChartItem {

    public ChartItem() {
       super();
    }
   
    public ChartItem(String label, int value, String color) {
        setLabel(label);
    setValue(value);
    setColor(color);
    }

    /**
     * <p>The label for this item.</p>
     */
    private String label = null;
    /**
     *<p>Return the label for this item.</p>
     */
    public String getLabel() {
        return label;
    }
    /**
     * <p>Set the label for this item.</p>
     */
    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * <p>The value for this item.</p>
     */
    private int value = 0;
    /**
     *<p>Return the value for this item.</p>
     */

    public int getValue() {
        return value;
    }
    /**
     * <p>Set the value for this item.</p>
     */
    public void setValue(int value) {
        this.value = value;
    }

    /**
     * <p>The color for this item.</p>
     */
    private String color = null;
    /**
     *<p>Return the color for this item.</p>
     */
    public String getColor() {
        return color;
    }
    /**
     * <p>Set the color for this item.</p>
     */
    public void setColor(String color) {
        this.color = color;
    }
}


Listing 4: ChartTag
/*
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *   
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

package components.taglib;

import components.components.ChartComponent;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;


/**
 * <p><strong>ChartTag</strong> is the tag handler that processes the
 * <code>chart</code> custom tag.
 */

public class ChartTag extends UIComponentTag {


    /**
     * <p>The width of the chart</p>
     */
    private String width = null;
    /**
     * <p>Set the width of the chart</p>
     */
    public void setWidth(String width) {
        this.width = width;
    }

    /**
     * <p>The height of the chart</p>
     */
    private String height = null;
    /**
     * <p>Set the height of the chart</p>
     */
    public void setHeight(String height) {
        this.height = height;
    }

    /**
     * <p>The layout of the chart.  This attribute is applicable to bar
     * charts, and the value can be "horizontal" or "vertical".</p>
     */
    private String orientation = null;
    /**
     * <p>Set the orientation of the chart</p>
     */
    public void setOrientation(String orientation) {
        this.orientation = orientation;
    }
   
    private String value = null;
    public void setValue(String value) {
        this.value = value;
    }
   
    /**
     * <p>The type of chart.  Values can be "bar" or "pie".</p>
     */
    private String type = null;
    /**
     * <p>Set the type of the chart</p>
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * <p>The title of the chart</p>
     */
    private String title = null;
    /**
     * <p>Set the title of the chart</p>

     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * <p>The label for the x-axis of the bar chart</p>
     */
    private String xlabel = null;
    /**
     * <p>Set the x-axis label for the bar chart</p>
     */
    public void setXlabel(String xlabel) {
        this.xlabel = xlabel;
    }

    /**
     * <p>The label for the y-axis of the bar chart</p>
     */
    private String ylabel = null;
    /**
     * <p>Set the y-axis label for the bar chart</p>
     */
    public void setYlabel(String ylabel) {
        this.ylabel = ylabel;
    }

    /**
     * <p>Return the type of the component.</p>
     */
    public String getComponentType() {
        return ("Chart");
    }


    /**
     * <p>Return the renderer type (if any)</p>
     */
    public String getRendererType() {
        return (null);
    }


    /**
     * <p>Release any resources used by this tag handler</p>
     */
    public void release() {
        super.release();
        width = null;
        height = null;

        orientation = null;
        title = null;
        xlabel = null;
        ylabel = null;
        type = null;
    }


    /**
     * <p>Set the component properties</p>
     */
    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        ChartComponent chart = (ChartComponent) component;
      
        if (width != null) {
            if (isValueReference(width)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(width);
                chart.setValueBinding("width", vb);
            } else {
                chart.setWidth(width);
            }
        }
       
        if (height != null) {
            if (isValueReference(height)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(height);
                chart.setValueBinding("height", vb);
            } else {
                chart.setHeight(height);
            }
        }
       
        if (orientation != null) {
            if (isValueReference(orientation)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(orientation);
                chart.setValueBinding("orientation", vb);
            } else {
                chart.setOrientation(orientation);
            }
        }
       
        if (type != null) {
            if (isValueReference(type)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(type);
                chart.setValueBinding("type", vb);
            } else {
                chart.setType(type);
            }
        }

       
        if (value != null) {
            if (isValueReference(value)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(value);
                chart.setValueBinding("value", vb);
            } else {
                chart.setValue(value);
            }
        }
       
        if (title != null) {
            if (isValueReference(title)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(title);
                chart.setValueBinding("title", vb);
            } else {
                chart.setTitle(title);
            }
        }
       
        if (xlabel != null) {
            if (isValueReference(xlabel)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(xlabel);
                chart.setValueBinding("xlabel", vb);
            } else {
                chart.setXlabel(xlabel);
            }
        }
       

        if (ylabel != null) {
            if (isValueReference(ylabel)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(ylabel);
                chart.setValueBinding("ylabel", vb);
            } else {
                chart.setYlabel(ylabel);
            }
        }
    }
}

Listing 5: ChartItemTag
/*
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package components.taglib;

import components.components.ChartItemComponent;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;
import javax.faces.webapp.UIComponentTag;

/**
 * <p><strong>ChartItemTag</strong> is the tag handler that processes the
 * <code>chartItem</code> custom tag.</p>
 */

public class ChartItemTag extends UIComponentTag {

    public ChartItemTag() {
        super();
    }

    //
    // Class methods
    //

    //
    // Accessors
    //

    /**
     * <p>The label for this item</p>
     */
    private String itemLabel = null;
    /**
     *<p>Set the label for this item.</p>
     */
    public void setItemLabel(String label) {
        this.itemLabel = label;
    }

    /**
     * <p>The color for this item.</p>
     */
    private String itemColor = null;
    /**
     *<p>Set the color for this item.</p>
     */
    public void setItemColor(String color) {
        this.itemColor = color;
    }
   
    /**
     * <p>The value for this item.</p>
     */
    private String itemValue = null;
    /**
     *<p>Set the ualue for this item.</p>
     */
    public void setItemValue(String itemVal) {
        this.itemValue = itemVal;
    }

    private String value = null;
    public void setValue(String value) {
        this.value = value;
    }

    //
    // General Methods
    //

    /**
     * <p>Return the type of the component.</p>
     */
    public String getComponentType() {
        return "ChartItem";
    }

    /**
     * <p>Return the renderer type (if any)</p>
     */
    public String getRendererType() {
        return null;
    }

    /**
     * <p>Release any resources used by this tag handler</p>

     */
    public void release() {
        super.release();
        itemLabel = null;
        itemValue = null;
        itemColor = null;
    }

    //
    // Methods from BaseComponentTag
    //

    /**
     * <p>Set the component properties</p>
     */
    protected void setProperties(UIComponent component) {
        super.setProperties(component);
        ChartItemComponent chartItem = (ChartItemComponent) component;
        if (null != value) {
            if (isValueReference(value)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(value);
                chartItem.setValueBinding("value", vb);
            } else {
                chartItem.setValue(value);
            }
        }

        if (null != itemLabel) {
            if (isValueReference(itemLabel)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(itemLabel);
                chartItem.setValueBinding("itemLabel", vb);
            } else {
                chartItem.setItemLabel(itemLabel);
            }
        }
       
        if (null != itemColor) {
            if (isValueReference(itemColor)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(itemColor);
                chartItem.setValueBinding("itemColor", vb);
            } else {
                chartItem.setItemColor(itemColor);
            }
        }
       
        if (null != itemValue) {
            if (isValueReference(itemValue)) {
                ValueBinding vb = FacesContext.getCurrentInstance()
                    .getApplication().createValueBinding(itemValue);
                chartItem.setValueBinding("itemValue", vb);
            } else {
                chartItem.setItemValue(itemValue);
            }
        }
    }

}


Listing 6: ChartBean
/*
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

package components.model;

import java.util.ArrayList;
import java.util.Collection;

/**
 * <p><strong>ChartBean</strong> is the backing bean for the <code>chart</code>
 *  component.</p>
 */
public class ChartBean {

    // Bar Chart Properties -------------------------
   
    public static final int    VERTICAL = 0;
    public static final int     HORIZONTAL = 1;

    /**
     * <p>The layout of the chart.  This attribute is applicable to bar
     * charts, and the value can be "horizontal" or "vertical".</p>
     */
    private int orientation = VERTICAL;
    /**
     * <p>Return the orientation of the chart</p>
     */
    public int getOrientation() {
        return orientation;
    }
    /**
     * <p>Set the orientation of the chart</p>
     */
    public void setOrientation(int orientation) {
        this.orientation = orientation;
    }
   
    // ----------------------------------------------
   
    /**
     * <p>The list of graphable items for this chart</p>
     */
    private ArrayList chartItems = null;
    /**
     * <p>Return the list of items for this chart</p>
     */
    public Collection getChartItems() {
        return chartItems;

    }

    /**
     * <p>The title of this chart</p>
     */
    private String title = null;
    /**
     * <p>Return the title of this chart</p>
     */
    public String getTitle() {
        return title;
    }
    /**
     * <p>Set the title of this chart</p>
     */
    public void setTitle() {
        this.title = title;
    }

    /**
     * <p>The width of this chart</p>
     */
    private int width = 400;
    /**
     * <p>Return the width of this chart</p>
     */
    public int getWidth() {
        return width;
    }
    /**
     * <p>Set the width of this chart</p>
     */
    public void setWidth(int width) {
        this.width = width;
    }
    /**
     * <p>The height of this chart</p>
     */
    private int height = 300;
    /**
     * <p>Return the height of this chart</p>
     */
    public int getHeight() {
        return height;
    }
    /**
     * <p>Set the height of this chart</p>
     */
    public void setHeight(int height) {
        this.height= height;
    }

    /**
     * <p>Constructor sets default values</p>
     */
    public ChartBean() {

    setWidth(400);
    setHeight(300);
    setOrientation(ChartBean.HORIZONTAL);

        chartItems = new ArrayList();
    chartItems.add(new ChartItem("one", 10, "red"));
    chartItems.add(new ChartItem("two", 20, "blue"));

    }
}


Listing 7: ChartServlet
/*
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any
 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed, licensed or
 * intended for use in the design, construction, operation or
 * maintenance of any nuclear facility.
 */

package components.renderkit;

import components.model.ChartItem;

import com.sun.image.codec.jpeg.*;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.geom.Ellipse2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * <p><strong>ChartServlet</strong> is used to render the chart image.
 */
public final class ChartServlet extends HttpServlet {

    /**
     * <p>The <code>ServletConfig</code> instance for this servlet.</p>
     */
    private ServletConfig servletConfig = null;


   
    /**
     * <p>Release all resources acquired at startup time.</p>
     */
    public void destroy() {
        servletConfig = null;
    }

    /**
     * <p>Return the <code>ServletConfig</code> instance for this servlet.</p>
     */
    public ServletConfig getServletConfig() {

        return (this.servletConfig);

    }

    /**
     * <p>Return information about this Servlet.</p>
     */
    public String getServletInfo() {

        return (this.getClass().getName());

    }

    /**
     * <p>Perform initialization.</p>
     *
     * @exception ServletException if, for any reason,
     * bn error occurred during the processing of
     * this <code>init()</code> method.
     */
    public void init(ServletConfig servletConfig) throws ServletException {
   
        // Save our ServletConfig instance
        this.servletConfig = servletConfig;
    }

    /**
     * <p>Process an incoming request, and create the corresponding
     * response.</p>
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs during processing
     * @exception ServletException if a servlet error occurs during processing
     */
    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {


    // Here's where we'd get the ChartBean from the session and determine
    // whether we're generating a pie chart or bar chart...
    //
    String type = request.getParameter("type");
    if ((type == null) ||
        (!type.equals("bar")) && (!type.equals("pie"))) {
        type = "bar";
    }
       
    if (type.equals("bar")) {
        generateBarChart(request, response);
    } else {
            generatePieChart(request, response);
    }
    }

    /**
     * <p>Process an incoming request, and create the corresponding
     * response.</p>
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs during processing
     * @exception ServletException if a servlet error occurs during processing
     */
    public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {
    doGet(request, response);
    }

    /**
     * <p> Generate a bar chart from data.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs during processing
     * @exception ServletException if a servlet error occurs during processing
     */
    private void generateBarChart(HttpServletRequest request,
                          HttpServletResponse response)
        throws IOException, ServletException {

        final int VERTICAL = 0;
    final int HORIZONTAL = 1;


    response.setContentType("image/jpeg");
   
        String id = request.getParameter("chartId");
       
    // get chart parameters
    String title = request.getParameter("title");
    if (title == null) {
        title = "Chart";
    }
   
    int orientation = VERTICAL;
    String orientationStr = request.getParameter("orientation");
    if ((orientationStr == null) ||
        (!orientationStr.equals("horizontal")) && (!orientationStr.equals("vertical"))) {
        orientation = VERTICAL;
    } else if (orientationStr.equals("vertical")) {
        orientation = VERTICAL;
    } else {
        orientation = HORIZONTAL;
    }

    // label for x/y axis
    String xLabel = request.getParameter("xlabel");
    String yLabel = request.getParameter("ylabel");

    // default image size
    int width = 400;
    int height = 300;

    String widthStr = request.getParameter("width");
    String heightStr = request.getParameter("height");
    if (widthStr != null) {
        width = Integer.parseInt(widthStr);
    }
    if (heightStr != null) {
        height = Integer.parseInt(heightStr);
    }
   
    // get an array of chart items containing our data..
        HttpSession session = request.getSession(true);
    ChartItem[] chartItems = (ChartItem[])session.getAttribute(id);
    if (chartItems == null) {
            System.out.println("No data items specified...");
        throw new ServletException("No data items specified...");
    }

    // maximum data value
    int maxDataValue = 0;
    // maximum label width
    int maxLabelWidth = 0;
    // space between bars
    int barSpacing = 10;
    // width of each bar
    int barWidth = 0;
    // x,y coordinates
    int cx, cy;
    // number of chart items
    int columns = chartItems.length;
    int scale = 10;
        // an individual chart data item
    ChartItem chartItem = null;
    String label = null;

    int value = 0;

        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bi.createGraphics();
        Font titleFont = new java.awt.Font("Courier", Font.BOLD, 12);
        FontMetrics titleFontMetrics = g2d.getFontMetrics(titleFont);
   
    // loop through and figure out the the widest item label, as well as
    // the maximum value.
        for (int i=0; i < columns; i++) {
        chartItem = chartItems[i];
        label = chartItem.getLabel();
        value = chartItem.getValue();
        if (value > maxDataValue) {
            maxDataValue = value;
        }
        maxLabelWidth = Math.max(titleFontMetrics.stringWidth(label), maxLabelWidth);
    }

    // calculate chart dimensions
    int[] xcoords = new int[columns];
    int[] ycoords = new int[columns];
    int totalWidth = 0;
    int totalHeight = 0;
    for (int i=0; i < columns; i++) {
        switch (orientation) {
          case VERTICAL:
          default:
                barWidth = maxLabelWidth;
        cx = (Math.max((barWidth + barSpacing),maxLabelWidth) * i) +
            barSpacing;

        totalWidth = cx + (4 * titleFont.getSize());
        break;
          case HORIZONTAL:
        barWidth = titleFont.getSize();
        cy = ((barWidth + barSpacing) * i) + barSpacing;
        totalHeight = cy + (4 * titleFont.getSize());
        break;
        }
    }
    if (orientation == VERTICAL) {
            totalHeight = maxDataValue + (8 * titleFont.getSize());
        totalWidth = totalWidth + 50;
    } else {
        totalWidth = maxDataValue + (4 * titleFont.getSize() +
        (Integer.toString(maxDataValue).length() * titleFont.getSize())+50);
    }

    // Make sure the the total height of the chart provides enough room
    // for the vertical label..
    //
    int yLabelHeight = 0;
    for (int i=0; i<yLabel.length(); i++) {
        yLabelHeight += titleFontMetrics.getAscent();
    }
    if ((yLabelHeight+(12 * titleFontMetrics.getDescent())) > totalHeight) {
        totalHeight = yLabelHeight+(8*titleFont.getSize());
    }
       

        bi = new BufferedImage(totalWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
        g2d = bi.createGraphics();
        titleFontMetrics = g2d.getFontMetrics(titleFont);


    // graph dimensions
    Dimension graphDim = new Dimension(totalWidth,totalHeight);
        Rectangle graphRect = new Rectangle(graphDim);

    // border dimensions
    Dimension borderDim = new Dimension(totalWidth-2,totalHeight-2);
        Rectangle borderRect = new Rectangle(borderDim);
   
    // background color
    g2d.setColor(Color.white);
    g2d.fill(graphRect);

    // draw border
    g2d.setColor(Color.black);
    borderRect.setLocation(1,1);
        g2d.draw(borderRect);

    // draw the title centered at the bottom of the bar graph
    int i = titleFontMetrics.stringWidth(title);
    g2d.setFont(titleFont);
    g2d.setColor(Color.black);
    g2d.drawString(title, Math.max((totalWidth - i)/2, 0),
        totalHeight - titleFontMetrics.getDescent());

    // draw the x axis label
    i = titleFontMetrics.stringWidth(xLabel);
    g2d.drawString(xLabel, Math.max((totalWidth - i)/2, 0),
        totalHeight - (6 * titleFontMetrics.getDescent()));

    // draw the y axis label
    i = titleFontMetrics.stringWidth(yLabel);
    cx = totalWidth-(totalWidth-6);
    cy = totalHeight - (12 * titleFontMetrics.getDescent());

    for (int j=yLabel.length(); j>0; j--) {
        g2d.drawString(yLabel.substring(j-1,j), cx, cy);
        cy -= titleFontMetrics.getAscent();
    }
       
    // loop through to draw the chart items.
        for (i=0; i < columns; i++) {
        chartItem = chartItems[i];
        label = chartItem.getLabel();
        value = chartItem.getValue();
        String colorStr = chartItem.getColor();

        Object color = getColor(colorStr);
        switch (orientation) {
          case VERTICAL:
          default:
                barWidth = maxLabelWidth;
        // set the next X coordinate to account for the label
        // being wider than the bar width.
        cx = (Math.max((barWidth + barSpacing),maxLabelWidth) * i) +
            barSpacing + 12;

        // center the bar chart
        cx += Math.max((totalWidth - (columns * (barWidth +
            (2 * barSpacing))))/2,0);
          
        // set the next Y coordinate to account for the height
        // of the bar as well as the title and labels painted
        // at the bottom of the chart.
        cy = totalHeight - (value) - 1 - (2 * titleFont.getSize());

   
        // draw the label
        g2d.setColor(Color.black);
        g2d.drawString((String)label, cx,
            totalHeight - titleFont.getSize() - (8 * titleFontMetrics.getDescent()));   

        // draw the shadow bar
        if (color == Color.black) {
            g2d.setColor(Color.gray);
        }
        g2d.fillRect(cx + 5, cy - 28, barWidth,  (value));
        // draw the bar with the specified color
        g2d.setColor((Color)(color));
                g2d.fillRect(cx, cy - 30, barWidth, (value));
                g2d.drawString("" + value, cx, cy - 30 - titleFontMetrics.getDescent());
        break;
          case HORIZONTAL:
        barWidth = titleFont.getSize();
        // set the Y coordinate
            cy = totalHeight - (((barWidth + barSpacing) * i) + barSpacing +
            (12 * titleFontMetrics.getDescent()));
       

        // set the X coordinate to be the width of the widest label
        cx = maxLabelWidth + 1;

        cx += Math.max((totalWidth - (maxLabelWidth + 1 +
                titleFontMetrics.stringWidth("" + maxDataValue) +
                    (maxDataValue))) / 2, 0);
        // draw the labels and the shadow
        g2d.setColor(Color.black);

        g2d.drawString((String)label, cx - maxLabelWidth - 1,
                 cy + titleFontMetrics.getAscent());
        if (color == Color.black) {
            g2d.setColor(Color.gray);
        }
        g2d.fillRect(cx + 3, cy + 5, (value), barWidth);

        // draw the bar in the current color
        g2d.setColor((Color)(color));
                g2d.fillRect(cx, cy, (value), barWidth);
                g2d.drawString("" + value, cx + (value ) + 3,
                    cy + titleFontMetrics.getAscent());
        break;
        }
    }
        OutputStream output = response.getOutputStream();
        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
        encoder.encode(bi);
        output.close();
       
        // remvoe the chart data from session now that chart has been rendered.
        session.removeAttribute(id);
    }

    /**
     * <p> Generate a pie chart from data.
     *
     * @param request The servlet request we are processing
     * @param response The servlet response we are creating
     *
     * @exception IOException if an input/output error occurs during processing
     * @exception ServletException if a servlet error occurs during processing
     */
    private void generatePieChart(HttpServletRequest request,
                          HttpServletResponse response)
        throws IOException, ServletException {
        response.setContentType("image/jpeg");
   
    // get chart parameters
        String id = request.getParameter("chartId");
    String title = request.getParameter("title");
    if (title == null) {
        title = "Chart";
    }
   
    // label for x/y axis
    String xLabel = request.getParameter("xlabel");
    String yLabel = request.getParameter("ylabel");

    // default image size
    int width = 400;
    int height = 200;
    String widthStr = request.getParameter("width");
    String heightStr = request.getParameter("height");
    if (widthStr != null) {
        width = Integer.parseInt(widthStr);
    }
    if (heightStr != null) {
        height = Integer.parseInt(heightStr);
    }
   
    // get an array of chart items containing our data..
        HttpSession session = request.getSession(true);
    ChartItem[] chartItems = (ChartItem[])session.getAttribute(id);
    if (chartItems == null) {
            System.out.println("No data items specified...");
        throw new ServletException("No data items specified...");
    }
       
        // begin pie chart
        Color dropShadow = new Color(240,240,240);
        //inner padding to make sure bars never touch the outer border
        int innerOffset = 20;
 
        int pieHeight = height - (innerOffset * 2);
        int pieWidth =  pieHeight;             
        int halfWidth = width/2;

        //Width of the inner graphable area
        int innerWidth = width - (innerOffset * 2);
       
        //graph dimension
        Dimension graphDim = new Dimension(width, height);
        Rectangle graphRect = new Rectangle(graphDim);


        //border dimensions
        Dimension borderDim = new Dimension(halfWidth-2,height-2);
        Rectangle borderRect = new Rectangle(borderDim);
       
        //Set content type
        response.setContentType("image/jpeg");

        //Create BufferedImage & Graphics2D
        BufferedImage bi = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = bi.createGraphics();

        // Set Antialiasing
        RenderingHints renderHints =
            new RenderingHints(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHints(renderHints);

        //Set graph background color to white:
        g2d.setColor(Color.white);
        g2d.fill(graphRect);

        //Draw black border
        g2d.setColor(Color.black);
        borderRect.setLocation(1,1);
        g2d.draw(borderRect);

        //Now draw border for legend
        borderRect.setLocation((width/2) + 1,1);
        g2d.draw(borderRect);

        //Draw data onto the graph:
        int x_pie = innerOffset;
        int y_pie = innerOffset;
        int border = 20;

        //Main chart Ellipse
        Ellipse2D.Double elb = new Ellipse2D.Double(x_pie - border/2,
            y_pie - border/2, pieWidth + border, pieHeight + border);
        //Shadow
        g2d.setColor(dropShadow);
        g2d.fill(elb);

        //Border
        g2d.setColor(Color.black);
        g2d.draw(elb);


        // Calculate the total value so that the pies can be calculated.
        float yTotal = 0.0f;
        int lastElement = 0;
        for(int i=0; i<chartItems.length; i++) {
           int ycoord = chartItems[i].getValue();
           if(ycoord > 0.0f) {
               yTotal += ycoord;
               lastElement = i;
          }
        }

        // Draw the pie chart
        int startAngle = 0;

        //Legend variables
        int legendWidth = 20;
        int x_legendText = halfWidth + innerOffset/2 + legendWidth + 5;
        int x_legendBar = halfWidth + innerOffset/2;
        int textHeight = 20;
        int curElement = 0;
        int y_legend = 0;

        //Dimensions of the legend bar
        Dimension legendDim = new Dimension(legendWidth , textHeight/2);
        Rectangle legendRect = new Rectangle(legendDim);
        for(int i=0; i< chartItems.length; i++) {
            int ycoord = chartItems[i].getValue();
            if(ycoord > 0.0f) {
                //Calculate percentage sales
                float perc = (ycoord/yTotal);
                //Calculate new angle
                int sweepAngle = (int)(perc * 360);
                //Check that the last element goes back to 0 position
                if (i == lastElement) {sweepAngle = 360-startAngle;}

                // Draw Arc
                g2d.setColor(getColor(chartItems[i].getColor()));
                g2d.fillArc(x_pie, y_pie, pieWidth, pieHeight, startAngle,
                        sweepAngle);
                //Increment startAngle with the sweepAngle
                startAngle += sweepAngle;

                //Draw Legend
                //Set y position for bar
                y_legend = curElement * textHeight + innerOffset;
                //Display the current column
                String display = chartItems[i].getLabel();
                g2d.setColor(Color.black);
                g2d.drawString(display, x_legendText, y_legend);
                //Display the total sales
                display = "" + ycoord;
                g2d.setColor(Color.black);
                g2d.drawString(display, x_legendText + 80, y_legend);
                //Display the sales percentage
                display = "  (" + (int)(perc*100) + "%)";
                g2d.setColor(Color.red);
                g2d.drawString(display, x_legendText + 110, y_legend);
                //Draw the bar
                g2d.setColor(getColor(chartItems[i].getColor()));
                legendRect.setLocation(x_legendBar,y_legend - textHeight/2);
                g2d.fill(legendRect);
                //Increment
                curElement++;
            }
        }
        // Encode the graph
        OutputStream output = response.getOutputStream();
        JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(output);
        encoder.encode(bi);
        output.close();
       
        // remvoe the chart data from session now that chart has been rendered.
        session.removeAttribute(id);

    }
   
    /**
     * Returns the Color instance corresponding the color pa ssed in.
     *
     * @param color a string representing a color instance.
     * @ return Color instance corresponding to the input color.
     */
    protected Color getColor(String colorStr) {
        Color color = null;
    if (colorStr == null) {
            color = Color.gray;
        }
        if (colorStr.equals("red")) {
            color = Color.red;
        } else if (colorStr.equals("green")) {
            color = Color.green;
        } else if (colorStr.equals("blue")) {
            color = Color.blue;
        } else if (colorStr.equals("pink")) {
            color = Color.pink;
        } else if (colorStr.equals("orange")) {
            color = Color.orange;
        } else if (colorStr.equals("magenta")) {
            color = Color.magenta;
        } else if (colorStr.equals("cyan")) {
            color = Color.cyan;
        } else if (colorStr.equals("white")) {
            color = Color.white;
        } else if (colorStr.equals("yellow")) {
            color = Color.yellow;
        } else if (colorStr.equals("gray")) {
            color = Color.gray;

        } else if (colorStr.equals("darkGray")) {
            color = Color.darkGray;
        } else {
            color = Color.gray;
        }
        return colo
r;
    }
}




Related Links
 


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.