The examples used in this package require a database. A great product demo database is the open source HSQL Database Engine which is written in Java and comes with a driver that you can drop into your WEB-INF\LIB directory.
Here's how to get the examples working:
1.Download HSQL Database Engine
2. Extract the hsqldb.jar from the lib directory and place it in a folder by itself.
3. Download the example database and extract to the same directory.
4. From a Dos window, run runserver.bat If running from Linux, you will need to edit the runServer.sh slightly. This starts the database server with a small database for the Struts project.
December 2004
Jakarta Struts is today dominant in the arena of Java web applications. The Struts framework targets complex web apps to provide a strong layer of control over servlets, jsps, taglibs and other components of a typical Java web application.
Perhaps its strongest suit is the ability to scrape all the elements off a web page using an ActionForm and to present the data to a controller-like Action. There's no need to write scads of code that interrogate the HTTP request. Each web page features an ActionForm and an Action that handles data capture and navigation.
Normally, this is all wonderful stuff. But when the number of web pages starts to increase, the maintenance of multiple classes per web page can be daunting. Quite often, you will see a single web pages represented by an Action and ActionForm classes. For large applications, this structure can be cumbersome.
This is nowhere more apparent than when the the Struts programmer has to deal with a reporting-based application. A reporting application tends to be a unique beast. You typically start with a very few well known reporting solutions -- nothing more than a couple of pages -- and then the business starts to react with requests for more of the same. Over time, the application starts to grow with no end in sight.
This doesn't work particularly well with Struts, especially if you adhere to the one-page-one-ActionForm scenario. The cost of a new report becomes several days' effort rather than several hours. And pretty soon the application starts to show sign of bloat.
The Reporting web application is often a series of almost identical pages, differiented only by its data. The display is normally a read-only HTML table. Input parameters are captured on pages that display a drop-down or two and a submit button. There is often a requirement to summarize the data or apply various function-like modules, like a record counter or a Total function for numeric fields.
This is really a scenario that requires an application design to utilize very few Action and ActionForm classes. As well, to maintain consistency of page format, a minimum of jsp code should be utilized. Where the Action is well-suited to defining navigation and the ActionForm is ideal as gathering and validating page data, the Reporting application needs a layer that reacts specificly to the report data.
This article defines a design that allows a Struts application to capture data from multiple sources and to apply formatting rules that are fairly simple to specify and implement.
The basis of the design is that each report should be defined in an XML repository. Each report is represented by an XML file acting as a specification. Each file requires three sections: input, data and formatting specifications.
Other features of the framework:
<Report>
<reportTitle>Products by Invoice</reportTitle>
<outputClass>
com.javazoid.report.format.HtmlJdbcTableFormatter
</outputClass>
<inputSpecification>
<inputs>
<input>
<type>
com.javazoid.report.parameter.format.ListParameter
</type>
<invokeMethod>getInvoices</invokeMethod>
<invokeClass>
com.javazoid.report.demo.Lookup
</invokeClass>
<label>Invoice</label>
<name>invoiceId</name>
<size>20</size>
<validationClass>
com.javazoid.report.input.validate.MustBeNumeric
</validationClass>
</input>
</inputs>
</inputSpecification>
<dataSpecification>
<type>jdbc</type>
<connectionClass>
com.javazoid.report.demo.HsqlConnection
</connectionClass>
<connectionMethod>getConnection</connectionMethod>
<connectionMethodParameter/>
<sql>
select product.*, item.* from product, item
where product.id=item.productid AND item.invoiceid=?
order by product.name
</sql>
</dataSpecification>
<formatSpecification>
<tableStyle>table</tableStyle>
<rowStyles>
<rowStyle>darkRow</rowStyle>
<rowStyle>lightRow</rowStyle>
</rowStyles>
<columns>
<column>
<title>Product Name</title>
<widthPercent>30</widthPercent>
<alignment>left</alignment>
<dataName>NAME</dataName>
<styleClass>highlight</styleClass>
</column>
<column>
<title>PRICE</title>
<widthPercent>20</widthPercent>
<alignment>right</alignment>
<dataName>PRICE</dataName>
<formatter>
com.javazoid.report.cell.DecimalCellFormatter
</formatter>
<formatPattern>#,###.00</formatPattern>
</column>
<column>
<title>QUANTITY</title>
<widthPercent>25</widthPercent>
<alignment>right</alignment>
<dataName>QUANTITY</dataName>
</column>
<column>
<title>Cost</title>
<widthPercent>25</widthPercent>
<alignment>right</alignment>
<dataName>COST</dataName>
<formatterClass>
com.javazoid.report.cell.DecimalCellFormatter
</formatterClass>
<formatPattern>#,###.00</formatPattern>
</column>
</columns>
<summaries>
<summary>
<summaryCaption>Total Costs</summaryCaption>
<summaryColumn>COST</summaryColumn>
<summaryFunction>
com.javazoid.report.function.Total
</summaryFunction>
<summaryFormatPattern>
$#,###.00
</summaryFormatPattern>
</summary>
</summaries>
</formatSpecification>
</Report>
Specifications are normally read-in once, during the first report access. Each report has one specification contained in an xml file. When this file is read, it is translated to a Java value object format when the specification build() method is invoked.
The ReportSpecification class contains three elements that define all attributes required to render the report.
The input section defines a series of components that will display components to capture inputs to the data layer. These components are currently geared towards Html display and include List, LongList, Text and Hidden inputs. These components honor a Parameter interface with methods like
public String getLabel(); public String getComponent(); public void setValues(Object in);
The getComponent() method returns a String which containing HTML markup for SELECTs or INPUTs. The INPUT style Parameters are, of course, easiest to render. Data capture is also pretty straightforward. However, for SELECT components, it is necessary for the XML specification to pass along a class and a method that can be invoked to obtain display data for the OPTION elements of the SELECT. The syntax of the XML is
<inputSpecification>
<inputs>
<input>
<type>
com.javazoid.report.parameter.format.ListParameter
</type>
<invokeMethod>getInvoices</invokeMethod>
<invokeClass>
com.javazoid.report.demo.Lookup
</invokeClass>
<label>Invoice</label>
<name>invoiceId</name>
<size>20</size>
<validationClass>
com.javazoid.report.input.validate.MustBeNumeric
</validationClass>
</input>
</inputs>
</inputSpecification>
The Parameter component is rendered on a page called "genericinput.jsp" that iterates the list of Parameter objects, display its label and calls its get components for the appropriate html. In this way the generic input can be dynamically built from the xml specification. Each component will display a name, param and displayName attribute that will be captured from the "genericinput" jsp. Here's what each attribute holds:
Data specifications indicate how the data is to be fetched within the application. Normally, most reporting applications will specify some sort of data-accessing class and method that will return either a stream-like structure like a JDBC ResultSet or a List of JavaBeans. AS much as possible, I have tried to permit report customizers the ability to define their own structure. However, to become completely flexible will require stepping away from having the framework return Specification objects.
That said, there are currently two supported structures, jdbc and listBean. They are, of course, completely different structures, so I will include examples of both.
JDBC
<dataSpecification>
<type>jdbc</type>
<connectionClass>
com.javazoid.report.demo.HsqlConnection
</connectionClass>
<connectionMethod>getConnection</connectionMethod>
<connectionMethodParameter/>
<sql>
select product.*, item.* from product, item
where product.id=item.productid AND item.invoiceid=?
order by product.name
</sql>
</dataSpecification>
In the above example, I am supposing that the application has a standard means of obtaining a JDBC Database connection, against which the SQL can be prepared.
Bean Data Access
<dataSpecification>
<type>listBean</type>
<accessorClass>
com.javazoid.report.demo.CustomerAccessor
</accessorClass>
<accessorMethod>getCustomers</accessorMethod>
</dataSpecification>
The assumption here is that the CustomorAccessor's getCustomers() method will return a java.util.List of JavaBean objects. You don't need to specify what the type of the objects in this List, but you do need to make sure that the type follows a JavaBean convention because each Column in the report will have a dataName like "getFirstName" against which this object will be queried using Java relection.
The Format section of the specification xml contains elements that assist in the page layout. As much as possible, formatting is deferred to Cascading Style Sheets. The Html layout is specified by an Html TABLE tag and all its descendents. However, beyond those tags, most of the formatting is accomplished through the CSS speicification. Struts Reporting accomodate three layers of formatting, TABLE, ROW and CELL formatting -- all of which are optional.
<TABLE class="XXX">
<TR class="xxx">The styles are applied sequentially so that indicating two styles will apply the first to row one and the second to row 2. This can accomplish a banded-look in this instance.
<TD class="XXX">Some of the attributes for a column can be specified outside the style sheet, but I have kept this to a relative minimum.
<columns>
<column>
<title>Product Name</title>
<widthPercent>30</widthPercent>
<alignment>left</alignment>
<dataName>NAME</dataName>
<styleClass>highlight</styleClass>
</column>
</columns>
The bulk of the attributes required for a layout is specified in the Columns collection, where each Column object is applied to the TD attribute of an Html layout. Here a list of the currently supported items:
Which of these attribute are required and which optional? The truth is that none of these are required, unless the run-time objects are written to require them. For this reason, I have not committed to a DTD or any definition of structure. My hope is that the Specification layer will become flexible enough to let developers add any attributes that their runtime Report Formatter objects. That type of flexibility will have to wait unfortunately because I've code the Specification layer to include JavaBeans that would themselves need to be subclassed or re-written to accomodate my goal of complete flexibility. Perhaps in a future release!
<summaries> <summary> <summaryCaption>Total Costs</summaryCaption> <summaryColumn>COST</summaryColumn> <summaryFunction> com.javazoid.report.function.Total </summaryFunction> <summaryFormatPattern> $#,###.00 </summaryFormatPattern> </summary> </summaries>
Beyond Column definition, there are also Summaries, a collection of "summary" objects that represent a single HTML row in the layout. These are, of course, presented at the end of the report and include the following
A note and apology about consistency. Whereas all the Column attributes are optional, all the Summary attributes are required. I may change this...
I have devised the Runtime objects so that they must honor interface com.javazoid.report.format.Formatter, which defines a series of methods to pass the various specification elements and a format() method which should fire off all the work to fetch and transform the data.
Most of the formatters currently supported are subclasses of HtmlFormatter, which proved the bulk of the code required to represent Titles, headers, data rows and report-end summaries. You'll find implementations of methods like these
makeRow(StringBuffer sb) getCellStart(ColumnValues column, StringBuffer buffer) printColumnHeaders()
Unfortunately, the actual data that these methods will work on is unknown. It could be a JDBC ResultSet. It could be a JavaBean. To handle variety in the abstract superclass, I have required the subclasses to transfer their row data into a Map that contains the row data , keyed by the "dataName" attribute of a Column. The type of this data is java.lang.Object, but we assume that calling the object's toString() method will deliver legible data for the report. Where items are not possible to represent via the toString() method, I would recommend speicifying (...and maybe implementing) a CellFormatter object in the data's respective Column.formatterClass attribute).
The implementation of a particular subclass should be relatively easy. For example, an Html ResultSet can be formatted via the HtmlJdbcTableFormatter. JavaBean is likewise translated to a Map object and passed along via the HtmlBeanTableFormatter. Finally, I've implemented a HtmlJdbcRowFormatter, which works on only one row in a ResultSet.
The single Struts Action class we use handle all these reports. The particular formatter that is required to render the report is specified in the report XML and it remains only for the Action to instantiate that class and call its format() method.
However, the Struts Action also needs to evaluate whether there is enough data present to even show the report. If the specification indicates that inputs of some sort are required and the parameters normally available from these are missing on the Struts ActionForm, the Action will forward to the "genericinput" page, which is designed to display generic input controls like SELECTs and text INPUTs . To do this, the action must instantiate a parameter builder that provides the code for the generic input page.
When a user posts back to the Action from genericinput, the Action should recognize that the parameter list's data is available and the report can now be shown.
I recommend evaluating the framework by examining the example application. That should give you enough detail to determine how much customization you will need to support the framework in your Struts application.
You will need to either start a brand new Struts app or modify an existing on. These are the instructions to get the frame up and running:
I can speak to some experience with customizing the package to suite a client's requirement. The framework was flexible enough to take care of the bulk of the client's reporting needs, but there was certainly a need to customize things.
<Report>
<userDefined>
<downloadable>true</downloadable>
<downloadFormatter>
org.myorg.reports.MySubclassedCSVFormatter
</downloadFormatter>
</userDefined>
...