# ReportFactory.java

    /*
    ** Module   : ReportFactory.java
    ** Abstract : Factory providing reporting capabilities to 4GL.
    **
    ** Copyright (c) 2017-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 SVL 20171030 Created initial version.
    ** 002 SVL 20171102 Load files using FwdJasperRepositoryService.
    ** 003 SVL 20180209 Added getBrowseReport.
    ** 004 EVL 20180423 Renamed Report to FwdReport.
    ** 005 OM  20181003 Separated paged and non-paged default templates.
    ** 006 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.reporting;

    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.logging.*;
    import net.sf.jasperreports.engine.*;
    import net.sf.jasperreports.engine.util.*;

    import java.io.*;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.logging.Level;

    /**
     * Factory providing reporting capabilities to 4GL.
     *
     * TODO: reports need to be dropped to GC at the end of life of their parent [browse] !!!
     */
    public class ReportFactory
    {
       /** Extension of Jasper report design files. */
       private static final String JRXML_EXTENSION = ".jrxml";

       /** Extension of compiled Jasper report files. */
       private static final String JASPER_EXTENSION = ".jasper";

       /**
        * Default browse report design template for PDF exports. These reports have constraints
        * related to pagination and the length of the text in tables' cells caused by page width.
        */
       private static final String DEFAULT_BROWSE_PDF_REPORT_TEMPLATE_FILE =
             "com/goldencode/p2j/reporting/templates/browse-pdf-report.jrxml";

       /**
        * Default browse report design template for CVS, XLS and XLSX exports. These reports do not
        * have any constraints related to pagination and the exported text in tables is not cut.
        */
       private static final String DEFAULT_BROWSE_XLS_REPORT_TEMPLATE_FILE =
             "com/goldencode/p2j/reporting/templates/browse-xls-report.jrxml";

       /** Logger. */
       private static final CentralLogger LOG = CentralLogger.get(ReportFactory.class.getName());

       /**
        * Jasper report objects cached by report design file names.
        */
       private final static Map<String, JasperReport> jasperReports = new HashMap<>();

       /** Prepared browse report design templates keyed by design template file names. */
       private final static Map<String, BrowseJasperReportTemplate> browseReportTemplates =
                                                                                  new HashMap<>();
       /** Compiled browse reports. */
       private final static Map<BrowseReportKey, JasperReport> browseReports =
                                                                      new ConcurrentHashMap<>();

       /**
        * Create a {@link FwdReport} object.
        *
        * @param reportHandle
        *        Handle which points to the created report.
        */
       public static void create(handle reportHandle)
       {
          reportHandle.assign(new JasperReportWrapper());
       }

       /**
        * Create a {@link FwdReport} object which uses the given browse as the data source.
        *
        * @param reportHandle
        *        Handle which points to the created report.
        * @param browse
        *        Browse used as the data source for the created report.
        */
       public static void create(handle reportHandle, BrowseWidget browse)
       {
          JasperReportWrapper report = new JasperReportWrapper();
          report.setReportDataSource(new handle(browse));
          reportHandle.assign(report);
       }

       /**
        * Show error as a message or message box.
        *
        * @param e
        *        Exception which caused the error.
        */
       public static void showError(Exception e)
       {
          showError(null, e);
       }

       /**
        * Show error as a message or message box.
        *
        * @param errorText
        *        Error text (without ending dot).
        */
       public static void showError(String errorText)
       {
          showError(errorText, null);
       }

       /**
        * Show error as a message or message box and log the exception. Error text will be postpended
        * with the exception message.
        *
        * @param errorText
        *        Error text (without ending dot).
        * @param e
        *        Exception to log.
        */
       public static void showError(String errorText, Exception e)
       {
          if (e != null)
          {
             if (errorText != null)
             {
                ErrorManager.recordOrShowError(-1, errorText + ": " + e.getMessage(), false);

                if (LOG.isLoggable(Level.SEVERE))
                {
                   LOG.log(Level.SEVERE, errorText + ".", e);
                }
             }
             else
             {
                ErrorManager.recordOrShowError(-1, e.getMessage(), false);

                if (LOG.isLoggable(Level.SEVERE))
                {
                   LOG.log(Level.SEVERE, e.getMessage(), e);
                }
             }
          }
          else
          {
             ErrorManager.recordOrShowError(-1, errorText, false);
          }
       }

       /**
        * Get Jasper report object. Reads specified report design file and compiles it into
        * {@link JasperReport} object.
        *
        * @param   fileName
        *          Name of the report design file.
        *
        * @return  Jasper report object.
        */
       static JasperReport getReport(String fileName)
       {
          boolean compiled = false;
          if (fileName.endsWith(JASPER_EXTENSION))
          {
             compiled = true;
          }
          else if (!fileName.endsWith(JRXML_EXTENSION))
          {
             showError(String.format("Name of a Jasper report should have %s or %s extension. Set " +
                   "a proper extension for file %s", JASPER_EXTENSION, JRXML_EXTENSION, fileName));
             return null;
          }

          JasperReport report = jasperReports.get(fileName);
          if (report != null)
          {
             return report;
          }

          synchronized (jasperReports)
          {
             report = jasperReports.get(fileName);

             if (report != null)
             {
                return report;
             }

             InputStream fileStream;
             try
             {
                fileStream =
                      FwdJasperExtensionRegistry.getRepositoryService().getInputStream(fileName);
             }
             catch (JasperReportException e)
             {
                showError(e);
                return null;
             }

             if (!compiled)
             {
                try
                {
                   report = JasperCompileManager.compileReport(fileStream);
                }
                catch (JRException e)
                {
                   showError(String.format("Report file %s cannot be compiled", fileName), e);
                   return null;
                }
             }
             else
             {
                try
                {
                   report = (JasperReport) JRLoader.loadObject(fileStream);
                }
                catch (JRException e)
                {
                   showError(
                         String.format("Compiled report could not be loaded from file %s", fileName),
                         e);
                   return null;
                }
             }

             jasperReports.put(fileName, report);

             return report;
          }
       }

       /**
        * Get Jasper report object for the given browse.
        *
        * @param  browse
        *         Target browse.
        * @param  designTemplateFile
        *         Name of the browse report design template file.
        *
        * @return  Jasper report object.
        */
       static JasperReport getBrowseReport(BrowseWidget browse, String designTemplateFile)
       {
          BrowseReportKey reportKey = new BrowseReportKey(browse, designTemplateFile);
          JasperReport jasperReport = browseReports.get(reportKey);
          if (jasperReport != null)
          {
             return jasperReport;
          }

          BrowseJasperReportTemplate template = browseReportTemplates.get(designTemplateFile);
          if (template == null)
          {
             synchronized (browseReportTemplates)
             {
                template = browseReportTemplates.get(designTemplateFile);
                if (template == null)
                {
                   template = new BrowseJasperReportTemplate();
                   try
                   {
                      template.prepare(designTemplateFile);
                   }
                   catch (JasperReportException e)
                   {
                      ReportFactory.showError(e);
                      return null;
                   }
                   browseReportTemplates.put(designTemplateFile, template);
                }
             }
          }

          JasperReport report;
          try
          {
             report = template.generateReport(browse);
          }
          catch (Exception e)
          {
             ReportFactory.showError(e);
             return null;
          }
          browseReports.putIfAbsent(reportKey, report);
          return report;
       }

       /**
        * Get default browse report design template for the given browse.
        *
        * @param   browse
        *          Browse for which we are getting default template.
        * @param   paged
        *          If {@code true} the output design will take into consideration constraints posed by
        *          paging and page width. Otherwsie the text will not be cut.
        *
        * @return  default browse report design template for the given browse.
        */
       public static String getDefaultReportTemplate(BrowseWidget browse, boolean paged)
       {
          return paged ? DEFAULT_BROWSE_PDF_REPORT_TEMPLATE_FILE :
                         DEFAULT_BROWSE_XLS_REPORT_TEMPLATE_FILE;
       }
    }