Chapter 7: Controllers
The controllers are used for defining actions (buttons, links, images) that final user can click. The controllers are defined in the
controllers.xml file that has to be in the
xava directory of your project.
The actions are not defined in components because there are a lot of generic actions that can be applied to any component.
In
OpenXava/xava you have a
default-controllers.xml that contains a group of generic controllers that can be used in your applications.
The
controllers.xml file contains an element of type
<controllers/> with the syntax:
<controllers>
<env-var ... /> ... <!-- 1 -->
<object ... /> ... <!-- 2 -->
<controller ... /> ... <!-- 3 -->
</controllers>
- env-var (several, optional): Variable that contains configuration information. This variable can be accessed from the actions and filters, and its value can be overwritten in each module.
- object (several, optional): Defines Java object with session scope; that is objects that are created for an user and exist during his session.
- controller (several, required): A controller is a group of actions.
Environment variables
The environment variables contain configuration information. These variables can be accessed from the actions and filters, and its value can be overwritten in each module. Its syntax is:
<env-var
name="name" <!-- 1 -->
value="value" <!-- 2 -->
/>
- name (required): Name of the environment variable in uppercase and using underscore to separate words.
- value (required): Value for the environment variable.
These are some example:
<env-var name="MYAPPLICATION_DEFAULT_YEAR" value="2007"/>
<env-var name="MYAPPLICATION_COLOR" value="RED"/>
Session objects
The Java objects declared in
controllers.xml have session scope; that is, they are objects that are created for a user and exist during his session. It's syntax is:
<object
name="objectName" <!-- 1 -->
class="objectType" <!-- 2 -->
value="initialValue" <!-- 3 -->
scope="module|global" <!-- 4 New in v2.1 -->
/>
- name (required): Name of the object, usually you use the application name as prefix to avoid name collision in large projects.
- class (required): Full qualified Java class for this object.
- value (optional): Initial value for the object.
- scope (optional): (New in v2.1) The default value is module. If you use module scope each module will have its own copy of this object. If you use global scope the same object will be shared by all modules of all OpenXava applications (running in the same .war).
Defining session objects is very easy, you can see the defined ones in
OpenXava/xava/default-controllers.xml:
<object name="xava_view" class="org.openxava.view.View"/>
<object name="xava_referenceSubview" class="org.openxava.view.View"/>
<object name="xava_tab" class="org.openxava.tab.Tab"/>
<object name="xava_mainTab" class="org.openxava.tab.Tab"/>
<object name="xava_row" class="java.lang.Integer" value="0"/>
<object name="xava_language" class="org.openxava.session.Language"/>
<object name="xava_newImageProperty" class="java.lang.String"/>
<object name="xava_currentReferenceLabel" class="java.lang.String"/>
<object name="xava_activeSection" class="java.lang.Integer" value="0"/>
<object name="xava_previousControllers" class="java.util.Stack"/>
<object name="xava_previousViews" class="java.util.Stack"/>
These objects are used by OpenXava in order to work, although it is quite normal that you use some of these from your actions. If you want to create your own objects you can do it in your
controllers.xml in the xava directory of your project.
The controller and its actions
The syntax of controller is:
<controller
name="name" <!-- 1 -->
>
<extends ... /> ... <!-- 2 -->
<action ... /> ... <!-- 3 -->
</controller>
- name (required): Name of the controller.
- extends (several, optional): Allows to use multiple inheritance, to do this the controller inherits all actions from other controller(s).
- action (several, required): Implements the logic to execute when the final user clicks a button or link.
The controllers consist of actions, and actions are the main things. Here is its syntax:
<action
name="name" <!-- 1 -->
label="label" <!-- 2 -->
description="description" <!-- 3 -->
mode="detail|list|ALL" <!-- 4 -->
image="image" <!-- 5 -->
class="class" <!-- 6 -->
hidden="true|false" <!-- 7 -->
on-init="true|false" <!-- 8 -->
on-each-request="true|false" <!-- 9 New in v2.1.2 -->
before-each-request="true|false" <!-- 10 New in v2.2.5 -->
by-default="never|if-possible|almost-always|always" <!-- 11 -->
takes-long="true|false" <!-- 12 -->
confirm="true|false" <!-- 13 -->
keystroke="keystroke" <!-- 14 New in v2.0.1 -->
>
<set ... /> ... <!-- 15 -->
<use-object ... /> ... <!-- 16 -->
</action>
- name (required): Action name that must be unique within its controller, but it can be repeated in other controllers. When you reference an action always use the format ControllerName.actionName.
- label (optional): Button label or link text. It's much better to use i18n files.
- description (optional): Description text of the action. It's much better to use i18n files.
- mode (optional): Indicates in which mode the action has to be visible. The default value is ALL, that means that this action is always visible.
- image (optional): URL of the image associated with this action. In the current implementation if you specify an image, it is shown to user in link format.
- class (optional): Implements the logic to execute. Must implement IAction interface.
- hidden (optional): A hidden action is not shown in the button bar, although it can be used in all other places, for example to associate it to an event, as action of a property, in collections, etc. The default is false.
- on-init (optional): If you set this property to true, then the action will be executed automatically on initiating the module. The default is false.
- on-each-request (optional): (New in v2.1.2) If you set this property to true, then the action will be executed automatically on each request of the user, that is, on first module execution and before each user action execution. In the moment of execution all OpenXava session objects are setup and ready to use. That is, from this action you can use xava_view and xava_tab. The default is false.
- before-each-request (optional): (New in v2.2.5) If you set this property to true, then the action will be executed automatically before each request of the user, that is, on first module execution and before each user action execution, but before the OpenXava session objects are setup and ready to use. That is, from this action you cannot use xava_view or xava_tab. The default is false.
- by-default (optional): Indicates the weight of this action on choosing the action to execute as the default one. The default action is executed when the user presses ENTER. The default is never.
- takes-long (optional): If you set it to true, then you are indicating that this action takes long time in executing (minutes or hours). In the current implementation OpenXava shows a progress bar. The default is false.
- confirm (optional): If you set it to true, then before executing the action a dialog is shown to the user to ask if he is sure to execute it. The default is false.
- keystroke (optional): (New in v2.0.1) Defines a keystroke that the user can press for executing this action. The possible values are the same as for javax.swing.KeyStroke. Examples: "control A", "alt x", "F7".
- set (several, optional): Sets a value of action properties. Thus the same action class can be configured in different ways and it can be used in several controllers.
- use-object (several, optional): Assigns a session object to an action property just before executing the action. After the execution the property value is put back into the context again (update the session object, thus you can update even immutable objects).
Actions are short life objects, when a user clicks a button, then the action object is created, configured (with set and
use-object) and executed. After that the session objects are updated, and finally the action object is discarded.
A plain controller might look like this:
<controller name="Remarks">
<action name="hideRemarks"
class="org.openxava.test.actions.HideShowPropertyAction">
<set property="property" value="remarks" />
<set property="hide" value="true" />
<use-object name="xava_view"/>
</action>
<action name="showRemarks" mode="detail"
class="org.openxava.test.actions.HideShowPropertyAction">
<set property="property" value="remarks" />
<set property="hide" value="false" />
<use-object name="xava_view"/>
</action>
<action name="setRemarks" mode="detail"
class="org.openxava.test.actions.SetPropertyValueAction">
<set property="property" value="remarks" />
<set property="value" value="Hell in your eyes" />
<use-object name="xava_view"/>
</action>
</controller>
Now you can include this controller into the module that you want; this is made by editing in
xava/application.xml the module in which you can use these actions:
<module name="Deliveries">
<model name="Delivery"/>
<controller name="Typical"/>
<controller name="Remarks"/>
</module>
Thus you have in your module the actions of
Typical (CRUD and printing) plus these defined by you in the controller named
Remarks. The top button bar of the module will have this aspect:
![controllers_en010.jpg controllers_en010.jpg](files/controllers_en010.jpg)
Ant the bottom button bar:
![controllers_en020.jpg controllers_en020.jpg](files/controllers_en020.jpg)
You can note as actions with image are located on top and actions without image are located at bottom.
You can write code for
hideRemarks like this:
package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class HideShowPropertyAction extends ViewBaseAction { // 1
private boolean hide;
private String property;
public void execute() throws Exception { // 2
getView().setHidden(property, hide); // 3
}
public boolean isHide() {
return hide;
}
public void setHide(boolean b) {
hide = b;
}
public String getProperty() {
return property;
}
public void setProperty(String string) {
property = string;
}
}
An action must implement IAction, but usually it extends from a base class that implements this interface. The base action more basic is
BaseAction that implements most of the method of
IAction except
execute(). In this case you use
ViewBaseAction as base class.
ViewBaseAction has the property
view of type
View. This joined to the next declaration in action...
<use-object name="xava_view"/>
...allows to manage the view (the user interface) from an action using view.
The
<use-object /> gets the session object
xava_view and assigns it to the property
view (removing the prefix
xava_, in general removes the prefix
myapplication_ before assigning object to property) of your action just before calling
execute().
Now inside the
execute() method you can use
getView() as you want (3), in this case for hiding a property. You can see all View possibilities in the JavaDoc of
org.openxava.view.View.
With...
<set property="property" value="remarks" />
<set property="hide" value="true" />
you can set constant values to the properties of your action.
Controllers inheritance
You can create a controller that inherits all actions from one or more controllers. An example of this is the generic controller called Typical, this controller is in
OpenXava/xava/default-controllers.xml:
<controller name="Typical">
<extends controller="Print"/>
<extends controller="CRUD"/>
</controller>
When you assign the controller
Typical to a module this module will have available all actions of
Print controller (to generate PDF reports and export to Excel) and
CRUD controller (to Create, Read, Update and Delete)
You can use inheritance to refine the way a standard controller works, e. g. like this:
<controller name="Family">
<extends controller="Typical"/>
<action name="new" image="images/new.gif"
class="org.openxava.test.actions.CreateNewFamilyAction">
<use-object name="xava_view"/>
</action>
</controller>
As you see the name of your action new matches with an action in
Typical controller (in reality in
CRUD controller from which extends
Typical). In this case the original action is ignored and your action is used. Thus you can put your own logic to execute when a final user clicks the 'new' link.
List mode actions
You can write actions that apply to several objects. These actions are usually are shown in list mode only and normally have effects on the objects chosen by user only.
An example can be:
<action name="deleteSelected" mode="list" <!-- 1 -->
confirm="true" <!-- 2 -->
class="org.openxava.actions.DeleteSelectedAction">
</action>
You set
mode=”list” in order to show it only in list mode (1). Since this action deletes records you require that the user must confirm explicitly before the action is executed (2). It's not needed to include a
<use-object/> for
xava_tab (new in v2.1.4).
The action source code:
package org.openxava.actions;
import java.util.*;
import org.openxava.model.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class DeleteSelectedAction extends TabBaseAction implements IModelAction { // 1
private String model;
public void execute() throws Exception {
int [] selectedOnes = getTab().getSelected(); // 2
if (selectedOnes != null) {
for (int i = 0; i < selectedOnes.length; i++) {
Map key = (Map)
getTab().getTableModel().getObjectAt(selectedOnes[i]);
try {
MapFacade.remove(model, key); // (3)
}
catch (ValidationException ex) {
addError("no_delete_row", new Integer(i), key);// (4)
addErrors(ex.getErrors());
}
catch (Exception ex) {
addError("no_delete_row", new Integer(i), key);
}
}
getTab().deselectAll(); // 5
resetDescriptionsCache(); // 6
}
}
public void setModel(String modelName) { // 7
this.model = modelName;
}
}
This action is a standard action of OpenXava, but it allows you to see the things that you can do within an action in list mode. You can observe (1) how the action extends from
TabBaseAction and implements
IModelAction. Since it extends from
TabBaseAction (new in v2.1.4) it has a group of utilities and you don't need to implement all methods of
IAction; and as it implements
IModelAction this action has a method called
setModel() (7) that receives the model name (the name of OpenXava component) before executing it.
You can access to the
Tab using the
getTab() method (2); this method is implemented in
TabBaseAction and it allows you to access to the
xava_tab session object. By means of
getTab() you are allowed to manage the list of displayed objects. For example, with
getTab().getSelected() (2) you obtain the indexes of selected rows, with
getTab().getTableModel() a table model to access to data, and with
getTab().deselectAll() you deselect the rows. You can take a look of
org.openxava.tab.Tab JavaDoc for more details on its possibilities.
Something very interesting you can see in this example is the use of
MapFacade (3).
MapFacade allows you to access the data model using Java maps (
java.util.Map). This is useful, if you get data from
Tab or
View in
Map format and you want to update the model (and therefore the database) with it, or vice versa. All generic classes of OpenXava use
MapFacade to manage the model and you also can use
MapFacade. As general design tip: working with maps is useful in the case of generic logic, but if you need to program specific things it is better to use directly the object of model layer. For more details have a look at the JavaDoc of
org.openxava.model.MapFacade.
You see here how to display messages to the user with
addError(). The
addError() method receives the id of an entry in your i18n files and the argument to send to the message. The added messages are displayed to the user as errors. If you want to add warning or informative messages you can use
addMessage() whose behavior is like
addError(). The i18n files that hold errors and messages must be called
MyProject-messages.properties and the language sufix (_en, _ca, _es, _it, etc). You can see the examples in
OpenXavaTest/xava/i18n. All not caught exceptions produces a generic error messages, except if the not caught exception is of the type
ValidationException. In this case the message exception is displayed.
The
resetDescriptionsCache() (6) method deletes all cache entries used by OpenXava to display descriptions list (combos). It's a good idea to call it whenever data is updated.
You can see more possibilities in
org.openxava.actions.BaseAction and
org.openxava.actions.TabBaseAction JavaDoc.
Since v2.1.4 this type of actions
can also be used as @ListAction (<list-action/> of a
<collection-view/>).
Overwriting default search
When a module is shown in list mode and the user clicks to display a detail, then OpenXava searches the corresponding object and displays it in detail. Now, if in detail mode the user fills the key fields and clicks on search (the binoculars), it also does the same. And when the user navigates by the records clicking the next or previous buttons then it does the same search. How can you customize this search? Let's see that:
You only need to define the module in
xava/application.xml this way:
<module name="Deliveries">
<env-var name="XAVA_SEARCH_ACTION" value="Deliveries.search"/>
<model name="Delivery"/>
<controller name="Typical"/>
<controller name="Remarks"/>
<controller name="Deliveries"/>
</module>
You see how it is necessary to define an environment variable named XAVA_SEARCH_ACTION that contains the action that you want to use for searching. This action is defined in
xava/controllers.xml:
<controller name="Deliveries">
<action name="search" mode="detail"
by-default="if-possible" hidden="true"
class="org.openxava.test.actions.SearchDeliveryAction"
keystroke="F8">
<use-object name="xava_view"/>
</action>
...
</controller>
And its code:
package org.openxava.test.actions;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.util.*;
/**
* @author Javier Paniza
*/
public class SearchDeliveryAction extends SearchByViewKeyAction { // 1
public void execute() throws Exception {
super.execute(); // 2
if (!Is.emptyString(getView().getValueString("employee"))) {
getView().setValue("deliveredBy", new Integer(1));
getView().setHidden("carrier", true);
getView().setHidden("employee", false);
}
else {
Map carrier = (Map) getView().getValue("carrier");
if (!(carrier == null || carrier.isEmpty())) {
getView().setValue("deliveredBy", new Integer(2));
getView().setHidden("carrier", false);
getView().setHidden("employee", true);
}
else {
getView().setHidden("carrier", true);
getView().setHidden("employee", true);
}
}
}
}
In this action you have to search the database (or through EJB2, EJB3 JPA or Hibernate) and fill the view. Most times it is better that it extends
SearchByViewKeyAction (1) and within
execute() write a
super.execute() (2).
OpenXava comes with 3 predefined search actions:
- CRUD.searchByViewKey: This is the default one. It does a search using the key values in the view, it executes no event.
- CRUD.searchExecutingOnChange: Works as searchByViewKey but it executes the @OnChange/on-change actions after search data.
- CRUD.searchReadOnly: Works as searchByViewKey but it set the detail view to not editable state on searching. Useful for creating read only modules.
If you want that the
@OnChange/on-change actions will be executed on search then you must define your module this way:
<module name="Products3ChangeActionsOnSearch">
<env-var name="XAVA_SEARCH_ACTION" value="CRUD.searchExecutingOnChange"/>
<model name="Product3"/>
<view name="WithDescriptionsList"/>
<controller name="Typical"/>
<controller name="Products3"/>
<mode-controller name="Void"/>
</module>
As you see, simply by setting the value of the XAVA_SEARCH_ACTION environment variable.
Initialize a module with an action
By setting
on-create=”true” when you define an action, you configure that this action will be executed automatically when the module is executed for the first time. This is a chance to initialize the module. Let's see an example. In your
controllers.xml you write:
<controller name="Invoices2002">
<action name="init" on-init="true" hidden="true"
class="org.openxava.test.actions.InitDefaultYearTo2002Action">
<use-object name="xavatest_defaultYear"/>
<use-object name="xava_tab"/>
</action>
...
</controller>
And in your action:
package org.openxava.test.actions;
import org.openxava.actions.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
public class InitDefaultYearTo2002Action extends BaseAction {
private int defaultYear;
private Tab tab;
public void execute() throws Exception {
setDefaultYear(2002); // 1
tab.setTitleVisible(true); // 2
tab.setTitleArgument(new Integer(2002)); // 3
}
public int getDefaultYear() {
return defaultYear;
}
public void setDefaultYear(int i) {
defaultYear = i;
}
public Tab getTab() {
return tab;
}
public void setTab(Tab tab) {
this.tab = tab;
}
}
In this action you set the default year to 2002 (1), you make the title list visible (2) and you assign a value as an argument to that title (3). The list title is defined in the i18n files, usually it's used for reports, but you can show it in list mode too.
Calling another module
Sometimes it's convenient to call programmatically one module from another one. For example, imagine that you want to show a list of customers and when the user clicks on one customer, then a list of its invoices is displayed and the user can choose an invoice to edit. One way to obtain this effect is to have a module with only list mode and when the user clicks on a detail, the user is directed to an invoices module that shows only the invoices of the chosen customer. Let's see it. First you need to define the module in
application.xml this way:
<module name="InvoicesFromCustomers">
<env-var name="XAVA_LIST_ACTION" value="Invoices.listOfCustomer"/> <!-- 1 -->
<model name="Customer"/>
<controller name="Print"/>
<controller name="ListOnly"/> <!-- 2 -->
<mode-controller name="Void"/> <!-- 3 -->
</module>
In this module only the list is shown (without detail part), for this you set the mode controller to
Void (3) thus 'detail' and 'list' links are not displayed; and also you add a controller called
ListOnly (2) in order to show the list mode, and only the list mode (if you only set the mode controller to Void the detail, and only the detail is displayed). Moreover you declare the variable XAVA_LIST_ACTION to define your custom action. When the user clicks the link in each row, then your own action will be executed. You must declare this action in
controllers.xml:
<controller name="Invoices">
<action name="listOfCustomer" hidden="true"
class="org.openxava.test.actions.ListCustomerInvoicesAction">
<use-object name="xava_tab"/>
</action>
...
</controller>
And the action code:
package org.openxava.test.actions;
import java.util.*;
import org.openxava.actions.*;
import org.openxava.controller.*;
import org.openxava.tab.*;
/**
* @author Javier Paniza
*/
public class ListCustomerInvoicesAction extends BaseAction
implements IChangeModuleAction, // 1
IModuleContextAction { // 2
private int row; // 3
private Tab tab;
private ModuleContext context;
public void execute() throws Exception {
Map customerKey = (Map) tab.getTableModel().getObjectAt(row); // 4
int customerNumber = ((Integer) customerKey.get("number")).intValue();
Tab invoiceTab = (Tab)
context.get("OpenXavaTest", getNextModule(), "xava_tab"); // 5
invoiceTab.setBaseCondition("${customer.number} = "+customerNumber); // 6
}
public int getRow() { // 3
return row;
}
public void setRow(int row) { // 3
this.row = row;
}
public Tab getTab() {
return tab;
}
public void setTab(Tab tab) {
this.tab = tab;
}
public String getNextModule() { // 7
return "CustomerInvoices";
}
public void setContext(ModuleContext context) { // 8
this.context = context;
}
public boolean hasReinitNextModule() { // 9
return true;
}
}
In order to change to another module the action implements
IChangeModuleAction (1) thus forces the action to have a method called
getNextModule() (7). This will indicate to which module OpenXava will switch after executing this action. The method
hasReinitNextModule() (9) indicates, whether you want that the target module has re-initiated on changing to it.
On the other hand this action implements
IModuleContextAction (2) too and therefore it receives an object of type
ModuleContext with the method
setContext() (8).
ModuleContext allows you to access the session objects of others modules. This is useful to configure the target module before changing to it.
Another detail is that the action specified in XAVA_LIST_ACTION must have a property named
row (3); before executing the action this property is filled with the row number that user has clicked.
If you keep in mind the above details it is easy to understand the action:
- Gets the key of the object associated to the clicked row (4), to do this it uses the tab of the current module.
- Accesses to the tab of the target module using context (5).
- Sets the base condition of the tab of target module using the key obtained from current tab.
Changing the module of current view
As an alternative to change the module you can choose changing the model of the current view. This is easy, you only need to use the APIs available in
View. An example:
public void execute() throws Exception {
try {
setInvoiceValues(getView().getValues()); // 1
Object number = getCollectionElementView().getValue("product.number");
Map key = new HashMap();
key.put("number", number);
getView().setModelName("Product"); // 2
getView().setValues(key); // 3
getView().findObject(); // 4
getView().setKeyEditable(false);
getView().setEditable(false);
}
catch (ObjectNotFoundException ex) {
getView().clear();
addError("object_not_found");
}
catch (Exception ex) {
ex.printStackTrace();
addError("system_error");
}
}
This is an extract of an action that allows to visualize an object of another type. First you need to memorize the current displayed data (1), to restore it on returning. After this, you change the model of view (2), this is the important part. Finally you fill the key values (3) and use
findObject() (4) to load all data in the view.
When you use this technique you have to keep in mind that each module has only one
xava_view object active at a time, thus if you wish to go back you have the responsibility of restoring the original model in the view and restoring the original data.
Go to a JSP page
The automatic view generator of OpenXava is good for most cases, but it can be required to display a JSP page hand-written by you. You can do this with an action like this:
package org.openxava.test.actions;
import org.openxava.actions.*;
/**
* @author Javier Paniza
*/
public class MySearchAction extends BaseAction implements INavigationAction { // 1
public void execute() throws Exception {
}
public String[] getNextControllers() { // 2
return new String [] { "MyReference" } ;
}
public String getCustomView() { // 3
return "doYouWishSearch.jsp";
}
public void setKeyProperty(String s) {
}
}
In order to go to a custom view (in this case a JSP page) your action has to implement
INavigationAction (
ICustomViewAction is enough). This way you can indicate with
getNextControllers() (2) the next controllers to use and with
getCustomView() (3) the JSP page to display (3).
Generating a custom report with JasperReports
OpenXava allows the final user to generate their own reports from the list model. The user can perform filtering, ordering, adding/removing fields, changing the positions of the fields and then generate a PDF report of the list.
But in all non-trivial business application you need to create programatically your own reports. You can do that easily by using JasperReports and then by integrating the reports into your OpenXava application with the action
JasperReportBaseAction.
In the first place you need to design your report with JasperReports, you can use iReport an excellent designer for JasperReports.
Then you can write your action to print the report in this way:
package org.openxava.test.actions;
import java.util.*;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.*;
import org.openxava.actions.*;
import org.openxava.model.*;
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* Report of products of the selected subfamily. <p>
*
* Uses JasperReports. <br>
*
* @author Javier Paniza
*/
public class FamilyProductsReportAction extends JasperReportBaseAction { // 1
private ISubfamily2 subfamily;
public Map getParameters() throws Exception { // 2
Messages errors =
MapFacade.validate("FilterBySubfamily", getView().getValues());
if (errors.contains()) throw new ValidationException(errors); // 3
Map parameters = new HashMap();
parameters.put("family", getSubfamily().getFamily().getDescription());
parameters.put("subfamily", getSubfamily().getDescription());
return parameters;
}
protected JRDataSource getDataSource() throws Exception { // 4
return new JRBeanCollectionDataSource(getSubfamily().getProductsValues());
}
protected String getJRXML() { // 5
return "Products.jrxml"; // To read from classpath
// return "/home/javi/Products.jrxml"; // To read from file system
}
private ISubfamily2 getSubfamily() throws Exception {
if (subfamily == null) {
int subfamilyNumber = getView().getValueInt("subfamily.number");
// Using JPA, the usual with OX3
subfamily = XPersistence.getManager().find(
Subfamily2.class, new Integer(subfamilyNumber));
// Using Hibernate, the usual with OX2, but still supported
// subfamily = (ISubfamily2)
// XHibernate.getSession().get(
// Subfamily2.class, new Integer(subfamilyNumber));
// Using EJB2, the usual with OX1, but still supported
//subfamily = Subfamily2Util.getHome().
// findByPrimaryKey(new Subfamily2Key(subfamilyNumber));
}
return subfamily;
}
}
Your action has to extend
JasperReportBaseAction (1) and it has to overwrite the next three method:
- getParameters() (2): A Map with the parameters to send to the report, in this case we validate the input data (using MapFacade.validate()) before (3).
- getDataSource() (4): A JRDataSource with data to print. In this case it is a collection of JavaBeans obtained calling a method of the model object. If you use EJB CMP2 Entities be careful and do not loop over an EJB2 Entity collection inside this method, as in this case is better only one EJB call to obtain all data.
- getJRXML() (5): The XML with the JasperReports design, this file can be in the classpath. You may have a source code folder called reports in your project to hold these files. Other option is put this file in the file system (new in v2.0.3), this is achieved by specifying the full path of file, for example: /home/javi/Products.jrxml.
By default the report is displayed in a popup window, but if you wish the report in the current window, then you can overwrite the method
inNewWindow().
You can find more examples of JasperReport actions in the
OpenXavaTest project, as
InvoiceReportAction for printing an Invoice.
Uploading and processing a file from client (multipart form)
This feature allows you to process in your OpenXava application a binary file (or several) provided by the client. This is implemented in a HTTP/HTML context using HTML multipart forms, although the OpenXava code is technologically neutral, hence your action will be portable to another environment with no recoding.
In order to upload a file the first step is creating an action to direct to a form where the user can choose his file. This action must implements
ILoadFileAction in this way:
public class ChangeImageAction extends BaseAction implements ILoadFileAction { // 1
...
public void execute() throws Exception { // 2
}
public String[] getNextControllers() { // 3
return new String [] { "LoadImage" };
}
public String getCustomView() { // 4
return "xava/editors/changeImage";
}
public boolean isLoadFile() { // 5
return true;
}
...
}
An
ILoadFileAction (1) action is also an
INavigationAction action that allows you to navigate to another controller (3) and to a custom view (4). The new controller (3) usually will have an action of type
IProcessLoadedFileAction. The method
isLoadFile() (5) returns
true in case that you want to navigate to the form to upload the file, you can use the logic in
execute() (2) to determine this value. The custom view is (4) a JSP with your own form to upload the file.
An example of a JSP for a custom view is:
<%@ include file="../imports.jsp"%>
<jsp:useBean id="style" class="org.openxava.web.style.Style" scope="request"/>
<table>
<th align='left' class=<%=style.getLabel()%>>
<fmt:message key="enter_new_image"/>
</th>
<td>
<input name = "newImage" class=<%=style.getEditor()%> type="file" size='60'/>
</td>
</table>
As you see, the HTML form is not specified, because the OpenXava module already has the form.
The last piece is the action for processing the uploaded files:
public class LoadImageAction extends BaseAction
implements INavigationAction, IProcessLoadedFileAction { // 1
private List fileItems;
private View view;
private String newImageProperty;
public void execute() throws Exception {
Iterator i = getFileItems().iterator(); // 2
while (i.hasNext()) {
FileItem fi = (FileItem)i.next(); // 3
String fileName = fi.getName();
if (!Is.emptyString(fileName)) {
getView().setValue(getNewImageProperty(), fi.get()); // 4
}
}
}
public String[] getNextControllers() {
return DEFAULT_CONTROLLERS;
}
public String getCustomView() {
return DEFAULT_VIEW;
}
public List getFileItems() {
return fileItems;
}
public void setFileItems(List fileItems) { // 5
this.fileItems = fileItems;
}
...
}
The action implements
IProcessLoadedFileAction (1), thus the action must have a method
setFileItem() (5) to receive the list of uploaded files. This list can be processed in
execute() (2). The elements of the collection are of type
org.apache.commons.fileupload.FileItem (4) (from fileupload project of apache commons). Only calling to get() (4) in the file item you will access to the content of the uploaded file.
Override the default controllers (new in v2.0.3)
The controllers in
OpenXava/xava/default-controllers.xml (before v2.0.3 it was
OpenXava/xava/controllers.xml) are used by OpenXava to give to application a default behavior. Many times the easier way to override the default behavior of OpenXava is creating our own controllers and use them in our applications, that is you can create a controller called
MyTypical, and using it in your modules instead of
Typical that comes with OpenXava.
Another option is override a default controller of OpenXava. In order to override a default controller you only need to create in your application a controller with the same name of default one. For example, if you want refine the collections behavior for your application then you have to create a
Collection controller in your
xava/controllers.xml, as following:
<controller name="Collection">
<action name="new"
class="org.openxava.actions.CreateNewElementInCollectionAction">
</action>
<action name="hideDetail" <!-- 1 -->
class="org.openxava.test.actions.MyHideDetailElementInCollection">
</action>
<action name="save"
class="org.openxava.actions.SaveElementInCollectionAction">
<use-object name="xava_view"/>
</action>
<action name="remove"
class="org.openxava.actions.RemoveElementFromCollectionAction">
<use-object name="xava_view"/>
</action>
<action name="edit"
class="org.openxava.actions.EditElementInCollectionAction">
<use-object name="xava_view"/>
</action>
<action name="view"
class="org.openxava.actions.EditElementInCollectionAction">
<use-object name="xava_view"/>
</action>
</controller>
In this case we only override the behavior of
hideDetail (1) action. But we must declare all actions of the original controller, because OpenXava rely on all these actions for working; we cannot remove or rename actions.
All action types
You have seen until now that the behavior of your actions depends on which interfaces they implement. Next the available interfaces for actions are enumerated:
- IAction: Basic interface to be implemented by all actions.
- IChainAction: Allows you to chain actions, that is when the execution of the action finishes, then the next action is executed immediately.
- IChainActionWithArgv: (New in v2.2) It's a refinement of IChainAction. It allows to send as arguments values for filling properties of the chained action before execute it.
- IChangeControllersAction: To change the controller (the actions) available to user.
- IChangeModeAction: To change the mode, from list to detail or vice versa.
- IChangeModuleAction: To change the module.
- ICustomViewAction: To use as view your custom JSP.
- IForwardAction: Redirects to a Servlet or JSP page. It is not like ICustomViewAction; ICustomViewAction puts your JSP inside the user interface generated by OpenXava (that can be inside a portal), while IForwardAction redirects completely to the specified URI.
- IHideActionAction, IHideActionsAction: Allows to hide an action or a group of actions in the User Interface (new in v2.0).
- IJDBCAction: Allows to use JDBC in an action directly. It receives an IConnectionProvider. Works like an IJDBCCalculator (see chapter 3).
- ILoadFileAction: Navigates to a view that allows the final user to load a file.
- IModelAction: An action that receives the model name.
- IModuleContextAction: Gets a ModuleContext in order to access the session objects of other modules.
- INavigationAction: Extends from IChangeControllersAction and ICustomViewAction.
- IOnChangePropertyAction: This interface must be implemented by the actions that react to the value change event in the user interface.
- IProcessLoadedFileAction: Processes a list of files uploaded from client to server.
- IRemoteAction: Useful when you use EJB2. Well used it can be a good substitute for a SessionBean.
- IRequestAction: Receives a servlet request. This type of actions links your application to the Servlet/JSP technology, hence it is better avoiding it. But sometimes a little bit of flexibility is needed.
- IShowActionAction, IShowActionsAction: Allows to show an action or a group of actions previously hidden in an IHideAction(s)Action (new in v2.0).
If you wish to learn more about actions the best thing you can do is to have a look at the JavaDoc API of the package
org.openxava.actions and to try out the examples of
OpenXavaTest project.