|
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 ChartComponent, which
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&width=300& orientation=vertical&type=bar&title=Employee Number By Department& xlabel=Departments&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-extension>
</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 color;
}
}
|
 |
 |