Custom services

This document describes how to implement custom services using external objects.

These custom services are only available as of version 3.1 and only on the API endpoint. The authentication mechanisms available on this endpoint are described in this document the credentials that needs to be passed to the calls are noted <credentials>.

The calls examples are given using the curl command line tool (that can easily be transposed to any HTTP client tool or API).

Note: in version 3.x the -b cookies.txt -c cookies.txt parameters of the curl calls below are required as they allow to re-use the same server session (identified by the JSESSIONID cookie). In versions 4.0+ a technical session is used to avoid taking care of the session cookie.

For an application deployed on myapp webapp root, the base URL of the custom services is:

http[s]://<host[:<port>]>/myapp/api/ext

For an application deployed on the default webapp root, the base URL of the custom services is:

http[s]://<host[:<port>]>/api/ext

It will be noted <base URL> in the rest of the document.

Warning: In production the services endpoint's URL should be restricted only to allowed origins e.g. using URL filtering based on request's origin IP address or similar approaches.

Service implementation

A custom service is just a plain external object (check this document for general principles of external objects).

In particular this external object needs to be granted to the user that will be calling it on the API endpoint.

Rhino

For a JSON/REST custom service, the Rhino implementation of the MyServiceV1 external object could be something like:

// The function to implement is the display method like for any external object
MyService.display = function(params) {
    // In this example the output is JSON, the MIME type needs to be forced explicitly
    this.setMIMEType(HTTPTool.getMimeTypeWithEncoding(HTTPTool.MIME_TYPE_JSON));
    // Note that if MIME type is left to default (HTML), a this.setDecoration(false) is generally required

    var method = params.getMethod();
    console.log("Call method = " + method);

    var req = params.getJSONObject();
    console.log("Request body = " + req);

    var res = new JSONObject();
    if (method == "POST") {
        if (req != null) {
            res.put("request", req);
            res.put("response", "Hello " + req.optString("name", "Unknown"));
        } else {
            res.put("error", "Call me with a request please!");
        }
    } else {
        res.put("error", "Call me in POST please!");
    }
    return res.toString();
};

Java

In Java as of version 4.0.P23 you can extends the more convenient com.simplicite.webapp.services.RESTServiceExternalObject helper class dedicated to custom JSON/REST services implementation.

The same example as above would then be something like:

public class MyServiceV1 extends com.simplicite.webapp.services.RESTServiceExternalObject {
    @Override
    public Object get(Parameters params) throws HTTPException {
        return error(400, "Call me in POST please!");
    }

    @Override
    public Object post(Parameters params) throws HTTPException {
        try {
            JSONObject req = params.getJSONObject();
            if (req != null ) {
                return new JSONObject()
                    .put("request", req)
                    .put("response", "Hello " + req.optString("name", "Unknown"));
            } else {
                return error(400, "Call me with a request please!");
            }
        } catch (Exception e) {
            return error(e);
        }
    }
}

Note: by default the non implemented method get/post/put/del/head of this helper class return a plain 400 ("Bad request") error.

Service call

Then the service could be called (using POST method in this example) like this:

curl <credentials> -b cookies.txt -c cookies.txt -X POST -H "Content-type:application/json" -d @req.json "<base URL>/MyServiceV1"

Where, for instance if req.json is:

{
    "name": "Bob"
}

The result is then:

{
    "request" : {
        "name": "Bob"
    },
    "response": "Hello Bob!"
}

Mapped business object services helper class

As of version 4.0.P23 a high-level helper class com.simplicite.webapp.services.RESTMappedObjectsExternalObject is provided to simply expose Simplicité business object CRUD in a simplified and customized way.

Example:

public class v1 extends com.simplicite.webapp.services.RESTMappedObjectsExternalObject {
    private static final long serialVersionUID = 1L;

    @Override
    public void init(Parameters params) {
        // Map the user business object attributes
        addObject("users", "User");
        addField("users", "login", "usr_login");
        addField("users", "firstname", "usr_first_name");
        addField("users", "lastname", "usr_last_name");
        addField("users", "email", "usr_email");

        // Map the user's responsibility business object attributes
        addObject("user-resps", "Responsability");
        addField("user-resps", "login", "rsp_login_id.usr_login");
        addField("user-resps", "group", "rsp_group_id.grp_name");
        addField("user-resps", "startDate", "rsp_start_dt");
        addField("user-resps", "endDate", "rsp_end_dt");
        addField("user-resps", "active", "rsp_activ");
        // Map the reference from responsibility to user
        addRefField("user-resps", "users", "userId", "rsp_login_id");
    }
}

With the above mapping the user and responsibilities standard objects are available on URI such as:

An OpenAPI schema is available on /api/v1/openapi.yml