Updating Pentaho PRPT files to add a PreProcessor

In my previous post (see here) I mentioned that I couldn’t add a pre-processor to a Pentaho report using the report designer. So, I’ve written a short Java program that does just that.
Note that I use a neat open source library called Zip4J (you can get it here).

package com.tona.rprt;

import java.io.File;
import java.io.FileWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.model.ZipParameters;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

public class Main {
	private static final String CONFIG_FILE_NAME = "layout.xml";
	
	public static void main(String[] args) throws Exception {
		ZipFile reportFile = new ZipFile("");

		File tempDirectory = createTempDirectory();
		String path = tempDirectory.getAbsolutePath();
		reportFile.extractFile(CONFIG_FILE_NAME, path);

		System.out.println("Extraced file to " + path);
		File updatedFile = new File(path + File.separator + CONFIG_FILE_NAME);

		// Update the file
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.parse(updatedFile);
		
		System.out.println("Parsed document");
		
		Element layoutNode = doc.getDocumentElement();
		Element preProcessorElement = doc.createElement("preprocessor");
		preProcessorElement.setAttribute("class", "com.tona.report.infra.TonaWizardProcessor");
		Node firstLayoutChild = layoutNode.getFirstChild(); 
		layoutNode.insertBefore(preProcessorElement, firstLayoutChild);
		
		System.out.println("Added child");

		FileWriter output = new FileWriter(updatedFile);
		javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(output);

		TransformerFactory tf = TransformerFactory.newInstance();
		Transformer t = tf.newTransformer();
		t.transform(new DOMSource(doc), result);
		
		System.out.println("Updated XML file");
		
		ZipParameters parameters = new ZipParameters();
		reportFile.removeFile(CONFIG_FILE_NAME);
		reportFile.addFile(updatedFile, parameters);
		
		System.out.println("Update ZIP file");
		
		tempDirectory.delete();
		
		System.out.println("Removed temporary directory");
	}
	
	private static File createTempDirectory() throws Exception
		{
		    File temp = File.createTempFile("temp", Long.toString(System.nanoTime()));

		    if(!(temp.delete())) {
		        throw new Exception("Could not delete temp file: " + temp.getAbsolutePath());
		    }

		    if(!(temp.mkdir())) {
		        throw new Exception("Could not create temp directory: " + temp.getAbsolutePath());
		    }

		    return temp;
		}	
}

Templating with Pentaho BI

I’m trying to build multiple reports on Pentaho Report Designer, and couldn’t find a really good way to implement templating. You see – I want the report design to change when I change my template.
So, I wrote some code. The WizardProcessor class is a Pentaho class to be used by report pre processors (see here for more info on the subject), and extending it is easy.


public class TonaWizardProcessor extends WizardProcessor {

public MasterReport performPreProcessing(MasterReport definition,
DefaultFlowController flowController)
throws ReportProcessingException {

// Init header
ReportHeader header = definition.getReportHeader();

float pageWidth = definition.getPageDefinition().getWidth();

// Add black background

Element e = RectangleElementFactory.createFilledRectangle(0, 0,
pageWidth, 100, Color.black);

header.addElement(e);

// Add report title

// Add date
Element dateElement = DateFieldElementFactory.createDateElement(
“TopDateLabel”, new Rectangle2D.Double(pageWidth – 100, 0, 100,
100), Color.WHITE, ElementAlignment.RIGHT,
new FontDefinition(“Arial”, 12), “-“, “MMM dd, yyyy”,
“report.date”);

header.addElement(dateElement);

// Init footer
ReportFooter footer = definition.getReportFooter();

// Add date
Element footerDateElement = DateFieldElementFactory.createDateElement(
“FooterDateLabel”, new Rectangle2D.Double(0, 0, pageWidth / 2,
14), Color.BLACK, ElementAlignment.RIGHT,
new FontDefinition(“Arial”, 12), “-“, “MMM dd, yyyy”,
“report.date”);

makeBorder(footerDateElement);

footer.addElement(footerDateElement);

// Add pages count

Element footerPagesElement = TextFieldElementFactory
.createStringElement(“FooterPageLabel”, new Rectangle2D.Double(
pageWidth / 2, 0, pageWidth / 2, 14), Color.BLACK,
ElementAlignment.RIGHT,
new FontDefinition(“Arial”, 12), “-“, “PageFunction0”);

makeBorder(footerPagesElement);

footer.addElement(footerPagesElement);

// Create orange row banding
Element[] items = definition.getItemBand().getElementArray();

// Setting temp names for the row banding
for (Element item : items) {
item.setName(“TempRowBand”);
makeBorder(item);
}

RowBandingFunction bandingFunction = new RowBandingFunction();
bandingFunction.setVisibleBackground(Color.ORANGE);
bandingFunction.setElement(“TempRowBand”);
definition.addExpression(bandingFunction);

// Change all group header background to orange

for (int i = 0; i < definition.getGroupCount(); ++i) { for (Element groupHeaderElement : definition.getGroup(i).getHeader().getElementArray()) { makeBorder(groupHeaderElement); groupHeaderElement.getStyle().setStyleProperty( ElementStyleKeys.BACKGROUND_COLOR, Color.ORANGE); } } return super.performPreProcessing(definition, flowController); } private void makeBorder(Element element) { element.getStyle().setStyleProperty(ElementStyleKeys.BOX_SIZING, BoxSizing.BORDER_BOX); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_TOP_WIDTH, new Float(1)); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_LEFT_WIDTH, new Float(1)); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BOTTOM_WIDTH, new Float(1)); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_RIGHT_WIDTH, new Float(1)); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BREAK_WIDTH, new Float(1)); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_TOP_COLOR, Color.black); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_LEFT_COLOR, Color.black); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BOTTOM_COLOR, Color.black); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_RIGHT_COLOR, Color.black); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BREAK_COLOR, Color.black); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_TOP_STYLE, BorderStyle.SOLID); element.getStyle().setStyleProperty(ElementStyleKeys.BORDER_LEFT_STYLE, BorderStyle.SOLID); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BOTTOM_STYLE, BorderStyle.SOLID); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_RIGHT_STYLE, BorderStyle.SOLID); element.getStyle().setStyleProperty( ElementStyleKeys.BORDER_BREAK_STYLE, BorderStyle.SOLID); } } [/java] After you write your class, write a simple JUnit class to test it: [java] public class ProcessorTest { @Test public void test() { ClassicEngineBoot.getInstance().start(); ResourceManager manager = new ResourceManager(); manager.registerDefaults(); String reportPath = "file:/home/liran/test.prpt"; try { Resource res = manager.createDirectly(new URL(reportPath), MasterReport.class); MasterReport report = (MasterReport) res.getResource(); report.addPreProcessor(new TonaWizardProcessor()); File file = new File("/home/liran/output.html"); PrintStream ps = new PrintStream(file); HtmlReportUtil.createStreamHTML(report, ps); } catch (Exception e) { e.printStackTrace(); } } } [/java] Implementing it at the report level is a bit more difficult, as I couldn't do it in the PRD (Pentaho Report Designer). But changing the layout.xml file (found inside your prpt file) is easy. Just add the following lines: [xml] [/xml]
And you’re done.

Note that your pre-processor class (contained in a JAR file) must be found in the REPORT_DESIGNER/lib directory – otherwise the designer won’t open your report. Same is true for the BI-SERVER (put it in biserver-ee/tomcat/lib directory).

I’m writing a Java code that will allow you to automatically patch the rprt file to use the pre-processor. I’ll post the code in a future post.