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.
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 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”.
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
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;
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.
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
.