Skip to content

Extension Libraries

Overview

If the functionality provided by the system does not meet your requirements during development, you can use extension libraries to introduce external code. Currently, only Java code is supported. The external code needs to be compiled and packaged into a zip file. You can execute the code in the zip file by calling the informat.system.invokeLibrary method.

To ensure the system remains stateless during calls, only static methods are allowed. It is also strongly recommended not to use static variables to preserve state in the code.

The following restrictions apply to methods exposed in extension libraries:

  • Method names must be unique; method overloading by return value or parameter type is not supported
  • Method parameters and return values must be serializable

When calling an extension library, method matching is performed based on the method name and number of parameters. An exception will be thrown if no method is found or if multiple methods match.

Methods in the extension library:

java
static int add(int a,int b);//method 1
static int add(int a,int b,int c);//method 2
static int add(String a,String b);//method 3

Calling from a script:

js
informat.system.invokeLibrary("mylibrary", "com.mycompany.MyLibaray", "add", [1, 2, 3]); //matches method 2
informat.system.invokeLibrary("mylibrary", "com.mycompany.MyLibaray", "add", [1, 2]); //will throw an exception

ZIP Package Description

  • Package the extension library project into a file named main.jar
  • Place the project's dependent jar files in the lib directory (multi-level directories are not supported)
  • Compress the directory into a password-free zip file
  • Upload the generated zip file to the Application Extensions in the application designer

Example ZIP package structure:

.
├──lib
│   ├── lib1.jar
│   └── lib2.jar
└──main.jar

Examples

Below is a simple extension library, which we'll identify as mylibrary

java
package com.mycompany;

import cn.hutool.crypto.digest.DigestUtil;

public class MyLibaray {
	public static int add(int a,int b) {
		return a+b;
	}
	public static String md5(String input) {//reference third-party library
        return DigestUtil.md5Hex(input);
    }
}

Calling the extension library:

java
var result=informat.system.invokeLibrary('mylibrary','com.mycompany.MyLibaray','add',[1,2]);
//result is 3
var result=informat.system.invokeLibrary('mylibrary','com.mycompany.MyLibaray','md5',['111111']);
//result is 3

INFO

Demo download address: https://next.informat.cn/types/informat-next-library-demo.zip

After packaging the project with mvn install, informat-next-library-demo-1.0.zip will be generated in the target directory

Application Design -> Global Design -> Advanced Settings -> Extension Libraries] Add a new extension library and upload the informat-next-library-demo-1.0.zip file

Calling Informat APIs in Extension Libraries

In extension libraries, you can call APIs provided by Informat through the Informat object. To call Informat APIs, you need to import informat-spi.jar from the download address: https://next.informat.cn/types/informat-spi-1.0.0.jar

Here is an example of calling an API provided by Informat:

java
package com.mycompany;
public class MyLibaray {
	public static String getUser() {
		String userId=Informat.app().userId();//get current user
        Informat.console().log("current userId is:"+userId);//output current user to application console
        return userId;
	}
}

Calling Informat Object via RPC

By default, extension libraries need to be deployed in the Informat environment to call methods provided by Informat. Informat also provides remote calls to its APIs through RPC.

INFO

When calling APIs remotely, it is context-independent, and you cannot get the current operating user through informat.app.userId().

When calling APIs via RPC, communication between the client and the Informat server is via HTTP requests. The client serializes the parameters and sends them to the server, and the server serializes the return value and sends it back to the client. The entire request process is based on a request->response model and is stateless. Additionally, the parameters and return values of the called functions must be serializable. For some APIs provided by Informat, such as informat.excel, informat.word, etc., they depend on certain context environments during calls, and their parameters may not be serializable, which will throw exceptions when called remotely.

The following parameters need to be set when calling APIs via RPC:

  • serverAddress Server address
  • appId APP ID
  • apiKey APP's apikey

Here is an example of calling via HTTP:

java
package com.mycompany;
public class RemoteTest {
	public static void main(String args[]) {
        Informat.setServerAddress("https://next.informat.cn/web0");//Informat API call address
        Informat.setAppId("appId");//APP ID
        Informat.setApiKey("apiKey");//APP's apikey
	    Map<String,Object>record=Informat.table().queryById("table","001");
        System.out.println("record is "+record);
	}
}

Debugging Code in IDEA

You need to configure serverAddress, appId, and apiKey for debugging.

Create a main method in the class you want to debug, and select Debug Run when running: debug

debug-2

Calling Informat Object via HTTP

If you are developing in other languages, you can also call Informat APIs via HTTP POST requests. The request method is as follows:

Request Address

https://next.informat.cn/web${cluster}/spi/invoke/${appId}?sign=${sign}
  • ${cluster} is the node ID for cluster deployment, default is 0
  • ${appId} is the application ID
  • ${sign} is the signature for calling the interface

Assuming the application ID is app1, the complete address would be:

https://next.informat.cn/web0/spi/invoke/app1

Request Parameters

The request body structure is:

ts
{
    method:string,//method to be called, for example, to call the `log` method of `console`, set it to `console.log`
    args:object[];,//parameter list, if a parameter is null, set it to null, e.g., ['abc',null,123]
    timestamp:integer,//timestamp when the request is called, ensure the clock difference between client and server is within 15 minutes; requests with a difference exceeding 15 minutes will be rejected
    nonce:string,//request ID, which will be returned in the result; it is recommended to use UUID to generate nonce to ensure each request has a unique ID
}

The content of the request body needs to be the JSON generated from the object described above. Here is an example request body:

json
{
  "method": "console.log",
  "args": ["arg1"],
  "timestamp": 1670917594517,
  "nonce": "001"
}

Request Signature (sign)

The purpose of passing a request signature is to:

  • Verify the legitimacy of the requester
  • Verify the integrity of parameters and whether they have been tampered with
  • Prevent replay attacks

The request signature needs to be calculated using the SHA256withRSA algorithm. The calculation method is:

js
let req; //request body
let json = JSON.stringify(req); //convert the request body to a JSON string
let sign = SHA256withRSA(json, privateKey); //calculate the signature using privateKey, which is the application's API Key

Return Content

The server returns the following structure:

js
{
    nonce:String,//nonce from the request parameters
    ret:Object,//return value of the method
    exceptionClass:String,//exception thrown by the method
    exceptionMessage:String//exception information thrown by the method
}

The content returned by the server is the string obtained by converting the above structure to JSON, with Content-Type set to text/json