Type Methods

Type methods are Cordra’s way of enabling custom operations to be added to the system. And these operations or methods defined in JavaScript can be enabled in the context of a given type; hence the name Type methods. This way to execute JavaScript is in addition to executing JavaScript methods at specific lifecycle points of the digital object management.

Instance Methods

JavaScript can be used to associate and define arbitrary named methods which can return information about an object and/or modify the object. We refer to these as Instance Methods to indicate that these methods can only be invoked by a client on a specific object instance (using its id).

Necessary object locking is performed by Cordra prior to executing these methods.

Suppose you have an instance of an object that has a property called “name”. The below instance method could be used to retrieve that single property:

exports.methods = {};
exports.methods.extractNameInstanceMethod = extractNameInstanceMethod;

function extractNameInstanceMethod(object, context) {
    return object.content.name;
}

Instance methods are made available to Cordra by assigning them to the object exports.methods.

The REST API can be invoked using curl. The param objectId specifies the object to invoke the method on and method specifies the method to call.

curl -k -X POST 'https://localhost:8443/cordra/call?objectId=test/abc&method=extractNameInstanceMethod'
-H 'Authorization: Bearer ACCESS_TOKEN'

The POST body of the method call API is available to the method, either as context.params (in which case the POST body is interpreted as JSON) or using context.directIo (see Direct I/O). The output of the method is either JSON returned by the JavaScript function implementing the method, or can be set using context.directIo.

See also Asynchronous Lifecycle Hooks and Throwing Errors in Schema JavaScript for setting the output using promises or exceptions.

If the method is accessed via GET (or POST with empty body) the input can be given by the URI query parameter “params”.

Instance methods can also act as an alternative means of object update. Here is an example to update the name property:

exports.methods = {};
exports.methods.updateNameInstanceMethod = updateNameInstanceMethod;

function updateNameInstanceMethod(object, context) {
    object.content.name = context.params.newName;
    return object.content.name;
}

Request:

curl -k -X POST 'https://localhost:8443/cordra/call?objectId=test/abc&method=updateNameInstanceMethod'
-H "Accept: application/json"
-H "Content-type: application/json"
-d '{"newName":"some name here"}'
-H 'Authorization: Bearer ACCESS_TOKEN'

Response:

"some name here"

Note that beforeSchemaValidation and beforeSchemaValidationWithId are not automatically run when updating an object in this manner. (The beforeSchemaValidation or beforeSchemaValidationWithId code could be called directly by the method code if desired.)

Static Methods

Static methods are are not associated with any particular instance of an object, and are useful for only reading information from one or more objects of any type. No object locking is performed by Cordra prior to the execution of these methods.

Since a static method is not associated with an object instance, it only has the single argument called context. When invoking a static method through the REST API, an optional POST body can be supplied which is made available either as JSON under context.params, or using context.directIo (see Direct I/O).

If the method is accessed via GET (or POST with empty body) the input can be given by the URI query parameter “params”.

The output of the method is either JSON returned by the JavaScript function implementing the method, or can be set using context.directIo. In the below example, the function echoes back whatever was included in the params along with a timestamp.

Static methods are made available to Cordra by assigning them to the object exports.staticMethods.

exports.staticMethods = {};
exports.staticMethods.exampleStaticMethod = exampleStaticMethod;
exports.staticMethods["123/abc"] = exampleStaticMethod;

function exampleStaticMethod(context) {
    const input = context.params;
    const result = {
        input: input,
        timestamp : new Date().getTime()
    };
    return result;
}

Request:

curl -k -X POST 'https://localhost:8443/cordra/call?type=Document&method=exampleStaticMethod'
-H "Accept: application/json"
-H "Content-type: application/json"
-d '{"foo":"hello", "bar":"world"}'
-H 'Authorization: Bearer ACCESS_TOKEN'

Response:

{"input":{"foo":"hello","bar":"world"},"timestamp":1532719152687}

The example static method shown in the JavaScript above also demonstrates how a method can be given a handle as an identifier as well as a name. Here the method is also exported with the handle “123/abc”.

Service-level Static Methods

Static methods can also be created which apply to the entire Cordra service instead of to a specific Type. These methods should be assigned to exports.staticMethods in the “javascript” property of the “design” object.

Service-level static methods can be called

  • With special objectId service, /call?objectId=service&method=methodName

  • With the DOIP service id, and in particular as DOIP custom operations targeting the service id. See DOIP and Examples.

For backward compatibility service-level static methods can also be called as follows; this is not recommended for new usage.

  • With type CordraDesign, /call?type=CordraDesign&method=methodName

  • With objectId design, /call?objectId=design&method=methodName

Allowing GET

By default calling a type method requires an HTTP POST. To allow the use of HTTP GET, you can specify a member of the JavaScript function object called “allowGet” to be true:

exports.methods = {};
exports.methods.example = function (object, context) {
    // ...
}
exports.methods.example.allowGet = true;

Direct I/O

The object context.directIo provides functionality for directly manipulating the input and output of a type method. If the methods for accessing input are used, it is an error to also access context.params. If the methods for accessing output are used, the value returned from the JavaScript function (which otherwise provides the type method output) will be ignored.

The primary use for context.directIo is to allow accessing input or providing output as bytes instead of as JSON.

The following methods are available:

context.directIo.getInputAsJavaInputStream();
context.directIo.getInputAsJavaReader();
context.directIo.getInputAsUint8Array();
context.directIo.getInputAsString();
context.directIo.getInputAsJson();

context.directIo.getOutputAsJavaOutputStream();
context.directIo.getOutputAsJavaWriter();
context.directIo.writeOutputUint8Array(bytes);
context.directIo.writeOutputString(string);
context.directIo.writeOutputJson(json);

// Access to the Content-Disposition: and Content-Type: request and response headers
context.directIo.getInputFilename();
context.directIo.getInputMediaType();
context.directIo.setOutputFilename(filename);
context.directIo.setOutputMediaType(mediaType);

Note that the writeOutput functions may be called multiple times.

Request Attributes

In addition to the “input” available via context.params or context.directIo, type methods can also make use of “request attributes”, a JSON object available as context.attributes. In DOIP all requests can optionally carry request attributes as a standard part of the protocol; in the Cordra HTTP REST API, attributes as made available via query parameters on the “call” API.

Consider for example the following JavaScript in the Document schema with the static schema method workWithAttributes:

exports.staticMethods = {};
exports.staticMethods.workWithAttributes = workWithAttributes;

function workWithAttributes(context) {
    return {
        attributes: context.attributes,
        requestContext: context.requestContext,
        filename: context.directIo.getInputFilename(),
        mediaType: context.directIo.getInputMediaType(),
        input: context.params
    }
}

And consider a DOIP request with targetId the Document schema object, operationId “workWithAttributes”, and “attributes” as follows:

{
    "filename": "testFilename",
    "requestContext": {
        "requestContextKey": "requestContextValue"
    },
    "mundaneAttribute": "testMundaneAttribute"
}

plus “input”:

{ "inputKey": "inputValue" }

The output from this request is

{
    "attributes": {
        "filename": "testFilename",
        "requestContext": {
            "requestContextKey": "requestContextValue"
        },
        "mundaneAttribute": "testMundaneAttribute"
    },
    "requestContext": {
        "requestContextKey": "requestContextValue"
    },
    "filename": "testFilename",
    "input": {
        "inputKey": "inputValue"
    }
}

The same can be accomplished using the /call HTTP API with type=Document and method=workWithAttributes.