Collections of web application techniques

Monday, September 13, 2010

Saving and Retrieving Objects with JAXB

I have done a lot of work using database back end but recently I discovered JAXB. JAXB is not new but for saving a retrieving a Java objects, it is extremely easy and wonderful to use. As demonstrated below, you can accomplish the task of saving the object regardless of how many attributes it might have to a file and read it back in in less than 10 lines of code. For this reason, JAXB perfectly fits my need in creating test cases to test my business logic.

JAXB covers composition and inheritance transparently. The objects to be stored and retrieved may contain other objects or may have inherited attributes.

Here is a simplified Java class. You only need to annotate with the XmlRootElement and the XmlAcessorType tags at the class level.

package com.inventasoft.domain;

import java.util.Date;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Person")
@XmlAccessorType(XmlAccessType.FIELD)
public class Person {
    private Date birthDate;

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }
}


Here is the code to create xml content from the object, write it out to a file, and read it back in.


package com.inventasoft.test.jaxb;

import com.inventasoft.domain.Person;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

import junit.framework.TestCase;

public class PersonSimpleTest extends TestCase {

    public void testPerson() throws Exception {
        Person testWrite = new Person();

        // write it out as XML
        JAXBContext jaxbContext = JAXBContext.newInstance(testWrite.getClass());
        StringWriter writer = new StringWriter();
        Marshaller marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(testWrite, writer);
        writeToFile("test.xml", writer.toString());

        String data = readFromFile("test.xml");

        // read it from XML
        Object o = jaxbContext.createUnmarshaller().unmarshal(
                new StringReader(data));

        Person testRead = (Person) o;

        assertEquals(testWrite.getBirthDate(), testRead.getBirthDate());
    }

    private void writeToFile(String filename, String data)
            throws Exception {
        BufferedWriter bw = null;
        try {
            File f = new File(filename);
            bw = new BufferedWriter(new FileWriter(f));
            bw.write(data);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bw != null)
                bw.close();
        }
    }

    private String readFromFile(String filename) throws Exception {
        BufferedReader br = null;
        StringBuilder content = new StringBuilder();
        try {
            File f = new File(filename);
            br = new BufferedReader(new FileReader(f));
            String line = null;
            while ((line = br.readLine()) != null) {
                content.append(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (br != null)
                br.close();
        }
        return content.toString();
    }
}


And here is the sample ouput XML:



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person>
    <birthDate>2010-09-13T08:52:25.298-06:00</birthDate>
</Person>


The JAXB files you will need are jaxb-impl.jar, jaxb-xjc.jar.

Saturday, September 4, 2010

How to Create Gantt Chart in Flex.

Gantt chart as below can be created using Flex components.  The chart below is created using mx:Barchart.


There are a few steps to create such a chart:
  1. Data model
  2. Configure mx:Barchart
  3. Tooltip
  4. Bar drawing

To encapsulate all the data to be sent to the chart, I define a model.  Below is the code to create the model which contains the data to be display on the chart.   The model contains an array collection of projects to be displayed on the chart.

Data Model

package model
{    
    import mx.collections.ArrayCollection;

    [Bindable]
    public class Model
    {
        private static var modelLocator:Model;

        public var projectsToDisplay:ArrayCollection = new ArrayCollection();
        
    }    
}

The projectsToDisplay collection contains a collection of Project objects.  The Project class is defined below.  To make things more interesting on my chart, I added phases and milestones which are also defined as ArrayCollection in a project.

Here are the attributes for my project:

public class Project
    {
        public var id:Number;
        public var status:StatusVO;
        public var title:String;
        public var startDate:Date;
        public var endDate:Date;
        public var notes:String;
        public var percentComplete:Number;
        public var currentStatus:String;
        public var persons:ArrayCollection;
        public var milestones:ArrayCollection;
        public var phases:ArrayCollection;
    }

BarChart

Here is how the chart is created using mx:BarChart.  Attributes to pay attention to are:
  • itemClick: this defines an event handler if you want anything to occur when one clicks on a bar on the chart.
  • dataProvider: notice how the data is sent to the chart.
  • dataTipProvider: this allows you to customize how the tool tip will be display when the cursor is on the bar.
  • Note how the vertical and horizontal axes are defined as this may take time to figure out.  You can pick what field of you class to be displayed on the vertical axis.  In my case I pick the title field.  Since I have many projects to display, I wanted some control over the date range so I specify them via the minimum and maximum attributes on the horizontal axis. This is the range for the horizontal scale. Figure out the attributes for the mx:BarSeries also took me a bit of time.  Notice that the startDate is specify in the minField attribute and the endDate is in the xField attribute.  itemRenderer is where you can customize how your bars are drawn.


<mx:BarChart id="bar1" height="50%" width="100%" paddingLeft="5" paddingRight="5" showDataTips="true" itemClick="setProject(event)" dataProvider="{myModel.projectsToDisplayPhase}" themeColor="#174F73" dataTipRenderer="project.renderers.ProjectToolTip">
    <mx:verticalAxis>
        <mx:CategoryAxis categoryField="title" labelFunction="chartLabel" />
    </mx:verticalAxis>
    <mx:horizontalAxis>
        <mx:DateTimeAxis dataUnits="days" minimum="{minDate}" maximum="{maxDate}" dataInterval="1"
                         minorTickInterval="3" minorTickUnits="months" labelUnits="months" />
    </mx:horizontalAxis>
    <mx:series>
        <mx:BarSeries yField="title" xField="endDate" minField="startDate" itemRenderer="project.renderers.ProjectRenderer3" />
    </mx:series>
</mx:BarChart>

Tooltip

To create the tooltip, you have to specify in the dataTipRenderer attribute of the BarChart component.  Here is the code for my tooltip:

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="0xFFBBBB" backgroundAlpha=".9"
         borderColor="0xFF0000" borderStyle="solid" paddingTop="5" paddingBottom="5"
         paddingRight="5" paddingLeft="5" verticalGap="-4" color="0x222222" minHeight="100"
         minWidth="350" maxWidth="500" xmlns:renderers="renderers.*"
         xmlns:local="project.renderers.*" creationComplete="init()">
    <mx:HBox>
        <mx:Label id="projectLabel" textAlign="center" fontWeight="bold" text="Project:" />
        <mx:Text text="{project.title}" />
    </mx:HBox>
    <mx:HBox>
        <mx:Label fontWeight="bold" text="Phases: {project.phases.length}" />
        <mx:Label fontWeight="bold" text="Milestones: {project.milestones.length}" />
        <mx:Label fontWeight="bold" text="Team: {project.persons.length}" />
    </mx:HBox>

    <mx:HBox horizontalGap="0">
        <mx:Label fontWeight="bold" text="Complete:" />
        <mx:Text text="{project.percentComplete}%" />
        <mx:Spacer width="20" />
        <mx:Label fontWeight="bold" text="Status:" />
        <mx:Text text="{project.currentStatus}" fontWeight="bold" color="green" />
        <mx:Spacer width="20" />
        <mx:Label fontWeight="bold" text="Date:" />
        <mx:Text id="dateRange" />
    </mx:HBox>

    <mx:HBox width="100%">
        <mx:Label fontWeight="bold" text="Notes:" width="45" />
        <mx:TextArea width="100%" text="{project.notes.length > 0 ? project.notes : '(No notes)'}"
                     backgroundColor="0xFFDDDD" alpha=".9" borderColor="0xFFAAAA" />
    </mx:HBox>

    <mx:DateFormatter id="dateFormatter" formatString="MM/DD/YYYY" />

    <mx:Script>
        <![CDATA[
            import vo.ProjectVO;
            import mx.charts.series.items.BarSeriesItem;
            import mx.charts.HitData;

            [Bindable]
            public var project:ProjectVO;

            [Bindable]
            private var projectView:String = "None";

            private function init():void
            {
                dateRange.text = dateFormatter.format(project.startDate) + " - " + dateFormatter.format(project.endDate);
            }

            override public function set data(value:Object):void
            {
                var hd:HitData = value as HitData;
                project = hd.item as ProjectVO;
                init();
            }
        ]]>
    </mx:Script>
</mx:VBox>

You need to override the set data method to get a handle to the data object to be displayed on the tooltip.  Once you figure that out, the rest is just pure fun on however you want to display the data.

Bar Drawing

Below is the code to draw the bars are drawn on the chart.


<?xml version="1.0"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" horizontalScrollPolicy="off" xmlns:com="com.*"
           verticalScrollPolicy="off" implements="mx.core.IDataRenderer"
           xmlns:renderers="project.renderers.*" creationComplete="init()">
    <mx:Script>
        <![CDATA[
            import mx.controls.Button;
            import mx.controls.Label;
            import mx.core.UIComponent;
            import mx.events.ToolTipEvent;
            import vo.*;

            import flash.display.Graphics;
            import mx.controls.Alert;

            import mx.charts.series.items.BarSeriesItem;
            import mx.collections.ArrayCollection;
            import mx.core.IDataRenderer;
            import mx.skins.ProgrammaticSkin;
            import flash.geom.Matrix;
            import flash.display.GradientType;
            import mx.charts.series.items.BarSeriesItem;

            public static var projectColors:Array = [0xFF3333, 0x00DDDD, 0x0066DD, 0x00DD99, 0x00DD22, 0xDDDD00, 0xAAAA00];

            private var _chartItem:BarSeriesItem;

            private static var MAX_BAR_HEIGHT:Number = 20;

            private static var HALF_MAX_BAR_HEIGHT:Number = 10;

            private var milestoneRenderers:ArrayCollection = new ArrayCollection();

            [Bindable]
            private var yPosition:Number;

            private function init():void
            {
            }

            private function handleEdit():void
            {
                var url:String = "<a href="http://inventasoft.blogspot.com">http://inventasoft.blogspot.com</a>"
                var request:URLRequest = new URLRequest(url);
                navigateToURL(request, "_blank");
            }

            private function handleClick():void
            {
                Alert.show("Test: ");
            }

            override public function get data():Object
            {
                return _chartItem;
            }

            override public function set data(value:Object):void
            {
                _chartItem = value as BarSeriesItem;
                invalidateDisplayList();
            }

            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
            {
                super.updateDisplayList(unscaledWidth, unscaledHeight);

                var status:Number = _chartItem.item.status != null ? _chartItem.item.status.id : 0;

                var yStart:Number;
                var yMid:Number;
                var yEnd:Number;
                var ySize:Number;

                var xStart:Number;
                var xMid:Number;
                var xEnd:Number;

                var project:ProjectVO = _chartItem.item as ProjectVO;

                var startDate:Date = project.startDate;
                var endDate:Date = project.endDate;
                var del:Number = endDate.getTime() - startDate.getTime();

                yStart = 0;
                ySize = unscaledHeight;

                // limit height
                if (unscaledHeight > MAX_BAR_HEIGHT)
                {
                    yStart = unscaledHeight / 2 - HALF_MAX_BAR_HEIGHT;
                    ySize = MAX_BAR_HEIGHT;
                }

                graphics.clear();

                var phases:ArrayCollection = project.phases;

                var m:Matrix = new Matrix();
                m.createGradientBox(unscaledWidth, ySize, 0, 0, 0);
                var color:uint = projectColors[(status >= 1 && status <= 6) ? status : 0];
                graphics.beginGradientFill(GradientType.LINEAR, [color, 0xDDDDDD], [.9, .9], [0, 255], m, null, null, 0);
                graphics.drawRoundRect(0, yStart, unscaledWidth, ySize, ySize);
                graphics.endFill();

                // links position
                yPosition = yStart;
            }
        ]]>
    </mx:Script>
</mx:Canvas>

The important things you need are the set and get data so you can get a handle to your object containing data to be drawn and the updateDisplayList where you actually draw the bar.  On my snapshot, I also show the milestones displayed as diamonds and the phases which are displayed in different color codes on the same horizontal bar.  They are just extension of the same concept.  If you have enough patient to figure out so far, chances are very good that you can customize your chart to however your heart’s content.