Báo cáo BIRT với Spring Boot

1. Giới thiệu

Trong hướng dẫn này, chúng tôi sẽ tích hợp BIRT (Công cụ báo cáo và thông minh kinh doanh) với Spring Boot MVC, để phục vụ các báo cáo tĩnh và động ở định dạng HTML và PDF.

2. CHIM LÀ GÌ ?

BIRT là một công cụ mã nguồn mở để tạo trực quan hóa dữ liệu có thể được tích hợp vào các ứng dụng web Java.

Đó là một dự án phần mềm cấp cao nhất trong Eclipse Foundation và tận dụng sự đóng góp của IBM và Innovent Solutions. Nó được bắt đầu và được tài trợ bởi Actuate vào cuối năm 2004.

Khung cho phép tạo báo cáo tích hợp với nhiều nguồn dữ liệu.

3. Sự phụ thuộc của Maven

BIRT có hai thành phần chính: một trình thiết kế báo cáo trực quan để tạo các tệp thiết kế báo cáo và một thành phần thời gian chạy để diễn giải và kết xuất các thiết kế đó.

Trong ứng dụng web mẫu của chúng tôi, chúng tôi sẽ sử dụng cả hai trên đầu Spring Boot.

3.1. Sự phụ thuộc của khung BIRT

Như chúng ta đã quen với suy nghĩ về quản lý phụ thuộc, lựa chọn đầu tiên sẽ là tìm kiếm BIRT ở Maven Central.

Tuy nhiên, phiên bản chính thức mới nhất của thư viện lõi hiện có là 4.6 từ năm 2016 , trong khi trên trang tải xuống Eclipse, chúng ta có thể tìm thấy các liên kết cho ít nhất hai phiên bản mới hơn ( hiện tại là 4.8 ).

Nếu chúng tôi chọn sử dụng bản dựng chính thức, cách dễ nhất để thiết lập và chạy mã là tải xuống gói Công cụ báo cáo BIRT, đây là một ứng dụng web hoàn chỉnh cũng hữu ích cho việc học. Sau đó, chúng tôi cần sao chép thư mục lib của nó vào dự án của chúng tôi (kích thước khoảng 68MB) và yêu cầu IDE bao gồm tất cả các lọ trong đó.

Không cần phải nói rằng, bằng cách sử dụng phương pháp này, chúng tôi sẽ chỉ có thể biên dịch thông qua IDE , vì Maven sẽ không tìm thấy các lọ đó trừ khi chúng tôi định cấu hình và cài đặt chúng theo cách thủ công (hơn 100 tệp!) Trong kho lưu trữ cục bộ của chúng tôi.

May mắn thay, Innovent Solutions đã quyết định giải quyết vấn đề trong tay và xuất bản trên Maven Central các bản dựng của riêng mình về các phụ thuộc BIRT mới nhất, điều này thật tuyệt vời, vì nó quản lý cho chúng ta tất cả các phụ thuộc cần thiết.

Đọc qua các bình luận trên các diễn đàn trực tuyến, không rõ liệu các hiện vật này đã sẵn sàng sản xuất hay chưa, nhưng Innovent Solutions đã làm việc với nhóm Eclipse ngay từ đầu, vì vậy dự án của chúng tôi dựa vào chúng.

Bao gồm BIRT giờ đây rất dễ dàng:

 com.innoventsolutions.birt.runtime org.eclipse.birt.runtime_4.8.0-20180626 4.8.0 

3.2. Sự phụ thuộc vào Spring Boot

Bây giờ BIRT đã được nhập vào dự án của chúng tôi, chúng tôi chỉ cần thêm các phụ thuộc Spring Boot tiêu chuẩn vào tệp pom của chúng tôi.

Tuy nhiên, có một cạm bẫy, vì jar BIRT chứa việc triển khai Slf4J của riêng nó , điều này không hoạt động tốt với Logback và ném ra một ngoại lệ xung đột trong khi khởi động.

Vì chúng tôi không thể xóa nó khỏi jar, nên để khắc phục sự cố này, chúng tôi cần loại trừ Logback :

 org.springframework.boot spring-boot-starter-logging   ch.qos.logback logback-classic   

Bây giờ chúng tôi cuối cùng đã sẵn sàng để bắt đầu!

4. Báo cáo BIRT

Trong khuôn khổ BIRT, báo cáo là một tệp cấu hình XML dài , được xác định bằng phần mở rộng rptdesign .

Nó cho Engine biết phải vẽ gì và ở đâu , từ kiểu tiêu đề cho đến các thuộc tính cần thiết để kết nối với nguồn dữ liệu.

Đối với một báo cáo động cơ bản, chúng ta cần định cấu hình ba điều:

  1. nguồn dữ liệu (trong ví dụ của chúng tôi, chúng tôi sử dụng tệp CSV cục bộ, nhưng nó có thể dễ dàng là một bảng cơ sở dữ liệu)
  2. các yếu tố chúng tôi muốn hiển thị (biểu đồ, bảng, v.v.)
  3. thiết kế trang

Báo cáo có cấu trúc giống như một trang HTML, với đầu trang, nội dung, chân trang, tập lệnh và kiểu.

Khung cung cấp một tập hợp các thành phần mở rộng để lựa chọn , bao gồm tích hợp với các nguồn dữ liệu chính thống, bố cục, biểu đồ và bảng. Và, chúng tôi có thể mở rộng nó để thêm của riêng chúng tôi!

Có hai cách để tạo tệp báo cáo: trực quan hoặc có lập trình.

5. Trình thiết kế báo cáo Eclipse

Để dễ dàng tạo báo cáo, nhóm Eclipse đã xây dựng một plugin công cụ thiết kế báo cáo cho IDE phổ biến của nó.

Công cụ này có giao diện kéo và thả dễ dàng từ Palette ở bên trái, tự động mở cửa sổ thiết lập cho thành phần mới mà chúng tôi thêm vào trang. Chúng tôi cũng có thể xem tất cả các tùy chỉnh có sẵn cho mỗi thành phần bằng cách nhấp vào nó trên trang và sau đó vào nút Trình chỉnh sửa thuộc tính (được đánh dấu trong hình ảnh bên dưới).

Để hình dung toàn bộ cấu trúc trang trong chế độ xem dạng cây, chúng ta chỉ cần nhấp vào nút Outline .

Các Data Explorer tab cũng chứa các nguồn dữ liệu được xác định cho báo cáo của chúng tôi:

Báo cáo mẫu hiển thị trong hình ảnh có thể được tìm thấy tại đường dẫn /reports/csv_data_report.rptdesign

Một lợi thế khác của trình thiết kế trực quan là tài liệu trực tuyến, tập trung nhiều hơn vào công cụ này thay vì cách tiếp cận theo chương trình.

Nếu chúng ta đã sử dụng Eclipse, chúng ta chỉ cần cài đặt plugin BIRT Report Design , bao gồm một phối cảnh được xác định trước và trình chỉnh sửa trực quan.

Đối với những nhà phát triển hiện không sử dụng Eclipse và không muốn chuyển đổi, có một gói Eclipse Report Designer , bao gồm cài đặt Eclipse di động với plugin BIRT được cài đặt sẵn.

Khi tệp báo cáo được hoàn thành, chúng tôi có thể lưu nó trong dự án của mình và quay lại mã hóa trong môi trường ưa thích của chúng tôi.

6. Phương pháp tiếp cận có lập trình

Chúng tôi cũng có thể thiết kế một báo cáo chỉ bằng cách sử dụng mã , nhưng cách làm này khó hơn rất nhiều do tài liệu kém sẵn có, vì vậy hãy chuẩn bị để tìm hiểu kỹ về mã nguồn và các diễn đàn trực tuyến.

Cũng đáng xem xét là tất cả các chi tiết thiết kế tẻ nhạt như kích thước, chiều dài và vị trí lưới dễ dàng hơn rất nhiều khi sử dụng trình thiết kế .

Để chứng minh quan điểm này, đây là một ví dụ về cách xác định một trang tĩnh đơn giản với hình ảnh và văn bản:

DesignElementHandle element = factory.newSimpleMasterPage("Page Master"); design.getMasterPages().add(element); GridHandle grid = factory.newGridItem(null, 2, 1); design.getBody().add(grid); grid.setWidth("100%"); RowHandle row0 = (RowHandle) grid.getRows().get(0); ImageHandle image = factory.newImage(null); CellHandle cell = (CellHandle) row0.getCells().get(0); cell.getContent().add(image); image.setURL("\"//www.baeldung.com/wp-content/themes/baeldung/favicon/favicon-96x96.png\""); LabelHandle label = factory.newLabel(null); cell = (CellHandle) row0.getCells().get(1); cell.getContent().add(label); label.setText("Hello, Baeldung world!");

Mã này sẽ tạo một báo cáo đơn giản (và xấu xí):

Báo cáo mẫu hiển thị trong hình trên có thể được tìm thấy tại đường dẫn này: /reports/static_report.rptdesign.

Khi chúng tôi đã mã hóa giao diện của báo cáo và dữ liệu mà nó sẽ hiển thị, chúng tôi có thể tạo tệp XML bằng cách chạy lớp ReportDesignApplication của chúng tôi .

7. Đính kèm nguồn dữ liệu

Chúng tôi đã đề cập trước đó rằng BIRT hỗ trợ nhiều nguồn dữ liệu khác nhau.

Đối với dự án mẫu của chúng tôi, chúng tôi đã sử dụng một tệp CSV đơn giản với ba mục nhập. Nó có thể được tìm thấy trong thư mục báo cáo và bao gồm ba hàng dữ liệu đơn giản, cùng với các tiêu đề:

Student, Math, Geography, History Bill, 10,3,8 Tom, 5,6,5 Anne, 7, 4,9

7.1. Định cấu hình nguồn dữ liệu

Để cho phép BIRT sử dụng tệp của chúng tôi (hoặc bất kỳ loại nguồn nào khác), chúng tôi phải định cấu hình Nguồn dữ liệu .

Đối với tệp của chúng tôi, chúng tôi đã tạo Nguồn dữ liệu tệp phẳng với trình thiết kế báo cáo, tất cả chỉ trong vài bước:

  1. Open the designer perspective and look at the outline on the right.
  2. Right-click on the Data Sources icon.
  3. Select the desired source type (in our case the flat file source).
  4. We can now choose either to load an entire folder or just one file. We used the second option (if our data file is in CSV format, we want to make sure to use the first line as column name indicator).
  5. Test the connection to make sure the path is correct.

We attached some pictures to show each step:

7.2. The Data Set

The data source is ready, but we still need to define our Data Set, which is the actual data shown in our report:

  1. Open the designer perspective and look at the outline on the right.
  2. Right-click on the Data Sets icon.
  3. Select the desired Data Source and the type (in our case there's only one type).
  4. The next screen depends on the type of data source and data set we're selected: in our case, we see a page where we can select the columns to include.
  5. Once the setup is complete, we can open the configuration at any time by double-clicking on our data set.
  6. In Output Columns, we can set the right type of the data displayed.
  7. We can then look at a preview by clicking on Preview Results.

Again, some pictures to clarify these steps:

7.3. Other Data Source Types

As mentioned in step 4 of the Data Set configuration, the options available may change depending on the Data Source referred.

For our CSV file, BIRT gives options related to which columns to show, the data type, and if we want to load the entire file. On the other hand, if we had a JDBC data source, we may have to write an SQL query or a stored procedure.

From the Data Sets menu, we can also join two or more data sets in a new data set.

8. Rendering the Report

Once the report file is ready, we have to pass it to the engine for rendering. To do this, there are a few things to implement.

8.1. Initializing the Engine

The ReportEngine class, which interprets the design files and generates the final result, is part of the BIRT runtime library.

It uses a bunch of helpers and tasks to do the job, making it quite resource-intensive:

Image source: Eclipse BIRT documentation

There is a significant cost associated with creating an engine instance, due primarily to the cost of loading extensions. Therefore, we should create just one ReportEngine instance and use it to run multiple reports.

The report engine is created through a factory supplied by the Platform. Before creating the engine, we have to start the Platform, which will load the appropriate plug-ins:

@PostConstruct protected void initialize() throws BirtException { EngineConfig config = new EngineConfig(); config.getAppContext().put("spring", this.context); Platform.startup(config); IReportEngineFactory factory = (IReportEngineFactory) Platform .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY); birtEngine = factory.createReportEngine(config); imageFolder = System.getProperty("user.dir") + File.separatorChar + reportsPath + imagesPath; loadReports(); }

When we don't need it anymore, we can destroy it:

@Override public void destroy() { birtEngine.destroy(); Platform.shutdown(); }

8.2. Implementing the Output Format

BIRT already supports multiple output formats:HTML, PDF, PPT, and ODT, to name a few.

For the sample project, we implemented two of them with the methods generatePDFReport and generateHTMLReport.

They differ slightly depending on the specific properties needed, such as output format and image handlers.

In fact, PDFs embed images together with text, while HTML reports need to generate them and/or link them.

Thus, the PDF rendering function is quite straightforward:

private void generatePDFReport(IReportRunnable report, HttpServletResponse response, HttpServletRequest request) { IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(report); response.setContentType(birtEngine.getMIMEType("pdf")); IRenderOption options = new RenderOption(); PDFRenderOption pdfRenderOption = new PDFRenderOption(options); pdfRenderOption.setOutputFormat("pdf"); runAndRenderTask.setRenderOption(pdfRenderOption); runAndRenderTask.getAppContext().put(EngineConstants.APPCONTEXT_PDF_RENDER_CONTEXT, request); try { pdfRenderOption.setOutputStream(response.getOutputStream()); runAndRenderTask.run(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { runAndRenderTask.close(); } }

While the HTML rendering function needs more settings:

private void generateHTMLReport(IReportRunnable report, HttpServletResponse response, HttpServletRequest request) { IRunAndRenderTask runAndRenderTask = birtEngine.createRunAndRenderTask(report); response.setContentType(birtEngine.getMIMEType("html")); IRenderOption options = new RenderOption(); HTMLRenderOption htmlOptions = new HTMLRenderOption(options); htmlOptions.setOutputFormat("html"); htmlOptions.setBaseImageURL("/" + reportsPath + imagesPath); htmlOptions.setImageDirectory(imageFolder); htmlOptions.setImageHandler(htmlImageHandler); runAndRenderTask.setRenderOption(htmlOptions); runAndRenderTask.getAppContext().put( EngineConstants.APPCONTEXT_BIRT_VIEWER_HTTPSERVET_REQUEST, request); try { htmlOptions.setOutputStream(response.getOutputStream()); runAndRenderTask.run(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } finally { runAndRenderTask.close(); } }

Most noteworthy, we set the HTMLServerImageHandler instead of leaving the default handler. This small difference has a big impact on the generated img tag:

  • the default handler links the img tag to the file system path, blocked for security by many browsers
  • the HTMLServerImageHandler links to the server URL

With the setImageDirectory method, we specify where the engine will save the generated image file.

By default, the handler generates a new file at every request, so we could add a caching layer or a deletion policy.

8.3. Publishing the Images

In the HTML report case, image files are external, so they need to be accessible on the server path.

In the code above, with the setBaseImageURL method, we tell the engine what relative path should be used in the img tag link, so we need to make sure that the path is actually accessible!

For this reason, in our ReportEngineApplication, we configured Spring to publish the images folder:

@SpringBootApplication @EnableWebMvc public class ReportEngineApplication implements WebMvcConfigurer { @Value("${reports.relative.path}") private String reportsPath; @Value("${images.relative.path}") private String imagesPath; ... @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler(reportsPath + imagesPath + "/**") .addResourceLocations("file:///" + System.getProperty("user.dir") + "/" + reportsPath + imagesPath); } }

Whatever the path we choose, we have to make sure the same path is used here and in the htmlOptions of the previous snippet, or our report won't be able to display images.

9. Displaying the Report

The last component needed to get our application ready is a Controller to return the rendered result:

@RequestMapping(method = RequestMethod.GET, value = "/report/{name}") @ResponseBody public void generateFullReport(HttpServletResponse response, HttpServletRequest request, @PathVariable("name") String name, @RequestParam("output") String output) throws EngineException, IOException { OutputType format = OutputType.from(output); reportService.generateMainReport(name, format, response, request); }

With the output parameter, we can let the user choose the desired format — HTML or PDF.

10. Testing the Report

We can start the application by running the ReportEngineApplication class.

During startup, the BirtReportService class will load all the reports found in the /reports folder.

To see our reports in action, we just need to point our browser to:

  • /report/csv_data_report?output=pdf
  • /report/csv_data_report?output=html
  • /report/static_report?output=pdf
  • /report/static_report?output=html

Here is how the csv_data_report report looks:

To reload a report after changing the design file, we just point our browser to /report/reload.

11. Conclusion

Trong bài viết này, chúng tôi đã tích hợp BIRT với Spring Boot, khám phá những cạm bẫy và thách thức, cũng như sức mạnh và tính linh hoạt của nó.

Mã nguồn của bài viết hiện có trên GitHub.