Blog Zenika

#CodeTheWorld

Java

Eclipse BIRT : Create your own SWT Emitter

As you probably know several emitters are available to render BIRT reports into PDF, HTML, EXCEL, WORD, etc… But why can’t we integrate them natively into a RCP Application ? I will show you how to do that by creating a SWT Emitter.

1. Requirements

First of all, you need to download and install the BIRT Report Designer All-In-One 2.3.2.1.
To avoid wasting time, I provide you a Report Design example that can be used (SwtEmitter.zip) Obviously, you can create your own report (just add a table that contains some data).

2. Create a simple RCP Application

Before we create the SWT Emitter, we need to build a RCP Application to « display » the report. Let’s be lazy for this part and use the default RCP Client wizard :

  • Create a « Plug-in Project »
  • Answer « Yes » to the question « Would you like to create a rich client application? » in the « Plug-in Content » settings
  • Select the « RCP Application with a View » in the « Template » settings

Once you get your RCP Application generated, you can notice several classes. Try to launch the application by clicking on « Launch an Eclipse Application » in the « Overview » part of the plugin.xml.
If you plan to use the « Classic Models Inc, Sample DataBase » in your BIRT report, you must add the corresponding plug-in in your launch configuration. To do so, open your « Run Configuration », add the plug-in « org.eclipse.birt.report.data.oda.sampledb » and click on « Add Required Plug-in ».

3. Render the Design Report

Now that the application is built, we can customize it to render the BIRT report. First, copy the provided report directly into the project. Then clean up the code of createPartControl() and setFocus() methods in the View class because we don’t need any TreeViewer. Now create the view content :

  • create a ReportEngine which will create a task to render the report
  • set up the URL according to the report path
  • set the output format of the task in order to render the report in SWT
  • submit the parent composite in which we would like to display the report
  • render the report in the ViewPart with the given composite
  1. public View extends ViewPart {
  2. public static final String ID = « com.zenika.emitter.view »;
  3. public static final String OUTPUT_FORMAT_SWT = « swt »;
  4. public static final String RENDER_OPT_SWT_VIEWER = « swtViewer »;
  5. private static final String reportName = « /products.rptdesign »;
  6. private IReportEngine engine = null;
  7. private EngineConfig config = null;
  8. private IReportRunnable design = null;
  9. private IRunAndRenderTask task;
  10. private IRenderOption options;
  11. public void createPartControl(Composite parent) {
  12. // We initialize the engine configuration
  13. config = new EngineConfig();
  14. IReportEngineFactory factory = (IReportEngineFactory) Platform
  15. .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
  16. // We create the report engine
  17. engine = factory.createReportEngine(config);
  18. // We get the right URL for our report design
  19. URL reportUrl = FileLocator.find(Activator.getDefault().getBundle(),
  20. new Path(reportName), null);
  21. // We open the Report Design from the url
  22. try {
  23. design = engine.openReportDesign(reportUrl.openStream());
  24. } catch (EngineException e) {
  25. e.printStackTrace();
  26. } catch (IOException e) {
  27. // TODO Auto-generated catch block
  28. e.printStackTrace();
  29. }
  30. // We create a task to run and render the report design
  31. task = engine.createRunAndRenderTask(design);
  32. // We set the options
  33. options = new RenderOption();
  34. /*
  35. * It’s very important to set the output format declared in the
  36. * extension org.eclipse.birt.report.engine.emitters in the plugin.xml.
  37. */
  38. options.setOutputFormat(OUTPUT_FORMAT_SWT);
  39. /*
  40. * We need to give a composite in order to retrieve it in our swt
  41. * emitter.< /div>
  42. */
  43. options.setOption(View.RENDER_OPT_SWT_VIEWER, parent);
  44. task.setRenderOption(options);
  45. // We run and render the task
  46. try {
  47. task.run();
  48. } catch (EngineException e) {
  49. e.printStackTrace();
  50. }
  51. // We close the task and destroy the engine once we have finished
  52. task.close();
  53. engine.destroy();
  54. }
  55. public void setFocus() {
  56. }
  57. }

4. Declare a BIRT Emitter

It’s very easy to create/declare a new emitter in BIRT, we just need to add an extension in the plugin.xml file.

  • Open your plugin.xml file and in the « Dependencies » tab, add the « org.eclipse.birt.report.engine » plug-in in the « Required Plug-ins » section.
  • Create a class named « SwtEmitter » which extends the class ContentEmitterAdapter : this class will be in charge of rendering the report.
  • In the « Extensions » tab, contribute to the extension point « org.eclipse.birt.report.engine.emitters » and create an « emitter » element.
    • Specify the fully qualified name of your SWT Emitter in the « class » field.
    • In the « format » field, type « swt ». The value of this field must match the argument of options.setOutputFormat("swt") in the View class. So when we set the output format of the task in the View class, it will use that new SWT Emitter class to render the report (instead of HTML or PDF, …).
    • Finally in the « mimeType » field, type « text/html ».

 

5. Creation of the SWT Emitter

Let’s now focus on the SWT Emitter class, which will render our report as a SWT component. To understand how an emitter works, we need to think that we are parsing an XML file (do not forget that BIRT Report Design and Document are XML files). By extending the ContentEmitterAdapter class, our class automatically benefits from many methods. These methods are triggered during the report rendering phase (like a SAX Parsing). That’s why we get methods such as :

  • startTable(ITableContent table) and endRow(IRowContent row)
  • startRow(IRowContent row) and endTable(ITableContent table)
  • startCell(ICellContent cell) and endCell(ICellContent cell)

If the report contains a table, the rendering phase will trigger a startTable(ITableContent table) and a endRow(IRowContent row) events. So, to render a report containing a table, we will overwrite these methods (those concerning the table, rows, cells and the text element contained in a cell).
These first level features are provided by overwritting the following methods :

  • initialize(IEmitterServices) : retrieve the parent composite given by the View class
  • startTable(ITableContent table) : create a new SWT Table from parent composite
  • startRow(IRowContent row) : create a new TableItem for each parsed row and assess if it is the header row
  • startCell(ICellContent cell) : create a new TableColumn for each cell, only if we are parsing a header row and we got that information from the previous method and we increment a cell count
  • startText(ITextContent) : set the text contained in the cell into the current TableItem or current TableColumn in the right cell according to the cell count.
  1. /*
  2. * The main composite submitted by the view
  3. */
  4. private Composite mainComposite;
  5. /*
  6. * The current Table we are processing
  7. */
  8. private Table currentTable;
  9. /*
  10. * The current TableItem we are processing
  11. */
  12. private TableItem currentTableItem;
  13. /*
  14. * The current TableColumn we are processing
  15. */
  16. private TableColumn currentColumn;
  17. /*
  18. * The index of the cell we are processing
  19. */
  20. private int cellIndex = 0;
  21. /*
  22. * Boolean to know if we are building the header of the Table
  23. */
  24. private boolean header = false;
  25. @Override
  26. public void initialize(IEmitterServices service) {
  27. super.initialize(service);
  28. // Important : we retrieve the composite from the View class
  29. mainComposite = (Composite) service.getRenderOption().getOption(
  30. View.RENDER_OPT_SWT_VIEWER);
  31. }
  32. @Override
  33. public void startTable(ITableContent table) {
  34. // We create the new Table from the current element
  35. currentTable = new Table(mainComposite, SWT.BORDER);
  36. // We want to see lines and headers
  37. currentTable.setLinesVisible(true);
  38. currentTable.setHeaderVisible(true);
  39. }
  40. @Override
  41. public void endTable(ITableContent table) {
  42. // We resize the table inside the view
  43. resizeTable();
  44. }
  45. @Override
  46. public void startCell(ICellContent cell) {
  47. if (header) {
  48. // If we have a header, we create a new TableColumn

    < /li>

  49. currentColumn = new TableColumn(currentTable, SWT.BORDER);
  50. }
  51. }
  52. @Override
  53. public void endCell(ICellContent cell) {
  54. // Once we have set the cell content we increment the index
  55. cellIndex++;
  56. }
  57. @Override
  58. public void startRow(IRowContent row) {
  59. // Everytime we begin to parse a row, we reset the cellIndex
  60. cellIndex = 0;
  61. // We retrieve the Band Type of the row to know what type of row we are
  62. // processing
  63. int bandType = row.getBand().getBandType();
  64. // Header of the table
  65. if (bandType == IBandContent.BAND_HEADER) {
  66. // We set the header attribute to true, we will use it to create the
  67. // TableColumn in the startRow method
  68. header = true;
  69. }
  70. // Detail Row of the table or Footer of the table
  71. else if (bandType == IBandContent.BAND_DETAIL
  72. || bandType == IBandContent.BAND_FOOTER) {
  73. // We create a new TableItem every time we start to parse a row
  74. currentT ableItem = new TableItem(currentTable, SWT.NONE);
  75. // We retrieve the background color of the row
  76. String birtColor = row.getStyle().getBackgroundColor();
  77. // We get the RGB color from the BIRT Color
  78. RGB rgb = computeColor(birtColor);
  79. // We set it onto the background of the TableItem
  80. if (rgb != null) {
  81. currentTableItem.setBackground(new Color(currentTable
  82. .getDisplay(), rgb));
  83. }
  84. }
  85. }
  86. @Override
  87. public void endRow(IRowContent row) {
  88. // Once we have finished the parse of the header, we reset the header
  89. // attribute
  90. if (header) {
  91. header = false;
  92. }
  93. }
  94. @Override
  95. public void startText(ITextContent text) {
  96. if (header) {
  97. // If we are on header row, we set the text of the current
  98. // TableColumn
  99. currentColumn.setText(text.getText());
  100. } else {
  101. // If we are a detail or footer row, we set the text on the current
  102. // TableItem
  103. currentTableItem.setText(cellIndex, text.getText());
  104. }
  105. }
  106. /*
  107. * Sets the size of the table to see the columns.
  108. */
  109. private void resizeTable() {
  110. for (TableColumn column : currentTable.getColumns()) {
  111. column.setWidth(200);
  112. }
  113. currentTable.pack();
  114. }

 

6. Example of processing a style : color of a row

Let’s make our table a little more « appealing » by changing the rows’ background color. To do so, we need to add some more code in the startRow(IRowContent row) method. The style of a row can be obtained by calling row.getStyle() ; from that style, we can then retrieve several properties such as the background color, the font or the size of the row for example.
Once we have retrieved the row color, we only have to set the background color of the corresponding TableItem. However, when we retrieve a color property from BIRT, we get it in a String format, so we need to create the correct SWT object corresponding to that property. For example, we can have « rgb(233, 233, 233) » or « Blue » for a color, we have to create a SWT Color representing that color.

  1. /*
  2. * Returns a RGB object from a BIRT color.
  3. *
  4. * There are two type of formats in BIRT :
  5. * – « rgb(233, 233, 233) »
  6. * – « Blue », »Red », »Yellow »,etc …
  7. *
  8. * We just process the first format in that method.
  9. *
  10. */
  11. private RGB computeColor(String birtColor) {
  12. int[] rgb = new int[3];
  13. if (birtColor != null && birtColor.startsWith(« rgb(« )) {
  14. String numbers = birtColor.substring(4, birtColor.length()1);
  15. String[] split = numbers.split(« , »);
  16. for (int i = 0; i < 3; i++) {
  17. rgb[i] = Integer.parseInt(split[i].trim());
  18. }
  19. return new RGB(rgb[0], rgb[1], rgb[2]);
  20. }
  21. return null;
  22. }

Now run your RCP Application again and enjoy the result : you have created a basic working SWT Emitter !

The original Birt Report Design :

BIRT_RptDesign.PNG

In PDF :

BIRT_PDF_Emitter.PNG

In SWT :

BIRT_SWT_Emitter.PNG
Obvioulsy, it’s easy to imagine how powerful that solution can be, especially once embedded into a RAP Application …
You can find all the sources to realise this SWT Emitter in the archive file SwtEmitter.zip.

2 réflexions sur “Eclipse BIRT : Create your own SWT Emitter

  • Mouquet Sylvain

    Quand on a un format bien défini, il est plus sympa d’utiliser des regex que de s’amuser à découper des chaînes :

    private RGB computeColorRegex(String birtColor) {

           Pattern pattern = Pattern.compile("^rgb\((\d+),\s(\d+),\s(\d+)\)$");        Matcher matcher = pattern.matcher(birtColor);        if (matcher.find()) {            return new RGB(Integer.valueOf(matcher.group(1)), Integer.valueOf(matcher.group(2)), Integer.valueOf(matcher.group(3)));        }        return null;    }
    Répondre
  • I am trying to run your code, it compiles ok, but when it is running I get the following error:

    20/06/2012 12:07:41 PM org.eclipse.birt.report.engine.api.impl.EngineTask handleFatalExceptions
    GRAVE: An error happened while running the report. Cause:
    org.eclipse.birt.report.engine.api.EngineException: Cant create data engine.
    at org.eclipse.birt.report.engine.executor.ExecutionContext.openDataEngine(ExecutionContext.java:888)
    at org.eclipse.birt.report.engine.executor.ExecutionContext.getDataEngine(ExecutionContext.java:900)

    Do you have any ideas to solve this issue?

    Thanks

    Manuel

    Répondre

Répondre à Mouquet SylvainAnnuler la réponse.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Continue reading