Java
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.txin 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
javacinstead, 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
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
Create a directory for the project and change to it:
mkdir my-application && cd my-applicationClone the SDK:
git clone https://github.com/TextControl/txtextcontrol-reportingcloud-java.gitDownload 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 classjavafx.util.Pairthe find and replace method uses:mkdir lib curl 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 lib/javafx-base-21.jar https://repo1.maven.org/maven2/org/openjfx/javafx-base/21/javafx-base-21-linux.jarOn Windows, download
javafx-base-21-win.jar, and on macOSjavafx-base-21-mac.jar, instead of the Linux file.Compile the sources of the SDK and pack them into a JAR file in lib:
javac sdk-classes "lib/*" $(find txtextcontrol-reportingcloud-java/src/main/java "*.java") jar cf lib/reportingcloud-1.3.jar 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 packagecom.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 BasicAuthorizationheader, and sends the requests tohttps://api.reporting.cloud.InvoiceandItemhold 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 asinvoice_no, and the listitemfills the merge blockitem, which repeats a table row for each line item. Merge fields without data are removed from the document.MergeBodycarries the records, as anObject[]or anIterable<Object>, and optionally merge settings and a template.mergeDocumentsends the requestPOST /v1/document/mergeand returns aList<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, bothfalseby default.ReturnFormat.PDFis the return formatPDF. The enumeration also hasPDFA,DOC,DOCX,RTF,TXandTXT; a trial account creates no PDF/A.Files.writewrites 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 "lib/*" MergeInvoice.java
java ".: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.javacdoesn'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 existwhile the SDK is built. lib doesn't hold thejavafx-basefile of step 3.Could not determine java versionfromgradlewin the SDK's directory. Its Gradle 2.9 doesn't run on a current JDK; build the SDK withjavacas 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
MergeSettingspasses, and the defaults of the Java SDK. - Java: every method of the Java SDK, its constructors and its errors.