JMIX Version 2.7.5 Error displaying pdf document generated with reports add-on

Hi,

In production mode, runAndShow function of the uiReportRunner interface displays PDF as raw binary/compressed data instead of readable text in the browser.

Jmix Studio Premium 2.7.5
IntelliJ IDEA 2025.3.3

Thanks in advance,

PDFError

1 Like

Hi

There is really a problem with the uiReportRunner.

If you run the actual jmx-crm from GitHub which is version 2.7.4, go to Invoices, select 1 invoice and download this invoice, you get a nice new tab with the invoice.

Now you update this project to version 2.7.5 and do the same; there is no invoice displayed and system offers a download of [GUID] file without any file extension.

One of my application with

uiReportRunner.byReportEntity(report)
   .withOutputType(ReportOutputType.PDF)
   .withParametersDialogShowMode(ParametersDialogShowMode.NO)
   .addParam("Account", account) //  Parameter alias
   .addParam("dateFrom", dateFrom)
   .addParam("dateTo", dateTo)
   .addParam(JRParameter.REPORT_LOCALE, getLocale())
   .runAndShow();

does the same; instead of opening a pdf with the defined filename, the user get a strange file download.

I think, before you distribute this version, this really should be repared.

Regards
Felix

2 Likes

Hi!

Thank you for reporting the issue. This is a regression, and we’re working on a fix.

Best regards,
Dmitriy

1 Like

I’m also having this issue, pdf report is generated without a file ext and resulting no display.

As a temporary workaround you can do the following:

Create a FixedFileDownloader class:

public class FixedFileDownloader extends JmixFileDownloader {

    private static final Logger log = LoggerFactory.getLogger(FixedFileDownloader.class);

    protected void beforeClientResponseDownloadHandler(UI ui) {
        String identifier = DOWNLOAD_RESOURCE_PREFIX + UUID.randomUUID();

        requestHandler = (session, request, response) -> {
            if (request.getPathInfo().endsWith(identifier)) {

                String type = isViewDocumentRequest ? "inline" : "attachment";

                response.setStatus(HttpServletResponse.SC_OK);
                response.setHeader(
                        "Content-Disposition",
                        ContentDisposition.builder(type)
                                .filename(getFileName(session, request), StandardCharsets.UTF_8)
                                .build()
                                .toString());
                response.setHeader("Cache-Control", "private, max-age=%s".formatted(cacheMaxAgeSec));

                if (isViewDocumentRequest && Strings.isNotEmpty(contentType)) {
                    response.setContentType(contentType);
                }

                try {
                    contentWriter.andThen(this::afterWriteHandler)
                            .accept(response.getOutputStream());
                    log.debug("response {} has been sent", response);
                } catch (IOException | RuntimeException e) {
                    if (!isViewDocumentRequest
                            || fileNotFoundExceptionHandler == null
                            || !fileNotFoundExceptionHandler.test(new FileNotFoundContext(e, response))) {

                        if (response instanceof VaadinServletResponse servletResponse) {
                            HttpServletResponse httpResponse = servletResponse.getHttpServletResponse();

                            if (httpResponse != null && httpResponse.isCommitted()) {
                                log.warn("Response is already committed. Status code cannot be changed.");
                            } else {
                                applyErrorHeaders(response);
                            }
                        }

                        // send exception further
                        // UI access is required to correct exception handling using UiExceptionHandlers
                        getUI().ifPresent(currentUi -> currentUi.access(() -> {
                            // should be removed manually because fileDownloaderRemoveHandler will not be executed
                            // due to DownloadFinishedEvent will not be published
                            removeFromParent();

                            throw new RuntimeException(e);
                        }));

                        return false;
                    } else {
                        // exception is handled in listener
                        return true;
                    }
                } finally {
                    response.getOutputStream().close();
                }

                return true;
            }
            return false;
        };

        ui.getSession().addRequestHandler(requestHandler);

        getContent().setHref("./" + identifier);
    }
}

Create a FixedDownloader bean:

public class FixedDownloader extends DownloaderImpl {

    public void download(DownloadDataProvider dataProvider,
                         String resourceName,
                         @Nullable DownloadFormat downloadFormat) {
        checkUIAccess();

        boolean showNewWindow = this.newWindow;

        // Replace all invalid 'resourceName' characters with underscores before downloading.
        // 'resourceName' parameter value will be used in URI (generated when resource is registered)
        // in a way that the name is the last segment of the path
        resourceName = normalize(resourceName);

        if (useViewList) {
            String fileExt;

            if (downloadFormat != null) {
                fileExt = downloadFormat.getFileExt();
            } else {
                fileExt = FilenameUtils.getExtension(resourceName);
            }

            showNewWindow = viewFilePredicate.test(StringUtils.lowerCase(fileExt));
        }

        if (downloadFormat != null) {
            if (StringUtils.isEmpty(FilenameUtils.getExtension(resourceName))) {
                resourceName += "." + downloadFormat.getFileExt();
            }
        }

        FixedFileDownloader fileDownloader = new FixedFileDownloader();

        UI ui = UI.getCurrent();

        ui.add(fileDownloader);

        fileDownloader.setFileName(resourceName);
        fileDownloader.setCacheMaxAgeSec(uiProperties.getFileDownloaderCacheMaxAgeSec());
        fileDownloader.addDownloadFinishedListener(this::fileDownloaderRemoveHandler);
        fileDownloader.setFileNotFoundExceptionHandler(this::handleFileNotFoundException);

        StreamResource resource = new StreamResource(resourceName, dataProvider::getStream);

        if (downloadFormat != null && StringUtils.isNotEmpty(downloadFormat.getContentType())) {
            resource.setContentType(downloadFormat.getContentType() + DEFAULT_CHARSET_SUFFIX);
        } else {
            resource.setContentType(FileTypesHelper.getMIMEType(resourceName) + DEFAULT_CHARSET_SUFFIX);
        }

        if (showNewWindow && isBrowserSupportsPopups() || isIPhone()) {
            fileDownloader.viewDocument(resource);
        } else {
            fileDownloader.downloadFile(resource);
        }
    }
}

Register FixedDownloader bean in your app configuration class:

    @Bean("flowui_Downloader")
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    @Primary
    public Downloader downloader() {
        return new FixedDownloader();
    }

Don’t forget to set application property to be able to override spring beans:

spring.main.allow-bean-definition-overriding=true

Best regards,
Dmitriy

Hi @d.kremnev

Thank you for the workaround.

How long is planned to offer the community a known damaged update to 2.7.5 ? I would suggest, to remove this version for the moment, until this is fixed.

What are the plans ?

Regards
Felix

1 Like

Hi Felix,
Unfortunately we cannot remove a release, it’s already in public repositories.
We are building the new release 2.7.6 with the fix. It will be available tomorrow.

Regards,
Konstantin

5 Likes

Hi @krivopustov

Thank you for your fast action !

Best regards

1 Like

@krivopustov Sorry, this question is not fully related to that topic: With how much delay does the more reliable global repository gets updated to new artifact versions?

I can find 2.7.6 on https://nexus.jmix.io, but not yet on https://global.repo.jmix.io/. Is it a question of minutes, hours, days?

It’s a question of hours. We do last tests on https://nexus.jmix.io and then publish to global.

1 Like