Bending Birt to Editing Tasks

brucem
edited February 11, 2022 in Analytics #1
Link to original blog post<br />
<br />
A recent project required that we generate PDF invoices several times a month. After some testing we settled on Eclipse BIRT. It was closer to WYSIWYG that the other tools and after an hour or so we could produce a first approximation of the required output.<br />
<br />
There was however one requirement that appeared harder to implement. The line item descriptions on some of the deals in the invoice were not correct. Getting these changed in the upstream application was time consuming, so they wanted a mechanism to change these but also protect all the financial data in the invoice. That meant that only non financial information should be editable.<br />
<br />
The project stored the invoices as XML blobs in the database. The intent was that the XML should have all the information required to display the invoice and get it delivered. No further calculations were to be made or databases accessed once the XML was saved to the database. BIRT report templates created multiple data sets pointing to various parts of each XML document to render an invoice. SSI’s, counter party information and invoice detail were stored in separate parts of the same XML document.<br />
<br />
After a few days we had a solution that worked quite well. By using a fairly obscure interface in BIRT, it is possible to get BIRT to call you back whilst it is assembling a document. Elements that might be editable might be single fields or a repeating item in the invoice body such as the item description. As BIRT calls you back, it passes in the immutable ID that the report designer has assigned to each field. Using this to identify the fields, we can tag the HTML fields being generated with additional HTML attributes that we can used during a post generation process.<br />
<br />
Once the BIRT generator returns the report HTML , we post process the HTML, parsing it looking for the tags emitted during report generation. These tags are used as meta data to drive input field generation.<br />
<br />
Eclipse Project source code<br />
<br />
The key to making this work is highlighted in the code below. By inserting a callback interface, we can add augment the HTML nodes that Birt generates to enable some smart post processing of the HTML. An example video of the editing process is shown above. The original blog post highlights the source code lines correctly below<br />
public byte&#91;] runReport(final ReportParams params) throws Exception {
		log.trace("Running a report request");
		IReportRunnable design = engine.openReportDesign(params.getTemplate());

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		IRunAndRenderTask task = engine.createRunAndRenderTask(design);
		try {
			// Set parent class loader for engine
			task.getAppContext().put(
					EngineConstants.APPCONTEXT_CLASSLOADER_KEY,
					BirtWrapper.class.getClassLoader());

			Map&lt;String, Object&gt; reportParams = params.getParams();
			if (reportParams != null) {
				// Set parameter values and validate
				for (String paramName : reportParams.keySet()) {
					Object value = reportParams.get(paramName);
					task.setParameterValue(paramName, value);
				}
				task.validateParameters();
			}
			task.setAppContext(getXmlDataSource(params.getXmlData()));

			log.trace("Setting render options :" + params.getType());
			String outputFormat = mapMimeTypeToBirtType(params.getType());

			if (MimeType.PDF.equals(params.getType())) {
				PDFRenderOption options = new PDFRenderOption();
				options.setOutputStream(baos);
				options.setOutputFormat(outputFormat);
				task.setRenderOption(options);
			} else if (MimeType.HTML.equals(params.getType())) {
				HTMLRenderOption options = new HTMLRenderOption();
				options.setOutputStream(baos);
				options.setOutputFormat(outputFormat);
				if (params.isGenerateForEditing()) {
					options.setEnableMetadata(true);
					options.setMetadataFilter(new MetaDataFilter(params));
				}
				task.setRenderOption(options);
			} else if (MimeType.DOC.equals(params.getType())) {
				// its DOC xml format
				RenderOption options = new RenderOption();
				options.setOutputStream(baos);
				options.setOutputFormat(outputFormat);
				task.setRenderOption(options);
			} else {
				throw new UnsupportedOperationException("Unsupported mime type: " + params.getType());
			}

			log.trace("render options set ok, running report...");

			// run and render report
			task.run();
			task.close();
			log.trace("Report run ok");
			// assuming we are synchronous
			return baos.toByteArray();
		} finally {
			task.close();
		}
	}