Tuesday, May 20, 2008

JTable to Dynamic PDF Report using JasperReport

Nice and easy dynamic programming with POJO -2

First article of this series I discussed about, how to use set of POJOs to render a JTable at run time. In this article I discuss about how we can use JTable to generate dynamic report using Jasper Report. JasperReport provides built-in support to use table model as a data source for reports but we need to provide report template as a JRXML file. Here I try to design Report design dynamically at the runtime using properties of TableModel.

We can reuse our DynamicTableModel class for this task also .

public class DynamicTableModel extends AbstractTableModel {
/** The column names. */
private String[] columnNames;
/** The data. */
private Object[][] data;

/**
* Instantiates a new dynamic table model.
*
* @param dataset the dataset
*/
public DynamicTableModel(Object[] dataset) {
super();
initModel(dataset);

}
/**
* Inits the model.
*
* @param dataset the dataset
*/
private void initModel(Object[] dataset) {
if (dataset.length > 0) {
Object obj = dataset[0];
System.out.println(obj.getClass().getName());
Field[] fields = obj.getClass().getDeclaredFields();
columnNames = new String[fields.length];
data = new String[dataset.length][fields.length];
for (int i = 0; i < fields.length; i++) {
columnNames[i] = fields[i].getName();

}
for (int j = 0; j < dataset.length; j++) {
try {
for (int k = 0; k < columnNames.length; k++) {

data[j][k] = BeanUtils.getProperty(dataset[j],
columnNames[k]);
}

} catch (Exception e) {
e.printStackTrace();
}

}

}

}

/* (non-Javadoc)
* @see javax.swing.table.TableModel#getColumnCount()
*/
public int getColumnCount() {
return columnNames.length;
}
/* (non-Javadoc)
* @see javax.swing.table.TableModel#getRowCount()
*/
public int getRowCount() {
return data.length;
}
/* (non-Javadoc)
* @see javax.swing.table.AbstractTableModel#getColumnName(int)
*/
public String getColumnName(int col) {
return columnNames[col];
}
/* (non-Javadoc)
* @see javax.swing.table.TableModel#getValueAt(int, int)
*/
public Object getValueAt(int row, int col) {
return data[row][col];
}
/**
* Gets the column names.
*
* @return the column names
*/
public String[] getColumnNames() {
return columnNames;
}
/**
* Sets the column names.
*
* @param columnNames the new column names
*/
public void setColumnNames(String[] columnNames) {
this.columnNames = columnNames;
}
/**
* Gets the data.
*
* @return the data
*/
public Object[][] getData() {
return data;
}
/**
* Sets the data.
*
* @param data the new data
*/
public void setData(Object[][] data) {
this.data = data;
}

}



The next important class is DynamicTableReport where we create report design dynamically and generate report. If some one want ,it is possible to directly map TableModel properties to JAsperDesign object , but for simplicity I ‘m going to use anther API called DynamicJasper .
buildReport method create a dynamic report design based on provided TableModel .

public DynamicReport buildReport(DynamicTableModel model) throws Exception {

/**
* Creates the DynamicReportBuilder and sets the basic options for
* the report
*/
FastReportBuilder drb = new FastReportBuilder();
Style columDetail = new Style();
columDetail.setBorder(Border.THIN);
Style columDetailWhite = new Style();
columDetailWhite.setBorder(Border.THIN);
columDetailWhite.setBackgroundColor(Color.WHITE);
Style columDetailWhiteBold = new Style();
columDetailWhiteBold.setBorder(Border.THIN);
columDetailWhiteBold.setBackgroundColor(Color.WHITE);
Style titleStyle = new Style();
titleStyle.setFont(new Font(18,Font._FONT_VERDANA,true));
Style numberStyle = new Style();
numberStyle.setHorizontalAlign(HorizontalAlign.RIGHT);
Style amountStyle = new Style();
amountStyle.setHorizontalAlign(HorizontalAlign.RIGHT);
amountStyle.setBackgroundColor(Color.cyan);
amountStyle.setTransparency(Transparency.OPAQUE);
Style oddRowStyle = new Style();
oddRowStyle.setBorder(Border.NO_BORDER);
Color veryLightGrey = new Color(230,230,230);
oddRowStyle.setBackgroundColor(veryLightGrey);oddRowStyle.setTransparency(Transparency.OPAQUE);


// table name column
String[] headings=model.getColumnNames();
for(int i=0;i<headings.length;i++){
String key=headings[i];
AbstractColumn column = ColumnBuilder.getInstance().setColumnProperty(key, String.class.getName())
.setTitle(key).setWidth(new Integer(100))
.setStyle(columDetailWhite).build();
drb.addColumn(column);

}
drb.setTitle("Sample Report")
.setTitleStyle(titleStyle).setTitleHeight(new Integer(30))
.setSubtitleHeight(new Integer(20))
.setDetailHeight(new Integer(15))
//.setLeftMargin(margin)
//.setRightMargin(margin)
//.setTopMargin(margin)
// .setBottomMargin(margin)
.setPrintBackgroundOnOddRows(true)
.setOddRowBackgroundStyle(oddRowStyle)
.setColumnsPerPage(new Integer(1))
.setUseFullPageWidth(true)
.setColumnSpace(new Integer(5));
DynamicReport dr = drb.build();
return dr;
}

Finally, method generates the report using our design and data source.

try {
DynamicReport dr = buildReport(model);


JRDataSource ds = new JRTableModelDataSource(model);
JasperPrint jp = DynamicJasperHelper.generateJasperPrint(dr, new ClassicLayoutManager(), ds);
JasperViewer.viewReport(jp);
} catch (Exception e) {
e.printStackTrace();
}




Download the sample eclipse project from here, to run this example you need following dependencies.


1. DynamicJasper
2. JasperReport
3. Commons BeanUtil and Collection

4 comments:

Dennis said...

I have found the same solution to create reports dynmically based upon a JTable. First of all it's quite cool lib! The only problem is that Jasper only accepts build/in java types. As solution I am using a tableModel as wrapper for the (data-)tableModel. The wrapper model does the conversion and so jasper run quite well.

/**
* Presents a view for the data in a {@link TableModel}. Allows to use
* {@link Convert} objects to presenting the data in another way.
*
* @author Dennis Guse
*/
public class TableModelRenderer implements TableModel {

protected TableModel model;

protected Map<Class<?>, Convert> converterMap = new HashMap<Class<?>, Convert>();

public TableModelRenderer(TableModel model) {
this.model = model;
}

public void set(Class<?> source, Convert convert) {
converterMap.put(source, convert);
}

public void remove(Class<?> source) {
converterMap.remove(source);
}

@Override
public Class<?> getColumnClass(int columnIndex) {
Class<?> columnClass;
columnClass = model.getColumnClass(columnIndex);
Convert convert = converterMap.get(columnClass);
Class<?> newColumnClass = null;
if (convert != null) {
newColumnClass = convert.getConvertForClass();
}
return newColumnClass != null ? newColumnClass : columnClass;
}

Another cool thing is that JTable implements almost all methods defined in TableModel (only add and remove tablemodellistener are missing). This methods present the table data like the user can view including all filters, rowsorting, column reordering and hidden columns. So a user can simply create a customized (data and view) report based upon a table!
And that's really cool and easy to set up.
View the sources of both mentioned files.

And a absolutly cool feature is the usability of templates. So the users can modify even the look of the report with as example iReport. Saves me a lot of work :D

Nice day!

System Admin said...

Thanks for ur post, Its very useful for me in my project.
Thanks a lot my friend

Anonymous said...

hi . the Api that you talked about returns a japerPrint object . and i need a JasperReport Object because im using my table as an subReport and i mean this line DynamicJasperHelper.* there is just one methode in DynamicJasperHelper that returns jasperReport Object but that methodes argument are no like other DynamicJasperHelper.jasperPerint methodes . thanks for you good article.

Alexandre Flores said...

Great!!

this is awesome!!


Very useful!

Thanks a lot!