openxava / documentation / Groovy

Groovy is an agile and dynamic language for the Java Virtual Machine. Since OpenXava 4m6 you can write the code of your applications using Groovy instead of Java. Even you can combine both languages in the same application.

Configuring OpenXava Studio

The easiest way to work with Grooy in OpenXava is using the Groovy Eclipse plugin. Given that OpenXava Studio is based on Eclipse, you can install the plugin on it. In OpenXava Studio go to Help > Eclipse Marketplace menu and look for "Groovy". Then install the one called "Groovy Development Tools":
If you're using an OpenXava version previous to v5.0 go to Preferences > Groovy > Compiler and choose Groovy Compiler 1.8.
You have to give Groovy nature to your OpenXava project: click the right button in your project, and choose Configure > Convert to Groovy project. Projects created between OpenXava v4m6 and v5.9.1 have Groovy nature by default, but in OpenXava v6 and v7 you have to add the Groovy nature explicitly.
From now on, you can add Groovy classes to your project, or convert some of your Java classes into Groovy classes, and it just will work. When you save a .groovy class it will compile into .class automatically. You can put your Groovy code in src/main/java folder of your project for OpenXava v7 or in any source folder in previous version of OpenXava.

Some sample code

You can write the JPA entities with Groovy:
package org.openxava.test.model;
 
import javax.persistence.*
 
import org.openxava.annotations.*
import org.openxava.calculators.*
import org.openxava.model.*
import org.openxava.jpa.*   @Entity @Table(name="TOrder") @View(members=""" year, number, date; customer; details; amount; remarks """ )   class Order extends Identifiable {   @Column(length=4) @DefaultValueCalculator(CurrentYearCalculator.class) int year   @Column(length=6) int number   @Required @DefaultValueCalculator(CurrentDateCalculator.class) Date date   @ManyToOne(fetch=FetchType.LAZY, optional=false) @ReferenceView("Simplest") Customer customer   @OneToMany(mappedBy="parent", cascade=CascadeType.ALL) @ListProperties("product.number, product.description, quantity, product.unitPrice, amount") Collection<OrderDetail> details = new ArrayList<OrderDetail>()   @Stereotype("MEMO") String remarks   @Stereotype("MONEY") BigDecimal getAmount() { BigDecimal result = 0 details.each { OrderDetail detail -> result += detail.amount } return result }   @PrePersist void calculateNumber() throws Exception { Query query = XPersistence.getManager() .createQuery("select max(o.number) from Order o " + "where o.year = :year") query.setParameter("year", year) Integer lastNumber = (Integer) query.getSingleResult() this.number = lastNumber == null?1:lastNumber + 1 }   }  
Or the actions:
package org.openxava.test.actions
 
import org.openxava.actions.*
 
class ChangeYearConditionAction extends TabBaseAction {
 
    int year;
 
    void execute() throws Exception {
        tab.setConditionValue("year", year)
    }
 
}
Even the test cases:
package org.openxava.test.tests
 
import javax.persistence.*
 
import org.openxava.tests.*
import org.openxava.util.*
 
import com.gargoylesoftware.htmlunit.html.*
 
import static org.openxava.jpa.XPersistence.*
 
class OrderTest extends ModuleTestBase {
 
    OrderTest(String testName) {
        super(testName, "Order")
    }
 
    void testCalculatedPropertiesFromCollection_generatedValueOnPersistRefreshedInView()
        throws Exception
    {
        String nextNumber = getNextNumber()
        execute("CRUD.new")
        assertValue("number", "")
        setValue("customer.number", "1")
        assertValue("customer.name", "Javi")
        assertCollectionRowCount("details", 0)
        execute("Collection.new", "viewObject=xava_view_details")
        setValue("product.number", "1")
        assertValue("product.description", "MULTAS DE TRAFICO")
        assertValue("product.unitPrice", "11.00")
        setValue("quantity", "10")
        assertValue("amount", "110.00")
        execute("Collection.save")
        assertNoErrors()
        assertCollectionRowCount("details", 1)
        assertValue("amount", "110.00")
        assertValue("number", nextNumber)
        execute("CRUD.delete")
        assertNoErrors()
    }
 
    void testDoubleClickOnlyInsertsACollectionElement() throws Exception {
        execute("CRUD.new")
        setValue("customer.number", "1")
        assertCollectionRowCount("details", 0)
        execute("Collection.new", "viewObject=xava_view_details")
        setValue("product.number", "1")
        setValue("quantity", "10")
        HtmlElement action = getForm().getElementById(decorateId("Collection.save"))
 
        action.click() // Not dblClick(), it does not reproduce the problem
        action.click()
        Thread.sleep(3000)
 
        assertNoErrors()
        assertCollectionRowCount("details", 1)
 
        execute("CRUD.delete")
        assertNoErrors()
    }
 
    private String getNextNumber() throws Exception {
        Query query = getManager().
            createQuery(
                "select max(o.number) from Order o where o.year = :year")
        query.setParameter("year", Dates.getYear(new Date()))
        Integer lastNumber = (Integer) query.getSingleResult()
        if (lastNumber == null) lastNumber = 0
        return Integer.toString(lastNumber + 1)
    }
 
}

Beware with nested annotations

Most of the Java syntax is compatible with Groovy. However, there is a litte detail of Groovy syntax that you have to take into account when using nested annotations: you have to use [] instead of {}, as following:
@Views([ // Use [ instead of {
    @View(members=""" // Use """ for multiline strings
        building [
            name, function;
            address
        ]
    """),
    @View(name="Simple", members="name")
]) // Use ] instead of }
Note how we use @Views([ ... ]) instead of @Views({ ... }). Note also that you can use """ for multiline strings.
For more details about Groovy syntax read about Groovy differences with Java.