Overview
The PubServerSDK includes a set of classes, which allow dynamic method invocation based on Strings or XML configuration files.
This can serve several purposes:
- calling Java methods from "outside" of the Java scope
- calling Java methods on unknown / invisible classes (e.g. call a method from another PubServer Plugin)
- storing method calls as configuration items
These classes, namely the ParameterInterpreter and the ParameterParser class, make up the key technology to load and save placeholders, invoking Java methods from cscript, executing configuration check rules from ISON and others.
If you start implementing Java methods, which are supposed to be called via the ParameterInterpreter, it will be helpful to know some of the backgrounds and common pitfalls.
Requirements
The ParameterParser implementation is based on the Parboiled grammar parser, therefore the parboiled jars (currently parboiled-java-1.1.5.jar and parboiled-core-1.1.5.jar) are required.
These jars are included in the PubServer release resp. developer stack / Eclipse workspaces delivered for PubServer development.
The documentation is valid for any 4.0.5, 4.1.0 or later release of the Publishing Server and PubServerSDK.
Syntax / Call Convention
General Syntax
The general syntax for ParameterInterpreter calls is
calltype(target)[parameters]
- calltype is either the keyword plugin or static
- target is described by mappedName and methodName for plugin calls resp. className and methodName for static calls.
- parameters is a comma separated list of primitive parameters,
- Strings: enclosed by single quotes.
Examples:'a legal string'
'escaped single quotes '''
- Numbers: digits and optionally one decimal point. Numbers may have a leading sign character ( - )
Examples:17
17.0
-17
- Booleans: any number greater than zero results to true, zero or negative numbers result to false
Examples:0
or-1
(evaluate to false)17
or0.0001
(evaluate to true)
- Tags: enclosed by
< >
, these are placeholders for parameters passed in a String / Object map
Example:<Constant.Identifier>
- Maps: comma separated list of key value pairs enclosed by
{ }
. Keys must be string type, values can be any of the supported inline types including maps. Each key value pair consists of the key, followed by the assignement operator=>
and the value.
Examples:{ 'key' => 'value' }
(simple string key, string value){ 'text' => 'text', 'number' => 17 }
(mixed type){ 'map' => { 'key' => 'value' } }
(nested maps)
- Strings: enclosed by single quotes.
Name Mangling
Given a class name, method name and a list of parameters, the ParameterInterpreter tries to find a method, which matches this call.
Due to automatic casts for some input types (namely number types and boolean), the result can be ambigous: more than one method can match the name and numbers of parameters defined in a command string.
It is important to know, that the ParameterInterpreter does not search for the best match, but invokes the first matching method found. The first match is the method, with
- the required name
- the required number of parameters
- compatible types for all parameters
While in general this is a great gain for interoperability, this can cause problems, when methods of a class have similar signatures.
Similar in this context means:
- the same method name
- the same numbers of parameters
Example:
public class MyUtils { public static String concatenate(String str, int v) { return "'" str + "', " + "#" + v; } public static String concatenate(int v, String str) { return "#" + v + ", '" str + "'"; } public static String concatenate(int v1, int v2) { return "#" + v1 + ", #" + v2; } public static String concatenate(String s1, String s2) { return "'" + s1 + "', '" + s2 + "'"; } } // Test: String method = "static(className='MyUtils',methodName='concatenate')"; String[] parameters = { "[17,17]", "['17','17']", "[17,'17']", "['17',17]" }; for (String parameter : parameters) { String commandString = method + parameter; Object result = ParameterInterpreter.interpret(commandString, new HasMap<String,Object>()); System.out.println(result); } /* Result is not predictable! All we can say is, that MOST PROBABLY the output will be the same for all runs. Beside of this, everything is possible: - #17'17' - '17'#17 - '17''17' - #17#17 */It is impossible to predict, which of the similar methods will be called. It depends, in which order the class has been initialized by the Java class loader and can change after each restart of the application or even during runtime.
A good practice therefore would be
Plugin Method Call using Command String
A Plugin method call starts with the keywordplugin
, followed by the call target.The target is specified by
- globalName: the JNDI name or Plugin name of a PubServer Plugin
- methodName: the name of the method
Examples
// call using the Plugin name String commandString = "plugin(globalName='" + Constants.MANAGER_ENTITY + "',methodName='getEntityRootBuckets')" + "[ /* ... parameters ... */]"; Object result = ParameterInterpreter.interpret(commandString, new HashMap<String, Object>());
// call using the JNDI name String commandString = "plugin(globalName='" + "java:global/PubServerEntityManager/EntityManager!" + "com.priint.pubserver.plugin.interfaces.EntityManagerLocal" + "',methodName='getEntityRootBuckets')" + "[ /* ... parameters ... */]"; Object result = ParameterInterpreter.interpret(commandString, new HashMap<String, Object>());
These calls are equivalent to
String sessionId = PluginControlDefault.getSessionId(); EntityManagerLocal entityManager = PluginUtils.getPlugin(Constants.MANAGER_ENTITY, sessionId, EntityManagerLocal.class); List<Bucket> result = entityManager.getEntityRootBuckets(/* ... parameters ... */);
Plugin Method Call using PluginMethod Object
Instead of using a command string, the ParameterInterpreter can also be innstructed to invoke a method based on a reference to a PluginMethod object.
The PluginMethod object can be read from a config file (e.g. from a DataProvider config) or can be created and initialized in Java.
The equivalent call to the example shown above using PluginMethod would be:
PluginMethod method = new PluginMethod(); method.setPluginMappedName("java:global/PubServerEntityManager/EntityManager!"); method.setMethodName("getEntityRootBuckets"); method.addParameter(Parameter.createStringParameter( "sessionId", PluginControlDefault.getSessionId())); // ... ParameterInterpreter.interpret(method, new HashMap<String,Object>());
Which way you prefer, probably depends on the situation: Using PluginMethods
- provides better type integrity when using the Parameter.create factory methods
- allows to modify a given command string according to actual requirements on runtime
- may provide better readabilty in the Java code
- requires more lines of code ...
In some situations, the following pattern may be useful:
String commandString = "plugin(globalName='" + "java:global/PubServerEntityManager/EntityManager!" + "com.priint.pubserver.plugin.interfaces.EntityManagerLocal" + "',methodName='getEntityRootBuckets')" + "[ /* ... parameters ... */]"; PluginMethod method = ParameterInterpreter.parse(commandString); // any modifications / evaluation of the method object Object result = ParameterInterpreter.interpret(method, new HasMap<String, Object>());
Static Method Call using Command String
A Static method call starts with the keyword static
, followed by the call target.
The target is determined by
- className: the qualified class name of the target class
- methodName: the name of the method
Example
String commandString = "static(className='" + "com.priint.pubserver.comet.util.PathUtils" + "',methodName='getClassByAbsolutePath')" + "[ /* ... parameters ... */]"; Object result = ParameterInterpreter.interpret(commandString, new HashMap<String, Object>());
The equivalent static method call would look like this
Class<?> result = com.priint.pubserver.comet.util.PathUtils.getClassByAbsolutePath(
/* ... parameters ... */
);
Conclusion
Though the benfits of using the ParameterInterpreter may be not that obvious in the examples above, there are situations, when using the ParameterInterpreter is helpful or required:
- when method calls and parameters should be stored or submitted as plain strings or (as PluginConfig objects) XML documents
- when the target class / method isn't part of a public SDK and thus not visible for the client application
- using lookup and cache services from the PluginManager when calling Plugin methods
Parameters
Parameters can be passed to a method call inline in the command string or via inline tags and corresponding values in a String / Object map. The types supported as inline parameters is limited to Java primitive types, such as String, Integers, Doubles etc.
All other types can be used, if passed as Map Arguments and using tags in the command string.
Inline Parameters
Inline parameters are notated directly in the command string as a comma separated list enclosed by [ ]
.
The following types are supported as inline parameters:
java.lang.String
Any character sequence enclosed by single quotes. Strings wont be decoded or encoded before being passed to the target method as parameters. This applies for escaped single quotes also, see examples below.
Examples:
String value | Parameter notation | Value passed to method |
---|---|---|
A String |
"...['A String']" |
A String |
\0007\000F |
"...['\u0007\u000F']" |
\u0007\u000F (binary) |
€ |
"...['\u20ac']" |
€ |
double quotes (") in strings |
"...['double quotes (\") in strings']" |
double quotes (") in strings |
single quotes (') |
"...['single quotes ('')']" |
single quotes ('') |
back \ slash |
"...['back \\ slash']" |
back \ slash |
java.lang.Number and derived Types
Any positive or negative short, byte, int, long, float or double value. Allowed characters are digits, one decimal point and the minus signs. Terms or expressions (e.g. 1 + 1) are not allowed.
Depending on the parameter type of the target method, values provided will be cast to a suitable Number type.
If a double value is expected, but "17" provided, the value will be cast to "17.0" and vice versa, if an integer is expected and "17.0" given, this will automatically be cast to "17".
Because of this behaviour, you should avoid methods with similar signatures. Consider the following situation:
public class Math { public static double max(double v1, double v2) { // ... } public static int max(int v, int v2) { // ... } }All of the following command strings send to the ParameterInterpreter will call any of these methods - just depending on in which order the Java class loader has initialized the methods:
String callstring = "static(className='Math',methodName='max')[17.0, 17.1]"; String callstring = "static(className='Math',methodName='max')[17, 17.1]"; String callstring = "static(className='Math',methodName='max')[17.1, 17]"; String callstring = "static(className='Math',methodName='max')[17, 18]";See the Name Mangling section above for further examples and explanation.
java.lang.Boolean
Boolean values can be provided as Strings or Numbers. Value mapping is quite straight forward as listed below
Value description | Parameter notation | Value passed to method |
---|---|---|
Almost any String | "...['17']" |
false |
String equalsIgnoreCase("true") | "...['tRuE']" |
true |
Positive Integer | "...[17]" |
true |
Negative Integer | "...[-17]" |
false |
Positive Double | "...[17.1]" |
true |
Zero Integer or Double | "...[0]" |
false |
java.util.Map
Parameters of type Map<String, Object> can be encoded as inline parameters as follows:
{
followed by a key value list and a terminal}
- a key value list is zero or more key value assignements separated by
,
- a key value assignement is a String key, followed by the assignement operator
=>
and a value - value is any value, which is allowed as inline parameter, including maps
java.util.Map<?>
.
Some examples
String commandString = "plugin(...)[ { 'name' => 'Christoph', 'age' => 32 }]"; String map = "{ 'recipe' => 'Cake', 'ingredients' => " + " {" + " 'sugar' => 180.0," + " 'butter' => 250," + " 'flour' => '1lb'" + " } }";
Parameters provided as Map Arguments
All ParameterInterpreter.interpret methods accept two parameters
- the command string resp. PluginConfig object representing the method call
- a Map with optional arguments
Instead of encoded inline, parameters can also be passed as Map Arguments. In this case, we use a placeholder ("tag") in the callstring and add an entry with the corresponding key to the arguments map.
The syntax for command string tags is
<
followed by a key and a terminal>
The corresponding key in the arguments map must include the <>
brackets.
Example
public class Complex { public static int getChildCount(Bucket bucket) { // ... } } String commandString = "plugin(globalName='Complex',methodName='getChildCount')['<bucket>']"; Bucket obj = new Bucket(); Map<String,Object> arguments = new HashMap<String,Object>(); arguments.put("<bucket>", obj); Object result = ParameterInterpreter.interpret(commandString, arguments);
Benefits
Mainly, there are two benefits when using arguments instead of inline parameters:
- Parse results are cached. With arguments, the Parser can reuse a command string, even if different values are provided to the method call via the arguments map
- arguments allow to use arbitrary types as parameters. The limitation for inline parameters (String, Numbers, Boolean and Map) do not apply for parameters provided as arguments.
Drawbacks
The drawback of using arguments is of course, that the method call cannot be fully serialized to String.
API / Method Overview
ParameterInterpreter
com.priint.pubserver.parameterparser.ParameterInterpreter
interpret(PluginMethod method, Map<String, Object> args)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- method: Reference to a method
- args: map of names arguments to be used in the call
interpret(PluginMethod method, Map<String, Object> args, List<Object> outParameters)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- method: Reference to a method
- args: map of names arguments to be used in the call
- outParameters: empty list, which will be filled with the actual parameters parsed when processing this method
interpret(String commandString, Map<String, Object> args)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- commandString: string to be interpreted
- args: map of names arguments to be used in the call
interpret(String commandString, Map<String, Object> args, List<Object> outParameters)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- commandString: string to be interpreted
- args: map of names arguments to be used in the call
- outParameters: empty list, which will be filled with the actual parameters parsed when processing this method
interpret(String commandString, Map<String, Object> args, LogSettings logSettings)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- commandString: string to be interpreted
- args: map of names arguments to be used in the call
- logSettings: log settings to be used for this call
interpret(String commandString, Map<String, Object> args, LogSettings logSettings, List<Object> outParameters)
Call interpreter to run a publishing server method using a list of arguments. Log settings will be taken from method.
Returns Object, result of the ParameterInterpreter call
Parameters
- commandString: string to be interpreted
- args: map of names arguments to be used in the call
- logSettings: log settings to be used for this call
- outParameters: empty list, which will be filled with the actual parameters parsed when processing this method
parse(String commandString)
Parses a command string and return a Plugin method object representing this call.
This method is particularly useful, if you need information about the number and types
of parameters passed to the method call or the name of the Plugin, class and method to
be called.
The PluginMethod object returned may be modified and sent again to the ParameterInterpreter
using the interpret(PluginMethod, Map, List) method.
Parameters
- commandString: string to be parsed
ParameterParser
com.priint.pubserver.parameterparser.ParameterParser
cacheParse(String input)
Retrieve a parsing result for the input string from the internal cache or create a new one. If not found a new parse will be triggered and the result stored in the cache.
Returns list of Objects / parsing result
Parameters
- input: string to be parsed, typically a command string
clearParsingResults()
Clears the Parser cache
Returns -
Parameters
Parameters
-
getCacheSize()
Retrieves the size of internal cache for parsing results.
Returns size of cache
Parameters
-
parse(String input)
Runs a parse and returns the result as list of Objects.
Parsing errors will be written as warnings to the log.
This method blocks other threads from executing it until it returns. Expect single parses to return in less than 1 msec.
Returns list of Objects / parsing result
Parameters
- input: string to be parsed, typically a command string
Parser Grammar
The EBNF grammar for the command string syntax is as follows:
digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "0" ; naturalnumber = digit | digit naturalnumber ; positivenumber = ([ naturalnumber ] [ "." ] naturalnumber) | (naturalnumber [ "." ] [ naturalnumber ]) ; signednumber = [ "-" ] positivenumber ; character = ? all characters ? ; whitespace = ? white space character ? escapedquote = "'" "'" ; string = "'" { character - "'" | escapedquote } "'" ; tag = "<" { character - ("<" | ">" ) } ">" ; parameter = signednumber | string | tag | map ; key = string ; association = "=" ">" ; keyvalue = key { whitespace } association { whitespace } parameter ; keyvaluelist = { whitespace } keyvalue { whitespace } [ "," whitespace keyvalue keyvaluelist ] ; map = "{" keyvaluelist "}" ; assignement = "=" ; class = ('globalName' | 'className') assignement string ; method = 'methodName' assignement string ; (* no whitespaces allowed in target definition *) target = "(" class "," method ")" ; parameterlist = { whitespace } parameter { whitespace } [ "," parameterlist ] ; parameters = "[" parameterlist "]" ; command = ('static' | 'plugin') target parameters ;