openxava / documentation / Lesson 5: Basic business logic

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

Table of contents

Lesson 5: Basic business logic
Calculated properties
Simple calculated property
Using @DefaultValueCalculator
Persistent properties with @Calculation
Total properties of a collection
Default value from a properties file
Manual schema evolution
JPA callback methods
Multiuser safe default value calculation
Synchronizing persistent and calculated properties
Database logic (@Formula)
JUnit tests
Modifying existing tests
Testing default values, calculated and @Calculation properties
Testing @Formula
Testing calculated and persistent synchronized properties
Summary
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 application. 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() { // this property is recalculated and redisplayed
    if (product == null || product.getPrice() == null) return BigDecimal.ZERO;
    return new BigDecimal(quantity).multiply(product.getPrice()); 
}
And the correspondings imports:
import java.math.*;
import org.openxava.annotations.*;
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.

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 com.yourcompany.invoicing.calculators; // In 'calculators' package

import org.openxava.calculators.*;
import com.yourcompany.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;
    }
}
Also add the next import in Detail class:
import com.yourcompany.invoicing.calculators.*;
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() {
    // The if is no longer needed because getPricePerUnit() never return null
    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.

Persistent properties with @Calculation

Sometimes calculated properties are not the best option. Imagine that you have a calculated property in Invoice, let's say discount:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
public BigDecimal getDiscount() {
    return getAmount().multiply(new BigDecimal("0.10"));
}
If you need to process all those invoices with an discount greater than 1000, you have to code something like the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
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.getDiscount() // Queries every object
        .compareTo(new BigDecimal("1000")) > 0) {
            i.doSomething();
    }
}
You cannot use a condition in the query to discriminate by discount, because discount 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 discount. 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 discount greater than 1000, then your process will be very inefficient. What alternative do we have?
Our alternative is to use the @Calculation annotation. @Calculation is an OpenXava annotation that allows to associate a simple calculation to a persistent property. You can define discount with @Calculation as shown the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
@ReadOnly
@Calculation("amount * 0.10")
private BigDecimal discount;

public BigDecimal getDiscount() {
    return discount;
}

public void setDiscount(BigDecimal discount) {
    this.discount = discount;
} 
This is a regular persistent property, that is with a corresponding column in the database, but it has a calculation defined with @Calculation. In this case the calculation is amount * 0.10, so whenever the user changes amount in the user interface discount will be recalculated instantly. The recalculated value will be saved in the database when user clicks on Save, just like in any persistent property. We also have annotated discount with @ReadOnly, so it looks and behaves like a calculated property, although you can omit @ReadOnly so the user could modify the calculated value.
The most useful thing of @Calculation properties is that you can use it in conditions, so that you can rewrite the above process as shown in the next code:
// DON'T ADD TO YOUR CODE, IT'S JUST TO ILLUSTRATE
Query query = getManager().createQuery("from Invoice i where i.discount > :discount"); // Condition allowed
query.setParameter("discount", 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 selecting the records on the database server, and not on the Java server. Moreover, the discounts are not recalculated each time, they are already calculated and saved.
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 persistent properties with @Calculation:
business-logic_en025.png
@Calculation is a good option when you need filtering and sorting, and a simple calculation is enough. A shortcoming of @Calculation properties is that their values are recalculated only when the user interact with the record and changes some value of the properties used in the calculation, therefore when you add a new @Calculation property to an entity with existing data you have to update the values of the new column in the table using SQL. On the other hand if you need a complex calculation, with loops or consulting other entities, you still need a calculated property with your Java logic in the getter. In this last case if you need to sort and filter in list mode for the calculated property an option is to have both, the calculated and the persistent property, and synchronize their values using JPA callback methods (we'll talk about callback methods below).

Total properties of 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 properties to CommercialDocument class. The next figure shows the user interface for these properties:
business-logic_en030.png
Add the next code (and generate the getters and setters) to CommercialDocument entity:
@Digits(integer=2, fraction=0) // To indicate its size
private BigDecimal vatPercentage;
   
@ReadOnly
@Stereotype("MONEY")
@Calculation("sum(details.amount) * vatPercentage / 100")
private BigDecimal vat;

@ReadOnly
@Stereotype("MONEY")
@Calculation("sum(details.amount) + vat")    
private BigDecimal totalAmount;    

// REMEMBER TO GENERATE THE GETTERS AND SETTERS FOR THE ABOVE FIELDS
To use BigDecimal and @Digits you should add the following imports in CommercialDocument:
import java.math.*;
import javax.validation.constraints.*;      
Note how we have chosen @Calculation + @ReadOnly persistent properties over calculated ones for vat and totalAmount, because the calculations are simple, and filtering and ordering for them is very useful. Also, you can see how in @Calculation you can use sum(details.amount) to refer to the sum of the column amount of the collection details, in this way we don't need to have a baseAmount property. On the other hand, vatPercentage is a conventional persistent property. In this case we use @Digits (an annotation from Bean Validation, the validation standard of Java) as an alternative to @Column to specify its size.
Now that you have written the amount properties of CommercialDocument, you must modify the list of properties of the collection to show the total properties of the CommercialDocument (Invoice and Order). Let's see it:
abstract public class CommercialDocument extends Identifiable {
 
    @ElementCollection
    @ListProperties(
        "product.number, product.description, quantity, pricePerUnit, " +
        "amount+[" + 
        	"commercialDocument.vatPercentage," +
        	"commercialDocument.vat," +
        	"commercialDocument.totalAmount" +
        "]" 
    )
    private Collection<Detail> details;
 
    ...
}
Total properties are regular properties of the entity (CommercialDocument in this case) that are placed in the user interface below the column of a collection. For that, in @ListProperties you use square brackets after the property to enumerate them, like amount[commercialDocument.totalAmount]. Moreover, if you want just the summation of the column you don't need a property for that, with a + after the property in @ListProperties is enough, like amount+. In our case we combine both things, + and total properties between [ ].
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 could use calculator (with @DefaultValueCalculator) that returns a fixed value, but in that case changing the default value means changing your source code. Otherwise you could read the default value from the database (using JPA from your calculator), but 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=21
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 com.yourcompany.invoicing.util. You have the code here:
package com.yourcompany.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 using this utility class over reading directly the properties file 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 com.yourcompany.invoicing.calculators; // In 'calculators' package

import org.openxava.calculators.*; // To use ICalculator
import com.yourcompany.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 21 or whatever other value you put in invoicing.properties.

Manual schema evolution

When you use things like @Calculation or @DefaultValueCalculator the automatic evolution schema provides by OpenXava falls short, because it adds a new column when you add a new property, but it does not fill the column with the correct values. In this case we have added several persistent properties with @Calculation whose values are not recalculated until the user interact with the record. Moreover, we have a default value for vatPercentage that only has effect when the user creates a new record but not on the existing records. We have to fill the new columns with reasonable values.
Given that we're in early development stage just removing all the records would be a good enough solution, but for sure that is not a good idea in production, so we're going to adjust our database to new code without lose data to illustrate manual evolution schema.
The easier way is to use the application itself to do the updates. We're going to do it for updating the product prices. In order our new calculated properties work nicely all the products should have price, so go to the Product module with your browser and make sure that all products have price:
business-logic_en040.png
If some product has no price edit it and enter a price.
The next changes are not so simple, therefore we're going to execute SQL statements against our database. To execute these SQL statements, first make sure your application is running, then go to the DBManager class in Invoicing/src/_run, and with the right mouse button choose Run As > Java Application:
inheritance_en040.png
Now you are ready to write and execute SQLs.First, we set value for pricePerUnit column in all details:
UPDATE INVOICING.COMMERCIALDOCUMENT_DETAILS 
SET PRICEPERUNIT = (
    SELECT PRICE FROM INVOICING.PRODUCT 
    WHERE NUMBER = PRODUCT_NUMBER
)
Then we update the vatPercentage for all invoices:
UPDATE INVOICING.COMMERCIALDOCUMENT
SET VATPERCENTAGE = 21
Next, the updating of vat:
UPDATE INVOICING.COMMERCIALDOCUMENT
SET VAT = (
    SELECT SUM(PRICEPERUNIT * QUANTITY) * 0.21 
    FROM INVOICING.COMMERCIALDOCUMENT_DETAILS D 
    WHERE D.COMMERCIALDOCUMENT_OID = COMMERCIALDOCUMENT.OID
)
Finally, we update the totalAmount of all invoices:
UPDATE INVOICING.COMMERCIALDOCUMENT
SET TOTALAMOUNT = (
    SELECT SUM(PRICEPERUNIT * QUANTITY) * 1.21 
    FROM INVOICING.COMMERCIALDOCUMENT_DETAILS D 
    WHERE D.COMMERCIALDOCUMENT_OID = COMMERCIALDOCUMENT.OID
)
Beware, the above sentences work nicely with HSQLDB, the database included with OpenXava. If you're using another database probably you have to adapt the syntax. After executing the above sentences you can try your application. It would behave as in figure at the begin of the section "Total properties of a collection" even for already existing invoices and orders.

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
private 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;
}
To use XPersistence you have to add the next import:
import org.openxava.jpa.*;
This 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 OpenXava 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 save the document the number is calculated and a message is shown with the year and the just generated number for that invoice/order.

Synchronizing persistent and calculated properties

As we already have learned, calculated properties do not allow to filter or ordering in list, so we prefer transient properties with @Calculation. However, @Calculation properties are only valid for simple arithmetic calculations. When you need loops, condition, read from database, connect to external services, or some complex logic, @Calculation is not enough. For these cases you need to write the logic with Java, in the getter. But, how we can do this and at the same time keep the ordering and filtering in list? Simple, you can use two properties, one calculated and one persistent, and synchronize both using JPA callback methods. You're going to learn how to do it in this section.
Let's add a new property to Order entity called estimatedDeliveryDays:
@Depends("date")
public int getEstimatedDeliveryDays() {
    if (getDate().getDayOfYear() < 15) {
        return 20 - getDate().getDayOfYear(); 
    }
    if (getDate().getDayOfWeek() == DayOfWeek.SUNDAY) return 2;
    if (getDate().getDayOfWeek() == DayOfWeek.SATURDAY) return 3;
    return 1;
}
To use DayOfWeek add the next import on top of Order code:
import java.time.*;
This is a pure calculated property, a getter with Java logic. It calculates the estimated delivery days using date as source. This case cannot be solved with @Calculation that only supports basic arithmetic calculations.
We also have to add estimatedDeliveryDays to the default @View declaration in Order code:
@View(extendsView="super.DEFAULT", 
    members=
        "estimatedDeliveryDays," + // ADD THIS LINE
        "invoice { invoice }"
)
...
public class Order extends CommercialDocument {
The result is this:
business-logic_en050.png
The value is recalculated each time the date changes in the user interface thanks to the @Depends("date") in estimatedDeliveryDays. This is all very nice, but when you go to list mode you cannot order or filter by estimated delivery days. To solve this problem we add a second property, this time a persistent one. Add the next code to your Order entity:
@Column(columnDefinition="INTEGER DEFAULT 1")
private int deliveryDays;
    
public int getDeliveryDays() {
    return deliveryDays;
}

public void setDeliveryDays(int deliveryDays) {
    this.deliveryDays = deliveryDays;
}
Note as we have used @Column(columnDefinition="INTEGER DEFAULT 1"), with this trick when OpenXava creates the column uses "INTEGER DEFAULT 1" as column definition, thus the new column has 1 as default value instead of null, and we avoid an ugly error with our int property. Yes, in many cases @Column(columnDefinition=) is an alternative to do an UPDATE over the table (as we did in "Manual schema evolution" section), although has the problem that is database dependent. Anyways, this columnDefinition dissertation is tangential to our calculated/persistent synchronization issue, @Column is not required at all, it's just convenient for this int property.
This new deliveryDays property will contain the same value as estimatedDeliveryDays, but deliveryDays will be persistent with its corresponding column in the database. The tricky issue here is to have the deliveryDays property synchronized. We will use the JPA callback methods in Order to achieve this. It's enough to assign the value of estimatedDeliveryDays to deliveryDays each time that a new Order is created (@PrePersist) or updated (@PreUpdate).
Add a new recalculateDeliveryDays() method to Order entity annotated with @PrePersist and @PreUpdate, thus:
@PrePersist @PreUpdate 
private void recalculateDeliveryDays() {
    setDeliveryDays(getEstimatedDeliveryDays());
}
Basically, the recalculateDeliveryDays() method is called every time an Order entity is registered in the database for the first time and when the order is updated.
You can try the Order module with this code, and you will see how when a order is created or modified, the column in the database for deliveryDays is correctly updated after saving, ready to be used in massive processing and available for ordering and filter in list.

Database logic (@Formula)

Another alternative to @Calculation or having calculated/persistent properties synchronized is the @Formula annotation. @Formula is a Hibernate extension to the JPA standard, that allows you to map a property to a SQL statement. For example, you can define estimatedProfit with @Formula in CommercialDocument as shown the next code:
@org.hibernate.annotations.Formula("TOTALAMOUNT * 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 user can filter and ordering by @Formula properties in list mode, but they are always read only and are not recalculated in real time in detail mode. Moreover, @Formula properties are database dependent, because you can use syntax only supported by certain database vendor.

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, most of 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();
    verifyEstimatedProfit(); // To test @Formula
    verifyCreated();
    remove();
}
As you see, we add a new line in the beginning to calculate the next document number, and call the new verifyEstimatedProfit() 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", LocalDate.now().getYear());
    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, calculated and @Calculation properties

In this lesson we have done some modifications related to default values. Now the default value for number is not calculated by means of @DefaultValueCalculator instead we use a JPA callback method. To test this case 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());
}
We verify that number the has no initial value, because now the number is not calculated until the document is saved (Multiuser safe default value calculation section). 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 (Simple calculated property section), the default value calculation for pricePerUnit (Using @DefaultValueCalculator section) and the amount properties of the document (Total properties of a collection section). Among the total properties we test vatPercentage whose default value is calculated reading from a properties file. We'll test all this with a few modifications in the already existing addDetails() method:
private void addDetails() throws Exception {
    assertCollectionRowCount("details", 0);

// Before running this test code make sure
// product 1 has 19 as price, and
// product 2 has 20 as price

 // Adding a detail line setValueInCollection("details", 0, "product.number", "1"); assertValueInCollection("details", 0, "product.description", "Peopleware: Productive Projects and Teams"); assertValueInCollection("details", 0, "pricePerUnit", "19.00"); // @DefaultValueCalculator setValueInCollection("details", 0, "quantity", "2"); assertValueInCollection("details", 0, "amount", "38.00"); // Calculated property, section 'Simple calculated property' // Verifying total properties of collection assertTotalInCollection("details", 0, "amount", "38.00"); // Sum of amounts using + assertTotalInCollection("details", 1, "amount", "21"); // Default value from properties file assertTotalInCollection("details", 2, "amount", "7.98"); // VAT, with @Calculation assertTotalInCollection("details", 3, "amount", "45.98"); // Total amount, with @Calculation // Adding another detail setValueInCollection("details", 1, "product.number", "2"); assertValueInCollection("details", 1, "product.description", "Arco iris de lágrimas"); assertValueInCollection("details", 1, "pricePerUnit", "20.00"); 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 total properties of collection assertTotalInCollection("details", 0, "amount", "48.00"); assertTotalInCollection("details", 1, "amount", "21"); assertTotalInCollection("details", 2, "amount", "10.08"); assertTotalInCollection("details", 3, "amount", "58.08"); }
As you see, with these simple modifications we test most of our new code. What remains is only the estimatedProfit and deliveryDays properties. We'll test them in the next sections.

Testing @Formula

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 value for this property is the expected one. You have already seen that in testCreate() we call the verifyEstimatedProfit(). Let's see its code:
private void verifyEstimatedProfit() 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, "estimatedProfit", "5.81"); // Asserts estimatedProfit
    execute("List.viewDetail", "row=0"); // 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 verifyEstimatedProfit(). 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.refresh"); // 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 verifyEstimatedProfit() method we went to list mode and chose the document, so we are already editing the document.

Testing calculated and persistent synchronized properties

In the Synchronizing persistent and calculated properties section we used a JPA callback method in Order to have a persistent property, deliveryDays, synchronized with a calculated one, estimatedDeliveryDays. The deliveryDays property is only shown in list mode.
Go to OrderTest class and add a new method testDeliveryDays():
public void testDeliveryDays() throws Exception {
    login("admin", "admin");
    assertListNotEmpty(); 
    execute("List.viewDetail", "row=0"); 
	
    setValue("date", "6/5/20");
    assertValue("estimatedDeliveryDays", "1");
    setValue("date", "6/6/20");
    assertValue("estimatedDeliveryDays", "3");
    setValue("date", "6/7/20");
    assertValue("estimatedDeliveryDays", "2");
    execute("CRUD.save");
    execute("Mode.list"); // To verify that deliveryDays is synchronized
    assertValueInList(0, "deliveryDays", "2"); 

    execute("List.viewDetail", "row=0");
    setValue("date", "1/13/20");
    assertValue("estimatedDeliveryDays", "7");
    execute("CRUD.save");
    execute("Mode.list"); // To verify that deliveryDays is synchronized
    assertValueInList(0, "deliveryDays", "7");        
}
We try several values for date in order to verify that estimatedDeliveryDays is correctly calculated each time, moreover we go to list mode to verify that deliveryDays has the correct value so both properties are synchronized.

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, @Calculation, 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