Skip to main content

Configuration

APEX Office Print can be configured through the aop_config.jsonc file or through parameters when AOP is being started.

Server Options

To view all available option, run ./APEXOfficePrintLinux64 -h

Following parameters can be used:

    -a or --activate                : Activate the software
-r or --reactivate : This reactivates the software.
--generate_lrf : Generate license request file
-h or --help : Show this menu
-p or --port : For giving in the running port default: 8010
Example: APEXOfficePrint -p 5555
-s or --startat : Directory to start at.
--license : The location of license file.
Default looks at the startup directory
--silent : Do everything quietly even start message
-v or --version : Show the current version of AOP
--verbose : Log what AOP is currently doing.
--enable_printlog : Log data about the printjobs to server_printjob.log.
--idle_timeout : The maximum idle seconds for a request.
: Default: 2 (pdf conversion time is considered idle)
-i or --instances : The number of instances of AOP to start
Giving 0 will start max instances available
--https_cert : The location of the https certificate
--https_key : The location of the https private key file
--https_port : The port for the https server to run on.
Having a key/cert but no port will make the https
server use the port from -p/--port (or the default)
Example: .. -p 80 --https_port 443 -https_key ...
http runs on port 80, https on port 443
Example: .. -p 80 --https_key ...
https runs on port 80, no regular http
--ipwhitelist : The file to read the IP whitelist from
If this parameter is empty, but 'ipwhitelist.txt' exists
File format: one IP (range) or subnetwork per line
Example: 127.0.0.1, abc::0-def::ffff
10.0.0.0/8, 192.0..
--ipp_test : Tests the given ipp url and version by sending pdf and
ps printouts
--pdf_error_threshold : Set the maximum amount of retries for processing a pdf file.
--max_parallel_conversions : Set the maximum number of parallel conversions
(per converter). default: number of cores
--enable_save : This enables the option write to disk
Default: outputfiles dir of AOP directory
--enable_local_resources : This enables accessing local files (e.g. templates)
--check_configuration : Checks the configurations needed for AOP to run
--deactivate : This creates deactivation request file.
--updatePuppeteer : This will update the puppeteer (used for converting HTML to PDF.)
It is expected to avoid security vulnerabilities.

AOP can also use a config file. These options can be saved in this config file. If no config file is there, AOP generates the config file in the first run. This configuration file is of JSON format. The default configuration is as follows:

{
"config": {
//access token that is required to get the logs from the logging page and general stats. either false or desired access token (string).
"access_token": false,
//folder to take into account if the provided temp folder is out of space.
"backup_pdf_temp_folder": "",
//determines the default converter if multiple are available
"default_converter": "",
//whether or not to use static key ciphers while running server in https mode.
"disable_static_key_ciphers": false,
//allows incoming requests to use local resources for templates, images, logos etc.
"enable_local_resources": false,
//whether or not to enable the macro when converting to pdf, might have security implications
"enable_macro": false,
//enables the log of all the incmoing requests, including get requests to invalid urls.
"enable_networklog": false,
//enables the printjob log
"enable_printlog": true,
//this option will allow you to save the output to the server, can be either false or location to the directory where the output should be saved
"enable_save": false,
//The folder where the hash files of given templates are stored.
"template_cache_folder": "./cache",
//Removal of cached hash files after given interval (hours).
"template_cache_removal_duration": 1,
//Removal of temp files after given interval (hours).
"temp_file_removal_duration": 10,
//The https certificate to use when running in https mode.
"https_cert": "",
//The https key to use when running in https mode
"https_key": "",
// enable and set this to a valid port number if you would like to run AOP in BOTH http and https mode. If you would like to run only in https mode, set it to false and change the port through port option.
"https_port": false,
// this is the timeout given when a conversion occurs in seconds.
"idle_timeout": 120,
// The number of threads AOP should run. This also determines the number of true parallel requests AOP can handle.
"instances": 2,
// The max number of instance that can be started. This determines the number of parallel request that can run.
"max_instances": 10,
//The file that contains the IP address that should be whitelisted.
"ipwhitelist": "",
// the command to use to call java (necessary for PDF operations and manipulations)
"java_command": "java",
//The time in seconds to dedicate to one pdf conversion. If this times out and conversion couldn't be finished a dedicated Libreoffice process is started (timeout of that is controlled by idle_timeout)
"libreoffice_idle_converter_timeout": 15,
//The number of libreoffice idle converters to start. Provide false to disable.
"libreoffice_idle_converters": 2,
//The location of license file.
"license": "./aop.license",
//Time in days to save the logs.
"log_duration": 30,
//number of max copies that can be specfied in a request.
"max_copies": 10,
//The number of max request that can be sent at a time. This should be set if your template usages a lot of external resources.
"max_outgoing_requests": 10,
//The number of parallel conversion that can be called at one moment. 0 -> unlimited.
"max_parallel_conversions": 0,
//If the output file is large, the conversion process can be quite long. This limitation in mb can be used to save resource.
"max_preconversion_size": 300,
//The max size of JSON that will be accepted by AOP. In mb.
"max_request_size": 200,
//The number of retries for pdf conversions until which AOP will give back an error.
"pdf_error_threshold": 3,
//The temp folder to use when AOP calls the PDF converters. (can be a memory folder for faster conversion)
"pdf_temp_folder": "",
// //Timeout for PDFBox Operations (in milliseconds)
"pdfbox_timeout": 60000,
//The port on which AOP should listen.
"port": 8010,
//The timeout for fetching the resource from a data source url.
"resource_timeout": 10000,
//When this is true, AOP will start without any logs.
"silent": false,
//The location to start at. This might be handy if your resources are location somewhere else and can be references relative to this path.
"startat": "./",
// The ciphers to user while in HTTPS mode. Run AOP with --list_ciphers parameter to get the available list of ciphers. Should be column separated eg: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA
"tls_ciphers": "",
//When enabled this will log more info on what its doing.
"verbose": false,
// Save the incoming requests to a file, this file can be sent to AOP Support for debugging.
"save_incoming_requests": false,
// The maximum memory a request can use in MB, default: 4096
"memory_per_request": 4096,
// The maximum time a print job can use in seconds. default: 600
"request_timeout": 600,
// The time an instance is allowed to be inactive, after this timeout the instance will be killed in order to save resource. In seconds default: 900
"inactive_instance_timeout": 900
},
"converters": {
"abiword":{
"command": "abiword --to={outputFormat} --to-name={outputFile} {inputFile}",
"handlesParallel": false
}
},
"pre_conversion_commands":{ // Since AOP 19.3 -> reads inputFile location for the modified file.
"echo":"echo \"{inputFile} with input format {inputFormat} has reached conversion stage.\" {p1} {p2} {p3} {p4}"
},
"post_conversion_commands":{ // Since AOP 19.3 -> reads inputFile location for the modified file.
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully converted.{p1} {p2} {p3} {p4} \" "
},
"post_merge_commands":{ // Since AOP 19.3 -> reads inputFile location for the modified file.
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully merged.{p1} {p2} {p3} {p4} \" "
},
"post_process_commands": {
//Parameters available after AOP 21.1
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully processed. Parameters passed {p1} {p2}\""
}
}

Now we go deeper in the configuration options.

Save file to local disk or server

It is possible to set up AOP to save output files directly to a server or disk. To do so, either set enable_save parameter in aop_config.jsonc configuration file or run the AOP with the --enable_save argument.

Example:

./aop_config.jsonc
// If no config file is there, AOP generates the config file in the first run.
"enable_save" : true,
// or
"enable_save" : "c:/Users/userName/Downloads/test"

The enable_save option in AOP can take three types of values:

  1. The default value is false, which disables the option to save output to a disk or server.
  2. If set to true, AOP will be enabled to save files to a disk or server. Note that the outputFiles folder in the directory where AOP is run will be used to save output files.
  3. By setting a directory path as the value, AOP will be able to save output files to the specified disk or server location. Note that the provided directory path will be used for saving files and you won't be able to save outside that folder.
caution

For this option to work, you have to provide output_directory. If you are using dynamic action, this can be provided in Init PL/SQL Code, see below

Important

If value for enable_save is true, you will not be able to save the files outside the outputFiles folder.
If value for enable_save is directory_path, you will not be able to save the files outside the mentioned directory as directory_path.

If you are using dynamic action then to provide the output_directory, you can set the value using: aop_api_pkg.g_output_directory in Init PL/SQL Code:

Init PL/SQL Code
aop_api_pkg.g_output_directory := './'

For Example, setting "enable_save": "\home\savedir" and "output_directory":"\first_batch" would save the output to \home\savedir\firstbatch.

IP Whitelisting

AOP allows ip whitelisting which means, only the mentioned ip-address can access the AOP. To implement this you would need a txt files with ip address separated by new lines.
The ipwhitelist option should provide the location of the file containing the list of IP addresses that need to be whitelisted.

Allowed patterns
IP (IPv4/IPv6)
Regular IPv4: 10.0.0.0
Wildcard IPv4: 10.0.0.* or even 10.*.0.*
Regular IPv6: 2001:0db8:85a3:0000:0000:8a2e:0370:7334
Shortened IPv6: 2001:db8:85a3::8a2e:0370:7334 or ::abc or abc::
Wildcard IPv6: 2001::* or even 2001::*:abc:*
Not allowed: 10.0.1*.0 or 2001::a*c
IP Range
IPv4: 10.0.0.0-10.1.2.3
IPv6: 2001::abc-2001::1:ffff
Note: Left side has to be "lower" than the right side
IP Subnetwork
IPv4: 10.0.0.0/16
IPv6: 2001::/64

For Example, I want to allow only my-machine to run AOP, so that others would not be able to access it. I would get my ip4 address or ip6 address using ipconfig (for windows) and ifconfig (for linux) then create a file named ipWhiteLists.txt(it can be any name). If my ip4 address is 192.168.1.71

ipWhiteLists.txt
192.168.1.71

Add the path of the configuration file to config object of aop_config.jsonc configuration file. If no config file is there, AOP generates the config file in the first run.

aop_config.jsonc
"config": {
"ipwhitelist": "./ipWhiteLists.txt",
}

No one except you would be able to access the AOP.

Pre / Post commands

AOP allows execution of pre-defined commands for each request. The pre defined commands should be defined in the aop_config.jsonc configuration file. If no config file is there, AOP generates the config file in the first run.

{inputFile}   : the location where the file for the conversion is located. (includes input format extension and is absolute path, i.e contains inputDir)
{inputFormat} : the input format of the input file. e.g. docx

Above are the default tags, that will be replaced automatically, that means one does not need to pass them as parameters. See below for more info.

Since AOP 19.3, we have introduced other hooking points where you can modify the given input file. The commands that should be run before the conversion should be provided with pre_conversion_commands. The post_conversion_commands object specifies the commands that can be called after conversion. The post_merge_commands object specifies the commands that can be run after the merging has happened. Please note that there is no pre_merge_commands object since this is the same as post_conversion_commands. See below for more info

pre_conversion_commands

Available From: v19.3

The commands will run before the conversion of file happens.

./aop_config.jsonc
"pre_conversion_commands":{
"echo":"echo \"{inputFile} with input format {inputFormat} has reached conversion stage.\" {p1} {p2} {p3} {p4}"
},

Here the echo is the name of command for pre_conversion. You can have multiple pre conversion commands and use accordingly for each request.In above example we have four parameters (keywords inside curly braces), inputFile, inputFormat, p1, p2, p3, p4.

If you are using dynamic action, you can use below code snippet structure to use this command in Init PL/SQL Code:

Init PL/SQL Code of dynamic action
aop_api_pkg.g_pre_conversion_command := 'echo';
aop_api_pkg.g_pre_conversion_command_p := '{"p1" : "parameter 1", "p2": "parameter 2", "p3" : "parameter 3", "p4":"parameter 4"}';

You do not need to pass inputFile and inputFormat parameters, they will be automatically detected by AOP. Only pass the value for p1, p2, p3, p4.

Use case of updating page numbers.

A useful usage of pre conversion commands is that you can update the page numbers. The page numbers can be updated in table of contents of docx file or page numbers in Excel sheet.

LibreOffice should be installed on your system and added to the system's environment variables

First, one needs to create a macro command that will update the table of contents. Sample macro command is:

sub updateIndex(sDocUrl as string)

dim oDocument as object
dim dispatcher as object

dim sNewUrl as string

if fileExists(sDocUrl) then
oDocument = starDesktop.loadComponentFromUrl(convertToUrl(sDocUrl), "_blank", 0, array())
oIndexes = ThisComponent.getDocumentIndexes()

for i = 0 to oIndexes.getCount() - 1
oIndexes (i).update
next i
oDocument.store()
oDocument.close(True)
end if

end sub

To add macro command in LibreOffice, please follow below steps depending on your OS Platform

  1. Open LibreOffice either run soffice command in cmd or search for LibreOffice and open the app.

  2. Go to Tools -> Macros -> Edit Macros

    edit macros libreoffice

  3. A code editor will be opened with following content:

    REM  *****  BASIC  *****
    Sub Main
    End Sub
    Note: There could be more code if other macros are already added
  4. You need to add above macro code as:

    add macro code

Please visit https://books.libreoffice.org/en/GS70/GS7013-GettingStartedWithMacros.html for official documentation of adding macros.

Second, add pre conversion command to execute above macro command in aop_config.jsonc configuration file. If no config file is there, AOP generates the config file in the first run.

aop_config.jsonc
"pre_conversion_commands": {
"updatePageNumbers": "soffice --invisible --headless \"macro:///Standard.Module1.updateIndex({inputFile})\""
}

Third, add above pre_conversion_command in Init PL/SQL Code of dynamic action as:

Init PL/SQL Code
aop_api_pkg.g_pre_conversion_command := 'updatePageNumbers';
caution

Note: updating page numbers will only work if the output is docx format.

post_conversion_commands

Available From: v19.3

The commands to run after the conversion of file happens.

./aop_config.jsonc
"post_conversion_commands":{
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully converted.{p1} {p2} {p3} {p4} \" "
}

Here the echo is the name of command for post_conversion. You can have multiple pre conversion commands and use accordingly for each request.In above example we have four parameters (keywords inside curly braces), inputFile, inputFormat, p1, p2, p3, p4.

If you are using dynamic action, you can use below code snippet structure to use this command in Init PL/SQL Code:

Init PL/SQL Code of dynamic action
aop_api_pkg.g_post_conversion_command := 'echo';
aop_api_pkg.g_post_conversion_command_p := '{"p1" : "parameter 1", "p2": "parameter 2", "p3" : "parameter 3", "p4":"parameter 4"}';

You do not need to pass inputFile and inputFormat parameters, they will be automatically detected by AOP. Only pass the value for p1, p2, p3, p4.

post_merge_commands

Available From: v19.3

The commands to run after the merging of prepend file and append file. This is normally the last step and the intput file will be the output of AOP that will be sent back. AOP will save the input file to the disk and call the command specified. You can manipulate this output file however you want and replace the input file. AOP will read back this file and provide this file as the response of the request.

./aop_config.jsonc
"post_merge_commands":{ 
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully merged.{p1} {p2} {p3} {p4} \" "
},

Here the echo is the name of command for post_merge. You can have multiple post merge commands and use accordingly for each request. In above example we have four parameters (keywords inside curly braces), inputFile, inputFormat, p1, p2, p3, p4.

If you are using dynamic action, you can use below code snippet structure to use this command in Init PL/SQL Code:

Init PL/SQL Code of dynamic action
aop_api_pkg.g_post_merge_command := 'echo';
aop_api_pkg.g_post_merge_command_p := '{"p1" : "parameter 1", "p2": "parameter 2", "p3" : "parameter 3", "p4":"parameter 4"}';

You do not need to pass inputFile and inputFormat parameters, they will be automatically detected by AOP. Only pass the value for p1, p2, p3, p4.

Please note that there is no pre_merge_commands object since this is the same as post_conversion_commands.

post_process_commands

The commands to run after the conversion of file happens.

./aop_config.jsonc
"post_process_commands": {
//Parameters available after AOP 21.1
"echo":"echo \"{inputFile} with input format {inputFormat} has been successfully processed. Parameters passed {p1} {p2}\""
}

Here the echo is the name of command for post_process. You can have multiple post process commands and use accordingly for each request. In above example we have three parameters (keywords inside curly braces), inputFile, inputFormat, p1, p2.

If you are using dynamic action, you can use below code snippet structure to use this command in Init PL/SQL Code:

Init PL/SQL Code of dynamic action
aop_api_pkg.g_post_process_command := 'echo';
aop_api_pkg.g_post_process_command_p := '{"p1" : "parameter 1", "p2": "parameter 2"}';

You do not need to pass inputFile and inputFormat parameters, they will be automatically detected by AOP. Only pass the value for p1, p2.

info

Post process commands does not have the access to other default parameters except inputFile and inputFormat

Implementation

Let's say, I want to a docx file as well as pdf file for the same report

  1. I would create an object named pre_conversion_commands in aop_config.jsonc file, and Create an object named copy as
"pre_conversion_commands":{
"copy" : "cp {inputFile} {filePath}"
},

In Init PL/SQL Code 2. I would set aop_api_pkg.g_pre_conversion_command to "copy" 3. I would set aop_api_pkg.g_pre_conversion_command_p to {filePath : "path of the file with full location"} Set the output to pdf

I will get a docx file as well as the pdf file. Note: above cp command does not work for windows, you would have to use copy instead of cp

note

Please note that if a command is specified in a request for pre_conversion_commands and post_conversion_commands, then these commands are called for all the files (i.e prepend and append files).

Custom converter

This allows for the users to use the converter of their choice. In the command section the following tags will be replaced:

{inputFile}   : the location where the file for the conversion is located. (includes input format extension and is absolute path, i.e contains inputDir)
{inputFormat} : the input format of the input file. e.g. docx
{outputFormat}: will be replaced by the output format needed. e.g: pdf
{outputFile} : the location and filename of the expected output. (includes output extension and is absolute path, i.e contains outputDir),
{inputDir} : the directory where the inputFile is located.
{outputDir} : the output dir where AOP expects the output PDF (will always be the same as inputDir)

The other option handlesParallel should be true if the specified custom converter is capable of handling multiple PDF conversions at the same time. If a custom converter is used and the specified converter cannot handle parallel conversion, then the conversions are placed in a queue and called on first come first serve basis (even when multiple instances of AOP are running).

To implement this, you need to create an object named converters and the desired converters. Please follow below syntax:

.aop_config.jsonc
"converters": {
"abiword":{ // name of converter as object name
"command": "abiword --to={outputFormat} --to-name={outputFile} {inputFile}", // command that will convert the one file type to another.
"handlesParallel": false
}
},

In Init PL/SQL Code, set aop_api_pkg.g_output_converter as abiword as

Init PL/SQL Code of dynamic action
aop_api_pkg.g_output_converter := 'abiword';

Now, above command will be used to convert the file.

HTTPS Configuration

AOP can be configured as an HTTPS server. It requires a crt file and its private key file.

A self-signed certificate and key can also be used. The certificate can be generated by using openssl command:

openssl req -x509 -newkey rsa:2048 -keyout keytmp.pem -out aop.crt -days 365
openssl rsa -in keytmp.pem -out aop.key

Given aop.crt certificate file and aop.key private key file AOP can be started with:

APEXOfficePrint --https_cert aop.crt --https_key aop.key

The port number can also be specified with the --https_port argument. If this is provided the server will run in HTTP and HTTPS mode.

APEXOfficePrint --https_cert aop.crt --https_key aop.key --https_port 8443

Then, the server will run 8010 (configurable value for port key of config) in http and 8443 in https You should see the following when started successfully on the console.

Starting HTTP and HTTPS on port 8010 and 443
AOP 1 (HTTP) running
AOP 1 (HTTPS) running

If you want to run the server in https mode only, then you can set https_port as false along with https_cert and https_key.
You can set these value in configuration file (aop_config.jsonc), if no config file is there, AOP generates the config file in the first run.

As arguments
APEXOfficePrint --https_cert aop.crt --https_key aop.key --https_port false

Then, the server will run in https mode only in the provided port (default is 8010). The port can be configured in configuration file (set value for port of config object) or passed as argument -p .

Please note that the certificate validation is done by the client.If you are using a self-signed certificate and visit the https location with browser, you will get a security warning (see below).

You can configure the browsers to trust the self-signed certificate by adding it to the security exception. You will also have to add this certificate in your Oracle wallet to get rid of 'Certificate Validation Error'.

SSL with Reverse Proxy.

For older versions however we recommend setting up an Apache Reverse Proxy which is doing the SSL in front of AOP. From Apache to AOP it would be unencrypted, but if it’s on the same machine as Apache and the port of AOP is not open and only accessible by localhost, we believe you're safe.

To prevent access to AOP other than the Apache Reverse Proxy, you can do (on Linux):

firewall-cmd --zone=public --add-port=8010/tcp --permanent
firewall-cmd --reload

iptables -A INPUT -p tcp --dport 8010 -s 127.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8010 -j DROP

(instead of DROP you can use REJECT too)

So that would mean only a program on localhost (like Apache) can connect to port 8010, all others are rejected.

Testing the connection to AOP

Make sure the database server can connect to the port where AOP is running. If not, open the port so the database server can connect to the webserver:port (note you don't need to open it up to everybody, just the database server is fine)

For example by running curl webserver:port (curl can be downloaded for free for windows via https://curl.haxx.se/download.html#Win64)

curl 127.0.0.1:8010

If you access the port running AOP it should return a webpage with a web-editor:

webeditor

Here you can check if the installation was successful. You can load sample documents using the Load Sample button and clicking on the desired template type. Please check if the PDF conversion is working by first selecting the Template Type and then changing the Output field to PDF.

If you are connected to your web server using putty or any other ssh client, you can enable X11 forwarding to run a web browser.

If you do not have a web browser installed on the server or are sshing from a remote location you can also enable port forwarding:

ssh -N -L 8010:remote.address:8080 remote.address

After this you should be able to go to a local web browser and access the same page with the following url:

http://localhost:8010/

High Available configuration of AOP

There are multiple ways to achieve high availability.

Application Level

First of all you can run AOP in multiple instance using -i argument. Running in -i 0 will spawn a number of AOP instances equal to the available cores of the system. AOP itself has a manager built in. If one of the instances for some reason should exit another one will be started. This is equivalent to PM from node.js

Server level

Here you can run multiple servers and put a load balancer which will divert the call between multiple running servers. Ideally you can use AWS auto scaling like we do. This will spin up a new instance depending on the load. Please note that if you use AOP on this way, you will need licenses for the max number of instance you would like to spawn.

Our AOP Cloud for example is configured to run 4 instances, together with auto scaling for up to 4 machines. The new machines will be started in one of the 3 EU regions provided by AWS. This is to ensure that clients will still have access to our API in case of regional blackouts.

APEX Plug-in and PL/SQL API

Finally you can set a fallback url in the AOP plugin or the PL/SQL API. This can point to our AOP Cloud or a server running in a different region when the primary URL fails.

Now the questions is how available you want AOP to be :)

Security Best Practices

These security best practices don't only apply to AOP, but also to any other software application.

  • Create a new user specific to run the AOP SERVER e.g. Give only the necessary privileges that is required: access to incoming requests on the given port and the ability to run soffice. Or run AOP inside a docker instance.

  • Setup the firewall so that only the database server can connect to the AOP Server port OR use the IP whitelist feature of AOP to only allow access from the IP of the database server

  • Configure HTTPS either using a reverse proxy or let AOP run in HTTPS mode, see HTTPS Configuration section

Speed improvements of larger documents and PDF conversion

In case you print larger documents, it's beneficial to use the high memory version of AOP. Further more to speed up the conversion time to PDF, the following plays a role:

  • the version of LibreOffice
  • the number of CPU cores and speed (at least 4 cores is recommended) Our CPU speed in the AOP Cloud can burst up to 3.1 Ghz
  • the memory speed
  • the disk speed
  • the network traffic time
  • the use of a RAM disk: typically if you can use a RAM disk for the temporary directory which is used by AOP for the PDF conversion, it will speed up the conversion with 50%. This is specified in aop_configc.jsonc “pdf_temp_folder”: “”, //The temp folder to use when AOP calls the PDF converters. (can be a memory folder for faster conversion) See https://access.redhat.com/solutions/30050 or https://medium.com/@sandeeparneja/how-to-make-tmp-directory-use-ram-over-filesystem-d50bc1966aee

For complex documents, we have seen that LibreOffice 6.3 is the fastest in doing the PDF conversion than any other version of LibreOffice. In case you lose time on the generation of the JSON; native JSON syntax is faster than cursor() syntax. The cursor() syntax has also a limit on the size of the document, so in case you have very large datasets, it's recommended to use the native JSON functions in the database.