During development of report generation solutions, various reporting tools and libraries may be selected for use to reduce development time and associated costs. Limiting the impact of changing or adding additional components, and supporting the different configurations that result, can be problematic in this context. Decomposing the system as much as possible into parts that can be removed as easily as they can be added or replaced alleviates some of the problem. Reducing the number of steps to remove or add parts reduces the number of potential errors and facilitates easier reconfiguration. This is the intent of the report-creation component, which defines a static interface whose underlying implementation can be handled by a variety of software product components.
Dependency among parts is the primary determinant of system complexity. Complexity exponentially correlates to increased difficulty in system maintenance, evolution, verifiability, and robustness. Too much dependency defeats the benefits achieved by decomposition. Using well-defined interfaces passing a limited set of parameters limits subsystem dependencies.
Encapsulation involves combining data and behavior behind an interface that exposes only abstracted behavior. Implementation of modules with crossdependencies or duplicate responsibilities increases system complexity and hinders evolution. Encapsulation of the reporting support tools' capabilities within the report content subsystem reduces system complexity.
The reporting system acts as a façade, decoupling the underlying report generation mechanisms completely from the client. It encapsulates the other report generation subsystems and defines an explicit interface defining available capabilities.
Figure 4 shows the basic sequence of events that take place when a report is requested. It is a high-level representation using packages defined in the architecture definition.
The main responsibilities and interfaces for the packages used in the sequence are as follows:
- Handle report requests
- Initiate report content creation
- Initiate report style application
- Initiate report output format rendering
- Return report to Requester
- Persist report to temporary or permanent storage
- Send notification of report completion to Requester
To access the reporting system, the following interfaces are defined:
For a synchronous request:
public OutputStream requestReport(String reportType, String styleSheet, int renderFormat, java.security.Principal principal)
For an asynchronous request:
public void requestReport(String reportType, String styleSheet, int notification, String notificationAddress, int renderFormat, java.security.Principal principal)
- Get the report template for the requested report type
- Authorize request for report and authorize each section of data content
- Create data producers for each section defined in the template
- Assemble report data content using data returned by data producers
- Using FOP libraries, apply style to the report content
- Using FOP libraries, convert report to final output format
To assemble the data content of the report:
mergeData(java.lang.Principal principal, String template, String tempFile)
principaldefines the user to be authorized.
Templatedefines the report template that is used as the basis of the report.
TempFileis the location of the report with data content only, in XML format.
To apply a style to the report and convert to requested output format:
styleAndRender(String tempFile, String styleSheet, int renderFormat, OutputStream output)
TempFileis the location of the report data content created from the
Stylesheetdefines the XSL information used to apply the report style.
RenderFormatis a constant value used to define the output format.
OuputStreamwhere the final formatted report is streamed.
- Determine if the requesting
Principalhas rights to view the sections of data content that are to become part of the report.
- The interfaces depend on the authorization engine to be integrated with this reporting framework. The design is such that access rights are defined for each named data producer. Access for a principal to use a data producer is determined before creation of the data producer.
- Access data sources containing data to be included in a report.
- Convert raw data requests to well-formed XML syntax that is used in the report.
- Return XML back to report creation subsystem.
To start the processing of the data producer that gets and returns the report data content as XML:
StringBuffer process( )
- Maintain the data used in reports.
- Provide access to the report data.
- Access is through JDBC or using third-party reporting tool APIs.
Separating Content, Style, and Output Format
The content of the report, the style applied to a report, and the rendered format are all distinct and separate operations. The same content can potentially have different styles applied to it and the styled report can be converted to a number of different formats (e.g., PDF or PostScript).
The first step in creating a report is to access and merge the dynamic content. Data content for a report is defined using XML and a custom defined set of tags and attributes that are relevant to the specific report type. A report template is used to define the type of content that will populate the report and data producers provide the dynamic content.
The second step in report creation is to apply a style to the report content generated in the first step. One or more stylesheets may be defined for a report. The requester of the reporting system may choose which style to apply. Stylesheets use the XSL:FO recommendations from the W3C. The XSL definitions are applied to the XML data using FOP classes. FOP includes a
Driver class that starts the translation process, given an XML and XSL file.
InputHandler inputHandler = new XSLTInputHandler(XMLFile, XSLFile); XMLReader parser = inputHandler.getParser();
XSLTInputHandler reads in the data and style information, performs the transformation, and streams the new data to an XMLReader.
The data is then ready to be rendered by the FOP tools by defining the desired output format and the
OutputStream to use. Using the Apache Formatting Object Processor makes these operations very simple.
driver.setRenderer(outputFormat); driver.setOutputStream(output); driver.render(parser, inputHandler.getInputSource());
In the implementation that goes along with this article, the XML data file is generated and saved as a file. The file is useful for validating that the data is correct and for applying various styles during development. A production system would most likely stream the XML data content directly to the XSL transformation processing, eliminating the time it takes to write the data to a file.
FOP provides a number of different renderers as part of the distribution. Additional renderers are in the works or can be added to the libraries as explained on the xml.apache.org FOP Web site.