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. 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
OpenXava is not just a CRUD framework, but a framework for developing full-fledged business applications. Until now we have learned how to create and enhance a data management application. We will now improve the application further by giving the user the possibility to perform specific business logic.
In this lesson we'll see how to add business logic to a model and call this logic from custom actions. In this way you can transform a database management application into a useful tool for the everyday work of your user.
Business logic in detail mode
We'll start with the simplest case: a button in the detail mode that executes some concrete logic. In this case we'll add a button for creating an invoice from an order:

This shows how this new action takes the current order and creates an invoice from it. It just copies all the order data to the new invoice, including the detail lines. A message is shown and the INVOICE tab of the order will display the recently created invoice. Let's see how to implement this.
Creating an action for custom logic
As you already know the first step towards having a custom action in your module is defining a controller for that action. Let's edit
controllers.xml, to add such a controller. Here you have the
Order controller definition:
<controller name="Order">
<extends controller="Invoicing"/> <!-- In order to have the standard actions -->
<action name="createInvoice" mode="detail"
class="com.yourcompany.invoicing.actions.CreateInvoiceFromOrderAction"/>
<!-- mode="detail" : Only in detail mode -->
</controller>
Since we follow the convention of giving the controller the same name as the entity and the module, you automatically have this new action available for
Order.
Order controller extends
Invoicing controller. Remember that we created
Invoicing controller in the previous lesson. It is a refinement of the
Typical controller.
Now we have to write the Java code for the action:
package com.yourcompany.invoicing.actions; // In 'actions' package
import org.openxava.actions.*;
import com.yourcompany.invoicing.model.*;
public class CreateInvoiceFromOrderAction
extends ViewBaseAction { // To use getView()
public void execute() throws Exception {
Order order = (Order) getView().getEntity(); // Order entity displayed in the view (1)
order.createInvoice(); // The real work is delegated to the entity (2)
getView().refresh(); // In order to see the created invoice in 'Invoice' tab (3)
addMessage("invoice_created_from_order", // Confirmation message (4)
order.getInvoice());
}
}
Really simple. We get the
Order entity (1), call the
createInvoice() method (2), refresh the view (3) and display a message (4). Note how the action is a mere intermediary between the view (the user interface) and the model (the business logic).
Remember to add the message text to the
invoicing-messages_en.properties file in
src/main/resources/i18n folder, as following:
invoice_created_from_order=Invoice {0} created from current order
However, just "as is" the message is not shown nicely, because we pass an
Invoice object as argument. We need a
toString() for
Invoice and
Order useful to the user. We'll overwrite the
toString() of
CommercialDocument (the parent of
Invoice and
Order) to achieve this. You can see this
toString() method here:
abstract public class CommercialDocument extends Deletable {
...
public String toString() {
return year + "/" + number;
}
}
Year and number are perfect to identify an invoice or order from the user perspective.
That's all for the action. Let's see the missing piece, the
createInvoice() method of the
Order entity.
Writing the real business logic in the entity
The business logic for creating the new
Invoice is defined in the
Order entity, not in the action. This is just the natural way to go. This is the natural way to go in accordance with the essential principle behind Object-Orientation where the objects are not just data, but data and logic. The most beautiful code is that whose objects contain the logic for managing their own data. If your entities are mere data containers (simple wrappers around database tables), and your actions contain all the logic for manipulating them, your code is a perversion of the original goal of Object-Orientation.
Apart from the spiritual reason, to put the logic for creating an
Invoice inside the
Order entity is a very pragmatic approach, because in this way we can use this logic from other actions, batch processes, web services, etc.
Let's see the code of the
createInvoice() method of the
Order class:
public class Order extends CommercialDocument {
...
public void createInvoice() throws Exception { // throws Exception is just
// to get simpler code for now
Invoice invoice = new Invoice(); // Instantiates an Invoice (1)
BeanUtils.copyProperties(invoice, this); // and copies the state (2)
// from the current Order
invoice.setOid(null); // To let JPA know this entity does not exist yet
invoice.setDate(LocalDate.now()); // The date for the new invoice is today
invoice.setDetails(new ArrayList<>(getDetails())); // Clones the details collection
XPersistence.getManager().persist(invoice);
this.invoice = invoice; // Always after persist() (3)
}
}
The logic consists of creating a new
Invoice object (1), copying the data from the current
Order to it (2) and assigning the resulting entity to the invoice reference in the current
Order (3).
There are three subtle details here. First, you have to write
invoice.setOid(null), otherwise the new
Invoice will get the same identity as the source
Order. Moreover, JPA does not like to persist objects with the autogenerated id pre-filled. Second, you have to assign the new
Invoice to the current
Order (
this.invoice = invoice) after your call to
persist(invoice), if not you get a error from JPA (something like "object references an unsaved transient instance". Third, we have to wrap the
details collection with a
new ArrayList(), so it is a new collection but with the same elements, because JPA don't want the same collection assigned to two entities.
Write less code using Apache Commons BeanUtils
Note how we have used
BeanUtils.copyProperties() to copy all properties from the current
Order to the new
Invoice. This method copies all properties with the same name from one object to another, even if the objects belong to different classes. This utility is from the Commons BeanUtils project from Apache. The jar for this utility,
commons-beanutils.jar, is already included in your project.
The next snippet shows how using BeanUtils you actually write less code:
BeanUtils.copyProperties(invoice, this);
// Is the same as
invoice.setOid(getOid());
invoice.setYear(getYear());
invoice.setNumber(getNumber());
invoice.setDate(getDate());
invoice.setDeleted(isDeleted());
invoice.setCustomer(getCustomer());
invoice.setVatPercentage(getVatPercentage());
invoice.setVat(getVat());
invoice.setTotalAmount(getTotalAmount());
invoice.setRemarks(getRemarks());
invoice.setDetails(getDetails());
However, the main advantage of using BeanUtils is not to save some typing, but that you have code more resilient to changes. Because, if you add, remove or rename some property of
ComercialDocument (the parent of
Invoice and
Order) you don't need to change your code, while if you're copying the properties manually you must change the code manually.
Application exceptions
Remember the phrase: "The exception that proves the rule". Rules, life and software are full of exceptions. And our
createInvoice() method is not an exception. We have written the code to work in the most common cases. But, what happens if the order is not ready to be invoiced, or if there is some problem accessing the database? Obviously, in these cases we need to take different paths.
This is to say that the simple
throws Exception we have written for
createInvoice() method is not enough to ensure a robust behavior. Instead we should use our own exception. Let's create it:
package com.yourcompany.invoicing.model; // In model package
import org.openxava.util.*;
public class CreateInvoiceException extends Exception { // Not RuntimeException
public CreateInvoiceException(String message) {
// The XavaResources is to translate the message from the i18n entry id
super(XavaResources.getString(message));
}
}
Now we can use our
CreateInvoiceException instead of
Exception in the
createInvoice() method of
Order:
public void createInvoice()
throws CreateInvoiceException // An application exception (1)
{
if (this.invoice != null) { // If an invoice is already present we cannot create one
throw new CreateInvoiceException(
"order_already_has_invoice"); // Allows an i18n id as argument
}
if (!isDelivered()) { // If the order is not delivered we cannot create the invoice
throw new CreateInvoiceException("order_is_not_delivered");
}
try {
Invoice invoice = new Invoice();
BeanUtils.copyProperties(invoice, this);
invoice.setOid(null);
invoice.setDate(LocalDate.now());
invoice.setDetails(new ArrayList<>(getDetails()));
XPersistence.getManager().persist(invoice);
this.invoice = invoice;
}
catch (Exception ex) { // Any unexpected exception (2)
throw new SystemException( // A runtime exception is thrown (3)
"impossible_create_invoice", ex);
}
}
Now we declare explicitly which application exceptions this method throws (1). An application exception is a checked exception that indicates a special but expected behavior of the method. An application exception is related to the method's business logic. You could create an application exception for every possible case, such as an
OrderAlreadyHasInvoiceException and an
OrderNotDeliveredException. This enables you to handle each case differently in the calling code. This is not needed in our case, so we simply use our
CreateInvoiceException, a generic application exception for this method.
Additionally, we have to deal with unexpected problems (2). Unexpected problems can be system errors (database access, net or hardware problems) or programmer errors (
NullPointerException,
IndexOutOfBoundsException, etc). When we find any unexpected problem we throw a runtime exception (3). In this instance we have thrown
SystemException, a runtime exception included in OpenXava for convenience, but you can throw any runtime exception you want.
You do not need to modify the action code. If your action does not catch the exceptions, OpenXava does it automatically. It displays the messages from the application exceptions to the user, and, for the runtime exceptions, shows a generic error message, and rolls back the transaction.
In order to be complete, we have to add the messages used for the exceptions in the i18n files. Edit the
invoicing-messages_en.properties file from
src/main/resources/i18n folder adding the next entries:
order_already_has_invoice=The order already has an invoice
order_is_not_delivered=The order is not delivered yet
impossible_create_invoice=Impossible to create invoice
There is some debate in the developer community regarding the correct way of using exceptions in Java. The approach in this section is the classic way to work with exceptions in the Java Enterprise world.
Validation from action
Usually the best place for validations is the model, i.e., the entities. However, sometimes it's necessary to put validation logic in the actions. For example, if you want to obtain the current state of the user interface, the validation must be done from the action.
In our case, if the user clicks on CREATE INVOICE when creating a new order, and this order is not yet saved, it will fail. It fails because it's impossible to create an invoice from an non-existent order. The user must first save the order.
For that add an condition at the begin of the
execute() method of
CreateInvoiceFromOrderAction to validate that the currently displayed order is saved:
public void execute() throws Exception {
// Add the next condition
if (getView().getValue("oid") == null) {
// If oid is null the current order is not saved yet (1)
addError("impossible_create_invoice_order_not_exist");
return;
}
...
}
The validation consists of verifying if the
oid is null (1), in which case the user is entering a new order, but he did not save it yet. In this case a message is shown, and the creation of the invoice is aborted.
Here we also have a message to add to the i18n file. Edit the
invoicing-messages_en.properties file in the
src/main/resources/i18n folder adding the next entry:
impossible_create_invoice_order_not_exist=Impossible to create invoice: The order does not exist yet
Validations tell the user that he has done something wrong. This is needed, of course, but better still is to create an application that helps the user to avoid any wrong doings. Let's see one way to do so in the next section.
On change event to hide/show an action programmatically
Our current code is robust enough to prevent user slips from breaking data. We will go one step further, preventing the user to slip at all. We're going to hide the action for creating a new invoice, if the order is not valid to be invoiced.
OpenXava allows to hide and show actions programmatically. It also allows the execution of an action when some property is changed by the user on the screen. We can use these two techniques to show the button only when the action is ready to be used.
Remember that an invoice can be generated from an order only if the order has been delivered and it does not yet have an invoice. So, we have to monitor the changes in the
invoice reference and
delivered property of the
Order entity. First, we'll create an action to show or hide the action for creating an invoice from order,
ShowHideCreateInvoiceAction, with this code:
package com.yourcompany.invoicing.actions; // In the 'actions' package
import org.openxava.actions.*; // Needed to use OnChangePropertyAction,
public class ShowHideCreateInvoiceAction
extends OnChangePropertyBaseAction { // Needed for @OnChange actions (1)
public void execute() throws Exception {
if (isOrderCreated() && isDelivered() && !hasInvoice()) { // (2)
addActions("Order.createInvoice");
}
else {
removeActions("Order.createInvoice");
}
}
private boolean isOrderCreated() {
return getView().getValue("oid") != null; // We read the value from the view
}
private boolean isDelivered() {
Boolean delivered = (Boolean)
getView().getValue("delivered"); // We read the value from the view
return delivered == null?false:delivered;
}
private boolean hasInvoice() {
return getView().getValue("invoice.oid") != null; // We read the value from the view
}
}
Then we annotate
invoice and
delivered in
Order with
@OnChange so when the user changes the value of
delivered or
invoice in the screen, the
ShowHideCreateInvoiceAction will be executed:
public class Order extends CommercialDocument {
...
@OnChange(ShowHideCreateInvoiceAction.class) // Add this
Invoice invoice;
...
@OnChange(ShowHideCreateInvoiceAction.class) // Add this
boolean delivered;
...
}
ShowHideCreateInvoiceAction is a conventional action with an
execute() method, moreover it extends
OnChangePropertyBaseAction (1). All the actions annotated with
@OnChange must implement
IOnChangePropertyAction, however it's easier to extend
OnChangePropertyBaseAction which implements it. From this action you can use the
getNewValue() and
getChangedProperty(), although in this specific case we don't need them.
The
execute() method asks if the displayed order is saved, delivered, and does not already have an invoice (2), in that case it shows the action with
addActions("Order.createInvoice"), otherwise it hides the action with
removeActions("Order.createInvoice"). Thus we hide or show the
Order.createInvoice action, only showing it when it is applicable.The
add/removeActions() methods allow to specify several actions to show or hide separated by commas.
Now when the user checks the delivered checkbox, or chooses an invoice, the action button is shown or hidden. Accordingly, when the user clicks on
New button to create a new order the button for creating the invoice is hidden. However, if you choose to modify an already existing order, the button is always present, regardless if the prerequisites are fulfilled. This is because when an object is searched and displayed the
@OnChange actions are not executed by default. We can change this with a little modification in
SearchExcludingDeleteAction:
public class SearchExcludingDeletedAction
// extends SearchByViewKeyAction {
extends SearchExecutingOnChangeAction { // Use this as base class
The default search action, i.e.,
SearchByViewKeyAction does not execute the
@OnChange actions, so we change our search action to extend from
SearchExecutingOnChangeAction.
SearchExecutingOnChangeAction behaves like
SearchByViewKeyAction but executes the on-change events. This way, when the user selects an order, the
ShowHideCreateInvoiceAction is executed.
A tiny detail remains to make all this perfect: when the user click on CREATE INVOICE, after the invoice has been created, the button should be hidden. It should not be possible to create the same invoice twice. We can implement this functionality by refining the
CreateInvoiceFromOrderAction:
public void execute() throws Exception {
...
// Everything worked fine, so we'll hide the action
removeActions("Order.createInvoice");
}
As you can see we just add the
removeActions("Order.createInvoice") at the end of the
execute() method.
Showing and hiding actions is not a substitute for validation in the model. Validations are still necessary since the entities can be used from any other part of the application, not just from the CRUD module. However, the trick of hiding and showing actions improves the user experience.
Business logic from list mode
In the previous lesson you learned
how to create list actions. List actions are very useful tools that provides the user with the ability to perform some specific logic on multiple objects at the same time. In our case, we can add an action in list mode to create a new invoice automatically from several selected orders in the list. We want this action to work this way:

This list action takes the selected orders and creates an invoice from them. It just copies the order data into the new invoice, adding the detail lines of all the orders in one unique invoice. Also a message is shown. Let's see how to code this behavior.
List action with custom logic
As you already know, the first step towards having a new custom action in your module is to add that action to a controller. So, let's edit
controllers.xml adding a new action to the
Order controller:
<controller name="Order">
...
<!-- The new action -->
<action name="createInvoiceFromSelectedOrders"
mode="list"
class="com.yourcompany.invoicing.actions.CreateInvoiceFromSelectedOrdersAction"/>
<!-- mode="list": Only shown in list mode -->
</controller>
This is all that is needed to have this new action available for
Order in list mode.
Now we have to write the Java code for the action:
package com.yourcompany.invoicing.actions;
import java.util.*;
import javax.ejb.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import com.yourcompany.invoicing.model.*;
public class CreateInvoiceFromSelectedOrdersAction
extends TabBaseAction { // Typical for list actions. It allows you to use getTab() (1)
public void execute() throws Exception {
Collection<Order> orders = getSelectedOrders(); // (2)
Invoice invoice = Invoice.createFromOrders(orders); // (3)
addMessage("invoice_created_from_orders", invoice, orders); // (4)
}
private Collection<Order> getSelectedOrders() // (5)
throws FinderException
{
Collection<Order> result = new ArrayList<>();
for (Map key: getTab().getSelectedKeys()) { // (6)
Order order = (Order) MapFacade.findEntity("Order", key); // (7)
result.add(order);
}
return result;
}
}
Really simple. We obtain the list of the checked orders in the list (2), call
createFromOrders() static method (3) of
Invoice and show a message (4). In this case we also put the real logic in the model class, not in the action. Since the logic applies to several orders and creates a new invoice the natural place to put it is a static method of
Invoice class.
The
getSelectedOrders() method (5) returns a collection containing the
Order entities checked by the user in the list. This is easily achieved using
getTab() (6), available from
TabBaseAction (1), that returns an
org.openxava.tab.Tab object. The
Tab object allows you to manage the tabular data of the list. In this case we use
getSelectedKeys() (6) that returns a collection with the keys of the selected rows. Since these keys are in
Map format we use
MapFacade.findEntity() (7) to convert them to
Order entities.
As always, add the message text to the
invoicing-messages_en.properties file in
src/main/resources/i18n folder:
invoice_created_from_orders=Invoice {0} created from orders: {1}
That's all for the action. Let's see the missing piece, the
createFromOrders() method of the
Invoice class.
Business logic in the model over several entities
The business logic for creating a new
Invoice from several
Order entities is in the model layer, i.e., the entities, not in the action. We cannot put the method in
Order class, because the process is done from several orders, not just one. We cannot use an instance method in
Invoice because the invoice does not exist yet, in fact we want to create it. Therefore, we are going to create a static factory method in the
Invoice class for creating a new invoice from several orders.
You can see this method here:
public class Invoice extends CommercialDocument {
...
public static Invoice createFromOrders(Collection<Order> orders)
throws CreateInvoiceException
{
Invoice invoice = null;
for (Order order: orders) {
if (invoice == null) { // The first order
order.createInvoice(); // We reuse the logic for creating an invoice
// from an order
invoice = order.getInvoice(); // and use the created invoice
}
else { // For the remaining orders the invoice is already created
order.setInvoice(invoice); // Assign the invoice
order.copyDetailsToInvoice(); // A method of Order to copy the lines
}
}
if (invoice == null) { // If there are no orders
throw new CreateInvoiceException(
"orders_not_specified");
}
return invoice;
}
}
We use the first
Order to create the new
Invoice using the already existing
createInvoice() method from
Order. Then we call to the
copyDetailsToInvoice() method of
Order to copy the lines from the remaining orders to the new
Invoice and accumulates on it the
vat and
totalAmount. Moreover, we set the new
Invoice as the invoice for the orders of the collection.
If
invoice is null at the end of the process it's because the
orders collection is empty. In this case we throw a
CreateInvoiceException. Since the action does not catch the exceptions, OpenXava shows the exception message to the user. This is fine. If the user does not check any orders and he clicks on the button for creating an invoice, then this error message will be shown to him.
As you can see, It copies the details of the current order to the invoice and accumulates the vat and totalAmount.