Community Tip - Learn all about PTC Community Badges. Engage with PTC and see how many you can earn! X
Hi,
I am very new to both Thingworx and developing custom extensions and I have a few questions about things I have not been able to solve.
Essentially, I want to be able to access custom functions inside my services, be they javascript or java.I am very familiar with javascript and jquery and have my own library of js functions that I'd ideally like to use, but if that is not possible, I'm happy to translate them as I need into java functions.
So to testy what is usable (and because I cannot find anything that is documented on how this works), I am going down the path of writing a custom extension and am able to do so, but how do I get my java functions/methods to show in the snippets section. I have added a "New Script Function Library" using the Eclipse Thingworx extension plugin and added services to that
But when I import this into Thingworx, it shows the library but nothing below it.
If I try and insert a "New Resource" instead (as some info implies this is what is required), I cannot import. I always get the error
[message: [1,018] Data store unknown error: [Error occurred while accessing the model provider.]]
So what do I need to do to get things to appear in the snippets area. It will be onerous if all the functions are not viewable or we have to access them through a data shape or thing template.
I'll further add that I have also tried to write a ThingShape and include all my functions in that instead but when I try to import I get this error
So pretty stuck with only one entity working. Got to be something fundamental I am missing here.
Thanks
Ben
Hi,
please check the "thingworx Extension Development Guide" of PTC.
On page 31 (Version 4.4 from November 2017), you'll find an example on how to write a service method.
It is not working when adding a service via "ThingWorx Source" functionality.
Please find an example below:
public class MySimpleScript { private static Logger _logger = LogUtilities.getInstance().getApplicationLogger(MySimpleScript.class); public MySimpleScript() { // TODO Auto-generated constructor stub } public static Object SaySomething( org.mozilla.javascript.Context cx, org.mozilla.javascript.Scriptable thisObj, Object[] args, org.mozilla.javascript.Function funObj) throws Exception { return "Hello " + args[0]; } }
Please consider to update the "metadata.xml"-file manually.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <Entities> <ExtensionPackages> <ExtensionPackage dependsOn="" description="" minimumThingWorxVersion="8.1.0" name="MySimpleScript" packageVersion="1.0.2" vendor=""> <JarResources> <FileResource description="" file="mysimplescript.jar" type="JAR"/> </JarResources> </ExtensionPackage> </ExtensionPackages> <ScriptFunctionLibraries> <ScriptFunctionLibrary aspect.isEditableExtensionObject="false" className="MySimpleScript" description="" name="MySimpleScript"> <FunctionDefinitions> <FunctionDefinition description="Says Something" name="SaySomething"> <ParameterDefinitions> <FieldDefinition baseType="STRING" description="The name" name="name"/> </ParameterDefinitions> <ResultType baseType="STRING" description="result" name="result"/> </FunctionDefinition> </FunctionDefinitions> </ScriptFunctionLibrary> </ScriptFunctionLibraries> </Entities>
Furthermore ensure to use the Eclipse IDE for Java EE and not the one for Java. Otherwise it won't work and the service method will not be available in snippets library.
Thanks tbraun.
I did manage to get this working and should have posted my working as a reply to my own question. So I'll do it now so others may (or may not) benefit. I have the following as a resource in my extension.
package com.thingworx.resources.regainresources; import com.thingworx.common.NamedValueCollection; import com.thingworx.common.RESTAPIConstants; import com.thingworx.common.exceptions.InvalidRequestException; import com.thingworx.entities.utils.ThingUtilities; import com.thingworx.logging.LogUtilities; import com.thingworx.metadata.annotations.ThingworxServiceDefinition; import com.thingworx.metadata.annotations.ThingworxServiceParameter; import com.thingworx.metadata.annotations.ThingworxServiceResult; import com.thingworx.resources.Resource; import com.thingworx.things.Thing; import com.thingworx.things.repository.FileRepositoryThing; import com.thingworx.things.repository.FileRepositoryThing.FileMode; import com.thingworx.types.InfoTable; import com.thingworx.webservices.serialization.InfoTableCSVConverter; import java.io.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; public class RegainResource extends Resource { protected static Logger _logger = LogUtilities.getInstance().getApplicationLogger(RegainResource.class); public RegainResource() { // TODO Auto-generated constructor stub } @ThingworxServiceDefinition(name="TestService", description="A test service") @ThingworxServiceResult(name="result", description="A simple string", baseType="STRING") public String TestService() throws Exception { /* _logger.trace("Entering Service: TestService"); _logger.trace("Exiting Service: TestService"); */ return "Hello"; } @ThingworxServiceDefinition(name="WriteCSVFile", description="Write CSV file to a repository") public void WriteCSVFile(@ThingworxServiceParameter(name="data", description="Data", baseType="INFOTABLE") InfoTable data, @ThingworxServiceParameter(name="fileRepository", description="File repository name", baseType="THINGNAME") String fileRepository, @ThingworxServiceParameter(name="path", description="Path to file", baseType="STRING") String path, @ThingworxServiceParameter(name="withHeader", description="Use headers for column names", baseType="BOOLEAN", aspects={"defaultValue:false"}) Boolean withHeader, @ThingworxServiceParameter(name="append", description="whether to append", baseType="BOOLEAN", aspects={"defaultValue:false"}) Boolean append) throws Exception { if (StringUtils.isEmpty(fileRepository)) { throw new InvalidRequestException("File Repository Must Be Specified", RESTAPIConstants.StatusCode.STATUS_NOT_ACCEPTABLE); } Thing thing = ThingUtilities.findThing(fileRepository); if (thing == null) { throw new InvalidRequestException("File Repository [" + fileRepository + "] Does Not Exist", RESTAPIConstants.StatusCode.STATUS_NOT_FOUND); } if (!(thing instanceof FileRepositoryThing)) { throw new InvalidRequestException("Thing [" + fileRepository + "] Is Not A File Repository", RESTAPIConstants.StatusCode.STATUS_NOT_FOUND); } _logger.debug("about to get repo with thingname = " + thing.getName()); FileRepositoryThing repo = (FileRepositoryThing)thing; FileMode mode; _logger.debug("about to set mode with append = " + Boolean.valueOf(append)); if(append) mode = FileRepositoryThing.FileMode.APPEND; else mode = FileRepositoryThing.FileMode.WRITE; Boolean bFileExists = FileExists(fileRepository, path); OutputStreamWriter osw = new OutputStreamWriter(repo.openFileForWrite(path, mode)); InfoTableCSVConverter converter = new InfoTableCSVConverter(); NamedValueCollection params = new NamedValueCollection(); if(append) { if (withHeader.booleanValue() && !bFileExists) { params.put("Heading", new Boolean(true)); } else { params.put("Heading", new Boolean(false)); } } else { if (withHeader.booleanValue()) { params.put("Heading", new Boolean(true)); } else { params.put("Heading", new Boolean(false)); } } converter.toWriter(osw, data, params); osw.close(); } @ThingworxServiceDefinition(name = "FileExists", description = "True or false for whether a file in a file repository actually exists", category = "", isAllowOverride = false, aspects = { "isAsync:false" }) @ThingworxServiceResult(name = "result", description = "True or false for actual file existence", baseType = "BOOLEAN", aspects = {}) public Boolean FileExists( @ThingworxServiceParameter(name = "filerepository", description = "Name of the File Repository thing", baseType = "THINGNAME", aspects = { "isRequired:true" }) String filerepository, @ThingworxServiceParameter(name = "path", description = "Path and filename", baseType = "STRING", aspects = { "isRequired:true" }) String path) throws InvalidRequestException { /* _logger.trace("Entering Service: FileExists"); _logger.trace("Exiting Service: FileExists"); */ if (StringUtils.isEmpty(filerepository)) { throw new InvalidRequestException("File Repository Must Be Specified", RESTAPIConstants.StatusCode.STATUS_NOT_ACCEPTABLE); } Thing thing = ThingUtilities.findThing(filerepository); if (thing == null) { throw new InvalidRequestException("File Repository [" + filerepository + "] Does Not Exist", RESTAPIConstants.StatusCode.STATUS_NOT_FOUND); } if (!(thing instanceof FileRepositoryThing)) { throw new InvalidRequestException("Thing [" + filerepository + "] Is Not A File Repository", RESTAPIConstants.StatusCode.STATUS_NOT_FOUND); } FileRepositoryThing repo = (FileRepositoryThing)thing; InfoTable table= null; Boolean bReturn = false; try { table = repo.GetFileInfo(path); bReturn = true; } catch (Exception e) { bReturn = false; } return bReturn; } }
I package that extension and import it into Thingworx and I finally get to see the functions
I did do this several months ago, some some step I must have performed that corrected my previous issues I cannot recall off the top of my head right now.
I can obviously piggy back from this base now and add all my existing javascript functions across to this resource as pure java functions.
The fundamental difference I see between this code and my previous code I posted is that here I extend Resource, whereas previously it was not an extended class. (But I may have made other fundamental changes)
Regards
Ben
Hi Ben,
thanks for your reply.
I guess you still need to update your "metadata.xml" file manually after adding the service methods?
Regards,
Tobias