An Example of Custom AdHoc Report Generator and Report Builders : Export Ad Hoc Table Report Data to JSON

Introduction - Why this Article?

I often get the question, "What can I do if I want the Reports generated from Ad Hoc to be different than the default ones?".

One part of the answer is, "you can use a Report Template".

But this is just one part of the answer. Indeed, what we barely know is that the introduction of this concept of Report Template was linked to a more generic and powerful engine : the Report Generator.

To be more precise, a Template is a part of a Generator.

As a reminder, a Report Template permits you to:

  • Create a pixel perfect report from an ad hoc view

  • Adjust the look and feel on the existing structure of an Ad-Hoc Report and will act as a Stamp.

But, if you want to touch in a deeper way on the Report Structure, the Template will not be the answer anymore.

To change the logic with which the report/JRXML is generated, starting from an AdHoc View, you will need to create/adapt a ReportGenerator and its dependencies.

We introduced last year the JSON exporter which requires the user to add some properties on the Text Fields to create the Json Structure (for further in formation please consider Json Metadata Exporter).

To adapt in a generic way the generated Report to be exportable in Json, we will so need to use another object which is part of a Report Generator, the Report Builder.

The Report Builder permits the user to automate the JRXML generation with the Ad-Hoc view as an entry point. It is important to be clear on the wording.


NB

You can use the sample code to build your own library as a JAR file and add it to your server.

http://community.jaspersoft.com/wiki/build-your-customized-jar-jasperreports-server

It is recommended to replace the level sample in the package name hierarchy with something else specific to your project or company name.

This code has been tested on the latest release of the Professional edition of JasperReports Server, which is 6.1.1 at the time of writing. It is likely to work on 6.1,6.0.1 and 6.2.

Please be aware that this sample code is provided AS IS, it is your responsibility to review it and make sure it satisfies your requirements. Feel free to modify it and adapt it to your needs.

Principles and Definitions / the existing part

As documented in TIBCO JasperReports® Server Administrator Guide, v6.0, Configuring Ad Hoc, Templates_and_Generators, JasperReports Server allows you to customize the way Reports are generated from Ad Hoc View.

A custom report generator can be written in java/groovy and enabled in configuration applicationContext-adhoc.xml. They should be designed as classes and MUST implement com.jaspersoft.ji.adhoc.service.AdhocReportGenerator interface.

To make them available to the user compiled jar should be placed under WEB-INF/lib folder or class file under WEB-INF/classes.

By default JasperReport Server is using the SimpleReportGenerator.

Which contains references to several ReportBuilders.

For our use case the only one we need to modify is the tableReportBuilder.

Let’s have a look to the related bean definition into the /WEB-INF/applicationContext-adhoc.xml, when we generate a report from ad-hoc without choosing any template this is what is used:

<bean id="actualSizeReportGenerator" class="com.jaspersoft.ji.adhoc.service.SimpleReportGenerator">
    <property name="id"       value="actual-size"/>
    <property name="template" value="/public/templates/actual_size.620.jrxml"/>
    <property name="crosstabReportBuilderName">
        <idref bean="defaultCrosstabReportBuilder"/>
    </property>
</bean>

The bean which interests us is the following:

<bean id="reportGeneratorFactory" class="com.jaspersoft.ji.adhoc.service.ReportGeneratorFactoryImpl">
    <property name="reportGenerators">
        <list>
            <!--<ref bean="actualSizeReportGenerator" />-->
            <!--<ref bean="letterPortraitReportGenerator" />-->
            <!--<ref bean="letterLandscapeReportGenerator" />-->
            <!--<ref bean="a4PortraitReportGenerator" />-->
            <!--<ref bean="a4LandscapeReportGenerator" />-->
        </list>
    </property>
</bean>

Showing that we can add easily a new Report Generator.

By default the list is empty, but once we activate a ReportGenerator reference, the popup menu which appears when you Save an AdHoc view and Create a report will change, a new Option permitting to choose a Report Generator will appear, and the popup window will then look like :

Back to our example : Let’s generate Json Reports !

To do so and following the previous explanations we will just need to:

  • Create our ReportGenerator, for our use case we will just copy the SimpleReportGenerator and rename it CustomWikiReportGenerator.<

  • Create a copy of the default TableReportBuilder named TableReportBuilderExtendable, I just switched all the methods from private to protected in order to extend it.

    I just modified some methods to accept a Boolean named group which determines the behavior and structure of the json regarding if some groups have been created into the used Table Ad Hoc view.

    All the modification are signaled by a comment containing the keyword JSON:

    First Example, I Just changed the signature of the method:

    /**
    * Modified by plambert for group in detail JSON path
    */
    protected JRDesignTextField getDetailField(int x, AdhocColumn column, boolean group)
    {
        JRDesignTextField textField = new JRDesignTextField();
     
        textField.setStyleNameReference("TableDetailTextStyle");
        textField.setStretchWithOverflow(true);
        textField.setStretchType(StretchTypeEnum.RELATIVE_TO_TALLEST_OBJECT);
        textField.setBlankWhenNull(true);
        textField.setEvaluationTime(EvaluationTimeEnum.NOW);
        textField.setHyperlinkType(HyperlinkTypeEnum.NONE);
        textField.setHyperlinkTarget(HyperlinkTargetEnum.SELF);
        textField.setX(x);
        textField.setY(0);
        textField.setWidth(column.getWidth());
        textField.setHeight(detailBandHeight);
        textField.setKey("textField");
        textField.setHorizontalAlignment(HorizontalAlignEnum.getByName(column.getAlignment()));
        setMaskPattern(textField, column);
        return textField;
    }

    Second Example, I added this statement into the getTable() method in order to use this boolean in every required method which have had their signature changed:

    //added for JSON
    boolean tableHasGroup = state.getGroups().size() > 0;
    //end

    Again I did not change any other logic there a part Signature and usage of boolean group.

  • Create TableReportBuilderJSON which extends TableReportBuilderExtendable

    This is where we will adapt the logic of the existing TableReportBuilder to add the json properties onto every concerned text fields.

    For instance the following snippet permits to get into the JSON export the group and column names as attributes and the content of the Detail Text Field and Group Footer Text Field as values:

    //to add the detail values to the JSON
    @Override
    protected JRDesignTextField getDetailField(int x, AdhocColumn column ,boolean group)
    {
        JRDesignTextField textField =  super.getDetailField(x, column ,group);
        if(!(column.getFieldName().equals("_artificial")))
        {
            if(group) {
                textField.getPropertiesMap().setProperty("net.sf.jasperreports.export.json.path", "table.group.detail."+column.getLabel());
            }
            else {
                textField.getPropertiesMap().setProperty("net.sf.jasperreports.export.json.path", "table.detail."+column.getLabel());}
            }
            return textField;
        }
        //to add the group name to the JSON
        @Override
        protected JRDesignTextField getGroupFooterTextField( AdhocGroup adhocGroup, AdhocColumn column, int level)
        throws Exception {
            JRDesignTextField summaryTextField =  super.getGroupFooterTextField(adhocGroup, column, level);
            summaryTextField.getPropertiesMap().setProperty("net.sf.jasperreports.export.json.path", "table.group."+adhocGroup.getLabel()+"."+column.getLabel());
            return summaryTextField;
        }

    Etc...

    Following this logic for such an AdHoc View you will be able to export this Json Structure:

    Another Example :

  • We need to put the .jar in the /WEB-INF/lib folder (ReportGeneratorTableJSON.jar if we consider the attached material).

  • Then we will just need to add this CustomWikiReportGenerator bean definition in the /WEB-INF/applicationContext-adhoc.xml

    To do so 5 steps :

    1. Add at the end of the Reportgenerator bean list:

      <bean id="wikiReportGenerator" class="com.jaspersoft.ji.adhoc.service.CustomWikiReportGenerator">
          <property name="id" value="wiki"/>
          <property name="template" value="/public/templates/actual_size.620.jrxml"/>
          <property name="crosstabReportBuilderName">
              <idref bean="defaultCrosstabReportBuilder"/>
          </property>
          <property name="tableReportBuilderName">
              <idref bean="TableReportBuilderJSON"/>
          </property>
      </bean>

    2. Just after add the new ReportBuilder TableReportBuilderJSON bean definition.

      <bean id="TableReportBuilderJSON" class="com.jaspersoft.ji.adhoc.service.TableReportBuilderJSON"/>
    3. Modify the reportGeneratorFactory list of reportGenerators to add our wikiReportGenerator.

      <bean id="reportGeneratorFactory" class="com.jaspersoft.ji.adhoc.service.ReportGeneratorFactoryImpl">
          <property name="reportGenerators">
              <list>
                  <!--<ref bean="actualSizeReportGenerator" />-->
                  <ref bean="wikiReportGenerator" />
                  <!--<ref bean="letterPortraitReportGenerator" />-->
                  <!--<ref bean="letterLandscapeReportGenerator" />-->
                  <!--<ref bean="a4PortraitReportGenerator" />-->
                  <!--<ref bean="a4LandscapeReportGenerator" />-->
              </list>
          </property>
      </bean>
    4. Edit /WEB-INF/jsp/modules/common/jrsConfigs.jsp to put "" around commonReportGeneratorsMetadata value as follow:

        ~ 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 should have received a copy of the GNU Affero General Public  License
        ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
        --%>
       
      <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
      <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
      <%@ taglib uri="/spring" prefix="spring"%>
      <%@ taglib uri="http://www.springframework.org/security/tags" prefix="authz"%>
      <%@ page import="com.jaspersoft.jasperserver.war.webHelp.WebHelpLookup" %>
       
      <%--Global JRS State/Config object --%>
       
      <script type="text/javascript">
          var JRS = {};
       
          var __jrsConfigs__ = {
       
              i18n: {},
              localContext: {},
              isIPad: "${isIPad}",
              contextPath: "${pageContext.request.contextPath}",
              publicFolderUri: "${not empty publicFolderUri ? publicFolderUri : commonProperties.publicFolderUri}",
              tempFolderUri: "${not empty tempFolderUri ? tempFolderUri : commonProperties.tempFolderUri}",
              enableAccessibility: "${not empty enableAccessibility ? enableAccessibility : commonProperties.enableAccessibility}",
              organizationId: "${not empty organizationId ? organizationId : commonProperties.organizationId}",
              commonReportGeneratorsMetadata: "${not empty reportGenerators ? reportGenerators : '[]'}",
              templatesFolderUri: '${not empty templatesFolderUri ? templatesFolderUri : templateProperties.templatesFolderUri}',
              defaultTemplateUri: '${not empty defaultTemplateUri ? defaultTemplateUri : templateProperties.defaultTemplateUri}',
    5. Add your report Generator message value into /WEB-INF/bundles/adhoc_messages.properties (ADH_REPORT_GENERATOR_wiki for us as defined in wikiReportGenerator bean)

      ADH_REPORT_GENERATOR_letter-portrait=Letter Size Portrait
      ADH_REPORT_GENERATOR_letter-landscape=Letter Size Landscape
      ADH_REPORT_GENERATOR_a4-portrait=A4 Size Portrait
      ADH_REPORT_GENERATOR_a4-landscape=A4 Size Landscape
      ADH_REPORT_GENERATOR_actual-size=Actual Size
      ADH_REPORT_GENERATOR_custom-template=Custom template
      ADH_REPORT_GENERATOR_report-title=Report title
      ADH_REPORT_GENERATOR_wiki=Custom Example Generator

      Now you can restart Jasper Reports Server , create an Ad Hoc View (a Table) create a Report thanks to your new Custom Report Generator and call the Json result thanks to our Rest APIs. Making this Json Export possible with the UI is another topic.

Attached you will find the Complete Material to run the the described Custom Generator in JasperReports Server v6.2. Inside JsonReportGeneratorMaterial you will find the full structure starting from the WEB-INF folder. ReportGeneratorTableJSON.jar contains the used source code.

Feedback
randomness