Wednesday, April 13, 2011

Groovy+Spring Configuration

Hey...

Spring 2.0+ introduces comprehensive support for using classes and objects that have been defined using a dynamic language (such as JRuby) with Spring.
This support allows you to write any number of classes in a supported dynamic language, and have the Spring container transparently instantiate, configure and dependency inject the resulting objects.
The dynamic languages currently supported are:
  • JRuby
  • Groovy
  • BeanShell
Here I'm going to describe about Groovy itself and its configuration in spring. Other dynamic languages are similar to this.

1. Example Detail

I'm going to create a sample to do major operation of typical calculator with groovy and leave it open to be upgratable to advance operations by groovy. So you will be able to add more operation to it. The application has two major parts.
  • Groovy Script In File(The gray lines in grid of Image 1)
    • Add
    • Subtract
    • Multiply
    • Divide
  • Groovy Script In Database(The black lines in grid of Image 1)
    • Power
    • Factorial
    • ...(You can add more operation dynamically)
You can add more operations without restart the application. This is the best advantage of dynamic languages like groovy.
Image 1
2. Spring Configuration
The only needed spring configuration is to create a bean from groovy script file. Take look here:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
    <lang:groovy  id="Calculator" refresh-check-delay="1000" scope="prototype" script-source="classpath:/com/calculator/CalculatorImpl.groovy"></lang:groovy>
</beans>
As you see I create a bean called "Calculator" from CalculatorImpl.groovy file.
Tag attributes and body:
2.1 refresh-check-delay
The delay (in milliseconds) between checks for updated sources when using the refreshable beans feature.
2.2 Scope
The scope of this scripted bean: typically "singleton" (one shared instance, which will be returned by all calls to getBean with the given id), or "prototype" (independent instance resulting from each call to getBean). Default is "singleton".
Singletons are most commonly used, and are ideal for multi-threaded service objects. Further scopes, such as "request" or "session", might be supported by extended bean factories (e.g. in a web environment).
2.3 script-source
The resource containing the script for the dynamic language-backed bean.
Examples might be '/WEB-INF/scripts/Anais.groovy', 'classpath:Nin.bsh', etc.
2.4 property(body tag)
Dynamic language-backed bean definitions can have zero or more properties.Property elements correspond to JavaBean setter methods exposed by the bean classes. Spring supports primitives, references to other beans in the same or related factories, lists, maps and properties.
2.5 inline-script
The source code for the dynamic language-backed bean. For example:
    <lang:groovy  id="Calculator" refresh-check-delay="1000" scope="prototype">
        <lang:inline-script>
            package com.calculator;
            class TestClass{
              public string showMessage() {
                return "Hello world";
              }
            }
        </lang:inline-script>
    </lang:groovy>






Here is the CalculatorImpl.groovy detail:
package com.calculator;

class CalculatorImpl implements Calculator{
  public int add(int val1, int val2) {
    return val1 + val2;
  }

  public int multiply(int val1, int val2) {
    return val1 * val2;
  }

  public int subtract(int val1, int val2) {
    return val1 - val2;
  }

  public int divide(int val1, int val2) {
    return val1 / val2;
  }
}
As you see the groovy file is a simple class containing some simple methods. After the Spring configuration you can have it from spring context and use it like other java classes. To use this class in your java code, you should have a type of it. You are not able to use its type(CalculatorImpl) directly. Because This type is defined in groovy script not in your java class hierarchy. So I this problem by creating an interface. The "CalculatorImpl" implements "Calculator" interface so is type of "Calculator". In my java codes I can define a variable typed "Calculator" and Initiate it by "CalculatorImpl" via spring context.
Calcualtor.java:
package com.calculator;

public interface Calculator {
    int add(int val1, int val2);

    int multiply(int val1, int val2);

    int subtract(int val1, int val2);

    int divide(int val1, int val2);
}
And here is the use of the CalculatorImpl in CalculatorController.java:
package com.calculator;

import org.springframework.stereotype.Controller;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import com.base.controller.BaseController;
import com.rule.service.RuleService;
import com.rule.Rule;
import com.groovy.GroovyProxy;

import javax.script.ScriptException;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

@Controller
@Scope("prototype")
@RequestMapping("/calculator")
public class CalculatorController extends BaseController {
    Integer input1;

    Integer input2;

    Integer result;

    List<Rule> ruleList;

    Long ruleId;

    @Autowired
    RuleService ruleService;

    @Autowired
    Calculator calculator;

    public Integer getInput1() {
        return input1;
    }

    public void setInput1(Integer input1) {
        this.input1 = input1;
    }

    public Integer getInput2() {
        return input2;
    }

    public void setInput2(Integer input2) {
        this.input2 = input2;
    }

    public Integer getResult() {
        return result;
    }

    public void setResult(Integer result) {
        this.result = result;
    }

    public List<Rule> getRuleList() {
        return ruleList;
    }

    public void setRuleList(List<Rule> ruleList) {
        this.ruleList = ruleList;
    }

    public Long getRuleId() {
        return ruleId;
    }

    public void setRuleId(Long ruleId) {
        this.ruleId = ruleId;
    }

    @RequestMapping("view")
    public String view() {
        ruleList = ruleService.findByAll();
        return "calculator.view";
    }

    @RequestMapping("add")
    public String add() {
        result = calculator.add(input1, input2);
        return view();
    }

    @RequestMapping("subtract")
    public String subtract() {
        result = calculator.subtract(input1, input2);
        return view();
    }

    @RequestMapping("multiply")
    public String multiply() {
        result = calculator.multiply(input1, input2);
        return view();
    }

    @RequestMapping("divide")
    public String divide() {
        result = calculator.divide(input1, input2);
        return view();
    }

    @RequestMapping("customrule")
    public String customRule() throws ScriptException {
        Rule rule = ruleService.findById(ruleId);
        GroovyProxy groovyProxy = new GroovyProxy();
        Map<String, Integer> params = new HashMap();
        params.put("val1", input1);
        params.put("val2", input2);
        result = (Integer) groovyProxy.execute(rule.getRuleScript(), params);

        return view();
    }
}
As you see I've just made "Calculator calculator" autowired like "RuleService ruleService". Then I've used it just like other classes.
3. Groovy Live Usage
You can add more scripts dynamically into database then use it in the application. To do that, I create an entity called Rule:
package com.rule;

import javax.persistence.*;

@Entity
@Table(name = "tb_rules")
public class Rule {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    Long id;

    @Column(name = "rule_name")
    String ruleName;

    @Column(name = "rule_script")
    String ruleScript;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getRuleName() {
        return ruleName;
    }

    public void setRuleName(String ruleName) {
        this.ruleName = ruleName;
    }

    public String getRuleScript() {
        return ruleScript;
    }

    public void setRuleScript(String ruleScript) {
        this.ruleScript = ruleScript;
    }
}
I fetch all of scripts from database and show them to user. User can execute whatever she/he wants. To execute the fetched record I create "GroovyProxy" calss:
package com.groovy;

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.Map;

public class GroovyProxy {
    ScriptEngineManager factory;
    ScriptEngine engine;

    public GroovyProxy() {
        factory = new ScriptEngineManager();
        engine = factory.getEngineByName("groovy");
    }

    public Object execute(String script, Map params) throws ScriptException {
        for (Object key : params.keySet()) {
            engine.put(key.toString(), params.get(key));
        }
        return engine.eval(script);
    }
}
This class creates a groovy engine(red lines) first then execute the passed method(blue lines). The needed parameters of the script is passed via the execute method parameter.
Here are some available groovy scripts in database:
  • Power:
    if(val2==0){
    return 1;
    } else {
    int result = 1;
    for(int i in 1..val2){
    result = result * val1;
    }
    return result;
    }
  • Factorial:
    int result = 1;
    for(int i in val1..1){
    result = result * i;
    }
    return result;
The red parameters are the passed parameters(Map params) in "execute" method of "GroovyProxy" class.
You can find the use of "GroovyProxy" class in customRule method of "CalculatorController" class(the blue lines).

Source Code
It's the time to download the source code. You can also download the application war file to deploy it in your application server directly. After deployment, the start page will be: http://localhost:8080/calculator/view.htm. The database script is [App Root]/db/groovydb.sql. You can restore it into your mysql server. The connection datasource detail is: [App Root]/src/database.properties



all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

No comments: