openxava / documentation / Lesson 11: @DefaultValueCalculator in collections

Course: 1. Getting started | 2. Basic domain model (1) | 3. Basic domain model (2) | 4. Refining the user interface | 5. Agile development6. Mapped superclass inheritance | 7. Entity inheritance | 8. View inheritance | 9. Java properties | 10. Calculated properties | 11. @DefaultValueCalculator in collections | 12. @Calculation and collections totals  | 13. @DefaultValueCalculator from file | 14. Manual schema evolution | 15. Multi user default value calculation | 16. Synchronize persistent and computed properties | 17. Logic from database  | 18. Validating with @EntityValidator 19. Validation alternatives  | 20. Validation on remove  21. Custom Bean Validation annotation  | 22. REST service call from validation  | 23. Attributes in annotations  | 24. Refining the standard behavior | 25. Behavior & business logic | 26. References & collections | A. Architecture & philosophy | B. Java Persistence API | C. Annotations | D. Automated testing

Table of contents

Lesson 11: @DefaultValueCalculator in collections
Using @DefaultValueCalculator
Summary
We have added business logic to our application using persistent properties and calculated properties, now we will use the @DefaultValueCalculator annotation for collections.

If you don't like videos follow the instructions below.

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 logic to calculate the initial value will be in PricePerUnitCalculator. 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 lombok.*;
 
import static org.openxava.jpa.XPersistence.*; // For using getManager()
 
public class PricePerUnitCalculator implements ICalculator {
 
    @Getter @Setter
    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
    }
 
}
Then we 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
)
@Money
BigDecimal pricePerUnit; // A regular persistent property
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:
@Money
@Depends("pricePerUnit, quantity") // pricePerUnit instead of product.number
public BigDecimal getAmount() {
    if (pricePerUnit == null) return BigDecimal.ZERO; // pricePerUnit instead of product and product.getPrice()
    return new BigDecimal(quantity).multiply(pricePerUnit); // pricePerUnit 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
Collection<Detail> details;
Try the Order and Invoice modules and observe the new behavior when adding details.


Summary

In this lesson we have learned how we can use the @DefaultValueCalculator annotation to define the value of different properties.

Download source code of this lesson

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