openxava / documentation / Lesson 3: Basic domain model - Part 2

Course: 1. Getting started | 2. Basic domain model (1) | 3. Basic domain model (2) | 4. Refining the user interface | 5. Agile development | 6. 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 propierties | 17. Logic from database | 18. Advanced validation | 19. Refining the standard behavior | 20. Behavior & business logic | 21. References & collections | A. Architecture & philosophy | B. Java Persistence API | C. Annotations | D. Automated testing

Table of contents

Lesson 3: Basic domain model - Part 2
Calculating default values
Regular reference (ManyToOne)
Collection of dependent objects
Summary
In this lesson you'll finish to create all the entities required for your project. We already have the basic entities, now we're going to write the code for the main entity of our invoicing application, the Invoice entity.

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

Calculating default values

We're going to write our Invoice entity with year, number and date. It would be nice to have default values for these properties, so the user does not have to type them. It's easy to do it using the @DefaultValueCalculator annotation. In this first version of Invoice you can see how we define default values for year and date:
package com.yourcompany.invoicing.model;
 
import java.time.*;
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.*;
import org.openxava.calculators.*;
import lombok.*;
 
@Entity @Getter @Setter
public class Invoice {

    @Id
    @GeneratedValue(generator="system-uuid")
    @Hidden
    @GenericGenerator(name="system-uuid", strategy="uuid")
    @Column(length=32)
    String oid;

    @Column(length=4)
    @DefaultValueCalculator(CurrentYearCalculator.class) // Current year
    int year;
 
    @Column(length=6)
    int number;
 
    @Required
    @DefaultValueCalculator(CurrentLocalDateCalculator.class) // Current date
    LocalDate date;
 
    @Stereotype("MEMO")
    String remarks;
 
}
Thus when the user clicks on the New button the year field will have the current year, and the date field the current date. These two calculators (CurrentYearCalculator and CurrentLocalDateCalculator) are included in OpenXava. You can explore the org.openxava.calculators package to see other useful built-in calculators.
Note that for the date we use the type LocalDate (from java.time package). Java has a type Date (in java.util package). However Date is not a date, but a moment in time, including hours, seconds and milliseconds, while LocalDate has just day, month and year, that is a date. For the invoice case, and for most cases in business applications, we'll use LocalDate over Date.
Sometimes you need your own logic for calculating the default value. For example, for number we want to add one to the last invoice number in the same year. Creating your own calculator with your logic is easy. First, create a package for calculators and call it com.yourcompany.invoicing.calculators.
For create a new package select the Invoice/src folder and click on the New Java Package button:
It shows a dialog where you enter the package name, com.yourcompany.invoicing.calculators, and click on Finish:
Packages are the way has Java to organize the code. You should change com.yourcompany by the domain of your organization, that is if you work for Google the package for calculators should be com.google.invoicing.calculators.
Then create in it a NextNumberForYearCalculator class, with the next code:
package com.yourcompany.invoicing.calculators;
 
import javax.persistence.*;
import org.openxava.calculators.*;
import org.openxava.jpa.*;
import lombok.*;
 
public class NextNumberForYearCalculator implements ICalculator { // A calculator must implement ICalculator

    @Getter @Setter // To be publicly accessible
    int year; // This value will be injected before calculating
 
    public Object calculate() throws Exception { // It does the calculation
        Query query = XPersistence.getManager() // A JPA query
            .createQuery("select max(i.number) from Invoice i where i.year = :year"); // The query returns
                                                                // the max invoice number of the indicated year
        query.setParameter("year", year); // We use the injected year as a parameter for the query
        Integer lastNumber = (Integer) query.getSingleResult();
        return lastNumber == null ? 1 : lastNumber + 1; // Returns the last invoice number
                                                        // of the year + 1 or 1 if there is no last number
    }
 
}
Your calculator must implement ICalculator interface (and therefore must have a calculate() method). We declare a year property to put in the year of the calculation. To implement the logic we use a JPA query. You can learn how to use JPA in appendix B. Now we only have to annotate the number property in the Invoice entity:
@Column(length=6)
@DefaultValueCalculator(value=NextNumberForYearCalculator.class,
    properties=@PropertyValue(name="year") // To inject the value of year from Invoice to
                                           // the calculator before calling to calculate()
)
int number;
In this case you see something new, an annotation @PropertyValue. By using this annotation you're saying that the value of year property of the current Invoice will be moved to the property year of the calculator before doing the calculation. Now when ever the user clicks on New the next invoice number is available for the year field. The way of calculating the invoice number is not the best for many concurrent users adding invoices. Don't worry, we'll improve this issue later on.
This is the visual effect of the default value calculators:
modeling_en070.png
Default values are only the initial values. The user can change them if he wishes to.
Note as year and number are not key, instead we use an oid property as key (annotated with @Id). Generally using single keys is better, however using composite keys is also possible.

Regular reference (ManyToOne)

Now that we have all atomic properties ready to use it's time to add relationships with other entities. We'll begin adding a reference from Invoice to Customer, because an invoice without customer is not very useful. Before adding the customer use the Invoice module to remove all the current invoices because we're going to make the customer required, so the old data could fail.
Add the next code to the Invoice entity:
@ManyToOne(fetch=FetchType.LAZY, optional=false) // Customer is required
Customer customer;
Nothing more is required. The Invoice module is now like this one:
modeling_en080.png
There is no more work left here now. Let's add the collection of details to your Invoice.

Collection of dependent objects

Usually an invoice needs to have a couple of lines with the details of products, quantities, etc. These details are part of the invoice. They are not shared with other invoices, and when an invoice is deleted its details are also deleted. So, the more natural way of modeling the invoice details is to use a collection of embeddable objects. To do it with JPA, declare in Invoice a regular collection annotated with @ElementCollection:
@ElementCollection
Collection<Detail> details;
Using @ElementCollection when the invoice is removed its details are removed too. The details are not saved in the database until the invoice is saved and they are saved all at once.
In order to make this collection works you need to write the Detail class:
package com.yourcompany.invoicing.model;
 
import javax.persistence.*;
import lombok.*;
 
@Embeddable @Getter @Setter
public class Detail {
 
    int quantity;
 
    @ManyToOne(fetch = FetchType.LAZY, optional = true)
    Product product;
 
}
Note that Detail is annotated with @Embeddable not with @Entity, you cannot define an @ElementCollection of entities. This @Embeddable class can contain properties and references but not collections.
At the moment we only have quantity and product and that is enough to get the Invoice running with details. The user can add, edit and remove elements from the collection just as in a spreadsheet:
modeling_en090.png
This screenshot emphasizes that the properties to show by default in a collection are the plain ones, that is the properties of references are not included by default. This fact produces an ugly user interface for our collection of invoice details, because only the quantity property is shown. You can fix it using @ListProperties, in this way:
@ElementCollection
@ListProperties("product.number, product.description, quantity")
Collection<Detail> details;
As you can see, you only have to feed the value for the annotation @ListProperties with the list of the properties you wish, separated by commas. You can use qualified properties, that is, to use the dot notation for accessing properties of references, such as product.number and product.description in this case. The visual result is:
modeling_en100.png

Summary

Congratulations! You have finished your domain model classes, and you have an application running. Now the user can work with products, categories, customers and even create invoices. In the case of products, categories and customers the user interface is pretty decent, though the user interface for invoices still can be improved a little. By the way, you already have used some OpenXava annotations for refining the presentation, such as @DescriptionsList, @NoFrame and @ListProperties. In the next lesson we'll use more of these annotations to give the Invoice user interface a better look without too much effort.

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