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);
	}

}

After you write your class, write a simple JUnit class to test it:

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();
		}
	}

}

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:



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.