Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Table of Contents

 

 

In this tutorial, we will be following the 在本教程中, 我们将会遵循 guideline for developing a plugin to develop our 来开发我们的 Bean Shell Hash Variable plugin插件

1.

...

问题是什么 ?

哈希变量使用起来很方便,但有时我们想在显示一个值之前做一些条件检查。但是,哈希变量不提供条件检查的能力。

2. 如何解决这个问题?

通过查看 Joget Workflow目前支持的  插件类型,我们可以开发一个  哈希变量插件,让我们编写脚本来检查条件。有相当多的Bean Shell插件作为几种插件类型的默认插件提供。我们也可以为哈希变量插件做一个。

3. 你的插件需要什么输入信息?

哈希变量插件不提供用户配置界面,但要开发一个Bean Shell哈希变量插件,我们需要在某处放置我们的Bean Shell脚本。我们可以重新使用  环境变量  来存储我们的脚本。所以哈希变量语法将是一个环境变量键的前缀。

E.g. #beanshell.EnvironmentVariableKey#

但是,这可能还不够,我们可能还需要一些其他方法来传递一些变量。我们可以考虑使用一个URL查询参数语法来传递我们的变量,因为以后可以更容易解析。

E.g. #beanshell.EnvironmentVariableKey[name=Joget&email=info@joget.org&message={form.sample.message?url}]#

4.插件的输出和预期结果是什么?

我们期望从这个Bean Shell Hash变量插件中得到什么?Bean Shell Hash变量插件适用于管理员/开发人员用户在构建/开发应用程序时使用。一旦使用,哈希变量将被来自Bean Shell解释器的输出返回替换。以便管理员用户在向普通用户显示内容之前可以进行条件检查。

例如,显示登录用户的欢迎消息,但在用户为匿名时不显示任何内容。

5.是否有任何资源/ API可以重复使用?

要开发Bean Shell Hash变量插件,我们可以参考 所有的Hash变量插件和Bean Shell插件的  源代码。尤其是,我们可以参考环境变量哈希变量插件如何使用变量键来检索环境变量。我们也可以参考Bean Shell Tool或者Bean Shell Form Binder插件来说明如何使用Bean Shell解释器来执行脚本。 

我们可以使用StringUtil中的 getUrlParams方法  来帮助我们解析使用URL查询参数语法传入的参数。

6. 准备你的开发环境

我们需要始终准备好Joget Workflow Source Code,并按照这个指导方针建立起来。 

以下教程是使用Macbook Pro和Joget源代码5.0.0版编写的。 其他平台命令请参阅  如何开发插件文章。

假设我们的文件夹目录如下所示。 

Code Block
- Home
  - joget
    - plugins
    - jw-community
      -5.0.0

“plugins”目录是我们要创建和存储我们所有插件的文件夹,“jw-community”目录是Joget Workflow源代码存储的地方。

运行以下命令在“plugins”目录下创建一个maven项目。

Code Block
languagebash
cd joget/plugins/
~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial beanshell_hash_variable 5.0.0

然后,shell脚本会要求我们输入插件的版本号,并在生成maven项目之前要求我们确认。

Code Block
languagebash
Define value for property 'version':  1.0-SNAPSHOT: : 5.0.0
[INFO] Using property: package = org.joget.tutorial
Confirm properties configuration:
groupId: org.joget.tutorial
artifactId: beanshell_hash_variable
version: 5.0.0
package: org.joget.tutorial
Y: : y

我们应该在终端上显示“BUILD SUCCESS”消息,在“plugins”文件夹中创建一个“beanshell_hash_variable”文件夹。

用你喜欢的IDE打开maven项目。我将使用 NetBeans。  

7. 开始编码吧!

a. 继承插件的抽象类

在“org.joget.tutorial”包下创建一个“BeanShellHashVariable”类。

Image Added

然后,基于  哈希变量插件文件,我们将不得不扩展  org.joget.apps.app.model.DefaultHashVariablePlugin抽象类。

b. 实现所有的抽象方法

让我们实现所有的抽象方法。我们将使用AppPluginUtil.getMessage方法来支持i18n,并将常量变量MESSAGE_PATH用于消息资源包目录。

Code Block
languagejava
titleImplementation of all basic abstract methods
collapsetrue
package org.joget.tutorial;
 
import org.joget.apps.app.model.DefaultHashVariablePlugin;
import org.joget.apps.app.service.AppPluginUtil;
 
public class BeanShellHashVariable extends DefaultHashVariablePlugin {
    
    private final static String MESSAGE_PATH = "messages/BeanShellHashVariable";
 
    public String getName() {
        return "BeanShellHashVariable";
    }
 
    public String getVersion() {
        return "5.0.0";
    }
 
    public String getClassName() {
        return getClass().getName();
    }
    
    public String getLabel() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.tutorial.BeanShellHashVariable.pluginLabel", getClassName(), MESSAGE_PATH);
    }
    
    public String getDescription() {
        //support i18n
        return AppPluginUtil.getMessage("org.joget.tutorial.BeanShellHashVariable.pluginDesc", getClassName(), MESSAGE_PATH);
    }
 
    public String getPropertyOptions() {
        //Hash variable plugin do not support property options
        return "";
    }
    
    public String getPrefix() {
        return "beanshell";
    }
    
    public String processHashVariable(String variableKey) {
        throw new UnsupportedOperationException("Not supported yet."); 
    }
}

现在,我们来关注我们的Hash变量插件的主要方法,它是processHashVariable。我们将参考环境变量哈希变量插件的源代码来了解如何检索环境变量。然后,参考Bean Shell Form Binder的源代码,了解如何执行一个bean shell脚本。

Code Block
languagejava
    public String processHashVariable(String variableKey) {
        try {
            String environmentVariableKey = variableKey;
            

Hash variable is convenient to use, but sometime we want to do some condition check before displaying a value. But, Hash variable does not provide the ability for condition checking.

2. How to solve the problem?

By looking at the Plugin Types that are currently supported by Joget Workflow, we can develop a 哈希变量插件 to allow us to write our scripting for condition checking. There are quite a number of Bean Shell plugins provided as default plugin for several plugin types. We can do one for Hash Variable plugin as well.

3. What is the input needed for your plugin?

Hash Variable plugin does not provide interface for user to configure, but to develop a Bean Shell Hash Variable plugin, we need somewhere to put our Bean Shell script. We can reuse the Environment Variable to store our script. So the Hash Variable syntax will be a prefix with environment variable key.

E.g. #beanshell.EnvironmentVariableKey#

But, this may not be enough, we may need some other way to pass in some variable also. We can consider using a URL query parameters syntax to pass in our variables because it is easier to parse later on.

E.g. #beanshell.EnvironmentVariableKey[name=Joget&email=info@joget.org&message={form.sample.message?url}]#

4. What is the output and expected outcome of your plugin?

What do we expected from this Bean Shell Hash variable plugin? The Bean Shell Hash Variable plugin is for admin/developer user to use when building/developing an app. Once used, the Hash Variable will be replaced by the output return from the Bean Shell interpreter. So that the admin user can do condition check before display something to normal user.

E.g. Display a welcome message for logged in user but display nothing when the user is an anonymous.

5. Are there any resources/API that can be reused?

To develop Bean Shell Hash Variable plugin, we can refer to the source code of all the Hash Variable plugin and Bean Shell plugin. Especially, we can refer to the Environment Variable Hash Variable plugin on how to retrieve environment variable using a variable key. We can also refer to the Bean Shell Tool or Bean Shell Form Binder plugin on what to execute the script with Bean Shell interpreter. 

We can use getUrlParams method from StringUtil to help us parse parameters passed in with URL query parameters syntax.

6. Prepare your development environment

We need to always have our Joget Workflow Source Code ready and builded by following this guideline

The following tutorial is prepared with a Macbook Pro and Joget Source Code version 5.0.0. Please refer to the 如何开发插件 article for other platform commands.

Let say our folder directory is as following. 

Code Block
- Home
  - joget
    - plugins
    - jw-community
      -5.0.0

The "plugins" directory is the folder we will create and store all our plugins and the "jw-community" directory is where the Joget Workflow Source code stored.

Run the following command to create a maven project in "plugins" directory.

Code Block
languagebash
cd joget/plugins/
~/joget/jw-community/5.0.0/wflow-plugin-archetype/create-plugin.sh org.joget.tutorial beanshell_hash_variable 5.0.0

Then, the shell script will ask us to key in a version number for the plugin and ask us for a confirmation before it generates the maven project.

Code Block
languagebash
Define value for property 'version':  1.0-SNAPSHOT: : 5.0.0
[INFO] Using property: package = org.joget.tutorial
Confirm properties configuration:
groupId: org.joget.tutorial
artifactId: beanshell_hash_variable
version: 5.0.0
package: org.joget.tutorial
Y: : y

We should get "BUILD SUCCESS" message shown in our terminal and a "beanshell_hash_variable" folder created in "plugins" folder.

Open the maven project with your favour IDE. I will be using NetBeans.  

7. Just code it!

a. Extending the abstract class of a plugin type

Create a "BeanShellHashVariable" class under "org.joget.tutorial" package.

Image Removed

Then, based on 哈希变量插件 document, we will have to extends org.joget.apps.app.model.DefaultHashVariablePlugin abstract class.

b. Implement all the abstract methods

Let us implement all the abstract methods. We will be using AppPluginUtil.getMessage method to support i18n and using constant variable MESSAGE_PATH for message resource bundle directory.

Code Block
languagejava
titleImplementation of all basic abstract methods
collapsetrue
package org.joget.tutorial;
 
import org.joget.apps.app.model.DefaultHashVariablePlugin;
import org.joget.apps.app.service.AppPluginUtil;
 
public class BeanShellHashVariable extends DefaultHashVariablePlugin {
    
    private final static String MESSAGE_PATH = "messages/BeanShellHashVariable";
 
    public String getName() {
        return "BeanShellHashVariable";
   //first }
check 
and retrieve parameters passed publicin Stringwith getVersion() {
        return "5.0.0";
URL query parameters syntax wrapped in square bracket []
      }
 
    public String getClassName() {
        return getClass().getName()queryParams = null;
    }
    
    public String getLabel()if (variableKey.contains("[") && variableKey.contains("]")) {
        //support i18n
       queryParams return AppPluginUtil.getMessage("org.joget.tutorial.BeanShellHashVariable.pluginLabel", getClassName(), MESSAGE_PATH);
    }
= variableKey.substring(variableKey.indexOf("[") + 1, variableKey.indexOf("]"));
      
    public String getDescription() {
   environmentVariableKey =    //support i18nvariableKey.substring(0, variableKey.indexOf("["));
        return AppPluginUtil.getMessage("org.joget.tutorial.BeanShellHashVariable.pluginDesc", getClassName(), MESSAGE_PATH);
    }
 
    public String getPropertyOptions() {
        //Hash variable plugin do not support property options
Parse the query parameters to a map
            Map<String, String[]> parameters return= ""null;
    }
       
 if (queryParams != publicnull String getPrefix&& !queryParams.isEmpty()) {
        return "beanshell"        parameters = StringUtil.getUrlParams(queryParams);
    }
    
    public String processHashVariable(String variableKey) {
        throw     new UnsupportedOperationException("Not supported yet."); 
    }
}

Now, let's focus on the main method of our Hash Variable plugin which is processHashVariable. We will refer to the source code of Environment Variable Hash Variable plugin on how to retrieve the Environment variable. Then, refer to the source code of Bean Shell Form Binder on how to execute a bean shell script.

Code Block
languagejava
    public String processHashVariable(String variableKey) {
        try { //put all parameters to plugin properties
                getProperties().putAll(parameters);
            }
 
            //Retrieve the environment variable based on environmentVariableKey
            StringAppDefinition environmentVariableKeyappDef = variableKey (AppDefinition) getProperty("appDefinition");
            
if (appDef != null) {
        //first check and retrieve parameters passed in with URLApplicationContext queryappContext parameters syntax wrapped in square bracket []
= AppUtil.getApplicationContext();
                EnvironmentVariableDao String queryParamsenvironmentVariableDao = null;
            if (variableKey.contains("[") && variableKey.contains("]")) {(EnvironmentVariableDao) appContext.getBean("environmentVariableDao");
                queryParamsEnvironmentVariable env = variableKeyenvironmentVariableDao.substring(variableKey.indexOf("[") + 1, variableKey.indexOf("]"));
loadById(environmentVariableKey, appDef);
                if environmentVariableKey(env != variableKey.substring(0, variableKey.indexOf("["));null) {
            }
 
       String script = env.getValue();
  //Parse   the query parameters to a map
          //execute the Map<String, String[]> parameters = null;script with all plugin properties
            if (queryParams != null && !queryParams.isEmpty()) {   return executeScript(script, getProperties());
                parameters} = StringUtil.getUrlParams(queryParams);else {
                
    //environment variable not found, return empty value
        //put all parameters to plugin properties
       return "";
        getProperties().putAll(parameters);
        }
    }
 
       }
     //Retrieve  the environment} variablecatch based(Exception one) environmentVariableKey{
            AppDefinition//log appDefthe =exception (AppDefinition) getProperty("appDefinition");using LogUtil
            if (appDef != null) {LogUtil.error(getClassName(), e, "#beanshell."+variableKey+"# fail to parse.");
        }
        ApplicationContext
 appContext = AppUtil.getApplicationContext();
     //return null to by pass the replacing
     EnvironmentVariableDao environmentVariableDao = (EnvironmentVariableDao) appContext.getBean("environmentVariableDao")return null;
    }
    
    protected String executeScript(String script, EnvironmentVariableMap envproperties) = environmentVariableDao.loadById(environmentVariableKey, appDef);throws Exception {
        Interpreter interpreter = new Interpreter();
    if (env != null) { interpreter.setClassLoader(getClass().getClassLoader());
        for (Object key : properties.keySet()) {
          String script = env.getValue() interpreter.set(key.toString(), properties.get(key));
        }
            //execute the script with all plugin propertiesLogUtil.debug(getClass().getName(), "Executing script " + script);
                    return executeScript(script, getProperties())String) interpreter.eval(script);
                } else {}

c. 管理你的插件的依赖库

我们的插件类无法解析“bsh.Interpreter”。所以,我们必须将bean shell库添加到我们的POM文件中。

Image Added

Code Block
languagexml
<!-- Change plugin specific dependencies here -->
<dependency>
    <groupId>org.beanshell</groupId>
    <artifactId>bsh</artifactId>
    <version>2.0b4</version>
</dependency>
<!-- End change plugin specific dependencies       //environment variable not found, return empty value
                    return "";
                }
            }
        } catch (Exception e) {
            //log the exception using LogUtil
            LogUtil.error(getClassName(), e, "#beanshell."+variableKey+"# fail to parse.");
        }
        
        //return null to by pass the replacing
        return null;
    }
    
    protected String executeScript(String script, Map properties) throws Exception {
        Interpreter interpreter = new Interpreter();
        interpreter.setClassLoader(getClass().getClassLoader());
        for (Object key : properties.keySet()) {
            interpreter.set(key.toString(), properties.get(key));
        }
        LogUtil.debug(getClass().getName(), "Executing script " + script);
        return (String) interpreter.eval(script);
    }

c. Manage the dependency libraries of your plugin

Our plugin class cannot resolve "bsh.Interpreter". So, we will have to add bean shell library to our POM file.

Image Removed

Code Block
languagexml
<!-- Change plugin specific dependencies here -->
<dependency>
    <groupId>org.beanshell</groupId>
    <artifactId>bsh</artifactId>
    <version>2.0b4</version>
</dependency>
<!-- End change plugin specific dependencies here -->

d. Make your plugin internationalization (i18n) ready

We are using AppPluginUtil.getMessage method to display i18n value for our getLabel and getDescription method. We will have to create a message resource bundle properties file for it. Create directory "resources/messages" under "beanshell_hash_variable/src/main" directory. Then, create a "BeanShellHashVariable.properties" file in the folder.

Image Removed

In our properties file, we will need to add the key we have used.

Code Block
languagetext
org.joget.tutorial.BeanShellHashVariable.pluginLabel=Bean Shell Hash Variable
org.joget.tutorial.BeanShellHashVariable.pluginDesc=Using environment variable to execute bean shell script.

e. Register your plugin to the Felix Framework

We will have to register our plugin class in Activator class to tell the Felix Framework that this is a plugin.

Image Removed

Code Block
languagejava
public void start(BundleContext context) {
    registrationList = new ArrayList<ServiceRegistration>();

    //Register plugin here
    registrationList.add(context.registerService(BeanShellHashVariable.class.getName(), new BeanShellHashVariable(), null));
}

f. Build it and test

Let build our plugin. Once the building process is done, we will find that a "beanshell_hash_variable-5.0.0.jar" file is created under "beanshell_hash_variable/target" directory.

Image Removed

 Then, let's upload the plugin jar to Manage Plugins. After uploading the jar file, double check that the plugin is uploaded and activated correctly.

Image Removed

Now, let's test our plugin.

Let assume that we have a HTML menu page in a userview that wants to display the following line to logged in user. Normally, we will use "Welcome #currentUser,username#," to display a welcome message.

Image Removed

But, in this use case there is a problem, which shows "Welcome ," without an username when the user is an anonymous.

Image Removed

Now, change the whole message to our Bean Shell Hash Variable and create an environment variable to put our script.

Change:

Code Block
Welcome #currentUser.username#,

to the following. We will need to pass the current user's username as one of our parameters and do not forget to escape it as url.

Code Block
#beanshell.welcome[username={currentUser.username?url}]?html#

Then, we can create an environment variable with ID "welcome" and use the following script. As we are using getUrlParams method from StringUtil to parse the parameters, all value from parameters are String array.

here -->

d. 让你的插件国际化(i18n)准备就绪

我们使用AppPluginUtil.getMessage方法来显示我们的getLabel和getDescription方法的i18n值。我们将不得不为它创建一个消息资源包属性文件。在“beanshell_hash_variable / src / main”目录下创建目录“resources / messages”。然后,在该文件夹中创建一个“BeanShellHashVariable.properties”文件。

Image Added

在我们的属性文件中,我们将需要添加我们已经使用的密钥。

Code Block
languagetext
org.joget.tutorial.BeanShellHashVariable.pluginLabel=Bean Shell Hash Variable
org.joget.tutorial.BeanShellHashVariable.pluginDesc=Using environment variable to execute bean shell script.

e. 注册你的插件到Felix框架

我们将不得不在Activator类中注册我们的插件类,告诉Felix框架这是一个插件。

Image Added

Code Block
languagejava
public void start(BundleContext context) {
    registrationList = new ArrayList<ServiceRegistration>();

    //Register plugin here
    registrationList.add(context.registerService(BeanShellHashVariable.class.getName(), new BeanShellHashVariable(), null));
}

f.构建并测试

让我们建立我们的插件。构建过程完成后,我们将发现在“beanshell_hash_variable / target”目录下创建了“beanshell_hash_variable-5.0.0.jar”文件

Image Added

 然后,让我们上传插件jar到  管理插件。上传jar文件后,再次检查插件是否正确上传并激活。

Image Added

现在,让我们测试我们的插件。

假设我们在一个用户视图中有一个HTML菜单页面,它想把下面的行显示给登录用户。通常,我们将使用“欢迎#currentUser,用户名#”来显示欢迎消息。

Image Added

但是,在这个用例中存在一个问题,当用户是匿名用户时,显示“Welcome”,没有用户名。

Image Added

现在,将整个消息更改为我们的Bean Shell哈希变量,并创建一个环境变量来放置我们的脚本。

更改:

Code Block
Welcome #currentUser.username#,

以下。我们将需要传递当前用户的用户名作为我们的参数之一,不要忘记把它作为url转义。

Code Block
#beanshell.welcome[username={currentUser.username?url}]?html#

然后,我们可以创建一个ID为“welcome”的环境变量,并使用下面的脚本。由于我们使用StringUtil的 getUrlParams方法   来解析参数,所以参数中的所有值都是String数组。

Code Block
languagejava
//all parameters passed in from Beanshell Hash Variable will converted to String array 
if (username != null && username.length == 1 && !username[0].isEmpty()) {
    return "Welcome " + username[0] + ",";
} else {
    return ""; 
}

Let go back to our HTML menu page to see the result.

When user is logged in, it shows the message correctly.

Image Removed

让我们回到我们的HTML菜单页面来查看结果。

当用户登录时,它显示正确的消息。

Image Added

当没有用户登录时,欢迎消息不显示。When no user is logged in, the welcome message is not shown.

8.

...

再进一步,分享或出售

您可以从You can download the source code from beanshell_hash_variable.zip.下载源代码。

要下载现成的插件jar,请在To download the ready-to-use plugin jar, please find it in http://marketplace.joget.org/.找到它