Friday, March 30, 2007

Reverse Ajax with Direct Web Remoting (DWR)

Direct Web Remoting (DWR), is an open source Java library that can be used to implement Ajax in Java web applications with minimal Javascript coding. Using DWR, we can invoke server-side Java methods from Javascript in the browser. DWR 2.0 introduces a new feature, dubbed "Reverse Ajax", using which server-side Java can "push" updates to the browser. In this post, I tried to use a simplistic web application that will demonstrate the use of DWR for "Reverse Ajax".
In this example, I use a servlet that will be pushing information to the browser clients. Here is how to implement the example.
  1. Download DWR 2.0 from here, dwr.jar file has to be included in the classpath.
  2. Create the Service: This service generates messages which will be written to the browser. Here is the code for the Service.
    package utils;

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

    import org.directwebremoting.ServerContext;
    import org.directwebremoting.proxy.dwr.Util;

    public class Service {

    private int count = 0;

    public void update(ServerContext wctx) {
    List<Data> messages = new ArrayList<Data>();
    messages.add(new Data("testing" + count++));

    // Collection sessions = wctx.getAllScriptSessions();
    Collection sessions = wctx.getScriptSessionsByPage("/ReverseAjax/index.html");
    Util utilAll = new Util(sessions);
    utilAll.addOptions("updates", messages, "value");
    }
    }
    Service.java
  3. Create the Message Container: The message container is a simple Java bean that holds the message.
    package utils;

    public class Data {
    private String value;

    public Data() {
    }

    public Data(String value) {
    this.value = value;
    }

    public String getValue() {
    return value;
    }

    public void setValue(String value) {
    this.value = value;
    }
    }
    Data.java
  4. Create the Servlet: Here is the code for the Servlet.
    package servlets;

    import java.io.IOException;
    import java.io.PrintWriter;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.directwebremoting.ServerContext;
    import org.directwebremoting.ServerContextFactory;

    import utils.Service;

    public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Service service = new Service();
    ServerContext wctx = ServerContextFactory.get(this.getServletContext());
    for (int i = 0; i < 10; i++) {
    service.update(wctx);
    try {
    Thread.sleep(1000);
    }
    catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    PrintWriter writer = response.getWriter();
    writer.println("Done");
    writer.close();

    }}
    TestServlet.java
    • The ServerContext is used by DWR to get information of the clients that have open sessions on the server.
  5. The Web Page: This is the code for the Web Page (index.html).
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <title>index</title>
    <script type='text/javascript' src='dwr/engine.js'></script>
    <script type='text/javascript' src='dwr/interface/Service.js'></script>
    <script type='text/javascript' src='dwr/util.js'></script>
    </head>
    <body onload="dwr.engine.setActiveReverseAjax(true);">
    <ul id="updates">
    </ul>

    </body>
    </html>
    index.html
    • engine.js handles all server communications.
    • util.js helps you alter web pages with the data you got from the server.
    • The path to the scripts is relative to the root of the web content. The DWR servlet (defined in the web.xml file) will provide these scripts.
    • dwr.engine.setActiveReverseAjax(true); is used to activate Reverse Ajax
    • The id of the list is the same as the parameter set in the Service.
  6. The Web Deployment Descriptor:
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>ReverseAjax</display-name>

    <servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
    <init-param>
    <param-name>debug</param-name>
    <param-value>true</param-value>
    </init-param>
    <init-param>
    <param-name>activeReverseAjaxEnabled</param-name>
    <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>servlets.TestServlet</servlet-class>
    </servlet>

    <servlet-mapping>
    <servlet-name>dwr-invoker</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>
    <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/testServlet</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    </web-app>
    WEB-INF/web.xml
    • The DWR Servlet has to be loaded on startup
    • Setting activeReverseAjaxEnabled to true sets Reverse Ajax to be active. In this case Reverse Ajax used will be through polling or comet requests (extended http requests). If this is false, then inactive Reverse Ajax (piggybacking) will be used. In this case, the server waits for requests from the client and piggybacks the updates with the response.
  7. The DWR Configuration: The DWR configuration is defined in the dwr.xml file
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr/dwr20.dtd">
    <dwr>
    <allow>
    <create creator="new" javascript="Service" scope="application">
    <param name="class" value="utils.Service" />
    </create>
    <convert converter="bean" match="utils.Data" />
    </allow>
    </dwr>
    WEB-INF/dwr.xml
    • "create" is used to define a Java object as being available to Javascript code.
    • The "javascript" attribute is the one that will be used in case of invoking the server-side methods from Javascript.
    • The converter definition allows utils.Data to be used as a parameter.

  8. Environment: This example was tested using DWR 2.0 RC 3, on Tomcat 5.5

Thursday, March 15, 2007

My First Guice Web Application

Google Guice is an open source Dependency Injection framework from Google. Guice is based on annotations and generics unlike Spring which depends on XML of Java for wiring dependencies. Guice injects constructors, fields and methods (any methods with any number of arguments, not just setters). Guice provides support for custom scopes, static member injection, Spring as well as Struts 2.x integration and AOP Alliance method interception. Guice is available at Google Code. You can find the User guide and Javadocs from there. This post describes how I implemented a simple Web application using Guice. For injecting dependencies into Servlets, I used a Listener (this idea was from Hani Suleiman in the Guice developer mailing list). Follow these steps to run the example.
  1. Download Guice from the Guice Site.
  2. Create the Service Interface and Implementation: These are fairly simple as shown below
    package services;

    public interface Service {
    public String doWork();
    }
    Service.java
    package services;

    public class ServiceImpl implements Service {

    private int count = 0;
    public String doWork() {
    count++;
    System.out.println("Service Impl Doing work " + count);
    return("ServiceImpl : " + count);
    }
    }
    ServiceImpl.java
  3. Create an Annotation: Guice allows you to select which implementation of an Interface you want to inject, by using annotations. You will see this used in the TestServlet. Here is the annotation I defined
    package annotations;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    import com.google.inject.BindingAnnotation;

    @Retention(RetentionPolicy.RUNTIME)
    @Target( { ElementType.FIELD, ElementType.PARAMETER })
    @BindingAnnotation
    public @interface MyAnnotation {

    }
    MyAnnotation.java
    • @Rentention(RUNTIME) makes the annotation visible at runtime.(this is mandatory when using Guice)
    • @Target({FIELD, PARAMETER}) prevents the annotation from being applied to methods, types, local variables, and other annotations.(This is optional)
    • @BindingAnnotation is a Guice-specific signal that you wish your annotation to be used in this way. Guice will produce an error whenever user applies more than one binding annotation to the same injectable element. (This is necessary when used for binding with Guice)
    • Optionally, you can also use annotations with attributes to differentiate between bindings.
  4. Create a Guice Module: Guice Modules are used to define bindings within an applications. It is possible to have multiple modules per application (and multiple applications can share a common module too). Here is the simple Module that I implemented.
    package modules;

    import services.Service;
    import services.ServiceImpl;
    import annotations.MyAnnotation;

    import com.google.inject.AbstractModule;
    import com.google.inject.Scopes;

    public class MyModule extends AbstractModule {

    public void configure() {
    bind(Service.class).annotatedWith(MyAnnotation.class).to(ServiceImpl.class).in(Scopes.SINGLETON);
    }

    }
    MyModule.java
    • Guice provides the module with a binder which is used to bind the Interfaces with implementations.
    • annotatedWith(MyAnnotation.class) defines the binding to the implementation based on the annotation.
    • in(Scopes.SINGLETON) is used to define this implementation as a Singleton
  5. Create a Listener: Here is the Code for a simple listener (for a more complete example see the above mentioned mailing list).
    package listeners;

    import javax.servlet.ServletContext;
    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;

    import modules.MyModule;

    import com.google.inject.Guice;
    import com.google.inject.Injector;
    import com.google.inject.Module;
    import com.google.inject.servlet.ServletModule;

    public class GuiceServletContextListener implements ServletContextListener {

    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    ServletContext servletContext = servletContextEvent.getServletContext();
    servletContext.removeAttribute(Injector.class.getName());
    }

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    Injector injector = Guice.createInjector(new Module[] { new MyModule(), new ServletModule() });
    ServletContext servletContext = servletContextEvent.getServletContext();
    servletContext.setAttribute(Injector.class.getName(), injector);
    }
    }
    GuiceServletContextListener.java
    • The listener is used to save the Injector in the servlet context to be used in the AbstractInjectableServlet.
    • An injector is to be built during startup and used to inject objects at runtime.
    • ServletModule is used for servlet-specific bindings. It adds a request/session scope, as well as allowing the injection of request,response, and request parameters into your beans. To use it you have to register GuiceFilter in your web.xml.
  6. Create an Abstract Servlet: The AbstractInjectableServlet will be super class of all the Servlets you use. This class uses the injector to inject dependencies in the concrete classes by the injector.injectMembers(this) method.
    package servlets;

    import javax.servlet.ServletConfig;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;

    import com.google.inject.Injector;

    public abstract class AbstractInjectableServlet extends HttpServlet {

    @Override
    public void init(ServletConfig config) throws ServletException{

    ServletContext context = config.getServletContext();
    Injector injector = (Injector) context.getAttribute(Injector.class.getName());
    if (injector == null) {
    throw new ServletException("Guice Injector not found");
    }
    injector.injectMembers(this);

    }
    }
    AbstractInjectableServlet.java
  7. Create the Test Servlet: Here is the code for the TestServlet
    package servlets;

    import java.io.IOException;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import services.Service;
    import annotations.MyAnnotation;

    import com.google.inject.Inject;


    public class TestServlet extends AbstractInjectableServlet implements javax.servlet.Servlet {

    private Service service;
    public TestServlet() {
    super();
    }

    @Inject
    public void setServiceInstance(@MyAnnotation Service service) {
    this.service = service;
    }

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    response.getWriter().println("Service instance : " + service.doWork());
    }
    }
    TestServlet.java
    • The @Inject is used to define methods to be injectable.
    • The @MyAnnotation for the Service parameter means that, the actual binding will be done with the implementation that is bound using the withAnnotation() method.
  8. The Web Deployment Descriptor
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app id="WebApp_ID" version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>GuiceWeb</display-name>
    <listener>
    <listener-class>listeners.GuiceServletContextListener</listener-class>
    </listener>
    <servlet>
    <description></description>
    <display-name>TestServlet</display-name>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>servlets.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/testServlet</url-pattern>

    </servlet-mapping>

    <welcome-file-list>
    <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
    </web-app>
    web.xml

Monday, March 12, 2007

Message Driven Bean in Java EE 5 : Part 2

In a previous post, I described how to implement Messaging in Java EE 5 using annotation. This post is an extension of that post to describe how to implement Messaging in Java EE 5 using a deployment descriptor. To implement this, you can use the same client that was described in the other post. The only change needed is in the MDB part. Here's how to implement a Message Driven bean using the deployment descriptor.
  1. Create the Message driven bean: The message driven bean here is a simple Java class that implements the MessageListener interface. All the configuration will be done in the deployment descriptors.
    package jms;

    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;

    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;

    public class Messaging3Mdb implements MessageListener {

    static final Logger logger = Logger.getLogger("MDB");

    public Messaging3Mdb() {
    }

    public void onMessage(Message inMessage) {
    TextMessage msg = null;
    logger.setLevel(Level.ALL);

    try {
    if (inMessage instanceof TextMessage) {
    msg = (TextMessage) inMessage;
    logger.info("MESSAGE BEAN: Message received: " + msg.getText());
    } else {
    logger.warn("Message of wrong type: " + inMessage.getClass().getName());
    }
    } catch (JMSException e) {
    logger.error("MessageBean.onMessage: JMSException: " + e.toString());
    } catch (Throwable te) {
    logger.error("MessageBean.onMessage: Exception: " + te.toString());
    }
    }
    }
    Messaging3Mdb.java
  2. The sun-ejb-jar.xml file: This file contains the mapping of the JMS resources.
    <?xml version="1.0" encoding="UTF-8"?>
    <sun-ejb-jar>
    <enterprise-beans>
    <name>Ejb3DD</name>
    <ejb>
    <ejb-name>Messaging3Mdb</ejb-name>
    <jndi-name>jms/testQueue</jndi-name>
    <mdb-connection-factory>
    <jndi-name>jms/connectionFactory</jndi-name>
    </mdb-connection-factory>
    </ejb>
    </enterprise-beans>
    </sun-ejb-jar>
    META-INF/sun-ejb-jar.xml
  • The EJB deployment descriptor:
    <?xml version="1.0" encoding="UTF-8" ?>
    <ejb-jar>
    <enterprise-beans>
    <message-driven>
    <ejb-name>Messaging3Mdb</ejb-name>
    <ejb-class>jms.Messaging3Mdb</ejb-class>
    <messaging-type>javax.jms.MessageListener</messaging-type>
    <message-destination-type>javax.jms.Queue</message-destination-type>

    </message-driven>
    </enterprise-beans>
    </ejb-jar>
    META-INF/ejb-jar.xml

  • Environment: This example was implemented on Glassfish v1, Milestone 7
  • Friday, March 09, 2007

    Securing Middle tier Objects with Acegi Security Framework

    Previously, I posted an example on implementing Security using Acegi Security Framework for applications using Spring framework. This post will describe an example on how to secure Middle-tier objects using Acegi, with Role-based authorization. Here is how to implement the example.
    1. Create the example project as shown in "Spring security with Acegi Security Framework". This will be the starting point.
    2. Create the Secure Object: The secure object here is SecureDAO, which is a dummy object that has the common create, read, update, delete methods.
      package test;

      public class SecureDAO {
      public String create() {
      return "create";
      }

      public String read() {
      return "read";
      }

      public String update() {
      return "update";
      }

      public String delete() {
      return "delete";
      }
      }
      SecureDAO.java
    3. Create the Test Servlet: The servlet is used to invoke the secure DAO object.
      package servlets;

      import java.io.IOException;

      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;

      import org.springframework.web.context.WebApplicationContext;
      import org.springframework.web.context.support.WebApplicationContextUtils;

      import test.SecureDAO;

      public class TestServlet extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

      public TestServlet() {
      super();
      }

      protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      String method = request.getParameter("method");

      WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

      SecureDAO obj = (SecureDAO) context.getBean("secureDAO");
      String result = "";
      if (method.equals("create")) {
      result = obj.create();
      }
      if (method.equals("read")) {
      result = obj.read();
      }

      if (method.equals("update")) {
      result = obj.update();
      }

      if (method.equals("delete")) {
      result = obj.delete();
      }

      response.getWriter().println(result);

      }
      }
      TestServlet.java
    4. Update the authenticatedusers.jsp file to invoke the TestSerlvet, as shown below
      <%@ page import="org.acegisecurity.context.SecurityContextHolder"%>
      <h1>Welcome: <%=SecurityContextHolder.getContext().getAuthentication().getName()%></h1>
      <p><a href="../">Home</a>
      <form action="/SpringSecurity/TestServlet"><select name="method">
      <option value="create">create</option>
      <option value="read">read</option>
      <option value="update">update</option>
      <option value="delete">delete</option>
      </select> <input type="submit" name="submit" /></form>
      <p><a href="../j_acegi_logout">Logout</a>
      authenticatedusers.jsp

    5. Update the applicationContext.xml file to include the security definitions by adding the following bean definitions as shown below
      <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
      <property name="authenticationManager">
      <ref bean="authenticationManager" />
      </property>
      <property name="accessDecisionManager">
      <bean class="org.acegisecurity.vote.AffirmativeBased">
      <property name="allowIfAllAbstainDecisions" value="false" />
      <property name="decisionVoters">
      <list>
      <bean class="org.acegisecurity.vote.RoleVoter" />
      <bean class="org.acegisecurity.vote.AuthenticatedVoter" />
      </list>
      </property>
      </bean>
      </property>
      <property name="objectDefinitionSource">
      <value>
      test.SecureDAO.*=IS_AUTHENTICATED_REMEMBERED
      test.SecureDAO.delete=ROLE_ADMIN
      </value>
      </property>
      </bean>


      <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
      <property name="interceptorNames">
      <list>
      <value>methodSecurityInterceptor</value>
      </list>
      </property>
      <property name="beanNames">
      <list>
      <value>secureDAO</value>
      </list>
      </property>
      </bean>

      <bean id="secureDAO" class="test.SecureDAO" />
      Note
      • The MethodSecurityInterceptor is used to define the mapping between the Methods and the roles that are allowed to invoke these methods.
      • The BeanNameAutoProxyCreator is used to create a proxy for the secureDAO object, so that an authorization check may be applied to every invocation on the object.
    6. Add the cglib JAR file available in the Spring download as a dependency to your project. This is used for creating the Proxy.

    Tuesday, March 06, 2007

    Charts with JFreeChart 1

    JFreeChart is a free Java chart library that can be used to display charts from Java applications. It features:
    • A wide range of chart types
    • support for many output types, including Swing components, image files (including PNG and JPEG), and vector graphics file formats (including PDF, EPS and SVG);
    • It is distributed under the terms of the LGPL, which permits use in proprietary applications.
    In this post, I will describe how to use JFreeChart to display a comparative barchart, with data that contains multiple series which have to be grouped (G1, G2) per date. This effectively gives three levels of information (series, group and date). In order to display this data in a chart, we can use a StackedBarChart from JFreeChart. Please note that this is a result of my playing around with JFreeChart, and is not from the official documentation, which means that some of my assumptions may not be accurate. Follow these steps to run the example
    1. Download JFreeChart from here.
    2. Create the DataSet: The StakedBarChart takes a CategoryDataset as input. The following piece of code shows how to add data to the CategoryDataset.
        DefaultCategoryDataset dataSet = new DefaultCategoryDataset();

      dataSet.addValue(100, "Series1 G1", "Jan 04");
      dataSet.addValue(150, "Series1 G2", "Jan 04");
      It is important to note that the order in which you add data to the Dataset, as this would affect how you assign colors to the different series.
    3. Create the renderer and assign series colors:
        GroupedStackedBarRenderer renderer = new GroupedStackedBarRenderer();
      renderer.setItemMargin(0.01);
      renderer.setDrawBarOutline(false);

      renderer.setSeriesPaint(0, Color.blue);
      renderer.setSeriesPaint(1, Color.blue);
      The order of the series in the Dataset, will dictate series number when assigning colors. You will see this in the full code shown below.
    4. Map series to groups: This is done using the KeyToGroupMap.
        KeyToGroupMap keytogroupmap = new KeyToGroupMap("G1");
      keytogroupmap.mapKeyToGroup("Series1 G1", "G1");
      keytogroupmap.mapKeyToGroup("Series2 G1", "G1");
    5. Create the Legend: Since I wanted to give same color to a given series in different groups, I had to add a legend collection that maps colors to the series
       private static LegendItemCollection createLegendItems() {
      LegendItemCollection legenditemcollection = new LegendItemCollection();
      LegendItem legenditem = new LegendItem("Series1", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.blue);
      LegendItem legenditem1 = new LegendItem("Series2", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.cyan);
      LegendItem legenditem2 = new LegendItem("Series3", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.green);
      LegendItem legenditem3 = new LegendItem("Series4", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.yellow);
      legenditemcollection.add(legenditem);
      legenditemcollection.add(legenditem1);
      legenditemcollection.add(legenditem2);
      legenditemcollection.add(legenditem3);
      return legenditemcollection;
      }
    6. The code
      package chart;

      import java.awt.Color;

      import org.jfree.chart.ChartFactory;
      import org.jfree.chart.ChartFrame;
      import org.jfree.chart.JFreeChart;
      import org.jfree.chart.LegendItem;
      import org.jfree.chart.LegendItemCollection;
      import org.jfree.chart.axis.SubCategoryAxis;
      import org.jfree.chart.plot.CategoryPlot;
      import org.jfree.chart.plot.Plot;
      import org.jfree.chart.plot.PlotOrientation;
      import org.jfree.chart.renderer.category.GroupedStackedBarRenderer;
      import org.jfree.data.KeyToGroupMap;
      import org.jfree.data.category.CategoryDataset;
      import org.jfree.data.category.DefaultCategoryDataset;

      public class StackedBarChartTest {

      private static CategoryDataset getData() {
      DefaultCategoryDataset dataSet = new DefaultCategoryDataset();

      dataSet.addValue(100, "Series1 G1", "Jan 04");
      dataSet.addValue(150, "Series1 G2", "Jan 04");

      dataSet.addValue(60, "Series2 G1", "Feb 04");
      dataSet.addValue(90, "Series2 G2", "Feb 04");

      dataSet.addValue(70, "Series3 G2", "Feb 04");
      dataSet.addValue(99, "Series3 G1", "Feb 04");

      dataSet.addValue(100, "Series4 G1", "Feb 04");
      dataSet.addValue(80, "Series4 G2", "Feb 04");

      dataSet.addValue(20, "Series1 G1", "Feb 04");
      dataSet.addValue(50, "Series1 G2", "Feb 04");

      dataSet.addValue(10, "Series2 G1", "Jan 04");
      dataSet.addValue(40, "Series2 G2", "Jan 04");

      dataSet.addValue(10, "Series3 G2", "Jan 04");
      dataSet.addValue(50, "Series3 G1", "Jan 04");

      dataSet.addValue(10, "Series4 G1", "Jan 04");
      dataSet.addValue(60, "Series4 G2", "Jan 04");

      // dataSet.addValue(100, "Series1 G1", "Jan 04");
      // dataSet.addValue(20, "Series1 G1", "Feb 04");
      // dataSet.addValue(10, "Series2 G1", "Jan 04");
      // dataSet.addValue(60, "Series2 G1", "Feb 04");
      // dataSet.addValue(99, "Series3 G1", "Feb 04");
      // dataSet.addValue(50, "Series3 G1", "Jan 04");
      // dataSet.addValue(100, "Series4 G1", "Feb 04");
      // dataSet.addValue(10, "Series4 G1", "Jan 04");
      //
      //
      // dataSet.addValue(150, "Series1 G2", "Jan 04");
      // dataSet.addValue(50, "Series1 G2", "Feb 04");
      // dataSet.addValue(40, "Series2 G2", "Jan 04");
      // dataSet.addValue(90, "Series2 G2", "Feb 04");
      // dataSet.addValue(10, "Series3 G2", "Jan 04");
      // dataSet.addValue(70, "Series3 G2", "Feb 04");
      // dataSet.addValue(60, "Series4 G2", "Jan 04");
      // dataSet.addValue(80, "Series4 G2", "Feb 04");

      return dataSet;
      }

      private static JFreeChart createChart(CategoryDataset categorydataset) {
      JFreeChart jfreechart = ChartFactory.createStackedBarChart("Sample", null, "value", categorydataset, PlotOrientation.VERTICAL, true, true, false);
      GroupedStackedBarRenderer renderer = new GroupedStackedBarRenderer();
      renderer.setItemMargin(0.01);
      renderer.setDrawBarOutline(false);

      renderer.setSeriesPaint(0, Color.blue);
      renderer.setSeriesPaint(1, Color.blue);

      renderer.setSeriesPaint(2, Color.cyan);
      renderer.setSeriesPaint(3, Color.cyan);

      renderer.setSeriesPaint(4, Color.green);
      renderer.setSeriesPaint(5, Color.green);

      renderer.setSeriesPaint(6, Color.yellow);
      renderer.setSeriesPaint(7, Color.yellow);

      // renderer.setSeriesPaint(0, Color.blue);
      // renderer.setSeriesPaint(4, Color.blue);
      //
      // renderer.setSeriesPaint(1, Color.cyan);
      // renderer.setSeriesPaint(5, Color.cyan);
      //
      // renderer.setSeriesPaint(2, Color.green);
      // renderer.setSeriesPaint(6, Color.green);
      //
      // renderer.setSeriesPaint(3, Color.yellow);
      // renderer.setSeriesPaint(7, Color.yellow);

      KeyToGroupMap keytogroupmap = new KeyToGroupMap("G1");
      keytogroupmap.mapKeyToGroup("Series1 G1", "G1");
      keytogroupmap.mapKeyToGroup("Series2 G1", "G1");
      keytogroupmap.mapKeyToGroup("Series3 G1", "G1");
      keytogroupmap.mapKeyToGroup("Series4 G1", "G1");

      keytogroupmap.mapKeyToGroup("Series1 G2", "G2");
      keytogroupmap.mapKeyToGroup("Series2 G2", "G2");
      keytogroupmap.mapKeyToGroup("Series3 G2", "G2");
      keytogroupmap.mapKeyToGroup("Series4 G2", "G2");

      renderer.setSeriesToGroupMap(keytogroupmap);
      SubCategoryAxis subcategoryaxis = new SubCategoryAxis("Series/Month");
      subcategoryaxis.setCategoryMargin(0.03);
      subcategoryaxis.addSubCategory("G1");
      subcategoryaxis.addSubCategory("G2");

      CategoryPlot categoryplot = (CategoryPlot) jfreechart.getPlot();
      categoryplot.setDomainAxis(subcategoryaxis);
      categoryplot.setRenderer(renderer);
      categoryplot.setFixedLegendItems(createLegendItems());

      return jfreechart;
      }

      private static LegendItemCollection createLegendItems() {
      LegendItemCollection legenditemcollection = new LegendItemCollection();
      LegendItem legenditem = new LegendItem("Series1", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.blue);
      LegendItem legenditem1 = new LegendItem("Series2", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.cyan);
      LegendItem legenditem2 = new LegendItem("Series3", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.green);
      LegendItem legenditem3 = new LegendItem("Series4", "-", null, null, Plot.DEFAULT_LEGEND_ITEM_BOX, Color.yellow);
      legenditemcollection.add(legenditem);
      legenditemcollection.add(legenditem1);
      legenditemcollection.add(legenditem2);
      legenditemcollection.add(legenditem3);
      return legenditemcollection;
      }

      public static void main(String args[]) {

      StackedBarChartTest demo = new StackedBarChartTest();

      ChartFrame frame = new ChartFrame("First", demo.createChart(demo.getData()));
      frame.pack();
      frame.setVisible(true);
      }
      }
      Note:
      • The SubCategoryAxis is used to assign labels to Category Axis' subcategories (i.e. groups).
      • The ChartFrame is used to display the results. The results could also be written to an output stream using the ChartUtilities.writeChartAsJPEG or ChartUtilities.writeChartAsPNG methods.
      • The commented lines show how the series color is affected by the order in which the data is entered into the Dataset.

    Popular Posts