Skip to main content
7-Bedrock
February 23, 2017
Solved

Modify service code programmatically

  • February 23, 2017
  • 5 replies
  • 11100 views

Hello,

Is there some simple solution for modifying the service implementation programmatically? I could create an empty service using AddServiceDefinition, but now I'm stuck with filling it.

I tried to do something like Export to XML > Modify XML > Import from XML, and the first two steps work fine, but for some reason I couldn't import the XML. Apparently the Importer servlet works differently from the regular TWX REST interfaces, and I can't figure out how to use it.

Is there any better way to do it, or maybe some example? Thanks!

Regards,

Constantine

Best answer by VladimirRosu_116627

Hi Constantine,

Let me give an example of dynamic code execution that can be used in the platform.

For this to work you need to have a wrapper service, called let's say "Execute Service". You can run inside a single line, a javascript function called eval(string) which takes a string input parameter.

So you have 2 choices: you either store the command itself in a property so whenever you want to modify the implementation you set the property to a new value (can be an through an editor inside a mashup) or you can simply have the service accepting the command as a parameter.

Can this be applied in your use case?

5 replies

22-Sapphire I
February 23, 2017

AddServiceDefinition is there to create a definition so that i can be bound as a Remote Service.

It isn't meant to allow for dynamic 'local' Service and Service Script creation.

7-Bedrock
February 24, 2017

Thanks Pai. What would be the best way to create a local service with JS body?

22-Sapphire I
February 24, 2017

Unfortunately I don't know of a way to do it permanently.

But you could store jscript in a datatable and execute that ... since this is pushing the boundaries of what is possible and what is recommended and what can become incredibly dangerous, email me and I can share an example.

7-Bedrock
March 13, 2017

So far, Costin's answer to this topic seems to be the closest to the solution: AddDynamicSubscription help

19-Tanzanite
March 13, 2017

Hi Constantine,

Let me give an example of dynamic code execution that can be used in the platform.

For this to work you need to have a wrapper service, called let's say "Execute Service". You can run inside a single line, a javascript function called eval(string) which takes a string input parameter.

So you have 2 choices: you either store the command itself in a property so whenever you want to modify the implementation you set the property to a new value (can be an through an editor inside a mashup) or you can simply have the service accepting the command as a parameter.

Can this be applied in your use case?

7-Bedrock
March 13, 2017

Hello Vladimir,

If I remember correctly, eval() was not available in TWX Rhino.

/ Constantine

1-Visitor
March 14, 2017

eval it's evil but for a flexible solution it's the swiss tool, I can't live without it.

7-Bedrock
March 14, 2017

Currently my main concern about it is performance.

1-Visitor
March 14, 2017

I'm using it on a lot of places, and it's not the bootle neck.

1-Visitor
March 16, 2017

//

// For this example, we'll have an Math service

// which takes two numbers, and an operation.

// The result will be that operation performed on the two inputs.

//

// We either need an Application Key,

// or user credentials to perform the reads and writes.

// App keys are a little safer.

// In this demo, we'll store it on the Entity as a Property.

var appKey = me.appKey;

//

// The service name needs to be unique and not already in use.

var serviceName = "MyMath";

//

// What are the inputs to the service?

// We'll define them nicely here, but manipulate this object later.

var parameters = {

    "op" : "STRING",

    "x" : "NUMBER",

    "y" : "NUMBER"

};

//

// What datatype does the service return?

// If it's an infotable,

// then you'll also have to specify the data shape

// as part of the resultType's aspect,

// but I won't demonstrate that here.

var output = "NUMBER";

//

// What is the actual service script?

// We'll define it here as an array of lines, and then join them together.

var serviceScript = [

    "var result = (function() {",

    "  switch(op) {",

    "    case \"add\": return x + y;",

    "    case \"sub\": return x - y;",

    "    case \"mult\": return x * y;",

    "    case \"div\": return x / y;",

    "    default: return op in Math ? Math[op](x, y) : 0;",

    "  };",

    "})();",

].join("\n");

////////

//

// Let's convert the friendly parameter definition

// into the structure that ThingWorx uses:

var parameterDefinitions = Object.keys(parameters).reduce(function(parameterDefinitions, parameterName, index) {

    var parameterType = parameters[parameterName];

    parameterDefinitions[parameterName] = {

        "name": parameterName,

        "aspects": {},

        "description": "",

        "baseType": parameterType,

        "ordinal": index

    };

    return parameterDefinitions;

}, {});

//

//  Now let's set up our service definition and implementation.

var definition = {

    "isAllowOverride": false,

    "isOpen": false,

    "sourceType": "Unknown",

    "parameterDefinitions": parameterDefinitions,

    "name": serviceName,

    "aspects": {

        "isAsync": false

    },

    "isLocalOnly": false,

    "description": "",

    "isPrivate": false,

    "sourceName": "",

    "category": "",

    "resultType": {

        "name": "result",

        "aspects": {},

        "description": "",

        "baseType": output,

        "ordinal": 0

    }

};

var implementation = {

    "name": serviceName,

    "description": "",

    "handlerName": "Script",

    "configurationTables": {

        "Script": {

            "isMultiRow": false,

            "name": "Script",

            "description": "Script",

            "rows": [{

                "code": serviceScript

            }],

            "ordinal": 0,

            "dataShape": {

                "fieldDefinitions": {

                    "code": {

                        "name": "code",

                        "aspects": {},

                        "description": "code",

                        "baseType": "STRING",

                        "ordinal": 0

                    }

                }

            }

        }

    }

};

////////

//

// Here are the URLs we'll need in order to make updates.

// You can change the thing name ('ServiceModifier' here)

// to something else.

// If you use credentials instead of an app key,

// then you can remove the appKey parameter here,

// but you'll have to add the username and password

// to the two ContentLoaderFunctions calls.

var url = {

    export : "http://127.0.0.1:8080/Thingworx/Things/ServiceModifier?Accept=application/json&appKey="+appKey,

    import : "http://127.0.0.1:8080/Thingworx/Things/ServiceModifier?appKey="+appKey

};

//

// We can download the entity to modify as a JSON object.

// Older versions of ThingWorx might not support this.

var config = Resources.ContentLoaderFunctions.GetJSON({

    url : url.export,

});

//

// We have to modify both the 'effectiveShape',

// as well as the 'thingShape'.

config.effectiveShape.serviceDefinitions[serviceName] = definition;

config.effectiveShape.serviceImplementations[serviceName] = implementation;

config.thingShape.serviceDefinitions[serviceName] = definition;

config.thingShape.serviceImplementations[serviceName] = implementation;

// Finally, we can push our updates back into ThingWorx.

Resources.ContentLoaderFunctions.PutText({

    url : url.export,

    content : JSON.stringify(config),

    contentType : "application/json",

});

// The end.

7-Bedrock
March 17, 2017

Awesome, that's exactly what I was looking for! Thanks.

/ Constantine

7-Bedrock
June 1, 2017

There's a document with the "correct answer" now: https://community.thingworx.com/docs/DOC-3853