openxava / documentation / Lesson 8: Behavior & 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 8: Behavior & business logic
Business logic in detail mode
Creating an action for custom logic
Writing the real business logic in the entity
Write less code using Apache Commons BeanUtils
Application exceptions
Validation from action
On change event to hide/show an action programmatically
Business logic from list mode
List action with custom logic
Business logic in the model over several entities
Showing a dialog
Using showDialog()
Define the dialog actions
Closing dialog
Plain view instead of dialog
JUnit tests
Testing the detail mode action
Finding an entity for testing using list mode and JPA
Testing hiding of the action
Testing the list mode action
Asserting test data
Testing exceptional cases
Summary
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:
business-logic-behavior_en010.png
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 lesson 7. 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 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, also remember to add the indicated imports:
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 Invoicing/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 Invoicing/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:
business-logic-behavior_en020.png
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 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
                invoice.getDetails().addAll(order.getDetails()); // Copies the lines
                invoice.setVat(
                    invoice.getVat().add(order.getVat())); // Accumalates the vat
                invoice.setTotalAmount( // and the total amount
                    invoice.getTotalAmount().add(order.getTotalAmount()));
            } 
        } 
        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 copy the lines from the remaining orders to the new Invoice and accumulates on it the vat and totalAmount of the orders. 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.
Remember to add the message text to the Invoicing-messages_en.properties file in i18n folder:
orders_not_specified=Orders not specified
These are not the only errors the user can get. All previously written validations for Invoice and Order still apply automatically. This ensures that the user has to choose orders from the same customer, that are delivered, that lacks an invoice, etc. Model validation prevents the user from creating invoices from the wrong orders.

Showing a dialog

After creating an invoice from several orders, it would be practical for the user to see and possibly edit the newly created invoice. One way of achieving this is by showing a dialog that allows to view and edit that just created invoice. In this way:
business-logic-behavior_en030.png
Let's see how to implement this behavior.

Using showDialog()

The first step is to modify CreateInvoiceFromSelectedOrdersAction to show a dialog after creating the invoice, just adding a few lines at the end of execute():
public void execute() throws Exception {
    Collection<Order> orders = getSelectedOrders(); 
    Invoice invoice = Invoice.createFromOrders(orders); 
    addMessage("invoice_created_from_orders", invoice, orders); 

    // Add the next lines to show the dialog
    showDialog(); // (1)
    // From now on getView() is the dialog
    getView().setModel(invoice); // Display invoice in the dialog (2)
    getView().setKeyEditable(false); // To indicate that is an existing object (3)
    setControllers("InvoiceEdition"); // The actions of the dialog (4)
}
We call to showDialog() (1), it shows a dialog and after that moment when we use getView() it references to the view in the dialog no the main view of the module. After the showDialog() the dialog is blank, until we assign our invoice to the view with getView().setModel(invoice) (2), now the invoice is displayed in the dialog. The next line, getView().setKeyEditable(false) (3), is to indicate that the invoice is already saved, so afterwards the corresponding save action knows how to behaves. Finally, we use setControllers("InvoiceEdition") to define the controller with the actions present in the dialog, that is the buttons on bottom of the dialog. Note as setControllers() is an alternative to addActions().
Obviously, this will not work until we have the InvoiceEdition controller defined. We'll do this in the next section.

Define the dialog actions

The dialog allows the user to change the invoice and save the changes or just close the dialog after examining the invoice. These actions are defined in the InvoiceEdition controller in controllers.xml:
<controller name="InvoiceEdition">

    <action name="save"
        class="com.yourcompany.invoicing.actions.SaveInvoiceAction"
        keystroke="Control S"/>
		
    <action name="close"
        class="org.openxava.actions.CancelAction"/>
		
</controller>
The two actions of this controller represent the two buttons, SAVE and CLOSE you saw in the previous image.

Closing dialog

SaveInvoiceAction contains just a minor extension of the standard SaveAction of OpenXava:
package com.yourcompany.invoicing.actions;

import org.openxava.actions.*;

public class SaveInvoiceAction
    extends SaveAction { // Standard OpenXava action to save the view content
	 
    public void execute() throws Exception {
        super.execute(); // The standard saving logic (1)
        closeDialog(); // (2)
    }
}
The action extends SaveAction overwriting the execute() method to just call to the standard logic, with super.execute() (1), and then to close the dialog with closeDialog() (2). In this way, when the user clicks on SAVE, the invoice data is saved and the dialog is closed, so the application returns to the list of orders, ready to continue the creation of invoices from orders.
For the CLOSE button we use the CancelAction, an action included in OpenXava that simply closes the dialog.

Plain view instead of dialog

Sometimes instead of showing a dialog on top:
business-logic-behavior_en040.png
You could prefer to replace the current view with a new one, thus:
business-logic-behavior_en050.png
This can be useful when the amount of data to show is huge and in a dialog looks clumsy. Using a plain view instead of a dialog is as easy as changing this line of from your CreateInvoiceFromSelectedOrdersAction:
showDialog();
By this one:
showNewView();
No more changes are needed. Well, maybe changing the name of the "close" action to "return" in InvoiceEdition controller in controllers.xml.

Our work is almost done. If you try out the Order module, choose several orders, and click on the CREATE INVOICE FROM SELECTED ORDERS to see a dialog with the newly created invoice. Just as you saw in the image at the beginning of this section.

JUnit tests

The code we have written in this lesson is not complete until we write the tests. Remember, all new code must have its corresponding test code. Let's write tests for the two new actions.

Testing the detail mode action

First we'll test the Order.createInvoice action, the action for creating an invoice from the displayed order in detail mode. Then next image shows how this process works:
business-logic-behavior_en010.png
Now we're going to write a test to verify that it really works in this way. Add the testCreateInvoiceFromOrder() method to the OrderTest class:
public void testCreateInvoiceFromOrder() throws Exception {
    login("admin", "admin");      
    
    // Looking for the order
    searchOrderSusceptibleToBeInvoiced(); // Locates an order
    assertValue("delivered", "true"); // The order is delivered
    int orderDetailsCount = getCollectionRowCount("details"); // Takes note of the
                                                     // details count of the order
    execute("Sections.change", "activeSection=1"); // The section of the invoice
    assertValue("invoice.year", ""); // There is no invoice yet
    assertValue("invoice.number", ""); // in this order

    // Creating the invoice
    execute("Order.createInvoice"); // Executes the action under test (1)
    String invoiceYear = getValue("invoice.year"); // Verifies that now
    assertTrue("Invoice year must have value", // there is an invoice in
        !Is.emptyString(invoiceYear)); // the invoice tab (2)
    String invoiceNumber = getValue("invoice.number");
    assertTrue("Invoice number must have value",
        !Is.emptyString(invoiceNumber)); // Is.emptyString() is from org.openxava.util
    assertMessage("Invoice " + invoiceYear + "/" + invoiceNumber +
        " created from current order"); // The confirmation message (3)
    assertCollectionRowCount("invoice.details", // The newly created invoice
        orderDetailsCount); // has the same details count as the order (4)

    // Restoring the order for running the test the next time
    setValue("invoice.year", "");
    assertValue("invoice.number", "");
    assertCollectionRowCount("invoice.details", 0);
    execute("CRUD.save");
    assertNoErrors();
}
As you can see, the test clicks the button for executing Order.createInvoice action (1), then verifies that an invoice has been created, is displayed in the invoice tab (2), and has the same number of detail lines as the current order (4). The test also verifies that the correct confirmation message is generated (3).
To run this test it's needed to choose an order suitable to be invoiced. This is done in the searchOrderSusceptibleToBeInvoiced() method that we are going to examine in the next section.

Finding an entity for testing using list mode and JPA

To select an order suitable for our test we'll use JPA to determine the year and number of that order, and then we'll use the list mode to select the order to be edited in detail mode. Below the methods to implement this:
private void searchOrderSusceptibleToBeInvoiced() throws Exception {
    searchOrderUsingList("delivered = true and invoice = null"); // Sends
} // the condition, in this case to search for a delivered order with no invoice

private void searchOrderUsingList(String condition) throws Exception {
    Order order = findOrder(condition); // Finds the order with the condition using JPA
    String year = String.valueOf(order.getYear());
    String number = String.valueOf(order.getNumber());
    setConditionValues(new String [] { year, number }); // Fills the year and number
    execute("List.filter"); // and clicks the filter button of the list
    assertListRowCount(1); // Only one row corresponding to the desired order
    execute("List.viewDetail", "row=0"); // To see the order in detail mode
    assertValue("year", year); // Verifies that the edited order
    assertValue("number", number); // is the desired one
}

private Order findOrder(String condition) {
    Query query = XPersistence.getManager().createQuery( // Creates a JPA query
        "from Order o where o.deleted = false and " // from the condition. Note the
        + condition); // deleted = false for excluding deleted orders
    List<Order> orders = query.getResultList();
    if (orders.isEmpty()) { // It's needed at least one order with the condition
        fail("To run this test you must have some order with " + condition);
    }
    return orders.get(0);
}
Also you have the next imports to OrderTest in order it compiles:
import java.util.*;
import javax.persistence.*;
import org.openxava.jpa.*;
import org.openxava.util.*;
import com.yourcompany.invoicing.model.*;
The searchOrderSusceptibleToBeInvoiced() method simply calls a more generic method, searchOrderUsingList(), to locate an entity from a condition. The searchOrderUsingList() method obtains an Order entity by means of findOrder(), then it uses the list to filter by year and number of this Order before finally going to detail mode. The findOrder() method uses plain JPA for searching.
Combining list mode and JPA can be a very useful technique in some cases. We will continue to use the methods searchOrderUsingList() and findOrder() in the remaining tests.

Testing hiding of the action

We refined the Order module to show the action for creating an invoice only when the displayed order would be suitable to be invoiced. This is the test method for this, add it to OrderTest:
public void testHidesCreateInvoiceFromOrderWhenNotApplicable() throws Exception {
    login("admin", "admin");
    searchOrderUsingList(
        "delivered = true and invoice <> null"); // If the order already has an invoice
    assertNoAction("Order.createInvoice"); // it cannot be invoiced again

    execute("Mode.list");

    searchOrderUsingList(
        "delivered = false and invoice = null"); // If the order is not delivered
    assertNoAction("Order.createInvoice"); // it cannot be invoiced

    execute("CRUD.new"); // If the order is not saved yet
    assertNoAction("Order.createInvoice"); // it cannot be invoiced
}
We test three cases when the button for creating an invoice should not be visible. Note the usage of assertNoAction() for asking if a link or button for an action is not present in the user interface. Here we are reusing the searchOrderUsingList() method developed in the previous section.
We have already implicitly tested that the button is present when applicable in our previous test, because execute() fails if the action is not in the user interface.

Testing the list mode action

Now we'll test the Order.createInvoiceFromSelectedOrders action, the action for creating an invoice from multiple selected orders in list mode. Remember that this process works in this way:
business-logic-behavior_en030.png
Let's write a test to verify that it works in just this way. Add the testCreateInvoiceFromSelectedOrders() method to the OrderTest class:
public void testCreateInvoiceFromSelectedOrders() throws Exception {
    login("admin", "admin");
    assertOrder(2021, 3, 2, "162.14"); // Order 2021/3 has 2 lines and 162.14 as total amount
    assertOrder(2021, 4, 1, "48.40"); // Order 2021/4 has 1 line and 48.40 as total amount

    execute("List.orderBy", "property=number"); // Sorts the list by number
    checkRow( // Checks the row from the row number
        getDocumentRowInList("2021", "3") // Obtains the row from order year and number
	); // So, this line checks the order 2021/3 in the list (1)
    checkRow(
        getDocumentRowInList("2021", "4")
    ); // Checks the order 2021/4 in the list (1)

    execute("Order.createInvoiceFromSelectedOrders"); // Executes the action we
                                                      // are currently testing (2)
    String invoiceYear = getValue("year"); // We are now in detail mode of the
    String invoiceNumber = getValue("number"); // newly created invoice
    assertMessage("Invoice " + invoiceYear + "/" + invoiceNumber +
        " created from orders: [2021/3, 2021/4]"); // The confirmation message
    assertCollectionRowCount("details", 3); // Asserts that the line count of the new
                        // invoice equals the sum of lines from the source orders (3)
    assertValue("totalAmount", "210.54"); // Asserts that total amount of the new
                    // invoice equals the sum of the amounts of the source orders (4)
    execute("Sections.change", "activeSection=1"); // Changes to the orders tab of invoice
    assertCollectionRowCount("orders", 2); // The new invoice has 2 associated orders (5)
    assertValueInCollection("orders", 0, 0, "2021"); // and they should be the correct 
    assertValueInCollection("orders", 0, 1, "3"); // ones
    assertValueInCollection("orders", 1, 0, "2021");
    assertValueInCollection("orders", 1, 1, "4");

    assertAction("InvoiceEdition.save"); // The SAVE (6)
    assertAction("InvoiceEdition.close"); // and CLOSE buttons (6)

    checkRowCollection("orders", 0); // We select the 2 orders
    checkRowCollection("orders", 1);
    execute("Collection.removeSelected", // and remove them, in order to be able to
        "viewObject=xava_view_section1_orders"); // repeat this test using the same orders
    assertNoErrors();

    execute("InvoiceEdition.close"); // Closes the dialog and returns to the orders list (7)
    assertDocumentInList("2021", "3"); // Asserts that we are really in orders list
    assertDocumentInList("2021", "4");
}
This test checks two orders (1) and clicks the CREATE INVOICE FROM SELECTED ORDERS (2). Then it verifies that a new invoice is created with the correct number of lines (3), total amount (4) and list of orders (5). Furthermore the test verifies that the SAVE and CLOSE actions are available (6) and uses the latter for returning to the orders list (7).
We use getDocumentRowInList() and assertDocumentInList(), methods from CommercialDocumentTest base class. They were originally defined as private, therefore we must redefine them as protected to use them from OrderTest. Edit CommercialDocumentTest and make the next changes:
// private void assertDocumentInList(String year, String number) ...
protected void assertDocumentInList(String year, String number) ...

// private int getDocumentRowInList(String year, String number) ...
protected int getDocumentRowInList(String year, String number) ...
The only remaining detail is the assertOrder() method that we'll see in the next section.

Asserting test data

In the lesson 3 (Automated testing) you learned how to use data existing in the database for your tests. Obviously, if your database is accidentally altered, your test, albeit correct, will not pass. So, asserting the database values before running a test that relies on them is a good practice. In our example we do this by calling assertOrder() at the beginning:
private void assertOrder(
    int year, int number, int detailsCount, String totalAmount)
{
    Order order = findOrder("year = " + year + " and number=" + number);
    assertEquals("To run this test the order " +
        order + " must have " + detailsCount + " details",
        detailsCount, order.getDetails().size());
    assertTrue("To run this test the order " +
        order + " must have " + totalAmount + " as total amount",
        order.getTotalAmount().compareTo(new BigDecimal(totalAmount)) == 0);
    assertTrue("To run this test the order " + order + " must be delivered",
        order.isDelivered());        
}
This method finds an order and verifies its details count, the total amount and if the order is delivered. Using this method has the advantage that if the required orders for the test are not in the database with the correct values you get a precise message. Thus, you will not waste time figuring out what is wrong. This is especially useful if the test is not performed by the original developer. By the way, if you find difficult to create the orders with the data to fit this test (order number, details count, amount), just adapt the values of the test to your current orders.

Testing exceptional cases

Given that the action for creating the invoice is hidden if the order is not ready to be invoiced, we cannot test the code from detail mode we wrote for handling exceptional cases. In list mode however, the user still has the option of choosing any order for invoicing. Therefore, we will create the invoice for verifying the correct behavior in exceptional cases from list mode. Add the next code to OrderTest:
public void testCreateInvoiceFromOrderExceptions() throws Exception {
    login("admin", "admin");      
    assertCreateInvoiceFromOrderException( // Verifies that when the order already has (1)
        "delivered = true and invoice <> null", // an invoice the correct error is produced
        "Impossible to execute Create invoice from selected orders action: " +
            "The order already has an invoice"
    );
    assertCreateInvoiceFromOrderException( // Verifies that when the order is not (2)
        "delivered = false and invoice = null", // delivered the correct error is produced
        "Impossible to execute Create invoice from selected orders action: " +
            "The order is not delivered yet"
    );
}

private void assertCreateInvoiceFromOrderException(
    String condition, String message) throws Exception
{
    Order order = findOrder(condition); // Finds an order satisfying the condition (3)
    int row = getDocumentRowInList( // and obtains the row number for that order (4)
        String.valueOf(order.getYear()),
        String.valueOf(order.getNumber())
    );
    checkRow(row); // Checks the row (5)
    execute("Order.createInvoiceFromSelectedOrders"); // Tries to create the invoice(6)
    assertError(message); // Is the expected message shown? (7)
    uncheckRow(row); // Uncheck the row so we can call this method again
}
The test verifies that the message is the correct one when trying to create an invoice from an order that already has an invoice (1), and also from an order not delivered yet (2). To do these verifications it calls the method assertCreateInvoiceFromOrderException(). This method finds an Order entity using the condition (3), locates the row where the entity is displayed (4) and checks it (5). Afterward, the test executes the action (6) and asserts that the expected message is shown (7).

Summary

The salt of your application comes from the actions and entity methods. Thanks to them you can convert a simple data management application into a useful tool. In this lesson, for example, we provided the user with a way to automatically create invoices from orders.
You have learned how to create instance and static methods for business logic, and how to call them from actions in detail and list mode. Along the way you also saw how to hide and show actions, use exceptions, validating from actions, show dialogs and how to test all this.
We still have many interesting things to learn, in the next lesson for example we are going to refine the behavior of references and collections.

Download source code of this lesson

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