ReportingCloud

Java

Topic

Java applications use ReportingCloud through the Java SDK, whose class ReportingCloud wraps the Web API. The SDK is source code on GitHub, not a package in Maven Central, so you build it first. With a current JDK and one program, you then merge the demo template sample_invoice.tx of your template storage with invoice data and save the result as a PDF file from the command line. A second variant of the program sends the template with the request instead of naming a stored one.

Prerequisites

  • A ReportingCloud account. A trial account is enough, and it has sample_invoice.tx in its template storage. Creating an Account shows how to sign up. The Java SDK authenticates with the user name and password of the account, the email address you signed up with and its password; it has no constructor for an API key.
  • A JDK. The steps were written with JDK 21. The SDK's own Gradle build needs JDK 8, as Installing an SDK describes; this tutorial compiles the sources with javac instead, which works with a current JDK.
  • Git and curl, to clone the SDK and download its two libraries. On Windows, run the commands in Git Bash, which Git for Windows installs.

Check Your Java Version

Open a terminal and print the version of the Java compiler:

javac -version

The output names the version, such as javac 21.0.12.1. A message that javac isn't found means that only a Java runtime is installed; install a JDK.

Build the SDK

  1. Create a directory for the project and change to it:

    mkdir my-application && cd my-application
    
  2. Clone the SDK:

    git clone https://github.com/TextControl/txtextcontrol-reportingcloud-java.git
    
  3. Download the two libraries the SDK uses into a lib directory: Gson 1.7.2, which writes and reads the JSON of the requests, and javafx-base, whose class javafx.util.Pair the find and replace method uses:

    mkdir lib
    curl -L -o lib/gson-1.7.2.jar https://repo1.maven.org/maven2/com/google/code/gson/gson/1.7.2/gson-1.7.2.jar
    curl -L -o lib/javafx-base-21.jar https://repo1.maven.org/maven2/org/openjfx/javafx-base/21/javafx-base-21-linux.jar
    

    On Windows, download javafx-base-21-win.jar, and on macOS javafx-base-21-mac.jar, instead of the Linux file.

  4. Compile the sources of the SDK and pack them into a JAR file in lib:

    javac -d sdk-classes -cp "lib/*" $(find txtextcontrol-reportingcloud-java/src/main/java -name "*.java")
    jar cf lib/reportingcloud-1.3.jar -C sdk-classes .
    

javac notes that ReportingCloud.java uses a deprecated API; the note doesn't stop the build. lib now holds the SDK, version 1.3, and the two libraries, the class path of your program.

Set the Credentials

The program reads the user name and password from the environment variables REPORTINGCLOUD_USERNAME and REPORTINGCLOUD_PASSWORD, which keeps them out of your source code. Set them in the terminal in which you run the program:

export REPORTINGCLOUD_USERNAME="<email address>"
export REPORTINGCLOUD_PASSWORD="<password>"

In PowerShell, the commands are $env:REPORTINGCLOUD_USERNAME = "<email address>" and $env:REPORTINGCLOUD_PASSWORD = "<password>". Put a password that contains $ in single quotes in bash, so that the shell doesn't expand it.

Write the Program

Create a file MergeInvoice.java in the project directory with the following code:

import com.textcontrol.reportingcloud.MergeBody;
import com.textcontrol.reportingcloud.ReportingCloud;
import com.textcontrol.reportingcloud.ReturnFormat;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

public class MergeInvoice {

    // One line item of the merge block item
    static class Item {
        String qty;
        String item_no;
        String item_description;
        String item_unitprice;
        String item_discount;
        String item_total;

        Item(String qty, String itemNo, String description, String unitPrice, String discount, String total) {
            this.qty = qty;
            this.item_no = itemNo;
            this.item_description = description;
            this.item_unitprice = unitPrice;
            this.item_discount = discount;
            this.item_total = total;
        }
    }

    // One record: the field names are the merge field names of sample_invoice.tx
    static class Invoice {
        String yourcompany_companyname = "Text Control, LLC";
        String invoice_no = "778723";
        String billto_name = "Joey Montana";
        String billto_companyname = "Montana, LLC";
        String billto_street = "1 Washington Dr";
        String billto_zip = "27878";
        String billto_city = "Charlotte";
        String payment_due = "20/1/2016";
        List<Item> item = new ArrayList<>();
        String total_sub = "7673.4";
        String total_tax = "537.138";
        String total = "8210.538";
    }

    public static void main(String[] args) throws IOException {
        // Read the credentials from the environment, never from source code
        String username = System.getenv("REPORTINGCLOUD_USERNAME");
        String password = System.getenv("REPORTINGCLOUD_PASSWORD");

        if (username == null || password == null) {
            System.out.println("Set REPORTINGCLOUD_USERNAME and REPORTINGCLOUD_PASSWORD.");
            return;
        }

        ReportingCloud reportingCloud = new ReportingCloud(username, password);

        Invoice invoice = new Invoice();
        invoice.item.add(new Item("1", "1", "Item description 1", "2663", "20", "2130.40"));
        invoice.item.add(new Item("1", "2", "Item description 2", "5543", "0", "5543"));

        MergeBody mergeBody = new MergeBody(new Object[] { invoice });

        try {
            // Merge the template of the template storage and receive one PDF per record
            List<byte[]> documents = reportingCloud.mergeDocument(
                    mergeBody, "sample_invoice.tx", ReturnFormat.PDF);

            Files.write(Paths.get("invoice.pdf"), documents.get(0));
            System.out.println("Written to invoice.pdf");
        } catch (IllegalArgumentException exception) {
            // The message is the response body of the Web API
            System.out.println(exception.getMessage());
        }
    }
}

What the Code Does

  • ReportingCloud, in the package com.textcontrol.reportingcloud, has a method for most endpoints of the Web API. The constructor takes the user name and password, which the SDK sends with every request in a Basic Authorization header, and sends the requests to https://api.reporting.cloud.
  • Invoice and Item hold the data of one record, the data of one document. Gson writes their fields with the names they have in Java, so the field names are the names of the merge fields in the template, placeholders such as invoice_no, and the list item fills the merge block item, which repeats a table row for each line item. Merge fields without data are removed from the document.
  • MergeBody carries the records, as an Object[] or an Iterable<Object>, and optionally merge settings and a template.
  • mergeDocument sends the request POST /v1/document/merge and returns a List<byte[]> with one entry per created document. Its parameters are the body, the name of a stored template and the return format; overloads add whether to append all records into one document and test mode, both false by default.
  • ReturnFormat.PDF is the return format PDF. The enumeration also has PDFA, DOC, DOCX, RTF, TX and TXT; a trial account creates no PDF/A.
  • Files.write writes the first and only document to invoice.pdf.

Run the Program

Compile the program with the SDK on the class path and run it in the terminal in which you set the credentials:

javac -cp "lib/*" MergeInvoice.java
java -cp ".:lib/*" MergeInvoice

On Windows, separate the entries of the class path with a semicolon: java -cp ".;lib/*" MergeInvoice. The program prints Written to invoice.pdf. Open the file in a PDF viewer to see the merged invoice; a trial account adds the ReportingCloud logo to every document. Each run is a merge request and counts 1 against the monthly document quota of your plan.

Send the Template With the Request

A template doesn't have to be in your storage. MergeBody also takes the template itself as a byte array, which the SDK sends Base64-encoded in the template property of the request; the Web API merges it without storing it. The template can be any file of a supported format that your program reads with Files.readAllBytes. Here, the program downloads sample_invoice.tx from the storage with downloadTemplate, which counts against no quota, and sends it back. In MergeInvoice.java, replace everything from the line MergeBody mergeBody = ... to the line before } catch with:

        try {
            // Download the template from the storage, as a template file of your own would be read
            byte[] template = reportingCloud.downloadTemplate("sample_invoice.tx");

            // Send the template with the data instead of naming a stored template
            MergeBody mergeBody = new MergeBody(new Object[] { invoice }, null, template);

            List<byte[]> documents = reportingCloud.mergeDocument(
                    mergeBody, null, ReturnFormat.PDF);

            Files.write(Paths.get("invoice_template_sent.pdf"), documents.get(0));
            System.out.println("Written to invoice_template_sent.pdf");

The null in place of the merge settings keeps the defaults, and the null in place of the template name says that no stored template is used: passing both a template name and a template, or neither, throws an InvalidParameterException. Compile and run the program again and open invoice_template_sent.pdf.

Troubleshooting

When the Web API answers with an error, the SDK throws an IllegalArgumentException whose message is the response body; the program prints it. The status code isn't part of the message.

  • Invalid username/password combination or invalid APIKey. The user name or password is wrong, or the environment variables are set in another terminal. The user name is the email address of the account.
  • Template doesn't exist in storage. The template name is not in your template storage. Check it on the My Templates page of the portal.
  • package com.textcontrol.reportingcloud does not exist. javac doesn't find the SDK. Run the commands in the project directory, in which lib holds reportingcloud-1.3.jar, and keep the quotes around "lib/*".
  • package javafx.util does not exist while the SDK is built. lib doesn't hold the javafx-base file of step 3.
  • Could not determine java version from gradlew in the SDK's directory. Its Gradle 2.9 doesn't run on a current JDK; build the SDK with javac as above.

Next Steps

  • Web API: the request of this program with curl.
  • Merge Data: how objects and lists map to merge fields and merge blocks.
  • Merge Settings: the settings that MergeSettings passes, and the defaults of the Java SDK.
  • Java: every method of the Java SDK, its constructors and its errors.