Creating a ZK Groovy Console

SECURITY WARNING: Please take note of the warning at the end of the tutorial !
UPDATE 2011-07-08: A good article on the security concern I recommend http://www.jroller.com/melix/entry/customizing_groovy_compilation_process

We use the Groovy Script Engine to run small Groovy scripts to prepare incoming interface messages for further processing. Here I share with you how to embed the engine into a ZK page. Very basic, though it shows you the concept and it is wasy to expand to your needs.

ZK Groovy Console

Pre-Requirements:

  • Netbeans with the ZK plugin (link)
  • A Groovy installation or at least the lib folder from the package (link)

Tutorial:

  1. Create a new Web Project ZKGroovyConsolewith the ZK Framework

    New Web Project

  2. Add the embeddable Groovy Library (from the downloaded Groovy package)

    Groovy Library

  3. Update the index.zul file
    <?xml version="1.0" encoding="UTF-8"?>
    <zk xmlns="http://www.zkoss.org/2005/zul">
    <window id="list" apply="controller.indexController" title="ZKGroovy 0.1">
    <grid>
    <columns width="100%">
    <column label="" width="10%"/>
    <column label=""/>
    <column label="" />
    </columns>
    <rows>
    
    <row >
    <label value="Script" />
    <vbox>
    <textbox value="" id="txtScript" cols="80" rows="10"/>
    <hbox>
    <label value="Parameter p1" />
    <textbox id="txtp1" value="1" cols="10"/>
    </hbox>
    <hbox>
    <label value="Parameter p2" />
    <textbox id="txtp2" value="2" cols="10"/>
    </hbox>
    <radiogroup Id="optTrace" onCheck="">
    <radio label="Full Stacktrace" checked="true"/>
    <radio label="Simplified"/>
    </radiogroup>
    
    </vbox>
    <vbox>
    <button label="Execute" id="btnExec" width="100px" />
    <button label="Load Sample Script 1" id="btnSample" width="100px" />
    <button label="Load Sample Script 2" id="btnSample2" width="100px" />
    <button label="Load Sample Script 3 (exception)" id="btnSample3" width="100px" />
    <button label="Clear Script" id="btnClearScript" width="100px" />
    </vbox>
    </row>
    
    <row align="left">
    <label value="Result" />
    <textbox value="" id="txtConsole" cols="140" rows="10"/>
    <button label="Clear" id="btnClearConsole" width="100px" />
    </row>
    
    <row>
    <label value="Errors"/>
    <textbox value="" id="txtError" cols="140" rows="10"/>
    <button label="Clear" id="btnClearError" width="100px" onClick=""/>
    </row>
    
    </rows>
    </grid>
    </window>
    </zk>
    
    
  4. Add a controller

    Controller Class

    package controller;
    
    import org.zkoss.zk.ui.Component;
    import org.zkoss.zk.ui.event.Event;
    import org.zkoss.zk.ui.util.GenericForwardComposer;
    
    import groovy.lang.Binding;
    import groovy.lang.GroovyShell;
    import java.io.PrintWriter;
    import java.io.StringWriter;
    import org.zkoss.zul.Radiogroup;
    import org.zkoss.zul.Textbox;
    
    public class indexController extends GenericForwardComposer {
    
    private Textbox txtConsole;
    private Textbox txtError;
    private Textbox txtScript;
    private Textbox txtp1;
    private Textbox txtp2;
    private Radiogroup optTrace;
    
    @Override
    public void doAfterCompose(Component comp) throws Exception {
    super.doAfterCompose(comp);
    }
    
    public void onClick$btnExec(Event evt) throws InterruptedException {
    execScript();
    }
    
    public void onClick$btnSample(Event evt) throws InterruptedException {
    txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring='dummyString'; ");
    }
    
    public void onClick$btnSample2(Event evt) throws InterruptedException {
    txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring=par1+'.'+new Date().format('MMMM'); ");
    }
    
    public void onClick$btnSample3(Event evt) throws InterruptedException {
    txtScript.setValue("println 'Sample Script';\rresult = par1 + par2;\rsomestring='dummyString';\rx=5/0; ");
    }
    
    public void onClick$btnClearConsole(Event evt) throws InterruptedException {
    txtConsole.setValue("");
    }
    
    public void onClick$btnClearScript(Event evt) throws InterruptedException {
    txtScript.setValue("");
    }
    
    public void onClick$btnClearError(Event evt) throws InterruptedException {
    txtError.setValue("");
    }
    
    void execScript() {
    try {
    
    Binding binding = new Binding();
    
    Integer val1 = Integer.valueOf(txtp1.getValue());
    Integer val2 = Integer.valueOf(txtp2.getValue());
    
    binding.setVariable("par1", val1);
    binding.setVariable("par2", val2);
    
    GroovyShell shell = new GroovyShell(binding);
    Object value = shell.evaluate(txtScript.getValue());
    
    String stringResult = (String) binding.getVariable("somestring");
    
    Integer result = (Integer) binding.getVariable("result");
    txtConsole.setValue("Integer Result: \t" + result.toString() + "\rString Result: \t" + stringResult);
    
    } catch (Exception ex) {
    if (optTrace.getSelectedIndex()==0)
    txtError.setValue(stackTraceToString(ex));
    else
    txtError.setValue(shortStackTraceToString(ex));
    }
    }
    
    private String stackTraceToString(Exception ex) {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    ex.printStackTrace(pw);
    return sw.toString();
    }
    
    private String shortStackTraceToString(Exception ex) {
    
    String returnTrace = ex.toString();
    String el;
    
    for (StackTraceElement element : ex.getStackTrace()) {
    el = element.toString();
    if (el.trim().startsWith("Script")) {
    int index = el.indexOf(':');
    returnTrace = returnTrace + " (line" + el.substring(index);
    }
    }
    return returnTrace;
    }
    }
    

    The actual Groovy magic is hidden in these few lines.

    Binding binding = new Binding();
    
    Integer val1 = Integer.valueOf(txtp1.getValue());
    Integer val2 = Integer.valueOf(txtp2.getValue());
    
    binding.setVariable("par1", val1);
    binding.setVariable("par2", val2);
    
    GroovyShell shell = new GroovyShell(binding);
    Object value = shell.evaluate(txtScript.getValue());
    
    String stringResult = (String) binding.getVariable("somestring");
    Integer result = (Integer) binding.getVariable("result");
    

    You can binding for inbound and outbound variables to “talk” with your script.

    Note:

    • The exception that happen in the script is caught by our container.
    • println statements are directed to our server logfile.

!!! WARNING !!!

Please be very careful to deploy this as part of a real life production web application. You open big back doors for hacker, and you dont even need to be experienced to create trouble with this. Look at this sample:

  • Executing System.exit(0) makes the application server shutdown (Glassfish)

Shutting Down

Shutting Down

Remarks:

  • I would make use of this in a restrictive fashion only, maybe allow certain commands or filter out dangerous stuff (no recipe for that yet)
  • I am not sure if you can consider the above problem as a Glassfish security problem, but rather a insecure implementation

Leave a comment