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
Solved! Go to Solution.
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?
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.
Thanks Pai. What would be the best way to create a local service with JS body?
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.
Constantine,
Any update on this? Was Pai Chung's post helpful? If so, could you click on the "correct answer" or "mark as helpful" button and let us know?
Hello Jeremy, unfortunately there's still no good answer for this question.
So far, Costin's answer to this topic seems to be the closest to the solution: AddDynamicSubscription help
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?
Hello Vladimir,
If I remember correctly, eval() was not available in TWX Rhino.
/ Constantine
eval is available.
let me just stress again that this is very dangerous, because now you are going to allow non validated code to run.
Hello Pai,
Indeed, I tested and it works! I believe it didn't work before, but that might have been my memory issue
Cool, that's all I need, thanks.
/ Constantine
I (obviously) agree with Pai. The context in which I used this was to build sequences of predefined services at runtime.
Having to write the service at runtime is definitely not a suggested course of action, just technically possible if you implement it.
Vladimir, we'll use it in controlled and safe way, for sure. It's just easier than implementing some custom business rules engine. I marked your answer correct, thanks!
/ Constantine
eval it's evil but for a flexible solution it's the swiss tool, I can't live without it.
Currently my main concern about it is performance.
I'm using it on a lot of places, and it's not the bootle neck.
//
// 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.
Awesome, that's exactly what I was looking for! Thanks.
/ Constantine
Can you actually do this though? I believe this needs an Administrator account to do the Import/Export?
It would be nice to see how they do it in Utilities, but I'm too lazy to check it.
As far as I know, you need a user account who has permission to Read and Update the Entity.
There's a document with the "correct answer" now: https://community.thingworx.com/docs/DOC-3853