Overview
Java methods of a priint:publishing server plug-in can be invoked in cscript. Some prerequisites must be fulfilled:
- the Java method must be annotated as @PubServerMethod
- in addition, the method must be annotated as @CometCScriptFunction
- all parameters of this method must be annotated as @PubServerMethodParameter
- regarding supported types for parameters and return values there are some limitation, see section Type-Mapping
Requirements
- priint:publishing server 4.0.5 or 4.1.0 recent release
- priint:comet ID plug-ins or priint:renderer 4.0.5 or 4.1.0
- support for Publication, Parameter, WorkflowStatus and related data types requires priint:publishing server version 4.1.0 and priint:comet ID plug-ins resp. priint:renderer version 4.1.0
- support for Element, Planning and related data types requires priint:publishing server 4.1.0 build >= 3710 and priint:comet ID plug-ins resp. priint:renderer 4.1.0 >= R18200
Hello World
We expect basic knowledge of priint:publishing server plug-in development and cscript programming
To try out one of the following examples
- create a new priint:publishing server PluginLibrary project in Eclipse
- put the Java sample code in a new priint:publishing server plug-in class
- deploy the plug-in
- in Ison connect to a Comet project and create a new cscript. I would suggest to create a panel action, which can then be run from one of the priint:comet InDesign Desktop panels.
- in InDesign connect to this project (or clear script cache, if you are already connected to this project) and select the test cscript from the panel menu
Example 1 - Hello
The basic concept is best explained with this example:
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { public static final String MAPPED_NAME = "com.priint.pubserver.comet.cscript.HelloWorld"; /** * Hello World * * @return Hello World */ @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction public String helloWorld() { return "Hello World"; } }the corresponding cscript code to call this function:
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c" int main() { char * message = helloWorld(); showmessage("Server said: %s", message); release(message); return 0; }
Example 2 - Parameters
The second example shows, how to pass parameters to a method:
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { public static final String MAPPED_NAME = "com.priint.pubserver.comet.cscript.HelloWorld"; /** * Hello World * * @return Hello World */ @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction public String helloWorld2(@PubServerMethodParameter String login) { return "Hello " + login + "!"; } }The corresponding cscript code:
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c" int main() { char * login = alloc(4096); char * message = 0; login = system::login(login); message = helloWorld2(login); showmessage("Server said: %s", message); release(message); release(login); return 0; }
Résumé
- Java methods of a priint:publishing server plug-in annotated as @PubServerMethod and @CometCScriptFunction can be invoked as a cscript function using the method name as function name
- to do so, the Java plug-in must be included in the cscript using a special include-directive
- schema for this directive is
#include "[pubserver]/plugin/<Mapped Name of the plug-in>.c"
- Java data types are mapped to cscript data types according to certain rules (in the examples we mapp String to char* and vice versa)
- the signature (i.e. name, number and type of arguments) of the cscript function is exactly the same as the signature of the Java method (beside of type mapping)
- (implied, but quite important): in cscript I do not have to bother about memory allocation, but results of a Java method call must be released (unless for the simple types int and float or if null is returned).
More basic Information
- the cscript code for priint:publishing server plug-in methods annotated as @CometCScriptFunction is generated at runtime.
- this means: the method, the generated code and headers are available only as long as the Java plug-in is deployed on the server
- this also means: changes in Java methods take effect immediately - without having to restart the publishing server or having to logout / login with InDesign (probably the script cache must be cleared)
- cscript function names are derived from the Java method names. Any valid cscript identifier is allowed (fortunately this corresponds with Java naming conventions), except for keywords (int, return, if ...) and names of builtin-functions of the cscript language (such as "strcmp" oder "alloc").
If for any reason your Java method must be named alloc, you can specify a different name for the cscript function using the name attribute of the @CometSCriptFunction annotation:@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(name="alloc_") public int alloc(...
- parameter names are not derived from Java parameter names, but are built up by a prefix and consecutive numbers (__gcsarg1_ .. __gcsargn_). To specify a parameter name, you can use the name attribute of the @PubServerMethodParameter annotation.
@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction public int min( @PubServerMethodParameter(name="value1") int n1, @PubServerMethodParameter(name="value2") int n2) ...
needless to say, that the following will lead to errors:@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction public int min( @PubServerMethodParameter(name="value") int n1, @PubServerMethodParameter(name="value") int n2) ...
Parameter names should not start with the prefix __gcs, because we use this prefix for local variables. The following example will cause errors also, though it is not so obvious:@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction public int min( @PubServerMethodParameter int n1, @PubServerMethodParameter(name="__gcsarg1_") int n2) ...
The Java / Eclipse project DemoCScript delivered with the Ison workspace contains lots of more examples and information.
Functions, Procedures and Error Handling
The cscript language and Java language - though similar regarding the syntax - differ in many aspects:
- in cscript supports functions only; subroutines always return a value, often errors are indicated by a special error value (e.g. nonzero).
- Java-methods can be functions (with) or procedure calls (without return value); errors are indicated by Exceptions
The @CometCScriptFunction allows to specify, whether the cscript stub for a Java method uses should be called as a function or as a proceddure, no matter, if the method returns a value or not. Use the attribute callStyle:
- CallStyle.FUNCTION: the cscript functions returns the value returned by the Java method
- CallStyle.PROCEDURE: return value of the cscript function is (int)0 or the ErrorCode of the exception raised by the method.
Example 1 - "function"
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(callStyle=CallStyle.FUNCTION /* = default! */ ) public int max( @PubServerMethodParameter(name="n1") int n1, @PubServerMethodParameter(name="n2") int n2) { return n1 > n2 ? n1 : n2; } }The corresponding cscript code:
// function signature int max(int n1, int n2); // example call int n = max(17, 23);
Example 2 - "procedure"
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(callStyle=CallStyle.PROCEDURE) public void sendEmail( @PubServerMethodParameter(name="recipient") String recipient) throws CometException { try { deliverEmail(recipient); } catch (Exception e) { int errorCode = 13; throw new CometException(errorCode, "Could not deliver Email, reason: " + e.getMessage(), e); } } }The corresponding cscript code:
// function signature int sendEmail(char* recipient); // example call int errorCode = sendEmail("support@priint.com");
Example 3 - "procedure with return value"
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(callStyle=CallStyle.PROCEDURE) public String sendEmail( @PubServerMethodParameter(name="recipient") String recipient) throws CometException { try { deliverEmail(recipient); String status = getMailDeliveryStatus(); return status; } catch (Exception e) { int errorCode = 13; throw new CometException(errorCode, "Could not deliver Email, reason: " + e.getMessage(), e); } } }The corresponding cscript code
// function signature // An additional parameter is inserted at the front of the // function parameter list (char** out), see explanations // above. int sendEmail(char** out, char* recipient); // example call char* status = 0; // please note the address-operator, we pass the address of // the string (char*) pointer. If call succeeds, status points // to a valid char* address, otherwise status will remain null. int errorCode = sendEmail(&status, "support@priint.com"); if (errorCode == 0) { wlog("", "Sending mail succeeded, current status: %s\n", status); } else { wlog("", "Sending mail failed with error code '%d'\n", errorCode); } if (status != 0) { release(status); }
Example 4 - error handling using function style
Unless specified otherwise, the return value of a function on errors is
- pointer types (i.e. all but int and float) null
- for int (int)0, for float (float)0.0
If another value should be returned in case of errors, you can use the failureValue attribute of the @CometCScriptFunction annotation.
Note:
- the failureValue attribute is supported for char*, int and float return type
- the failure value defined for a method is also returned by the cscript function, if calling the method fails at all (e.g. network errors, session expired etc.)
@Stateless(mappedName=HelloWorld.MAPPED_NAME) @LocalBean @PubServerPlugin public class HelloWorld extends PluginControlDefault { public static final String MAPPED_NAME = "com.priint.pubserver.comet.cscript.HelloWorld"; /** * Hello World * * @return Hello World */ @PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(failureValue="You are not welcome. Goodbye.") public String helloWorld3(@PubServerMethodParameter String login) throws PubServerException { if (login.equals("me")) { return "Hello " + login + "!"; } else { throw new AuthenticationFailureException(); } } }Corresponding cscript code:
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c" int main() { char * login = alloc(4096); char * message = 0; int result = 0; login = system::login(login); message = helloWorld3(login); // in real life you chould check for null if (strcmp(message, "You are not welcome. Goodbye.") == 0) { showmessage("Hello world failed: %s", message); result = 1; } release(message); release(login); return result; }
Error Reporting
With PublishingServer 4.1.5.1 Build #2327, a slight improvement has been introduced to control, how errors are reported to the user.
The @CometCScriptFunction annotation now supports the additional attribute errorReporting, which can be set to
- USER: error reporting must be handled by the user (i.e. cscript developer). This is just the old behaviour and therefore the default.
- ALERT: an alert dialog is shown in case of errors.
- LOCALIZED: an alert dialog is shown in case of errors, the exception message is translated.
Example:
@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(errorReporting=CometCScriptFunction.ErrorReporting.ALERT) public void myMethod() throws CometException { try { // working code } catch (Exception e) { // we use the server side TranslationUtils to localize the message. // This can be useful, if setting up the message also requires some // server side calculation: String checkFailureCause = TranslationUtils.getTranslation("permissionDenied"); throw new CometCScriptException(checkFailureCause); } }
Example 2:
@PubServerMethod(type=PluginMethod.MethodType.GENERIC) @CometCScriptFunction(errorReporting=CometCScriptFunction.ErrorReporting.LOCALIZED) public void myMethod() throws CometException { try { // working code } catch (Exception e) { // we directly pass the translation key. Message will be translated on // client side, because we set errorReporting=LOCALIZED. // The result will be exactly the same like in the first example throw new CometCScriptException("permissionDenied"); } }
Using Context
Starting with PubServer 4.1.6, the Comet Project Default Context can be injected in Java methods exposed to cscript.
To configure the project default context, navigate to the comet project in the ISON Comet Explorer, right-click on the project and choose "Edit project properties". This will open an editor and allow to configure context criteria just like for the Product panel configuration, placeholder or table insert methods.
The following code snippets show, how to use context in Java methods exposed to cscript:
Java Method:
@PubServerMethod(type=PluginMethod.MethodType.GENERIC, label="Example for methods with context parameter", description="") @CometCScriptFunction( name="printContext", resultDescription="Context as string") public String printContext(@PubServerMethodParameter() Context context) { return context.toString(); }
CScript Function Signature:
char * printContext(); // no parameters!
Usage Example:
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.UsingContext" int main() { char * context = printContext(); showmessage("Context: %s", context); release(context); return 0; }
The following rules apply for Context parameters:
- the comet project default rules are used. We cannot use any other rules (e.g. from placeholder), because when generating the cscript client stub code, we don't know, in which context this script will be run.
- only one Context parameter is allowed in a Java method exposed to cscript.
It is allowed to add exactly one parameter of type com.priint.pubserver.plugin.entitydata.Context to a Java method. More than one parameter of this type will cause an Exception when generating the cscript client code. - the context parameter is not exposed to cscript. The value will be filled automatically according to the rules defined for the comet project, from the cscript developers point of view, the context parameter is not visible.
Type Mapping
Overview
The Java / cscript interface supports the types listed below as parameter or return types. General directives in cscript:
- variables passed as parameters - except for the primitive types int and float - must be allocated before.
- the return value of a function - again except for the primitive types int and float - must be released, unless 0 was returned.
- string literals can be passed for char* parameters, but the address of a string literal must never be used for the out variable of a procedure style method call.
// valid: WorkflowStatus status = 0; status = getStatusOfPublication("67110123"); publication::workflowstatus::release(status); // invalid: WorkflowStatus status = publication::workflowstatus::alloc(); status = getStatusOfPublication("67110123"); publication::workflowstatus::release(status); // Why? The memory formerly allocated for status is not // referenced any more and therefore can no more be released. // valid, but dubious: WorkflowStatus status = publication::workflowstatus::alloc(); publication::workflowstatus::release(status); status = getStatusOfPublication("67110123"); publication::workflowstatus::release(status); // valid, but dubious: char * publicationId = "67110123"; publicationId = findParentPublication(publicationId); release(publicationId); // wrong! int n = getMax(17, 23); release(n);Notes for Java developers:
- all Comet data types supported can be found in the CometBridgeSDK, package com.priint.pubserver.comet.bridge.entitydata. In the following table, we use the shortcut c.p.p.c.b.e for this package.
- Classes of the EntityManager / EntityModel (Bucket, Text, KeyValue, MediaAsset etc.) cannot be used directly, they first must be transformed into a suitable Comet data type. The CometBridgeSDK provides various utility classes and methods for data mapping, id parsing and mapping etc.
Type Mapping Table
Java Type | CScript Type | CScript Type (out) | Remarks |
---|---|---|---|
void, Void | int | - | There is no void equivalent in cscript, the int return type can just be ignored. |
boolean, Boolean | int | int* | Possible loss of data |
byte, Byte | int | int* | Possible loss of data |
short, Short | int | int* | Possible loss of data |
int, Integer | int | int* | |
long, Long | int | int* | |
BigDecimal | int | int* | Possible loss of data |
BigInteger | int | int* | Possible loss of data |
float, Float | float | float* | |
double, Double | float | float* | |
String | char* | char** | For the cscript result variable no memory must be allocated, but the variable must be released: Release: |
c.p.p.c.b.e.RecordId | IDType | IDType* | Release: |
c.p.p.c.b.e.Element | Element | Element* | Release: |
c.p.p.c.b.e.Parameter | Parameter | Parameter* | Release: |
c.p.p.c.b.e.Product | Product | Product* | Release: |
c.p.p.c.b.e.Planning | Planning | Planning* | Release: |
c.p.p.c.b.e.Publication | Publication | Publication* | Release: |
c.p.p.c.b.e.PublicationType | PublicationType | PublicationType* | Release publication::publication::release(pubType); |
c.p.p.c.b.e.WorkflowStatus | WorkflowStatus | WorkflowStatus* | Release: |
java.util.List<Boolean> | List | List* | Possible loss of data Release: |
java.util.List<Byte> | List | List* | Possible loss of data Release: |
java.util.List<Short> | List | List* | Possible loss of data Release: |
java.util.List<Integer> | List | List* | Release: |
java.util.List<Long> | List | List* | Release: |
java.util.List<BigDecimal> | List | List* | Possible loss of data Release: |
java.util.List<BigInteger> | List | List* | Possible loss of data Release: |
java.util.List<Float> | FloatList | FloatList* | Release: |
java.util.List<Double> | FloatList | FloatList* | Release: |
java.util.List<String> | StringList | StringList* | Release: |
java.util.List<c.p.p.c.b.e.RecordId> | IDTypeList | IDTypeList* | Release: |
java.util.List<c.p.p.c.b.e.Element> | ElementList | ElementList* | Release: |
java.util.List<c.p.p.c.b.e.Parameter> | ParameterList | ParameterList* | Release: |
java.util.List<c.p.p.c.b.e.Product> | ProductList | ProductList* | Release: |
java.util.List<c.p.p.c.b.e.Planning> | PlanningList | PlanningList* | Release: |
java.util.List<c.p.p.c.b.e.Publication> | PublicationList | PublicationList* | Release: |
java.util.List<c.p.p.c.b.e.PublicationType> | PublicationTypeList | PublicationTypeList* | Release: |
java.util.List<c.p.p.c.b.e.WorkflowStatus> | WorkflowStatusList | WorkflowStatusList* | Release: |
java.util.List<c.p.p.c.b.e.KeyValue> c.p.p.c.b.e.KeyValueList |
KeyValues | KeyValues* | Release: |
c.p.p.p.e.Context | - | - | See section Using Context Context parameters are invisible for the cscript developer and will be initialized / released automatically |
Helpers
The @PubServerMethod, @CometCScriptFunction and @PubServerMethodParameter annotations allow detailed documentation of the Java methods and corresponding cscript functions.
At the time (Version 4.0.5) there is no support for this in the Ison editor, so cscript developers must derive all information from the Java classes and methods - or download generated headers with documentation using a suitable tool.
You can use the Placeholder values panel in InDesogn Desktop (this requires a developer license for the comet plug-ins):
Enter a File ID in the Script ID / File ID field and click the disk symbol to download this file.
Start with File-ID [pubserver]/plugins.c, this will list all available includes, Example::
#include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c" #include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.PrimitiveTypes.c" #include "[pubserver]/plugin/com.priint.pubserver.comet.bridge.cscript.CScriptStandardLibrary.c"
Now you can repeat this procedure for any include of interest.
Alternatively you can download [pubserver]/plugins.h , this will show all function signatures including inline documentation:
/** * <p>sophisticated hello world example</p> * <p> * This is a more sophisticated hello world example. * * The PubServerMethod label and description are used for cscript inline * documentation. * Try downloading * [pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.h * from your client application (e.g. InDesign Desktop), to see the effect. * The following cscript code calls this method: * <pre> * #include "[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c" * int main() { * char * login = alloc(4096); * char * hello = helloWorld3(system::login(login)); * showmessage("Server said: %s", hello); * release(hello); * release(login); * return 0; * } * </pre> * </p> * @return char* * @param char* login user login on the client host */ char* helloWorld(char* login); ...This works for all plug-ins or for a particular plug-in, e.g..
[pubserver]/plugin/com.priint.pubserver.comet.cscript.HelloWorld.c
Evident Questions
Question
Can I define only one cscript / Java method per plug-in?
Answer
Of course not, you can define an arbitrary number of methods. The more methods, the larger the generated cscript stub code will be, therefore you should not define too much methods in one plug-in.
There is a second reason, why too many methods in one plug-in could cause problems: function names must be unique in cscript. It is not possible to overload functions in cscript.
The resulting limitations apply for methods in one plug-in and also for methods in all plug-ins, that potentially are included in one cscript at the same time: if plug-in A contains a function named foo and plug-in B contains a function named foo also, they cannot be included in one cscript at the same time.
Question
Can I propagate existing Java methods as cscript functions?
Answer
Yes, if your method is a PubServer plug-in method and if you respect the limitations regarding supported types mentioned above. In this case, all you have to do is add the @CometCScriptFunction annotation to your method..
Question
What about performance?
Answer
When invoking Java methods from cscript, the following happens: cscript objects and values are serialized to XML, the XML is sent to the priint:publishing server via SOAP, the server unpacks the SOAP message and parameter XML and invokes the Java plug-in. The call result is again transformed in XML, sent back to InDesign via SOAP, unpacked, unserialized and assigned to the cscript result variables. Sounds like much effort and costs.
Typically a roundtrip (without any calculation done on the server) should take about 30ms. This is an acceptable overhead for complex operations, but far too much for simple calculations (such as strcmp, strcat ...), which can easily and much more efficient be implemented in cscript.
Question
Why is the char resp. Character type not supported?
Answer
There is no suitable corresponding XML Schema builtin type for single characters. But more important: single characters could be misinterpreted, because strings in cscript are utf-8 encoded. This means, a visible character can consist of several character bytes. All cscript string functions respect this: the length returned by strlen is the number of visible characters, not of character bytes; any string position in any function refers to visible characters (not bytes).
Question
What is the maximum length for Strings returned from Java methods?
Answer
Virtually infinite. The memory is allocated dynamically.
Question
Is the number and size of parameters limited?
Answer
No - at least not regarding translations and transport.
Questions
Are tags (such as <Record.Id> ...) supported in scripts, which include Java methods?
Answer
Java methods can be included in any cscript: placeholder scripts, panel scripts, layout rules, build scripts etc.
Which tags and global variables are available, depends on the call environment, there are no exceptions for scripts including Java methods.
Example:
if run in a Placeholder load script context, the global variables denoting the placeholder and product linked with this placeholders are available, auch as int gRecordID, char * gRecordStringID etc.
Refer to the InDesign Plugin cscript documentation (section "placeholder") for details or to the separate documentation about tags and evaluation rules in PubServer environemnt.
Question
Can I access document poperties from Java?
Answer
Only those passed as parameter. The communication is unidirectional, you cannot callback InDesign from the Java plug-in. Therefore it is neither possible to access document frame properties nor to manipulate page items from your Java plug-in.
Question
We were talking about InDesign, is the cscript / Java bridge supported in the PDF Renderer too?
Answer
yes, of course.