绪论
这篇文章,我们讨论j2ee应用程序结合水晶报表的经验,这个例子叫做“Task Workbench (TWB)”。这篇文章的目的是帮助大家评估在j2ee应用程序中使用水晶报表的优点和缺点。
TWB(www.taskworkbench.com)是提供工程和每日运作的实时信息的工作管理工具。它是一个web应用程序,jsp作为表示层,EJBs处理商业逻辑,CMP实体bean作为持久层映射到一个oracle数据库。
TWB帮助项目经理了解不同方法下项目花费时间和员工在每个项目上的消耗。当设计这个程序的时候,一个非常重要的需求就是提供给项目经理和项目管理人员一个高水平的项目成员和工程数据的视图。
图一: 显示一个员工的任务和每个任务花费的时间。
最初,我们用jsp创建报表。但是这个方法被证实太耗费时间,特别是创建联合报表的时候。所以开始寻找一个java报表解决方案。我们的基本要求是这个方案能够快速配置,并且轻松生成和修改报表,还有要容易整合到j2ee程序中。
我们选择水晶报表因为它完全满足我们的需求。它是一个成熟的、同类产品中处于领导地位的的工具。这是一个重要的考虑,因为我们希望我们的报表方案可以随着TWB一起成长。
实现
水晶报表被整合进TWB中有两个关键目的:第一是实现花费最小代价增加新报表和维护现有报表。第二个目的是转换到更复杂报表方案时,可以通过简单配置实现。
Crystal Java Reporting Component 有一个jsp标签库,提供创建基本报表的简单方法。我们决定使用自己的自定义jsp标签库,来使用Crystal Java Reporting Component显示报表。这个方法允许我们定制报表外观和报表参数,这样可以保持设计简单容易。
在jsp上唯一并且必须的java代码是一个为了得到一个显示在页面上的报表的实例而去访问工厂class的java方法。图二是一个可以请求报表的简单的jsp例子。Form传递必需的参数到view_report.jsp(图三)。
<%@ page import="com.ensemsys.twb.presentation.crystal.ReportParameter,
com.ensemsys.twb.presentation.crystal.ReportType"%>
<html>
<head>
<title>Report Viewer</title>
<link rel="stylesheet" href="style/main.css" type="text/css"/>
</head>
<body>
<h1>Simple Banked Hours Report</h1>
<p>Enter the date range for the report and click "Submit" to view
the report.</p>
<form method="post" action="view_report.jsp">
<input type="hidden" name="<%=ReportParameter.TYPE%>"
value="<%=ReportType.BANKED_TIME_SIMPLE%>"/>
<table>
<tr>
<td class="label">Start Date:</td>
<td>
<input type="text" name="<%=ReportParameter.START_DATE%>"
value="2003.06.30"/>
</td>
</tr>
<tr>
<td class="label">End Date:</td>
<td>
<input type="text" name="<%=ReportParameter.END_DATE%>"
value="2003.12.01"/>
</td>
</tr>
<tr>
<td> </td>
<td style="text-align: right">
<input type="submit" name="submitBtn" value="Submit"/>
</td>
</tr>
</table>
</form>
</body>
</html>
图二:simple_banked_time_report.jsp
<%@ page import="com.ensemsys.twb.presentation.crystal.ReportFactory,
com.ensemsys.twb.presentation.crystal.Report"%>
<%@ taglib uri="/simplereportviewer.tld" prefix="viewer" %>
<html>
<head>
<title>Report</title>
<link rel="stylesheet" href="style/main.css" type="text/css"/>
</head>
<body>
<%
// "CrystalEventTarget" is the report action name used by Crystal
final String REPORT_NAME = "CrystalEventTarget";
String reportName = (String) session.getAttribute( REPORT_NAME );
// refresh might have done via a report action
if ( reportName == null )
{
reportName = (String) request.getParameter( REPORT_NAME );
}
Report report = null;
if ( reportName == null )
{
report = ReportFactory.newInstance( request );
reportName = report.getName();
session.setAttribute( REPORT_NAME, reportName );
session.setAttribute( reportName, report );
}
else
{
report = (Report) session.getAttribute( reportName );
}
%>
<viewer:viewer report="<%=report%>" />
</body>
</html>
图三:view_report.jsp
报表工厂类创建一个报表实例,用request参数来确定报表类型。这种方法巧妙的把商业逻辑通过一个方法(method)装入单独报表。在ReportFactory子集中,工厂方法对于复合报表来说是一个最好的解决办法-ReportFactory能够代表子集创建一个报表。
package com.ensemsys.twb.presentation.crystal;
import com.crystaldecisions.sdk.occa.report.data.Fields;
import com.crystaldecisions.sdk.occa.report.data.ParameterField;
import com.crystaldecisions.sdk.occa.report.data.ParameterFieldDiscreteValue;
import com.crystaldecisions.sdk.occa.report.data.Values;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.JspException;
import java.sql.Date;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* Provides methods for creating an instance of a Report
* from the parameters in the request.
*/
public class ReportFactory
{
public static final DateFormat FORMAT =
new SimpleDateFormat( "yyyy.MM.dd" );
public static Report newInstance( ServletRequest request )throws JspException
{
String reportType = getParameter( request, ReportParameter.TYPE );
Report report = null;
// For brevity, only the simple report type is shown here
if ( reportType.equals( ReportType.BANKED_TIME_SIMPLE ) )
{
report = newSimpleBankedTimeReport( request );
}else
{
throw new InvalidReportTypeException( reportType );
}
return report;
}
图四:ReportFactory.java 基于form提交的参数创建一个实例
private static Report newSimpleBankedTimeReport( ServletRequest request )
throws JspException
{
Date startDate = getDateParameter( request, ReportParameter.START_DATE );
Date endDate = getDateParameter( request, ReportParameter.END_DATE );
Fields fields = new Fields();
ParameterField prevYearField = newDateField( "Prev Fiscal Year", startDate);
ParameterField reportDateField = newDateField( "Report Date", endDate );
fields.add( prevYearField );
fields.add( reportDateField );
return new Report( "protected/reports/bhr_simple_demo.rpt",
ReportType.BANKED_TIME_SIMPLE, fields );
}
public static ParameterField newDateField( String name, Date date )
{
ParameterField field = new ParameterField();
Values vals = new Values();
ParameterFieldDiscreteValue value = new ParameterFieldDiscreteValue();
field.setName( name );
value.setValue( date );
field.setReportName( "" );
value.setDescription( "" );
vals.add( value );
field.setCurrentValues( vals );
return field;
}
public static Date newDate( String s )
throws InvalidDateStringException
{
java.sql.Date date = null;
try{
date = new java.sql.Date( FORMAT.parse( s ).getTime() );
}
catch ( ParseException e )
{
throw new InvalidDateStringException( e );
}
return date;
}
protected static void assertHasParameter( ServletRequest request,
String name)
{
String parameter = request.getParameter( name );
if ( parameter == null || parameter.equals( "" ) )
{
throw new NoSuchParameterException( name );
}
}
图四:ReportFactory.java
protected static Date getDateParameter( ServletRequest request, String name )
throws JspException
{
String parameter = getParameter( request, name );
Date date = null;
try{
date = newDate( parameter );
}
catch ( InvalidDateStringException e )
{
throw new JspException( e );
}
return date;
}
protected static String getParameter( ServletRequest request, String name )
{
assertHasParameter( request, name );
return request.getParameter( name );
}
}
图四:ReportFactory.java
SimpleReportViewerTag.java 呈现报表,这个类使用水晶报表的API来完成画出报表的工作。这个类非常简单――它创建一个CrystalReportViewer实例,设置数据库连接信息和报表需要的参数,最后显示报表。对应的TLD,simplereportviewer.tld,在图六列出。
package com.ensemsys.twb.presentation.crystal;
import com.crystaldecisions.report.web.viewer.CrystalReportViewer;
import
com.crystaldecisions.reports.reportengineinterface.JPEReportSourceFactory;
import com.crystaldecisions.sdk.occa.report.data.ConnectionInfo;
import com.crystaldecisions.sdk.occa.report.data.ConnectionInfos;
import com.crystaldecisions.sdk.occa.report.data.Fields;
import com.crystaldecisions.sdk.occa.report.data.IConnectionInfo;
import com.crystaldecisions.sdk.occa.report.lib.ReportSDKExceptionBase;
import com.crystaldecisions.sdk.occa.report.reportsource.IReportSource;
import com.crystaldecisions.sdk.occa.report.reportsource.IReportSourceFactory2;
import com.ensemsys.twb.ApplicationProperties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import java.util.Locale;
图五:SimpleReportViewerTag.java 被执行来显示一个报表。
/**
* Displays a Crystal report.
*/
public class SimpleReportViewerTag extends TagSupport
{
private Report report;
public Report getReport()
{
return report;
}
public void setReport( Report report )
{
this.report = report;
}
public int doEndTag() throws JspException
{
CrystalReportViewer viewer = newViewer( report );
try{
IReportSourceFactory2 rptSrcFactory = new JPEReportSourceFactory();
IReportSource reportSource =
(IReportSource) rptSrcFactory.createReportSource(
report.getReportFileName(), getLocale() );
viewer.setReportSource( reportSource );
viewer.setDatabaseLogonInfos( newDBConnectionInfos() );
viewer.setEnableLogonPrompt( false );
Fields fields = report.getFields();
if ( fields != null )
{
viewer.setParameterFields( fields );
viewer.setEnableParameterPrompt( false );
}
viewer.processHttpRequest(
(HttpServletRequest) pageContext.getRequest(),
(HttpServletResponse) pageContext.getResponse(),
pageContext.getServletConfig().getServletContext(),
pageContext.getOut() );
}
catch ( ReportSDKExceptionBase e )
{
throw new JspException( e );
}
finally
{
if ( viewer != null )
{
viewer.dispose();
}
}
return EVAL_PAGE;
}
图五:SimpleReportViewerTag.java
private Locale getLocale()
{
// generally you want to change this method
// to return the specific locale that your J2EE
// application is using
return pageContext.getRequest().getLocale();
}
private static ConnectionInfos newDBConnectionInfos()
{
ConnectionInfos infos = new ConnectionInfos();
IConnectionInfo con = new ConnectionInfo();
con.setUserName( ApplicationProperties.getJDBCUser() );
con.setPassword( ApplicationProperties.getJDBCPassword() );
infos.add( con );
return infos;
}
private static CrystalReportViewer newViewer( Report report )
{
CrystalReportViewer viewer = new CrystalReportViewer();
viewer.setName( report.getName() );
// set the viewer formatting and behaviour options
//viewer.setSeparatePages( false );
//viewer.setBestFitPage( true );
// ... and so on
return viewer;
}
}
图五:SimpleReportViewerTag.java
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>1.2</jsp-version>
<short-name>Crystal Viewer Tag Library</short-name>
<description>Display a Crystal Report with paramters</description>
<tag>
<name>viewer</name>
<tagclass>
com.ensemsys.twb.presentation.crystal.SimpleReportViewerTag
</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>report</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
图六:simplereportviewer.tld
应为TWB仅仅允许管理者查看报表,TWB现有的接口控制系统控制对报表的访问。将来,TWB可以要求更精细的访问控制-例如,用户可以在有限的条件下查看特定项目的报表。在这个案例中,为了到达这种访问标准,我们使用水晶报表的安全特性。