package au.com.vaadinutils.jasper.ui; import java.io.File; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.google.common.base.Preconditions; import com.vaadin.data.Item; import com.vaadin.event.UIEvents.PollEvent; import com.vaadin.event.UIEvents.PollListener; import com.vaadin.server.ExternalResource; import com.vaadin.server.StreamResource; import com.vaadin.server.StreamResource.StreamSource; import com.vaadin.server.VaadinServlet; import com.vaadin.shared.ui.MarginInfo; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.BrowserFrame; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Component; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.JavaScript; import com.vaadin.ui.JavaScriptFunction; import com.vaadin.ui.Label; import com.vaadin.ui.NativeButton; import com.vaadin.ui.Notification; import com.vaadin.ui.Notification.Type; import com.vaadin.ui.Table; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.themes.Reindeer; import au.com.vaadinutils.dao.JpaBaseDao; import au.com.vaadinutils.jasper.JasperManager; import au.com.vaadinutils.jasper.JasperManager.OutputFormat; import au.com.vaadinutils.jasper.JasperProgressListener; import au.com.vaadinutils.jasper.QueueEntry; import au.com.vaadinutils.jasper.ReportStatus; import au.com.vaadinutils.jasper.filter.ExpanderComponent; import au.com.vaadinutils.jasper.filter.ReportFilterUIBuilder; import au.com.vaadinutils.jasper.parameter.ReportChooser; import au.com.vaadinutils.jasper.parameter.ReportParameter; import au.com.vaadinutils.jasper.parameter.ReportParameterConstant; import au.com.vaadinutils.jasper.scheduler.JasperReportEmailWindow; import au.com.vaadinutils.jasper.scheduler.JasperReportSchedulerWindow; import au.com.vaadinutils.jasper.scheduler.ScheduleIconBuilder; import au.com.vaadinutils.jasper.scheduler.entities.ReportEmailScheduleEntity; import au.com.vaadinutils.jasper.scheduler.entities.ReportEmailScheduleEntity_; import au.com.vaadinutils.listener.CancelListener; import au.com.vaadinutils.listener.ClickEventLogged; import au.com.vaadinutils.ui.WorkingDialog; import elemental.json.JsonArray; import elemental.json.JsonObject; /** * Base class for a view that provides a report filter selection area and a * report viewing area. */ class JasperReportLayout extends VerticalLayout { private static final int MAX_FILENAME_LENGTH = 100; /** * */ private static final long serialVersionUID = -4195751089112256038L; public static final String NAME = "ReportView"; static private final transient Logger logger = LogManager.getLogger(); private static final String PRINT_PANEL_ID = "nj-print-panel-id-for-pdf"; private BrowserFrame displayPanel; JasperManager manager; private ReportFilterUIBuilder builder; private List<ExpanderComponent> components; private NativeButton showButton; private NativeButton printButton; private NativeButton exportButton; private VerticalLayout splash; private JasperReportProperties reportProperties; private BrowserFrame csv; private SplitPanel splitPanel; private NativeButton scheduleButton; private NativeButton emailButton; protected JasperReportLayout(JasperReportProperties reportProperties) { this.reportProperties = reportProperties; this.builder = reportProperties.getFilterBuilder(); } protected void initScreen(SplitPanel panel) { this.setSizeFull(); splitPanel = panel; this.addComponent(splitPanel.getComponent()); splitPanel.setFirstComponent((AbstractComponent) getOptionsPanel()); splash = new VerticalLayout(); splash.setMargin(true); Label titleLabel = new Label("<h1>" + reportProperties.getReportTitle() + "</h1>"); titleLabel.setContentMode(ContentMode.HTML); splash.addComponent(titleLabel); Label splashLabel = new Label( "<font size='4' >Set the desired filters and click a print button to generate a report</font>"); splashLabel.setContentMode(ContentMode.HTML); splitPanel.setSecondComponent(splash); // generate the report immediately if there are no visible filters if (!builder.hasFilters()) { splashLabel = new Label("<font size='4' >Please wait whilst we generate your report</font>"); splashLabel.setContentMode(ContentMode.HTML); // disable the buttons and any filters printButton.setEnabled(false); exportButton.setEnabled(false); showButton.setEnabled(false); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(false); } // what follows is a horrible hack... // if we create the progress dialog at the same time as the popup // report window the progress dialog will be behind the popup report // window. // so I've created a refresher, and 1 seconds after the popup report // window opens we kick of the report generation which creates the // progress dialog then, which allows it to be in front. UI.getCurrent().setPollInterval(500); UI.getCurrent().addPollListener(new PollListener() { private static final long serialVersionUID = 1L; @Override public void poll(PollEvent event) { try { UI.getCurrent().setPollInterval(-1); UI.getCurrent().removePollListener(this); generateReport(reportProperties.getDefaultFormat(), JasperReportLayout.this.builder.getReportParameters()); } catch (Exception e) { logger.catching(e); Notification.show("Error", e.getMessage(), Type.ERROR_MESSAGE); } } }); } splash.addComponent(splashLabel); JavaScript.getCurrent().addFunction("au.com.noojee.reportDrillDown", new JavaScriptFunction() { // expected syntax of a call to this javascript hook method // // window.parent.au.com.noojee.reportDrillDown( // { // 'reportFileName': // 'CallDetailsPerTeamAgentPerHour_CallDetails.jasper', // 'reportTitle': 'Call Details Per Team Agent Per Hour' // }, // { // 'ReportParameterStartDate'='$P{StartDate}', // 'ReportParameterEndDate'='$P{EndDate}', // 'ReportParameterExtension'='$F{loginid}', // 'ReportParameterTeamId'='$P{TeamId}', // 'ReportParameterHour'='$F{Day}.toString()' // } // // ); private static final long serialVersionUID = 1L; @Override public void call(JsonArray arguments) { try { JsonObject args = arguments.getObject(0); String subReportFileName = args.getString("ReportFileName"); String subTitle = args.getString("ReportTitle"); JsonObject params = arguments.getObject(1); List<ReportParameter<?>> subFilters = new LinkedList<>(); boolean insitue = false; String[] itr = params.keys(); for (String key : itr) { if (key.equalsIgnoreCase("ReportParameterInsitue")) { insitue = true; } else { subFilters.add(new ReportParameterConstant<>(key, params.getString(key), key, params.getString(key))); } } if (!insitue) { new JasperReportPopUp(new ChildJasperReportProperties(reportProperties, subTitle, subReportFileName, subFilters)); } else { generateReport(OutputFormat.HTML, subFilters); } } catch (Exception e) { logger.error(arguments.toString()); logger.error(e, e); } } }); } private Component getOptionsPanel() { VerticalLayout layout = new VerticalLayout(); layout.setId("OptionsPanel"); // layout.setMargin(new MarginInfo(false, false, false, false)); // layout.setSpacing(true); layout.setSizeFull(); String buttonHeight = "" + 40; HorizontalLayout buttonBar = new HorizontalLayout(); buttonBar.setSpacing(true); buttonBar.setStyleName(Reindeer.LAYOUT_BLUE); buttonBar.setWidth("100%"); buttonBar.setHeight(buttonHeight); buttonBar.setMargin(new MarginInfo(false, false, false, false)); HorizontalLayout buttonContainer = new HorizontalLayout(); buttonContainer.setSizeFull(); buttonContainer.setWidth("230"); showButton = new NativeButton(); showButton.setIcon(new ExternalResource("images/seanau/Print preview.png")); showButton.setDescription("Preview"); showButton.setWidth("50"); showButton.setHeight(buttonHeight); showButton.setDisableOnClick(true); addButtonListener(showButton, OutputFormat.HTML); buttonContainer.addComponent(showButton); printButton = new NativeButton(); printButton.setIcon(new ExternalResource("images/seanau/Print_32.png")); printButton.setDescription("Print (PDF)"); printButton.setWidth("50"); printButton.setHeight(buttonHeight); printButton.setDisableOnClick(true); addButtonListener(printButton, OutputFormat.PDF); buttonContainer.addComponent(printButton); exportButton = new NativeButton(); exportButton.setDescription("Export (Excel - CSV)"); exportButton.setIcon(new ExternalResource("images/exporttoexcel.png")); exportButton.setWidth("50"); exportButton.setDisableOnClick(true); exportButton.setHeight(buttonHeight); // exportButton.setStyleName(Reindeer.BUTTON_LINK); addButtonListener(exportButton, OutputFormat.CSV); buttonContainer.addComponent(exportButton); createEmailButton(buttonHeight, buttonContainer); createScheduleButton(buttonHeight, buttonContainer); if (reportProperties instanceof JasperReportPopUp) { // This is disabled because there are serious problems with // transient (JasperReportProperties is not aware of them) // parameters in drill // downs, these can not currently be save or represented in the // ReportEmailSchedule emailButton.setEnabled(false); scheduleButton.setEnabled(false); } buttonBar.addComponent(buttonContainer); layout.addComponent(buttonBar); components = builder.buildLayout(false); if (components.size() > 0) { VerticalLayout filterPanel = new VerticalLayout(); filterPanel.setMargin(new MarginInfo(false, false, true, false)); filterPanel.setSpacing(true); filterPanel.setSizeFull(); Label filterLabel = new Label("<b>Filters</b>"); filterLabel.setStyleName(Reindeer.LABEL_H2); filterLabel.setContentMode(ContentMode.HTML); filterPanel.addComponent(filterLabel); for (ExpanderComponent componet : components) { filterPanel.addComponent(componet.getComponent()); if (componet.shouldExpand()) { filterPanel.setExpandRatio(componet.getComponent(), 1); } } layout.addComponent(filterPanel); layout.setExpandRatio(filterPanel, 1.0f); } // hidden frame for downloading csv csv = new BrowserFrame(); csv.setVisible(true); csv.setHeight("1"); csv.setWidth("1"); csv.setImmediate(true); layout.addComponent(csv); return layout; } private void createEmailButton(String buttonHeight, HorizontalLayout buttonContainer) { emailButton = new NativeButton(); emailButton.setIcon(new ExternalResource("images/seanau/Send Email_32.png")); emailButton.setDescription("Email"); emailButton.setWidth("50"); emailButton.setHeight(buttonHeight); emailButton.addClickListener(new ClickEventLogged.ClickListener() { private static final long serialVersionUID = 7207441556779172217L; @Override public void clicked(ClickEvent event) { new JasperReportEmailWindow(reportProperties, builder.getReportParameters()); } }); buttonContainer.addComponent(emailButton); } private void createScheduleButton(String buttonHeight, HorizontalLayout buttonContainer) { scheduleButton = new NativeButton(); JpaBaseDao<ReportEmailScheduleEntity, Long> dao = JpaBaseDao.getGenericDao(ReportEmailScheduleEntity.class); Long count = dao.getCount(ReportEmailScheduleEntity_.JasperReportPropertiesClassName, reportProperties.getReportClass().getCanonicalName()); ScheduleIconBuilder iconBuilder = new ScheduleIconBuilder(); String baseIconFileName = "Call Calendar_32"; String path = VaadinServlet.getCurrent().getServletContext().getRealPath("templates/images/seanau/"); // HACK: scoutmaster stores images in a different directory so if the // images isn't found in the above templates directory // then search in the /images/seanau director. if (path == null || !new File(path).exists()) { path = VaadinServlet.getCurrent().getServletContext().getRealPath("/images/seanau/"); } String targetFileName = baseIconFileName + "-" + count + ".png"; iconBuilder.buildLogo(count.intValue(), new File(path), baseIconFileName + ".png", targetFileName); scheduleButton.setIcon(new ExternalResource("images/seanau/" + targetFileName)); scheduleButton.setDescription("Schedule"); scheduleButton.setWidth("50"); scheduleButton.setHeight(buttonHeight); scheduleButton.addClickListener(new ClickEventLogged.ClickListener() { private static final long serialVersionUID = 7207441556779172217L; @Override public void clicked(ClickEvent event) { new JasperReportSchedulerWindow(reportProperties, builder.getReportParameters()); } }); buttonContainer.addComponent(scheduleButton); } private void addButtonListener(Button button, final OutputFormat format) { button.addClickListener(new ClickEventLogged.ClickListener() { private static final long serialVersionUID = 1L; @Override public void clicked(ClickEvent event) { try { printButton.setEnabled(false); exportButton.setEnabled(false); showButton.setEnabled(false); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(false); } generateReport(format, JasperReportLayout.this.builder.getReportParameters()); } catch (Exception e) { printButton.setEnabled(true); exportButton.setEnabled(true); showButton.setEnabled(true); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(true); } Notification.show(e.getMessage(), Type.ERROR_MESSAGE); logger.error(e, e); } finally { } } }); } private BrowserFrame getDisplayPanel() { displayPanel = new BrowserFrame(); displayPanel.setSizeFull(); displayPanel.setStyleName("njadmin-hide-overflow-for-help"); displayPanel.setImmediate(true); displayPanel.setId(PRINT_PANEL_ID); splitPanel.setSecondComponent(displayPanel); return displayPanel; } /** * intended to allow the pdf to print as it opens. * * firefox prevents it, and for large pdfs in chrome it is firing too soon * and prevents display of the pdf as a result */ protected void readyToPrint(final OutputFormat format) { // if (format == OutputFormat.PDF) // { // JavaScript.getCurrent().execute( // "window.document.getElementById('" + PRINT_PANEL_ID + // "').childNodes[0].contentWindow.focus()"); // JavaScript.getCurrent().execute( // "window.document.getElementById('" + PRINT_PANEL_ID + // "').childNodes[0].contentWindow.print()"); // } } private boolean cancelled; volatile boolean streamReady = false; volatile boolean streamConnected = false; private void generateReport(final JasperManager.OutputFormat outputFormat, final Collection<ReportParameter<?>> params) { streamConnected = false; streamReady = false; splitPanel.setSecondComponent(splash); manager = null; boolean validParams = true; // if there is a report chooser parameter then swap out the report // manager for the selected report. for (ReportParameter<?> p : params) { if (p instanceof ReportChooser) { ReportChooser chooser = (ReportChooser) p; manager = new JasperManager(chooser.getReportProperties(reportProperties)); Preconditions.checkNotNull(manager, "chooser returned a NULL JasperManager."); } else { validParams &= p.validate(); } } if (!validParams) { Notification.show("Please correct your filters and try again", Type.ERROR_MESSAGE); printButton.setEnabled(true); exportButton.setEnabled(true); showButton.setEnabled(true); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(true); } return; } if (manager == null) { this.manager = new JasperManager(reportProperties); } CancelListener cancelListener = getProgressDialogCancelListener(); final WorkingDialog dialog = new WorkingDialog("Generating report, please be patient", "Please wait", cancelListener); dialog.setHeight("150"); UI.getCurrent().addWindow(dialog); getProgressDialogRefreshListener(dialog); getStreamConnectorRefreshListener(outputFormat); JasperProgressListener listener = getJasperManagerProgressListener(UI.getCurrent(), dialog, outputFormat); manager.exportAsync(outputFormat, params, listener); } private JasperProgressListener getJasperManagerProgressListener(final UI ui, final WorkingDialog dialog, final OutputFormat outputFormat) { JasperProgressListener listener = new JasperProgressListener() { @Override public void outputStreamReady() { // flag that the stream is ready, and on the next // "Refresher call" it will connect to the stream streamReady = true; } @Override public void failed(String string) { // show the error message Notification.show(string, Type.ERROR_MESSAGE); reportFinished(); } private void reportFinished() { // re-enable fields after report finished. printButton.setEnabled(true); exportButton.setEnabled(true); showButton.setEnabled(true); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(true); } dialog.close(); } @Override public void completed() { ui.access(new Runnable() { @Override public void run() { reportFinished(); JasperReportLayout.this.readyToPrint(outputFormat); } }); } }; return listener; } private void getStreamConnectorRefreshListener(final JasperManager.OutputFormat outputFormat) { UI.getCurrent().setPollInterval(500); UI.getCurrent().addPollListener(new PollListener() { private static final long serialVersionUID = -5641305025399715756L; @Override public void poll(PollEvent event) { if (streamReady && !streamConnected) { // jasper manager is ready, so get the report stream and set // it as the source for the display panel streamConnected = true; StreamResource resource = new StreamResource(getReportStream(), "report"); resource.setMIMEType(outputFormat.getMimeType()); resource.setCacheTime(-1); resource.setFilename(exportFileName(outputFormat)); if (outputFormat == OutputFormat.CSV) { csv.setSource(resource); showCsvSplash(); } else { getDisplayPanel().setSource(resource); } UI.getCurrent().removePollListener(this); } } private String exportFileName(final JasperManager.OutputFormat outputFormat) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); String name = reportProperties.getReportTitle() + "At" + sdf.format(new Date()); for (ReportParameter<?> param : builder.getReportParameters()) { if (param.showFilter()) { name += "-" + param.getLabel(""); for (String parameterName : param.getParameterNames()) { name += "-" + param.getDisplayValue(parameterName); } } } if (name.length() > MAX_FILENAME_LENGTH) { name = name.substring(0, MAX_FILENAME_LENGTH); } name = name.replace("/", "-"); return name + outputFormat.getFileExtension(); } }); } private void showCsvSplash() { VerticalLayout csvSplash = new VerticalLayout(); csvSplash.setMargin(true); Label titleLabel = new Label("<h1>" + reportProperties.getReportTitle() + "</h1>"); titleLabel.setContentMode(ContentMode.HTML); csvSplash.addComponent(titleLabel); Label label = new Label("<font size='4' >Excel (CSV) download initiated.</font>"); label.setContentMode(ContentMode.HTML); csvSplash.addComponent(label); splitPanel.setSecondComponent(csvSplash); } private StreamSource getReportStream() { @SuppressWarnings("serial") StreamSource source = new StreamSource() { @Override public InputStream getStream() { try { InputStream stream = manager.getStream(); if (stream == null) { Notification.show("Couldn't attach to stream, if you cancelled a report this is normal", Type.ERROR_MESSAGE); } return stream; } catch (InterruptedException e) { logger.error(e, e); } return null; } }; return source; } private void getProgressDialogRefreshListener(final WorkingDialog dialog) { final Table reportQueue = new Table(); reportQueue.addContainerProperty("Time", String.class, ""); reportQueue.addContainerProperty("Report Name", String.class, ""); reportQueue.addContainerProperty("User", String.class, ""); reportQueue.addContainerProperty("Status", String.class, ""); reportQueue.setSizeFull(); reportQueue.setHeight("150"); reportQueue.setWidth("100%"); reportQueue.setColumnWidth("Time", 50); reportQueue.setColumnWidth("Report Name", 150); reportQueue.setColumnWidth("User", 100); // reportQueue.setColumnWidth("Status", 100); UI.getCurrent().setPollInterval(500); UI.getCurrent().addPollListener(new PollListener() { int refreshDivider = 0; boolean tableAdded = false; private static final long serialVersionUID = -5641305025399715756L; @SuppressWarnings("unchecked") @Override public void poll(PollEvent event) { if (manager == null) { return; } ReportStatus status = manager.getStatus(); dialog.progress(0, 0, status.getStatus()); if (refreshDivider % 4 == 0) { if (status.getEntries().size() > 0) { reportQueue.removeAllItems(); for (QueueEntry entry : status.getEntries()) { Object id = reportQueue.addItem(); Item item = reportQueue.getItem(id); item.getItemProperty("Time").setValue(entry.getTime()); item.getItemProperty("Report Name").setValue(entry.getReportName()); item.getItemProperty("User").setValue(entry.getUser()); item.getItemProperty("Status").setValue(entry.getStatus()); } if (!tableAdded) { dialog.addUserComponent(reportQueue); tableAdded = true; } dialog.setWidth("600"); dialog.setHeight("350"); dialog.center(); } else { dialog.removeUserComponent(reportQueue); dialog.setWidth("300"); dialog.setHeight("150"); dialog.center(); } } if (cancelled) { printButton.setEnabled(true); exportButton.setEnabled(true); showButton.setEnabled(true); for (ExpanderComponent componet : components) { componet.getComponent().setEnabled(true); } UI.getCurrent().removePollListener(this); UI.getCurrent().setPollInterval(-1); dialog.close(); } refreshDivider++; } }); } private CancelListener getProgressDialogCancelListener() { cancelled = false; CancelListener cancelListener = new CancelListener() { @Override public void cancel() { manager.cancelPrint(); cancelled = true; // change to the splash page as the report may still complete splitPanel.setSecondComponent(splash); } }; return cancelListener; } }