...
Info |
---|
This page is loosely based on a great manual written by Patrick Talbot (as found on the Servoy forum), and is done so with the author's approval. |
Stoc |
---|
Client plugins are Java classes contained in jar files from plugins folder that inherit IServerPlugin, ISmartClientPlugin or IClientPlugin. The plugin jar can use Java Service Provider to expose Servoy Plugin classes. There should be a file inside jar at path: META-INF/services/com.servoy.j2db.plugins.IPlugin which contains a line for each plugin that jar should expose (a class that implements IPlugin). The plugin should also have a default constructor (with no parameters). If file com.servoy.j2db.plugins.IPlugin is missing or contains invalid entries Servoy will scan the jar for all classes that implement interface IPlugin. An example of file content (for mail plugin) is:
Code Block |
---|
com.servoy.extensions.plugins.mail.MailServer
com.servoy.extensions.plugins.mail.client.MailPlugin |
Java Code Example
This is an example of a plugin that delivers a converter from joda time (which has support for time persiods) to java-util-date (which is supported by Servoy UI elements) time.
...
stored under /{servoyInstall}/application_server/plugins/
folder, and by which new features can be added to Servoy Developer.
Plugins can be developed in any Java development environment, like Eclipse for example.
The minimal set of libraries may be found in /{servoyInstall}/application_server/lib/
folder, and is the following:
- j2db.jar
- j2dbdev.jar
- js.jar
- wicket.jar
Info |
---|
A 'User Library' can be defined in Window > Preferences which will be available for all future relevant projects. Name it 'Servoy', for example. |
Example This is an example of a Java project called WhoisPlugin, and the Servoy libraries added to it.
A package is a neat way in Java to organize libraries coming from arbitrary sources and make them work together without problems of 'Name collision'.
The developer can name the package whatever they want, but, by convention, the package hierarchy should reflect the domain name (if there is any) in reverse (from domain to sub-domains). This will avoid name collision with any other libraries, and will spare the developer from having to type the 'fully qualified name' in their code; Eclipse will automatically do that.
Example
This is an example of naming a package: com.servoy.plugins.whois
- where 'com.servoy' is a prefix which follows the rule listed above, and 'whois' is the plugin name.
In order to implement a client plugin, create a class that implements one or more of the three interfaces: IServerPlugin, ISmartClientPlugin, and IClientPlugin.
Before starting coding, do study these interfaces on api docs.
Example
This example shows the implementation of a component which will query a whois server and retrieve whois information about a domain.
Code Block |
---|
public class WhoisPlugin implements IClientPlugin { public static final String PLUGIN_NAME = "whois"; private WhoisPluginProvider provider; @Override public Properties getProperties() { Properties props = new Properties(); props.put(DISPLAY_NAME, getName()); return props; } @Override public void load() throws PluginException { // ignore } @Override public void unload() throws PluginException { provider = null; } @Override public void propertyChange(PropertyChangeEvent arg0) { // ignore } @Override public IScriptable getScriptObject() { if (provider == null) { provider = new WhoisPluginProvider(); } return provider; } @Override public Icon getImage() { URL iconUrl = getClass().getResource("images/whois.png"); //the image is added under a package 'com.servoy.plugins.whois.images' added to the WhoisPlugin project. if (dbvalueiconUrl =!= null) { return new ImageIcon(iconUrl); } else { return null; } } @Override returnpublic new DateTime(Utils.getAsLong(dbvalue))String getName() { return PLUGIN_NAME; } @Override public Objectvoid convertFromObject(Map<String, String> props, int column_type, Object obj) throws Exception { if (obj instanceof DateTime) {initialize(IClientPluginAccess arg0) throws PluginException { // ignore } } |
The method getStriptObject
inherited by IClientPlugin from IScriptableProvider interface, returns the object that will provide the plugin with scripting properties and methods. So, by convention, it is called a Provider.
A provider implements interfaces IScriptable and IReturnedTypesProvider. Do study them on api docs.
Create a class which implements the two interfaces. This class will provide methods representing the plugin behavior.
In order to specify which methods are what, use the JavaDoc annotations system which identifies getter/setter methods for plugin properties, as well as function methods for the plugin functions. The JavaDoc annotation system is also used for documenting the plugin.
For a proper understanding of how to use JavaDoc and how to build the documentation of a plugin, see Documenting the Plugin Api.
Example
This example shows the implementation of the WhoisPluginProvider - the scriptable object which provides the behavior for the 'whois' plugin.
The plugin will expose an overloaded query
method, as well as three other properties: port
, server
, and timeout
.
The main query
method contains JavaDoc which provides a description of the function and a sample. The other overloaded methods will display the same description and sample, having them copied via @clonedesc
and @sampleas
annotations from the main method.
Code Block |
---|
@ServoyDocumented(publicName = WhoisPlugin.PLUGIN_NAME, scriptingName = "plugins." + WhoisPlugin.PLUGIN_NAME) public class WhoisPluginProvider implements IScriptable, IReturnedTypesProvider { @Override public Class<?>[] getAllReturnedTypes() { return null; } private String server return= Long.valueOf(((DateTime)obj).getMillis())"whois.networksolutions.com"; private int port = 43; } private int timeout = 30 return* obj1000; // or throw an exception?? unit is milliseconds @JSGetter public String getServer() { return server; } @JSSetter public Map<String, String> getDefaultProperties() void setServer(String server) { this.server = server; } @JSGetter public int getPort() { return nullport; } @JSSetter public void setPort(int[] getSupportedColumnTypes() port) { this.port = port; } @JSGetter public int getTimeout() { return new int[] { IColumnTypes.INTEGER } timeout; } @JSSetter public void setTimeout(int timeout) { this.timeout = timeout; } /** * @clonedesc public String getName()query(String, String, int, int) * @sampleas query(String, String, int, int) * @param domainName */ @JSFunction public String query(String domainName) { return getClass().getSimpleName(query(domainName, this.server, this.port, this.timeout); } public int getToObjectType(Map<String, String> props) /** * @clonedesc query(String, String, int, int) * @sampleas query(String, String, int, int) * {@param domainName * @param server // Servoy does not understand joda time natively. */ @JSFunction public String query(String domainName, String server) { return IColumnTypes.MEDIA query(domainName, server, this.port, this.timeout); } } /** static class JodaToDateConverter implements* IUIConverter { static final JodaToDateConverter INSTANCE = new JodaToDateConverter(); @clonedesc query(String, String, int, int) * @sampleas query(String, String, int, int) * @param domaninName * @param server * @param port */ @JSFunction public ObjectString convertFromObjectquery(Map<StringString domainName, String>String propsserver, int input_type, Object converted) throws Exception { port) { return query(domainName, server, port, this.timeout); } /** * Calls a whois server to retrieve information about the provided domain name * if (converted instanceof Date)* @sample * // {call a whois server by providing a domain name and get info in return new DateTime(((Date)converted).getTime()); * var result = plugins.whois.query('servoy.com'); * // alternatively, provide an alternate server (default is networksolutions.com) * var result = plugins.whois.query('servoy.com', 'whois.internic.net'); * // provide a port, if not standard (43 by deafault) * var result = plugins.whois.query('servoy.com', 'whois.internic.net', 43); * // provide a timeout length (unit is milliseconds, default is 30 seconds) * var result = plugins.whois.query('servoy.com', 'whois.internic.net', 43, 50000); * * @param domainName * @param server * @param port } * @param timeout return converted; // or return* null@return or throw an exception?? */ } @JSFunction public ObjectString convertToObjectquery(Map<StringString domainName, String>String propsserver, int input_typeport, Objectint inputtimeout) { throws Exception try { // create the socket Socket socket if= new (input instanceof DateTime) {Socket(server, port); socket.setSoTimeout(timeout); // create a reader to get the response from the server BufferedReader in return= new DateBufferedReader(new InputStreamReader(socket.getInputStream(DateTime))input).getMillis()); ); // create an output stream to send our query to the server DataOutputStream out = new DataOutputStream(socket.getOutputStream()); // call the service with the domainName supplied } // and terminate with carriage return input; // or return null or throw an exception??) out.writeBytes(domainName + " \r\n"); // read the response from the server String str1 = null; StringBuffer buffer = new StringBuffer(); } public Map<String, String> getDefaultProperties()while ((str1 = in.readLine()) != null) { { return null; buffer.append(str1); } public int[] getSupportedDataproviderTypes() buffer.append("\r\n"); } { // Servoy does not understand joda time natively. close our stream and reader out.close(); return new int[] { IColumnTypes.MEDIA };in.close(); // close the socket socket.close(); } // return the result publicas String getName() { return buffer.toString(); } catch (IOException ioEx) { return getClass().getSimpleName ioEx.getLocalizedMessage(); } catch (Exception ex) { return ex.getLocalizedMessage(); } } public int getToObjectType(Map<String, String> props) { return IColumnTypes.DATETIME; } }} } |
Note |
---|
Make sure to have selected the correct target against which the project is compiled. It needs to be consistent with the Java version the Servoy install is built against. For this, do check Project > Properties > Java Compiler Node > JDK Compliance Panel. |
Since the class which implements the IServerPlugin, ISmartClientPlugin or IClientPlugin is one file among many inside the jar, it's advised indicate which file is the plugin entry point.
The plugin jar can use Java Service Provider to expose Servoy Plugin classes. There should be a file inside the plugin jar at the path: META-INF/services/com.servoy.j2db.plugins.IPlugin
which contains a line for each class in the jar that implements IPlugin). The plugin should also have a default constructor (with no parameters). If file com.servoy.j2db.plugins.IPlugin
is missing or contains invalid entries Servoy will automatically scan the jar for all classes that implement interface IPlugin. An example of file content (for whois plugin) is:
Code Block | ||
---|---|---|
| ||
com.servoy.plugins.whois.WhoisPlugin |
Right click on the project and choose Export > Java > JAR file.
Click Next
Note |
---|
Optionally, deselect the |
Select the export destination. One may choose to export the jar directly into the /{servoyInstall}/application_server/plugins
directory.
Leave the 2 Export class files… checked, and check the Save the description of this JAR in the workspace. Use the browse button to navigate to the project, and give a name to the definition. Eclipse automatically adds the 'jardesc' extension.
Info |
---|
What is nice about this option is that the next time the developer wants to deploy the jar (with modified sources for example), all they will need to do is right-click on the file |
Click Next once more. Leave it as is, or choose other options.
When opening the Servoy Developer, it should be visible under Plugins node in Solution Explorer.
Example #1 This example shows how the 'whois' plugin is displayed in the Solution Explorer. Notice the overloaded function query
and the three properties.
Example #2 This example shows a small sample solution 'WhoisTest' which tests the 'whois' plugin.
The solution has a simple test form with two fields based on two form variables domainName
and result
, and a button Query whois
whose action will call the query
function.
The Form Editor
The Script Editor
Code Block |
---|
var domainName = "";
var result = "";
function onQueryWhois()
{
if (domainName != null && domainName.length > 0) {
result = plugins.whois.query(domainName, "whois.internic.net");
}
} |
Running Smart Client