Course: 1. Getting started | 2. Modeling with Java | 3. Automated testing | 4. Inheritance | 5. Basic business logic | 6. Advanced validation | 7. Refining the standard behavior | 8. Behavior & business logic | 9. References & collections | A. Architecture & philosophy | B. Java Persistence API | C. Annotations

Lesson 5: Basic business logic

You have made your domain model to run a web application. This application is already a useful one, but still there are a lot of refinements that can be made to it. Let's do what's necessary to convert your application into a better application and, in this way you shall learn some new interesting things about OpenXava.
We'll start adding business logic to your entities in order to convert your application into something more than a simple database manager.

Calculated properties

Perhaps the most simple business logic you can add to your application is a calculated property. The properties you have used until now are persistent, i.e., each property is stored in a column in a table in the database. A calculated property is a property that does not store its value in the database but it's calculated any time the property is accessed. See the difference between a persistent and a calculated property.
// Persistent property
private int quantity; // Has a field, so it's persistent
public int getQuantity() { // A getter to return the field value
    return quantity;
}
public void setQuantity(int quantity) { // Changes the field value
    this.quantity = quantity;
}
 
// Calculated property
public int getAmount() { // It has no field and no setter, only a getter
    return quantity * price; // with a calculation
}
Calculated properties are automatically recognized by OpenXava. You can use them in views, tabular lists or any other part of your code.
We are going to use calculated properties to add the money element to our Invoicing applications. Because, we have details, products, quantities. But what about amounts?

Simple calculated property

The first step will be to add an amount property to the Detail. We want the detail amount to be recalculated and shown to the user when the user chooses a product and type in the quantity:
business-logic_en010.png
Adding this feature to your current code is practically adding a calculated property to Detail. Just add the next code to the Detail:
@Stereotype("MONEY")
@Depends("product.number, quantity")  // When the user changes product or quantity
public BigDecimal getAmount() {
    return new BigDecimal(quantity).multiply(product.getPrice()); // this property is recalculated and redisplayed
}
Simply put the calculation in getAmount() and use @Depends to indicate to OpenXava that the amount property depends on product.number and quantity, thus each time the user changes any of these values the property will be recalculated.
Now you have to add this new property to the details collection of CommercialDocument:
@ElementCollection
@ListProperties("product.number, product.description, quantity, amount") // amount added
private Collection<Detail> details;
Nothing else is required. The mere addition of the getter and modifying the list properties is enough. Try the Invoice and Order modules to see the amount property in action.
Note:
Verify that the products have their price recorded.

Using @DefaultValueCalculator

The way we calculated the amount for the detail line is not the best approach. There are at least two drawbacks to it. Firstly, the user may want to have the option to overwrite the unit price. Secondly, if the price of the product changes the amounts for all your invoices changes too, this is not good.
To avoid these drawbacks it's better to store the price of the product for each detail. Let's add a pricePerUnit persistent property to the Detail class and let's calculate its value from the price in Product using a @DefaultValueCalculator. Just to obtain the effect you can see:
business-logic_en020.png
The first obvious step is to add the property pricePerUnit. Add the next code to your Detail class:
@DefaultValueCalculator(
    value=PricePerUnitCalculator.class, // This class calculates the initial value
    properties=@PropertyValue(
        name="productNumber", // The productNumber property of the calculator...
        from="product.number") // ...is filled from product.number of the detail
)
@Stereotype("MONEY")
private BigDecimal pricePerUnit; // A regular persistent property...
 
public BigDecimal getPricePerUnit() { // ...with its getter and setter
    return pricePerUnit==null ? BigDecimal.ZERO : pricePerUnit; // Thus never returns null
}
 
public void setPricePerUnit(BigDecimal pricePerUnit) {
    this.pricePerUnit = pricePerUnit;
}
PricePerUnitCalculator contains the logic to calculate the initial value. It simply reads the price from the product. See the next code for this calculator:
package org.openxava.invoicing.calculators; // In 'calculators' package

import org.openxava.calculators.*;
import org.openxava.invoicing.model.*;
 
import static org.openxava.jpa.XPersistence.*; // For using getManager()
 
public class PricePerUnitCalculator implements ICalculator {
 
    private int productNumber; // Contains the product number when calculate() is called
 
    public Object calculate() throws Exception {
        Product product = getManager() // getManager() from XPersistence
            .find(Product.class, productNumber); // Find the product
        return product.getPrice(); // Returns its price
    }
 
    public void setProductNumber(int productNumber) {
        this.productNumber = productNumber;
    }
 
    public int getProductNumber() {
        return productNumber;
    }
}
In this way when the user chooses a product the price per unit field is filled with the price of that product but because it's a persistent property, the user can change it. And if in the future the price of the product changes this price per unit of the detail will not change.
This means that you have to adapt your amount calculated property:
@Stereotype("MONEY")
@Depends("pricePerUnit, quantity") // pricePerUnit instead of product.number
public BigDecimal getAmount() {
    return new BigDecimal(quantity).multiply(getPricePerUnit()); // getPricePerUnit() instead of product.getPrice()
}
The getAmount() method uses pricePerUnit as source instead of product.price.
Finally, we have to edit the CommercialDocument entity and modify the list of properties to show in the collection to show the new property:
@ElementCollection
@ListProperties("product.number, product.description, quantity, pricePerUnit, amount") // pricePerUnit added
private Collection<Detail> details;
Try the Order and Invoice modules and observe the new behavior when adding details.

Calculated properties depending on a collection

We want to add amounts to Order and Invoice too. To calculate vat, base amount and total amount are indispensable. To do so you only need to add a few calculated properties to CommercialDocument class. The next figure shows the user interface for these properties:
business-logic_en030.png
Let's start with baseAmount. The next code shows its implementation.
public BigDecimal getBaseAmount() {
    BigDecimal result = new BigDecimal("0.00");
    for (Detail detail: getDetails()) { // We iterate through all details
        result = result.add(detail.getAmount()); // Adding up the amount
    }
    return result;
}
The implementation is simple, just adding up the amount from all the details.
The next property to add is vatPercentage to calculate the vat. See the next code:
@Digits(integer=2, fraction=0) // To indicate its size
@Required
private BigDecimal vatPercentage;
 
public BigDecimal getVatPercentage() {
    return vatPercentage == null ? BigDecimal.ZERO : vatPercentage; // Thus never returns null
}
 
public void setVatPercentage(BigDecimal vatPercentage) {
    this.vatPercentage = vatPercentage;
}
You can see that vatPercentage is a conventional persistent property. In this case we use @Digits (an annotation from the Hibernate Validator framework) as an alternative to @Column to specify the size.
We continue adding the vat property. See the next code:
public BigDecimal getVat() {
    return getBaseAmount() // baseAmount * vatPercentage / 100
        .multiply(getVatPercentage())
        .divide(new BigDecimal("100"));
}
It's a simple calculation.
Only totalAmount remains. You can see its code:
public BigDecimal getTotalAmount() {
    return getBaseAmount().add(getVat()); // baseAmount + vat
}
Again a simple calculation.
Now that you have written the amount properties of CommercialDocument, you must modify the view by default to show vatPercentage and the list of properties of the collection to show the total properties of the CommercialDocument (Invoice and Order). Let's see a first approximation:
@View(members=
    "year, number, date, vatPercentage;" + // vatPercentage added
    "data {" +
        "customer;" +
        "details;" +
        "remarks" +
    "}"
)
abstract public class CommercialDocument extends Identifiable {
 
    @ElementCollection
    @ListProperties(
        "product.number, product.description, quantity, pricePerUnit, " +
        "amount[invoice.baseAmount, invoice.vat, invoice.totalAmount] " // Entity parent Invoice => [invoice.baseAmount, ...]
    )
    private Collection<Detail> details;
 
    // The rest of the source code
    ...
}
Try the Invoice module and you will see the new calculated properties, but if you try Order module the properties will not be displayed and you will get an ugly exception in the log of Eclipse IDE. This is because the total properties for Order are not defined, we have only defined the Invoice. Let's see how to define the total properties for Invoice and Order:
// @ElementCollection
// @ListProperties("product.number, product.description, quantity, pricePerUnit, " +
//     "amount[invoice.baseAmount, invoice.vat, invoice.totalAmount] "
// )
// private Collection<Detail> details; // The details collection from 'CommercialDocument' deleted
 
// public Collection<Detail> getDetails() {  // Getter deleted
//     return details;
// }
 
// public void setDetails(Collection<Detail> details) { // Setter deleted
//     this.details = details;
// }
 
abstract public Collection<Detail> getDetails(); // Abstract method added
First we delete the details from CommercialDocument and declare an abstract method that will allow us to obtain the details of the subclasses of CommercialDocument.
Now let's see Invoice and Order:
public class Invoice extends CommercialDocument {
 
    @ElementCollection
    @ListProperties("product.number, product.description, quantity, pricePerUnit, " +
        "amount[invoice.baseAmount, invoice.vat, invoice.totalAmount] "
    )
    private Collection<Detail> details;     // Details collection added
 
    // Getter added
    public Collection<Detail> getDetails() { // This method implement the abstract method of 'CommercialDocument'
        return details;
    }
 
    public void setDetails(Collection<Detail> details) { // Setter added
        this.details = details;
    }
 
    // The rest of the source code
   ...
}
public class Order extends CommercialDocument {
 
    @ElementCollection
    @ListProperties("product.number, product.description, quantity, pricePerUnit, " +
        "amount[order.baseAmount, order.vat, order.totalAmount] " // Entity parent 'Order' => [order.baseAmount, ...]
    )
    private Collection<Detail> details;
 
    // Getter added
    public Collection<Detail> getDetails() { // This method implement the abstract method of 'CommercialDocument'
        return details;
    }
 
    public void setDetails(Collection<Detail> details) { // Setter added
        this.details = details;
    }
 
    // The rest of the source code
   ...
}
The source code added to Invoice and Order will generate two detail tables INVOICE_DETAILS and ORDER_DETAILS respectively.
Note:
Delete the COMMERCIALDOCUMENT_DETAILS table, you learned to do it in the previous lesson.
Now you can try your application. It would behave almost as in figure at the begin of this section. “Almost” because vatPercentage does not have a default value yet. We add it in the next section.

Default value from a properties file

It's useful for the user to have the default value populated for the vatPercentage. You can use calculator (with @DefaultValueCalculator) that returns a fixed value. In this case changing the default value means changing your source code. Otherwise you can read the default value from the database (using JPA from your calculator). In that case changing the default value means updating a database table.
Another option is to store this configuration value in a properties file, a plain file with key=value pairs. In this case changing the default value for vatPercentage is just a matter of editing a plain file with a text editor.
Let's implement the properties file option. Create a file named invoicing.properties in the Invoicing/properties folder with the next content:
defaultVatPercentage=18
Though you can use the java.util.Properties class from Java to read this file we prefer to create a custom class to read these properties. We are going to call this class InvoicingPreferences and we'll put it in a new package named org.openxava.invoicing.util. You have the code here:
package org.openxava.invoicing.util; // in 'util' package

import java.io.*;
import java.math.*;
import java.util.*;
 
import org.apache.commons.logging.*;
import org.openxava.util.*;
 
public class InvoicingPreferences {
 
    private final static String FILE_PROPERTIES="invoicing.properties";
    private static Log log = LogFactory.getLog(InvoicingPreferences.class);
    private static Properties properties; // We store the properties here
 
    private static Properties getProperties() {
        if (properties == null) { // We use lazy initialization
            PropertiesReader reader = // PropertiesReader is a utility class from OpenXava
                new PropertiesReader( InvoicingPreferences.class, FILE_PROPERTIES);
            try {
                properties = reader.get();
            }
            catch (IOException ex) {
                log.error( XavaResources.getString( // To read a i18n message
                    "properties_file_error", FILE_PROPERTIES), ex);
                properties = new Properties();
            }
        }
        return properties;
    }
 
    public static BigDecimal getDefaultVatPercentage() { // The only public method
        return new BigDecimal(getProperties().getProperty("defaultVatPercentage"));
    }
}
As you can see InvoicingPreferences is a class with one static method, getDefaultVatPercentage(). The advantage of this class approach over the properties files is that if you change the way the preferences are obtained, for example reading from a database or an LDAP directory, you only have to change this class in your entire application.
You can use this class from the default calculator for the vatPercentage property. See the calculator in the next code:
package org.openxava.invoicing.calculators; // In 'calculators' package

import org.openxava.calculators.*; // To use ICalculator
import org.openxava.invoicing.util.*; // To use InvoicingPreferences
 
public class VatPercentageCalculator implements ICalculator {
 
    public Object calculate() throws Exception {
        return InvoicingPreferences.getDefaultVatPercentage();
    }
}
As you see, it just returns the defaultVatPercentage from InvoicingPreferences. Now, you can use this calculator in the definition of vatPercentage property in CommercialDocument:
@DefaultValueCalculator(VatPercentageCalculator.class)
private BigDecimal vatPercentage;
With this code when the user clicks to create a new invoice, the vatPercentage field will be filled with 18 or whatever other value you put in invoicing.properties.

JPA callback methods

Another useful way to add business logic to your model is using JPA callback methods. A callback method is a method in your entity that is called in some specific moment of its life cycle as a persistent object. That is, you can specify some logic to execute on save, read, remove or modification of the entity.
In this section we'll see some practical applications of JPA callback methods.

Multiuser safe default value calculation

Until now we were calculating the Invoice and Order number using @DefaultValueCalculator. This calculates the default value when the user clicks to create a new Invoice or Order. So, if several users click on the “new” button at the same time all of them get the same number. This is not multiuser safe. The way to generate a unique number is by generating it just on save.
We are going to implement it using a JPA callback method. JPA allows you to mark any method of your class to be executed in any part of its life cycle. We'll indicate the calculation of the number just before the saving of the CommercialDocument. Using this approach we'll improve the number calculation for having a different numeration for Order and Invoice.
Edit the CommercialDocument entity and add the calculateNumber() method:
@PrePersist // Executed just before saving the object for the first time
public void calculateNumber() throws Exception {
    Query query = XPersistence.getManager()
        .createQuery("select max(i.number) from " +
        getClass().getSimpleName() + // Thus it's valid for both Invoice and Order
        " i where i.year = :year");
    query.setParameter("year", year);
    Integer lastNumber = (Integer) query.getSingleResult();
    this.number = lastNumber == null ? 1 : lastNumber + 1;
}
The previous code is the same as that of the NextNumberForYearCalculator but using getClass().getSimpleName() instead of “CommercialDocument”. The getSimpleName() method returns the name of the class without the package, i.e., just the entity name. It will be “Order” for Order and “Invoice” for Invoice. Thus we can get a different numeration for Order and Invoice.
JPA specification states that you should not use JPA API inside a JPA callback method. So the above method is not legal from a strict JPA viewpoint. But, Hibernate (the JPA implementation OX uses by default) allows you to use it in @PrePersist. And since JPA is the easier way to do this calculation we use it in our practice.
Now you can delete the NextNumberForYearCalculator class from your project, and modify the number property of CommercialDocument to avoid using it:
@Column(length=6)
// @DefaultValueCalculator(value=NextNumberForYearCalculator.class, // Remove this
//     properties=@PropertyValue(name="year")
// )
@ReadOnly // The user cannot modify the value
private int number;
Note that in addition to removing @DefaultValueCalculator, we have added the @ReadOnly annotation. This means that the user cannot enter or modify the number. This is the right approach given that the number is generated on saving the object, so the user typed value would always be overridden.
Try now the Invoice or Order module and you will see that the number is empty and not editable, and when you add the first detail, that saves the container document, the document number is calculated and updated in the user interface.

Synchronizing persistent and calculated properties

The approach towards calculating the vat, base amount and total amount of a commercial document is natural and practical. We used calculated properties that calculate, using pure Java, the values each time they are called.
But, calculated properties have some drawbacks. For example, if you want to do batch processing or a report for all invoices with a total amount between some range, it cannot be done using calculated properties. If you have a huge database the process will be very slow, because you will have to instantiate all invoices for calculating its total amount. For these cases having a persistent property, a column in the database, for the invoice or order amount, can yield far better performance.
In our case we'll maintain the current calculated properties, but we are going to add a new one, called amount, that will contain the same value as totalAmount, but this amount will be persistent with a corresponding column in the database. The tricky issue here is to have the amount property synchronized. We will use the JPA callback method in CommercialDocument (and one more trick) in order to achieve this.
The first step is to add the amount property to CommercialDocument. See the next code:
@Stereotype("MONEY")
private BigDecimal amount;
 
public BigDecimal getAmount() {
    return amount;
}
 
public void setAmount(BigDecimal amount) {
    this.amount = amount;
}
When the user adds, modifies or removes a detail in the user interface, the vat, base amount and total amount values are recalculated with fresh data instantly, however, in order to persist the changes in details the user must Save the CommercialDocument. To synchronize amount with totalAmount the first time we register a commercial document, we already know that we must use @PrePersist, but it turns out that JPA does not allow to mark more than one method with the same annotation, therefore, we will reorder our code. Let's see:
//@PrePersist // '@PrePersist' annotation deleted
public void calculateNumber() throws Exception {
    Query query = XPersistence.getManager()
        .createQuery("select max(i.number) from " +
            getClass().getSimpleName() + // Thus it's valid for both Invoice and Order
            " i where i.year = :year");
    query.setParameter("year", year);
    Integer lastNumber = (Integer) query.getSingleResult();
    this.number = lastNumber==null ? 1 : lastNumber + 1;
}
 
@PrePersist // Executed just before saving the object for the first time
private void onPrePersist() throws Exception {
    calculateNumber();
    recalculateAmount();
}
 
public void recalculateAmount() {
    setAmount(getTotalAmount());
}
Basically, we call the recalculateAmount() method every time a CommercialDocument entity is registered in the database for the first time. But recalculateAmount() must also be executed in the details updates. A first approximation might be to mark recalculateAmount with @PreUpdate, but it would be executed only when changes were made to the properties of CommercialDocument, not changes in details. We will solve this by executing the recalculateAmount() method whenever the user clicks save on a CommercialDocument. Let's see the next code:
@Version
private Integer version; // 'version' property added, without getter or setter
 
@PreUpdate // '@PreUdate' added
public void recalculateAmount() {
    setAmount(getTotalAmount());
}
The version property property ensures that @PreUpdate callback is executed whenever the user Save a CommercialDocument, as this property will always be updated.
You can try the Invoice or Order module with this code, and you will see how when a detail line is added, removed or modified, the column in the database for amount is correctly updated after saving, ready to be used in massive processing.
Note:
Delete the COMMERCIALDOCUMENT table to be recreated including the "version" column.

Database logic (@Formula)

Ideally you write all your business logic in Java, inside your entities. Nevertheless, sometimes that is not the most convenient way. Imagine that you have a calculated property in CommercialDocument, let's say estimatedProfit:
@Stereotype("MONEY")
public BigDecimal getEstimatedProfit() {
    return getAmount().multiply(new BigDecimal("0.10"));
}
If you need to process all those invoices with an estimatedProfit greater than 1000, you have to code something like the next code:
Query query = getManager().createQuery("from Invoice"); // No condition in query
for (Object o: query.getResultList()) { // Iterates over all objects
    Invoice i = (Invoice) o;
    if (i.getEstimatedProfit() // Queries every object
        .compareTo(new BigDecimal("1000")) > 0) {
            i.doSomething();
    }
}
You cannot use a condition in the query to discriminate by estimatedProfit, because estimatedProfit is not in the database, it's only in the Java object, so you have to instantiate every object in order to ask by the estimated profit. In some cases this way is a good option, but if you have a really huge amount of invoices, and only a few of them have the estimatedProfit greater than 1000, then your process will be very inefficient. What alternative do we have?
Our alternative is to use the @Formula annotation. @Formula is a Hibernate extension to the JPA standard, that allows you to map a property to a SQL statement. You can define estimatedProfit with @Formula as shown the next code:
@org.hibernate.annotations.Formula("AMOUNT * 0.10") // The calculation using SQL
@Stereotype("MONEY")
private BigDecimal estimatedProfit; // A field, as in the persistent property case
 
public BigDecimal getEstimatedProfit() { // Only the getter is needed
    return estimatedProfit;
}
This means that when a CommercialDocument is read from the database, the estimatedProfit field will be filled with the calculation for @Formula that is done by the database. The most useful thing of @Formula properties is that you can use it in different conditions, so that you can rewrite the above process as shown the next code:
Query query = getManager().createQuery("from Invoice i where i.estimatedProfit > :estimatedProfit"); // Condition allowed
query.setParameter("estimatedProfit", new BigDecimal(1000));
for (Object o: query.getResultList()) { // Iterates only over selected objects
    Invoice i = (Invoice) o;
    i.doSomething();
}
In this way we put the weight of calculating the estimated profit and selecting the record on the database server, and not on the Java server.
This fact also has effect in the list mode, because the user cannot filter or order by calculated properties, but he can do so using @Formula properties:
business-logic_en040.png
@Formula is a good option for improving performance in some cases. Anyways, generally it's better to use calculated properties and writing your logic in Java. The advantage of calculated properties over @Formula is that your code is not database dependent. Moreover with calculated properties you can re-execute the calculation without reading the object from the database, so that you can use @Depends.

JUnit tests

Before we move on to the next lesson, we are going to write the JUnit code for this one. Remember, the code is not done if it has no tests. You can write the tests before, during or after coding. But you always have to write the tests.
The test code here is not only to give you a good example, but also to teach you ways to test different cases in your OpenXava application.

Modifying existing tests

Creating a new test for each new case seems like a good idea from a structural viewpoint, but in most cases it is not practical because in doing so your test code would grow very fast, and execution of all the tests for your application would take a substantial amount of time.
The more pragmatic approach is to modify the existing test code to cover all the new cases we have developed. Let's do it in this way.
In our case, all the code for this lesson applies to CommercialDocument, so we are going to modify the testCreate() method of CommercialDocumentTest to match the new functionality. We leave the testCreate() method as you see in the next code:
public void testCreate() throws Exception {
    login("admin", "admin");
    calculateNumber(); // Added to calculate the next document number first
    verifyDefaultValues();
    chooseCustomer();
    addDetails();
    setOtherProperties();
    save();
    verifyAmountAndEstimatedProfit(); // To test callback method and @Formula
    verifyCreated();
    remove();
}
As you see, we add a new line in the beginning to calculate the next document number, and call the new verifyAmountAndEstimatedProfit() method.
Now it's more convenient to calculate the next document number in the beginning to use it during the test. To do this, change the old getNumber() method for the following two methods:
private void calculateNumber() {
    Query query = getManager().createQuery("select max(i.number) from " +
        model + // We change CommercialDocument for a variable
        " i where i.year = :year");
    query.setParameter("year", Dates.getYear(new Date()));
    Integer lastNumber = (Integer) query.getSingleResult();
    if (lastNumber == null) lastNumber = 0;
    number = Integer.toString(lastNumber + 1);
}
 
private String getNumber() {
    return number;
}
Previously we only had getNumber() to calculate and return the number, now we have a method to calculate (calculateNumber()), and another one to return the result (getNumber()). You can note that the calculation logic has a little change, instead of using “CommercialDocument” as the source of the query we use model, a variable. This is because now the numeration for invoices and orders are separated. We fill this variable, a field of the test class, in the test constructor, just as shows in the next code:
private String model; // The model name to use in the query. Can be “Invoice” or “Order”
 
public CommercialDocumentTest(String testName, String moduleName) {
    super(testName, "Invoicing", moduleName);
    this.model = moduleName; // In this case module name matches model
}
In this case module name, Invoice or Order, coincides with model name, Invoice or Order, so the easiest way to get the model name is from the module name.
Let's see the actual testing of the new functionality.

Testing default values and calculated properties

In this lesson we have done some modifications related to default values. Firstly, the default value for number is not calculated by means of @DefaultValueCalculator instead we use a JPA callback method. Secondly, we have a new property, vatPercentage, whose initial value is calculated by reading from a property file. To test these cases we have to modify the verifyDefaultValues() method as you see:
private void verifyDefaultValues() throws Exception {
    execute("CRUD.new");
    assertValue("year", getCurrentYear());
    // assertValue("number", getNumber()); // Now number has no initial value
    assertValue("number", ""); // on create a new document
    assertValue("date", getCurrentDate());
    assertValue("vatPercentage", "18");// Default value from properties file
}
We test the vatPercentage default value calculation and we verify that the has no initial value, because now the number is not calculated until the document is saved (section Multiuser safe default value calculation). When the document (invoice or order) will be saved we'll verify that the number is calculated. When the detail is added we can test the amount for Detail calculation (section Simple calculated property), the default value calculation for pricePerUnit (section Using @DefaultValueCalculator) and the amount properties of the document (section Calculated properties depending on a collection). We'll test all this with a few modifications in the already existing addDetails() method:
private void addDetails() throws Exception {
    assertCollectionRowCount("details", 0);
 
    // Adding a detail line
    setValueInCollection("details", 0, "product.number", "1");
    assertValueInCollection("details", 0,
        "product.description", "Peopleware: Productive Projects and Teams");
    assertValueInCollection("details", 0,
        "pricePerUnit", "31.00"); // @DefaultValueCalculator, section 'Using @DefaultValueCalculator'
    setValueInCollection("details", 0, "quantity", "2");
    assertValueInCollection("details", 0,
        "amount", "62.00"); // Calculated property, section 'Simple calculated property'
 
    // Verifying calculated properties of document
    assertTotalInCollection("details", 0, "amount", "62.00"); // Calculated properties
    assertTotalInCollection("details", 1, "amount", "11.16");  // depending on a collection,
    assertTotalInCollection("details", 2,
        "amount", "73.16"); // section 'Calculated properties depending on a collection'
 
    // Adding another detail
    setValueInCollection("details", 1, "product.number", "2");
    assertValueInCollection("details", 1, "product.description", "Arco iris de lágrimas");
    assertValueInCollection("details", 1,
        "pricePerUnit", "15.00"); // @DefaultValueCalculator, section 'Using @DefaultValueCalculator'
    setValueInCollection("details", 1, "pricePerUnit", "10.00"); // Modifying the default value
    setValueInCollection("details", 1, "quantity", "1");
    assertValueInCollection("details", 1, "amount", "10.00");
 
    assertCollectionRowCount("details", 2); // Now we have 2 rows
 
    //Verifying calculated properties of document
    assertTotalInCollection("details", 0, "amount", "72.00");
    assertTotalInCollection("details", 1, "amount", "12.96");
    assertTotalInCollection("details", 2, "amount", "84.96");
}
As you see, with these simple modifications we test most of our new code. What remains is only the amount and estimatedProfit properties. We'll test them in the next section.

Testing calculated and persistent synchronized properties / @Formula

In section Synchronizing persistent and calculated properties we used a JPA callback method in CommercialDocument to have a persistent property, amount, synchronized with a calculated one, totalAmount. The amount property is only shown in list mode.
In section Database logic (@Formula) we have created a property that uses @Formula, estimatedProfit. This property is shown only in list mode.
Obviously, the simplest way to test it is by going to list mode and verifying that the values for these two properties are the expected ones. You have already seen that in testCreate() we call the verifyAmountAndEstimatedProfit(). Let's see its code:
private void verifyAmountAndEstimatedProfit() throws Exception {
    execute("Mode.list"); // Changes to list mode
    setConditionValues(new String [] { // Filters to see in the list
        getCurrentYear(), getNumber() // only the newly created document
    });
    execute("List.filter"); // Does the filter
    assertValueInList(0, 0, getCurrentYear()); // Verifies that
    assertValueInList(0, 1, getNumber()); // the filter has worked
    assertValueInList(0, "amount", "84.96"); // Asserts amount
    assertValueInList(0, "estimatedProfit", "8.50"); // Asserts estimatedProfit
    execute("Mode.detailAndFirst"); // Goes to detail mode
}
Because we now go to list mode and then we go back to detail. We have to make a small modification to the verifyCreated() method, that is executed just after verifyAmountAndEstimatedProfit(). Let's see the modification in the next code:
private void verifyCreated() throws Exception {
    // setValue("year", getCurrentYear()); // We delete these lines
    // setValue("number", getNumber()); // for searching the document
    // execute("CRUD.search"); // because we already searched it with list mode
 
    // The rest of the test...
    ...
We remove these lines because now it's not necessary to search the newly created document. Now in the verifyAmountAndEstimatedProfit() method we went to list mode and chose the document, so we are already editing the document.
Congratulations! Now you have your tests up to date with your code. It's a good time to run all the tests for your application.

Summary

In this lesson you have learned some common ways to add business logic to your entities. There should be no doubt about the utility of calculated properties, callback methods or @Formula. Nevertheless, there are many other ways to add logic to your OpenXava application, and we are going to learn them.
In the coming lessons you'll see how to add validation, modify the standard module behavior and add your own business logic, among other ways to add custom logic to your application.

Download source code of this lesson

Any problem with this lesson? Ask in the forum Everything fine? Go to Lesson 6