The model layer in an object
oriented application contains the business logic, that is the structure of
the data and all calculations, validations and processes associated to
this data.
OpenXava is a model oriented framework where the model is the most
important, and the rest (e.g. user interface) depends on it.
The way to define the model in OpenXava is using XML and a few of Java.
OpenXava generates a complete Java implementation of your model from your
definition.
Java
Implementation
Currently OpenXava generates code for the next 4 alternatives:
- Plain Java Classes (the so-called POJOs) for the model using
Hibernate for persistence.
- Plain Java Classes for the model using EJB3 JPA (Java Persistence
API) for persistence (new in v2.1).
- Classic EJB2 EntityBeans for the model and persistence.
- POJOs + Hibernate inside an EJB2 container.
The option 2 is the default one
(new in v3.0) and the best for
the most cases. The option 1 is also good, specially if you need to use
Java 1.4. The option 3 is for supporting all the OpenXava applications
written using EJB2 (EJB2 was the only option in OpenXava 1.x). The option
4 can be useful in some circumstances. You can see how to configure this
in
OpenXavaTest/properties/xava.properties.
Business
Component
As you have seen the basic unit to create an OpenXava application is the
business component. A business component is defined using a XML file. The
structure of a business component in OpenXava is:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE component SYSTEM "dtds/component.dtd">
<component name="ComponentName">
<!-- Model -->
<entity>...</entity>
<aggregate name=”...”>...</aggregate>
<aggregate name=”...”>...</aggregate>
...
<!-- View -->
<view>...</view>
<view name="...">...</view>
<view name="...">...</view>
...
<!-- Tabular data -->
<tab>...</tab>
<tab name=”...”>...</tab>
<tab name=”...”>...</tab>
...
<!-- Object relational mapping -->
<entity-mapping table="...">...</entity-mapping>
<aggregate-mapping aggregate=”...” table="...">...</aggregate-mapping>
<aggregate-mapping aggregate=”...” table="...">...</aggregate-mapping>
...
</component>
The first part of the component, the part of entity and aggregates, is
used to define the model. In this chapter you will learn the complete
syntax of this part.
Entity
and aggregates
The definition for entity and aggregate are practically identical. The
entity is the main object that represents the business concept, while
aggregates are additional object needed to define the business concept but
cannot have its own life. For example, when you define an
Invoice
component, the heading data of invoice are in entity, while for invoice
lines you can create an aggregate called
InvoiceDetail; the life
cycle of an invoice line is attached to the invoice, that is an invoice
line without invoice has no meaning, and sharing an invoice line by
various invoices is not possible, hence you will model
InvoiceDetail
as aggregate.
Formally, the relationship between A and B is aggregation, and B can be
modeled as an aggregate when:
- You can say that A has an B.
- If A is deleted then its B is deleted too.
- B is not shared.
Sometimes the same concept can be modeled as aggregate or as entity in
another component. For example, the address concept. If the address is
shared by various persons then you must use a reference to entity, while
if each person has his own address maybe an aggregate is a good option.
Entity
The syntax of entity is:
<entity>
<bean ... /> <!-- 1 -->
<ejb ... /> <!-- 2 -->
<implements .../> ... <!-- 3 -->
<property .../> ... <!-- 4 -->
<reference .../> ... <!-- 5 -->
<collection .../> ... <!-- 6 -->
<method .../> ... <!-- 7 -->
<finder .../> ... <!-- 8 -->
<postcreate-calculator .../> ... <!-- 9 -->
<postload-calculator .../> ... <!-- 10 -->
<postmodify-calculator .../> ... <!-- 11 -->
<preremove-calculator .../> ... <!-- 12 -->
<validator .../> ... <!-- 13 -->
<remove-validator .../> ... <!-- 14 -->
</entity>
- bean (one, optional): Allows you to use an already
existing JavaBean (a simple Java class, the so-called POJO). This
applies if you use JPA o Hibernate as persistence engine. In this case
the code generation for POJO and Hibernate mapping of this component
will not be produced.
- ejb (one, optional): Allows you to use an already
existing EJB. This only applies if you use EJB CMP2 as persistence
engine. In this case code generation for EJB code of this component
will not be produced. It does not apply to EJB3.
- implements (several, optional): The generated code
will implement this interface.
- property (several, optional): The properties
represent Java properties (with its setters an getters) in the
generated code.
- reference (several, optional): References to other
models, you can reference to the entity of another component or an
aggregate of itself.
- collection (several, optional): Collection of
references. In the generated code it is a property that returns a java.util.Collection.
- method (several, optional): Creates a method in the
generated code, in this case the method logic is in a calculator (ICalculator).
- finder (several, optional): Used to generate finder
methods. Finder methods are static method located in the POJO class.
In the case of EJB2 generation EJB2 finders are generated.
- postcreate-calculator (several, optional): Logic to
execute after making an object persistent. In Hibernate in a PreInsertEvent,
in EJB2 in the ejbPostCreate method.
- postload-calculator (several, optional): Logic to
execute just after load the state of an object from persistent
storage. In Hibernate in a PostLoadEvent, in EJB2 in the ejbLoad
method.
- postmodify-calculator (several, optional): Logic to
execute after modifying a persistent object and before storing its
state in persistent storage. In Hibernate in a PreUpdateEvent,
in EJB2 in the ejbStore method.
- preremove-calculator (several, optional): Logic to
execute just before removing a persistent from persistent storage. In
Hibernate in PreDeleteEvent, in EJB2 in the ejbRemove
method.
- validator (several, optional): Executes a
validation at model level. This validator can receive the value of
various model properties. To validate a single property it is better
to use a property level validator.
- remove-validator (several, optional): It's executed
before removal, and can deny the object removing.
Bean
With
<bean/> you can specify that you want to use your own
Java class.
For example:
<entity>
<bean class="org.openxava.test.model.Family"/>
...
In this simple way you can write your own Java code instead of using
OpenXava to generate it.
For our example you can write a
Family class as follows:
package org.openxava.test.model;
import java.io.*;
/**
* @author Javier Paniza
*/
public class Family implements Serializable {
private String oid;
private int number;
private String description;
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
If you want a reference from the OpenXava generated code to your own
handwritten code, then your Java class has to implement an interface (
IFamily
in this case) that extends
IModel (see org.openxava.test.Family
in OpenXavaTest/src).
Additionally you have to define the mapping using Hibernate:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
SYSTEM "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="org.openxava.test.model">
<class
name="Family"
table="XAVATEST@separator@FAMILY">
<id name="oid" column="OID" access="field">
<generator class="uuid"/>
</id>
<property name="number" column="NUMBER"/>
<property name="description" column="DESCRIPTION"/>
</class>
</hibernate-mapping>
You can put this file in the hibernate folder of your project. Moreover in
this folder you have the
hibernate.cfg.xml file that you have to
edit in this way:
...
<session-factory>
...
<mapping resource="Family.hbm.xml"/>
...
</session-factory>
...
In this easy way you can wrap your existing Java and Hibernate code with
OpenXava. Of course, if you are creating a new system it is much better to
rely on the OpenXava code generation.
EJB (2)
With
<ejb/> you can specify that you want to use your own
EJB (1.1 and 2.x version).
For example:
<entity>
<ejb remote="org.openxava.test.ejb.Family"
home="org.openxava.test.ejb.FamilyHome"
primaryKey="org.openxava.test.ejb.FamilyKey"
jndi="ejb/openxava.test/Family"/>
...
In this simple way you can write you own EJB code instead of using code
that OpenXava generates.
You can write the EJB code from scratch (only for genuine men), if you are
a normal programmer (hence lazy) probably you prefer to use wizards, or
better yet XDoclet. If you choose to use XDoclet, then you can put your
own XDoclet classes in the package model (or another package of your
choice. This depends on the value of the model.package variable in
build.xml)
in src folder of your project.; and your XDoclet code will be generated
with the rest of OpenXava code.
For our example you can write a
FamilyBean class in this way:
package org.openxava.test.ejb.xejb;
import java.util.*;
import javax.ejb.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @ejb:bean name="Family" type="CMP" view-type="remote"
* jndi-name="OpenXavaTest/ejb/openxava.test/Family"
* @ejb:interface extends="org.openxava.ejbx.EJBReplicable"
* @ejb:data-object extends="java.lang.Object"
* @ejb:home extends="javax.ejb.EJBHome"
* @ejb:pk extends="java.lang.Object"
*
* @jboss:table-name "XAVATEST@separator@FAMILY"
*
* @author Javier Paniza
*/
abstract public class FamilyBean
extends org.openxava.ejbx.EJBReplicableBase // 1
implements javax.ejb.EntityBean {
private UUIDCalculator oidCalculator = new UUIDCalculator();
/**
* @ejb:interface-method
* @ejb:pk-field
* @ejb:persistent-field
*
* @jboss:column-name "OID"
*/
public abstract String getOid();
public abstract void setOid(String nuevoOid);
/**
* @ejb:interface-method
* @ejb:persistent-field
*
* @jboss:column-name "NUMBER"
*/
public abstract int getNumber();
/**
* @ejb:interface-method
*/
public abstract void setNumber(int newNumber);
/**
* @ejb:interface-method
* @ejb:persistent-field
*
* @jboss:column-name "DESCRIPTION"
*/
public abstract String getDescription();
/**
* @ejb:interface-method
*/
public abstract void setDescription(String newDescription);
/**
* @ejb:create-method
*/
public FamilyKey ejbCreate(Map properties) // 2
throws
javax.ejb.CreateException,
org.openxava.validators.ValidationException,
java.rmi.RemoteException {
executeSets(properties);
try {
setOid((String)oidCalculator.calculate());
}
catch (Exception ex) {
ex.printStackTrace();
throw new EJBException(
"Impossible to create Family because:\n" +
ex.getLocalizedMessage()
);
}
return null;
}
public void ejbPostCreate(Map properties) throws javax.ejb.CreateException {
}
}
On writing your own EJB you must fulfill two little restrictions:
- The class must extend from org.openxava.ejbx.EJBReplicableBase
- It is required at least a ejbCreate (with its ejbPostCreate)
that receives as argument a map and assign its values to the bean, as
in the example.
Yes, yes, a little intrusive, but are not the EJB the intrusion
culmination?
Implements
(3)
With
<implements/> you specify a Java interface that will
be implemented by the generated code. Let's see it:
<entity>
<implements interface="org.openxava.test.model.IWithName"/>
...
<property name="name" type="String" required="true"/>
...
And you can write your Java interface in this way:
package org.openxava.test.model;
import java.rmi.*;
/**
* @author Javier Paniza
*/
public interface IWithName {
String getName() throws RemoteException;
}
Beware to make that generated code implements your interface. In this case
you have a property named name that generates a method called
getName()
that implements the interface.
In your generated code you can find an
ICustomer interface:
public interface ICustomer extends org.openxava.test.model.IWithName {
...
}
In the POJO generated code you can see:
public class Customer implements Serializable, org.openxava.test.model.ICustomer {
...
}
In the EJB generated code (if you generate it) you can see the remote
interface:
public interface CustomerRemote extends
org.openxava.ejbx.EJBReplicable,
org.openxava.test.model.ICustomer
and the EJB bean class is affected too
abstract public class CustomerBean extends EJBReplicableBase
implements
org.openxava.test.model.ICustomer,
EntityBean
This pithy feature makes the polymorphism a privileged guest of OpenXava.
As you can see OpenXava generates an interface for each component. It's
good that in your code you use these interfaces instead of POJO classes or
EJB2 remote interfaces. All code made in this way can be used with POJO
and EJB2 version on same time, or allows you to migrate from a EJB2 to a
POJO version with little effort. Although, if you are using POJOs
exclusively you may use the POJOs classes directly and ignore the
interfaces, as you wish.
Property
(4)
An OpenXava property corresponds exactly to a Java property. It represents
the state of an object that can be read and in some cases updated. The
object does not have the obligation to store physically the property data,
it only must return it when required.
The syntax to define a property is:
<property
name="propertyName" <!-- 1 -->
label="label" <!-- 2 -->
type="type" <!-- 3 -->
stereotype="STEREOTYPE" <!-- 4 -->
size="size" <!-- 5 -->
scale="scale" <!-- 6 new in v2.0.4 -->
required="true|false <!-- 7 -->
key="true|false" <!-- 8 -->
hidden="true|false" <!-- 9 -->
search-key="true|false" <!-- 10 new in v2.2.4 -->
version="true|false" <!-- 11 new in v2.2.3 -->
>
<valid-values .../> <!-- 12 -->
<calculator .../> <!-- 13 -->
<default-value-calculator .../> <!-- 14 -->
<calculation .../> <!-- 15 new in v5.7 -->
<validator .../> .... <!-- 16 -->
</property>
- name (required): The property name in Java,
therefore it must follow the Java convention for property names, like
starting with lower-case. Using underline (_) is not advisable.
- label (optional): Label showed to the final user.
Is much better use the i18n files.
- type (optional): It matches with a Java type. All
types valid for a Java property are valid here, this include classes
defined by you. You only need to provide a converter to allow saving
in database and a editor to render as HTML; thus that things like java.sql.Connection
or so can be a little complicated to manage as a property, but not
impossible. It's optional, but only if you have specified <bean/>
or <ejb/> or this property has a stereotype with a
associated type.
- stereotype (optional): Allows to specify an special
behavior for some properties.
- size (optional): Length in characters of property.
Useful to generate user interfaces. If you do not specify the size,
then a default value is assumed. This default value is associated to
the stereotype or type and is obtained from default-size.xml.
- scale (optional): (new in v2.0.4) Scale
(size of decimal part) of property. Only applies to numeric
properties. If you do not specify the scale, then a default value is
assumed. This default value is associated to the stereotype or type
and is obtained from default-size.xml.
- required (optional): Indicates if this property is
required. By default this is true for key properties hidden (new
in v2.1.3) or without default value calculator on create and
false in all other cases. On saving OpenXava verifies if the required
properties are present. If this is not the case, then saving is not
done and a validation error list is returned. The logic to determine
if a property is present or not can be configured by creating a file
called validators.xml in your project. You can see the syntax in OpenXava/xava/validators.xml.
- key (optional): Indicates that this property is
part of the key. At least one property (or reference) must be key. The
combination of key properties (and key references) must be mapped to a
group of database columns that do not have duplicate values, typically
the primary key.
- hidden (optional): A hidden property has a meaning
for the developer but not for the user. The hidden properties are
excluded when the automatic user interface is generated. However at
Java code level they are present and fully functional. Even if you put
it explicitly into a view the property will be shown in the user
interface.
- search-key (optional): (new in v2.2.4)
The search key properties are used by the user as key for searching
objects. They are editable in user interface of references allowing to
the user type its value for searching. OpenXava uses the key (key=”true”)
properties for searching by default, and if the key (key=”true”)
properties are hidden then it uses the first property in the view.
With search-key you can choose explicitly the properties for
searching.
- version (optional): (new in v2.2.3) A
version property is used for optimistic concurrency control. If you
want control concurrency you only need to have a property marked as version=”true”
in your component. Only a single version property should be used per
component. The following types are supported for version properties: int,
Integer, short, Short, long, Long, Timestamp. The version
properties are considered hidden.
- valid-values (one, optional): To indicate that this
property only can have a limited set of valid values.
- calculator (one, optional): Implements the logic
for a calculated property. A calculated property only has getter and
is not stored in database.
- default-value-calculator (one, optional):
Implements the logic to calculate the default (initial) value for this
property. A property with default-value-calculator has
setter and it is persistent.
- calculation (one, optional): (new in v5.7)
Arithmetic expression to do the calculation for the property. The
calculation is done in the user interface when any operand changes.
- validator (several, optional): Implements the
validation logic to execute on this property before modifying or
creating the object that contains it.
Stereotype
A stereotype is the way to determine a specific behavior of a type. For
example, a name, a comment, a description, etc. all correspond to the Java
type
java.lang.String but you surely wish validators, default
sizes, visual editors, etc. different in each case and you need to tune
finer; you can do this assigning a stereotype to each case. That is, you
can have the next sterotypes NAME, MEMO or DESCRIPTION and assign them to
your properties.
OpenXava comes with these generic stereotypes:
- DINERO, MONEY
- FOTO, PHOTO, IMAGEN, IMAGE
- TEXTO_GRANDE, MEMO, TEXT_AREA
- ETIQUETA, LABEL
- ETIQUETA_NEGRITA, BOLD_LABEL
- HORA, TIME
- FECHAHORA, DATETIME
- GALERIA_IMAGENES, IMAGES_GALLERY new in v2.0
- RELLENADO_CON_CEROS, ZEROS_FILLED new in v2.0.2
- TEXTO_HTML, HTML_TEXT (text with editable format) new in v2.0.3
- IMAGE_LABEL, ETIQUETA_IMAGEN (image depending on property content) new
in v2.1.5
- EMAIL new in 2.2.3
- TELEFONO, TELEPHONE new in 2.2.3
- WEBURL new in 2.2.3
- IP new in 2.2.4
- ISBN new in 2.2.4
- TARJETA_CREDITO, CREDIT_CARD new in 2.2.4
- LISTA_EMAIL, EMAIL_LIST new in 2.2.4
Now you will learn how to define your own stereotype. You will create one
called PERSON_NAME to represent names of persons.
Edit (or create) the file editors.xml in your folder xava. And add:
<editor url="personNameEditor.jsp">
<for-stereotype stereotype="PERSON_NAME"/>
</editor>
This way you define the editor to render for editing and displaying
properties of stereotype PERSON_NAME.
Also you can edit
stereotype-type-default.xml and the line:
<for stereotype="PERSON_NAME" type="String"/>
Furthermore it is useful to indicate the default size; you can do this by
editing
default-size.xml of your project:
<for-stereotype name="PERSON_NAME" size="40"/>
Thus, if you do not put the size in a property of type PERSON_NAME a value
of 40 is assumed.
Not so common is changing the validator for required, but if you wish to
change it you can do it adding to
validators.xml of your project
the next definition:
<required-validator>
<validator-class class="org.openxava.validators.NotBlankCharacterValidator"/>
<for-stereotype stereotype="PERSON_NAME"/>
</required-validator>
Now everything is ready to define properties of stereotype PERSON_NAME:
<property name="name" stereotype="PERSON_NAME" required="true"/>
In this case a value of 40 is assumed as size,
String as type
and the
NotBlankCharacterValidator validator is executed to
verify if it is required.
IMAGES_GALLERY
stereotype (new in v2.0)
If you want that a property of your component hold a gallery of images.
You only have to declare your property with the IMAGES_GALLERY stereotype,
in this way:
<property name="photos" stereotype="IMAGES_GALLERY"/>
Furthermore, in the mapping part you have to map your property to a table
column suitable to store a
String with a length of 32 characters
(VARCHAR(32)).
And everything is done.
In order to support this stereotype you need to setup the system
appropriately for your application.
First, create a table in your database to store the images:
CREATE TABLE IMAGES (
ID VARCHAR(32) NOT NULL PRIMARY KEY,
GALLERY VARCHAR(32) NOT NULL,
IMAGE BLOB);
CREATE INDEX IMAGES01
ON IMAGES (GALLERY);
The type of IMAGE column can be a more suitable one for your database to
store
byte [] (for example LONGVARBINARY) .
The name of the table is arbitrary. You need to specify the table name and
schema
(new in v2.2.4) name in your configuration file (a
.properties file in the root of your OpenXava project). In this way:
images.schema=MYSCHEMA
images.table=IMAGES
If you want to work without schema
(new in v3.0.2) you need to
put the property
image.schema.definition of your configuration
to empty string, as following:
images.schema.definition=
And finally you need to define the mapping in your
hibernate/hibernate.cfg.xml
file, thus:
<hibernate-configuration>
<session-factory>
...
<mapping resource="GalleryImage.hbm.xml"/>
...
</session-factory>
</hibernate-configuration>
After this you can use the IMAGES_GALLERY stereotype in all components of
your application.
Concurrency
and version property (new in v2.2.3)
Concurrency is the ability of the application to allow several users to
save data at same time without losing data. OpenXava uses an optimistic
concurrency schema. Using optimistic concurrency the records are not
locked allowing high concurrency without losing data integrity.
For example, if a user A read a record and then a user B read the same
record, modify it and save the changes, when the user A try to save the
record he receives an error, then he need to refresh the data and retry
his modification.
For activating concurrency support for an OpenXava component you only need
to declare a property using
version=”true”, in this way:
<property name="version" type="int" version="true"/>
This property is for use of persistence engine (Hibernate or JPA), your
application or your user must not use this property directly.
Valid
values
The element
<valid-values/> allows you to define a
property that can hold one of the indicated values only. Something like a
C (or Java 5) enum.
It's easy to use, let's see this example:
<property name="distance">
<valid-values>
<valid-value value="local"/>
<valid-value value="national"/>
<valid-value value="international"/>
</valid-values>
</property>
The distance property only can take the following values:
local,
national or
international, and as you have not put
required=”true”
the blank value is allowed too. The type is not necessary,
int
is assumed.
At user interface level the current implementation uses a combo. The label
for each value is obtained from the
i18n files.
At Java generated code level creates a distance property of type int that
can take the values 0 (no value), 1 (local), 2 (national) o 3
(international).
At database level the value is by default saved as an integer, but you can
configure easily to use another type and work with no problem with legate
databases. See more at
object/relational
mapping chapter.
Calculator
A calculator implements the logic to execute when the getter method of a
calculated property is called. The calculated properties are read only
(only have getter) and not persistent (they do not match with any column
of database table).
A calculated property is defined in this way:
<property name="unitPriceInPesetas" type="java.math.BigDecimal" size="18">
<calculator class="org.openxava.test.calculators.EurosToPesetasCalculator">
<set property="euros" from="unitPrice"/>
</calculator>
</property>
Now when you (or OpenXava to fill the user interface) call to
getUnitPriceInPesetas()
the system executes
EurosToPesetasCalculator calculator, but
before this it sets the value of the property euros of
EurosToPesetasCalculator
with the value obtained from
unitPrice of the current object.
Seeing the calculator code may be instructive:
package org.openxava.test.calculators;
import java.math.*;
import org.openxava.calculators.*;
/**
* @author Javier Paniza
*/
public class EurosToPesetasCalculator implements ICalculator { // 1
private BigDecimal euros;
public Object calculate() throws Exception { // 2
if (euros == null) return null;
return euros.multiply(new BigDecimal("166.386")).
setScale(0, BigDecimal.ROUND_HALF_UP);
}
public BigDecimal getEuros() {
return euros;
}
public void setEuros(BigDecimal euros) {
this.euros = euros;
}
}
You can notice two things, first (1) a calculator must implement
org.openxava.calculators.ICalculator, and (2)
the method
calculate() executes the logic to generate the value
returned by the property.
According to the above definitions now you can use the generated code in
this way:
Product product = ...
product.setUnitPrice(2);
BigDecimal result = product.getUnitPriceInPesetas();
And result will hold 332.772.
You can define a calculator without set from to define values for
properties, as shown below:
<property name="detailsCount" type="int" size="3">
<calculator class="org.openxava.test.calculators.DetailsCountCalculator">
<set property="year"/>
<set property="number"/>
</calculator>
</property>
In this case the property year and number of
DetailsCountCalculator
calculator are filled from properties of same name from the current
object.
The from attribute supports qualified properties, as following:
<aggregate name="Address">
<property name="street" type="String" size="30" required="true"/>
<property name="zipCode" type="int" size="5" required="true"/>
<property name="city" type="String" size="20" required="true"/>
<reference name="state" required="true"/>
<property name="asString" type="String">
<calculator class="org.openxava.calculators.ConcatCalculator">
<set property="string1" from="street"/>
<set property="int2" from="zipCode"/>
<set property="string3" from="city"/>
<set property="string4" from="state.name"/> <!-- 1 -->
<set property="int5" from="customer.number"/> <!-- 2 -->
</calculator>
</property>
</aggregate>
The property
string4 (1) of the calculator is filled using the
value of name of the state (that is a reference), this a qualified
property (
reference.property). In the case of
int5 (2)
you can see that customer reference is not declared in
Address,
because it is referenced from the
Customer entity, therefore
Address has an implicit reference to its container model (its parent) that
you can use in from attribute
(new in v2.0.4). That is, the
int5
property is filled with the number of the customer which has this address.
Also it's possible to assign a constant value to a calculator property:
<property name="fullName" type="String">
<calculator class="org.openxava.calculators.ConcatCalculator">
<set property="string1" from="id"/>
<set property="separator" value=" - "/>
<set property="string2" from="name"/>
</calculator>
</property>
In this case the property separator of
ConcatCalculator has a
constant value.
Another interesting feature of calculator is that you can access from it
to the model object (entity or aggregate) that contains the property that
is being calculated:
<property name="amountsSum" stereotype="MONEY">
<calculator class="org.openxava.test.calculators.AmountsSumCalculator"/>
</property>
And the calculator:
package org.openxava.test.calculators;
import java.math.*;
import java.rmi.*;
import java.util.*;
import javax.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class AmountsSumCalculator implements IModelCalculator { // 1
private IInvoice invoice;
public Object calculate() throws Exception {
Iterator itDetails = invoice.getDetails().iterator();
BigDecimal result = new BigDecimal(0);
while (itDetails.hasNext()) {
IInvoiceDetail detail = (IInvoiceDetail) itDetails.next();
result = result.add(detail.getAmount());
}
return result;
}
public void setModel(Object model) throws RemoteException { // 2
invoice = (IInvoice) model;
}
}
This calculator implements
IModelCalculator (1)
(new in v2.0)
and to do this it has a method
setModel (2), this method is
called before calling the
calculate() method and thus allows
access to the model object (in this case an invoice) that contains the
property inside
calculate().
Within the code generated by OpenXava you can find an interface for each
business concept that is implemented by the POJO class, the EJB2 remote
interface and the EJB2 Bean class. That is for
Invoice you have
a
IInvoice interface implemented by Invoice (POJO class),
InvoiceRemote
(EJB2 remote interface) and
InvoiceBean (EJB2 bean class), this
last two only if you generate EJB2 code. In the calculator of type
IModelCalculator it is advisable to cast to
this interface, because in this cases the same calculator works with
POJOs, EJB2 remote interface and EJB2 bean class. If you are developing
with a POJO only version (maybe the normal case) you can cast directly to
the POJO class, in this case Invoice.
This calculator type is less reusable than that which receives simple
properties, but sometimes are useful. Why is it less reusable? For
example, if you use
IInvoice to calculate a discount, this
calculator only could be applied to invoices, but if you uses a calculator
that receives amount and
discountPercentage as simple properties
this last calculator could be applied to invoices, deliveries, orders,
etc.
From a calculator you have direct access to JDBC connections, here is an
example:
<property name="detailsCount" type="int" size="3">
<calculator class="org.openxava.test.calculators.DetailsCountCalculator">
<set property="year"/>
<set property="number"/>
</calculator>
</property>
And the calculator class:
package org.openxava.test.calculators;
import java.sql.*;
import org.openxava.calculators.*;
import org.openxava.util.*;
/**
* @author Javier Paniza
*/
public class DetailsCountCalculator implements IJDBCCalculator { // 1
private IConnectionProvider provider;
private int year;
private int number;
public void setConnectionProvider(IConnectionProvider provider) { // 2
this.provider = provider;
}
public Object calculate() throws Exception {
Connection con = provider.getConnection();
try {
PreparedStatement ps = con.prepareStatement(
"select count(*) from XAVATEST_INVOICEDETAIL “ +
“where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
ps.setInt(1, getYear());
ps.setInt(2, getNumber());
ResultSet rs = ps.executeQuery();
rs.next();
Integer result = new Integer(rs.getInt(1));
ps.close();
return result;
}
finally {
con.close();
}
}
public int getYear() {
return year;
}
public int getNumber() {
return number;
}
public void setYear(int year) {
this.year = year;
}
public void setNumber(int number) {
this.number = number;
}
}
To use JDBC your calculator must implement
IJDBCCalculator (1) and then it will receive a
IConnectionProvider (2) that you can use
within
calculate(). Yes, the JDBC code is ugly and awkward, but
sometime it can help to solve performance problems.
The calculators allow you to insert your custom logic in a system where
all code is generated; and as you see it promotes the creation of reusable
code because the calculators nature (simple and configurable) allows you
to use them time after time to define calculated properties and methods.
This philosophy, simple and configurable classes that can be plugged in
several places is the cornerstone that sustains all OpenXava framework.
OpenXava comes with a set of predefined calculators, you can find them in
org.openxava.calculators.
Default
value calculator
With
<default-value-calculator/> you can associate logic
to a property, but in this case the property is readable, writable and
persistent. This calculator is for calculating its initial value. For
example:
<property name="year" type="int" key="true" size="4" required="true">
<default-value-calculator
class="org.openxava.calculators.CurrentYearCalculator"/>
</property>
In this case when the user tries to create a new Invoice (for example) he
will find that the year field already has a value, that he can change if
he wants to do.
You can indicate that the value will be calculated just before creating
(inserting into database) an object for the first time; this is done this
way:
<property name="oid" type="String" key="true" hidden="true">
<default-value-calculator
class="org.openxava.calculators.UUIDCalculator"
on-create="true"/>
</property>
If you use
on-create=”true” then you will obtain that effect.
A typical use of the
on-create=”true” is for generating
identifiers automatically. In the above example, an unique identifier of
type String and 32 characters is generated. Also you can use other
generation techniques, for example, a database sequence can be defined in
this way:
<property name="id" key="true" type="int" hidden="true">
<default-value-calculator
class="org.openxava.calculators.SequenceCalculator" on-create="true">
<set property="sequence" value="XAVATEST_SIZE_ID_SEQ"/>
</default-value-calculator>
</property>
Or maybe you want to use an identity (auto increment) column as key:
<property name="id" key="true" type="int" hidden="true">
<default-value-calculator
class="org.openxava.calculators.IdentityCalculator" on-create="true"/>
</property>
SequenceCalculator (new in v2.0.1)
and
IdentityCalculator (new in v2.0.2) do not work
with EJB2. They work with Hibernate and EJB3.
If you define a hidden key property with no default calculator with
on-create=”true”
then it uses identity, sequence or hilo techniques automatically depending
upon the capabilities of the underlying database. As this:
<property name="oid" type="int" hidden="true" key="true"/>
Also, this only works with Hibernate and EJB3, not in EJB2.
All others issues about
<default-value-calculator/> are as
in
<calculator/>.
Validator
The validator execute validation logic on the value assigned to the
property just before storing. A property may have several validators.
<property name="description" type="String" size="40" required="true">
<validator class="org.openxava.test.validators.ExcludeStringValidator">
<set property="string" value="MOTO"/>
</validator>
<validator class="org.openxava.test.validators.ExcludeStringValidator"
only-on-create="true">
<set property="string" value="COCHE"/>
</validator>
</property>
The technique to configure the validator (with
<set/>) is
exactly the same than in calculators. With the attribute
only-on-create="true"
you can define that the validation will be executed only when the object
is created, and not when it is modified.
The validator code is:
package org.openxava.test.validators;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class ExcludeStringValidator implements IPropertyValidator { // 1
private String string;
public void validate(
Messages errors, // 2
Object value, // 3
String objectName, // 4
String propertyName) // 5
throws Exception {
if (value==null) return;
if (value.toString().indexOf(getString()) >= 0) {
errors.add("exclude_string", propertyName, objectName, getString());
}
}
public String getString() {
return string==null?"":string;
}
public void setString(String string) {
this.string = string;
}
}
A validator has to implement
IPropertyValidator (1), this obliges to the
calculator to have a
validate() method where the validation of
property is executed. The arguments of
validate() method are:
- (2) Messages errors: A object of type Messages
that represents a set of messages (like a smart collection) and where
you can add the validation errors that you find.
- (3) Object value: The value to validate.
- (4) String objectName: Object name of the
container of the property to validate. Useful to use in error
messages.
- (5) String propertyName: Name of the property to
validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with
errors.add())
by sending a message identifier and the arguments. If you want to obtain a
significant message you need to add to your i18n file the next entry:
exclude_string={0} cannot contain {2} in {1}
If the identifier sent is not found in the resource file, this identifier
is shown as is; but the recommended way is always to use identifiers of
resource files.
The validation is successful if no messages are added and fails if
messages are added. OpenXava collects all messages of all validators
before saving and if there are messages, then it display them and does not
save the object.
The package
org.openxava.validators contains some common
validators.
Default
validator (new in v2.0.3)
You can define a default validator for properties depending of its type or
stereotype. In order to do it you have to use the file
xava/validators.xml
of your project to define in it the default validators.
For example, you can define in your
xava/validators.xml the
following:
<validators>
<default-validator>
<validator-class
class="org.openxava.test.validators.PersonNameValidator"/>
<for-stereotype stereotype="PERSON_NAME"/>
</default-validator>
</validators>
In this case you are associating the validator
PersonNameValidator
to the stereotype PERSON_NAME. Now if you define a property as the next
one:
<property name="name" stereotype="PERSON_NAME" required="true"/>
This property will be validated using
PersonNameValidator
although the property itself does not define any validator.
PersonNameValidator
is applied to all properties with PERSON_NAME stereotype.
You can also assign a default validator to a type.
In
validators.xml files you can also define the validators for
determine if a required value is present (executed when you use
required="true").
Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining
OpenXava/xava/validators.xml
and
OpenXavaTest/xava/validators.xml.
Calculation
(new in v5.7)
With
<calculation/> you can define an arithmetic
expression to do the calculation for the property. The expression can
contain +, -, *, (), numeric values and properties names of the same
entity. For example:
<property name="total" type="BigDecimal">
<calculation>((hours * worker.hourPrice) + tripCost - discount) * (1 + vatPercentage / 100)</calculation>
</property>
Note as
worker.hourPrice is used to get the value from the
reference.
The calculation is executed and displayed when the user changes any value
of the properties used in the expression in the user interface, however
the value is not saved until the user clicks on save button.
Reference
(5)
A reference allows access from an entity or an aggregate to another entity
or aggregate. A reference is translated to Java code as a property (with
its getter and its setter) whose type is the referenced model Java type.
For example a Customer can have a reference to his Seller, and that allows
you to write code like this:
ICustomer customer = ...
customer.getSeller().getName();
to access to the name of the seller of that customer.
The syntax of reference is:
<reference
name="name" <!-- 1 -->
label="label" <!-- 2 -->
model="model" <!-- 3 -->
required="true|false" <!-- 4 -->
key="true|false" <!-- 5 -->
search-key="true|false" <!-- 6 New in v3.0.2 -->
role="role" <!-- 7 -->
>
<default-value-calculator .../> <!-- 8 -->
</reference>
- name (optional, required if model is not
specified): The name of reference in Java, hence must follow the rules
to name members in Java, including start by lower-case. If you do not
specify name the model name with the first letter in lower-case is
assumed. Using underline (_) is not advisable.
- label (optional): Label shown to the final user.
It's much better use i18n.
- model (optional, required if name is not
specified): The model name to reference. It can be the name of another
component, in which case it is a reference to entity, or the name of a
aggregate of the current component. If you do not specify model the
reference name with the first letter in upper-case is assumed.
- required (optional): Indicates if the reference is
required. When saving OpenXava verifies if the required references are
present, if not the saving is aborted and a list of validation errors
is returned.
- key (optional): Indicates if the reference is part
of the key. The combination of key properties and reference properties
should map to a group of database columns with unique values,
typically the primary key.
- search-key (optional): (New in v3.0.2)
The search key references are used by the user as key for searching
objects. They are editable in user interface of references allowing to
the user type its value for searching. OpenXava uses the key (key=”true”)
members for searching by default, and if the key (key=”true”)
members are hidden then it uses the first property in the view. With search-key
you can choose explicitly references for searching.
- role (optional): Used only in references within
collections. See below.
- default-value-calculator (one, optional):
Implements the logic for calculating the initial value of the
reference. This calculator must return the key value, that can be a
simple value (only if the key of referenced object is simple) or key
object (a special object that wraps the key and is generated by
OpenXava).
A little example of references use:
<reference model="Address" required="true"/> <!-- 1 -->
<reference name="seller"/> <!-- 2 -->
<reference name="alternateSeller" model="Seller"/> <!-- 3 -->
- A reference to an aggregate called Address, the reference
name will be address.
- A reference to the entity of Seller component. The model
is deduced from name.
- A reference called alternateSeller to the entity of
component Seller.
If you assume that this is in a component named
Customer, you
could write:
ICustomer customer = ...
Address address = customer.getAddress();
ISeller seller = customer.getSeller();
ISeller alternateSeller = customer.getAlternateSeller();
Default
value calculator in references
In a reference
<defaut-value-calculator/> works like in a
property, only that it has to return the value of the reference key, and
on-create="true"
is not allowed.
For example, in the case of a reference with simple key, you can write:
<reference name="family" model="Family2" required="true">
<default-value-calculator class="org.openxava.calculators.IntegerCalculator">
<set property="value" value="2"/>
</default-value-calculator>
</reference>
The
calculate() method is:
public Object calculate() throws Exception {
return new Integer(value);
}
As you can see an integer is returned, that is, the default value for
family is 2.
In the case of composed key:
<reference name="warehouse" model="Warehouse">
<default-value-calculator
class="org.openxava.test.calculators.DefaultWarehouseCalculator"/>
</reference>
And the calculator code:
package org.openxava.test.calculators;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class DefaultWarehouseCalculator implements ICalculator {
public Object calculate() throws Exception {
Warehouse key = new Warehouse();
key.setNumber(4);
key.setZoneNumber(4);
return key; // This works with POJO and EJB2
// return new WarehouseKey(new Integer(4), 4); // This only work with EJB2
}
}
Returns an object of type
Warehouse, (or
WarehouseKey
if you use only EJB2).
Collection
(6)
With
<collection/> you define a collection of references
to entities or aggregates. This is translated to Java as a property of
type
java.util.Collection.
Here syntax for collection:
<collection
name="name" <!-- 1 -->
label="label" <!-- 2 -->
minimum="N" <!-- 3 -->
maximum="N" <!-- 4 new v2.0.3 -->
>
<reference ... /> <!-- 5 -->
<condition ... /> <!-- 6 -->
<order ... /> <!-- 7 -->
<calculator ... /> <!-- 8 -->
<postremove-calculator ... /> <!-- 9 -->
</collection>
- name (required): The collection name in Java,
therefore it must follow the rules for name members in Java, including
starting with lower-case. Using underline (_) is not advisable.
- label (optional): Label shown to final user. Is
much better to use i18n files.
- minimum (optional): Minimum number of expected
elements. This is validated just before saving.
- maximum (optional): (new v2.0.3) Maximum
number of expected elements.
- reference (required): With the syntax you can see
in the previous point.
- condition (optional): Restricts the elements that
appear in the collection.
- order (optional): The elements in collections will
be in the indicated order.
- calculator (optional): Allows you to define your
own logic to generate the collection. If you use this, then you cannot
use neither condition nor order.
- postremove-calculator (optional): Execute your
custom logic just after an element is removed from collection.
Let's have a look at some examples. First a simple one:
<collection name="deliveries">
<reference model="Delivery"/>
</collection>
If you have this within an Invoice, then you are defining a deliveries
collection associated to that Invoice. The details to make the
relationship are defined in the
object/relational
mapping.
Now you can write a code like this:
IInvoice invoice = ...
for (Iterator it = invoice.getDeliveries().iterator(); it.hasNext();) {
IDelivery delivery = (IDelivery) it.next();
delivery.doSomething();
}
To do something with all deliveries associated to an invoice.
Let's look at another example a little more complex, but still in Invoice:
<collection name="details" minimum="1"> <!-- 1 -->
<reference model="InvoiceDetail"/>
<order>${serviceType} desc</order> <!-- 2 -->
<postremove-calculator <!-- 3 -->
class="org.openxava.test.calculators.DetailPostremoveCalculator"/>
</collection>
In this case you have a collection of aggregates, the details (or lines)
of the invoice. The main difference between collection of entities and
collection of aggregates is when you remove the main entity; in the case
of a collection of aggregates its elements are deleted too. That is when
you delete an invoice its details are deleted too.
- The restriction minimum="1" requires at least one detail
for the invoice to be valid.
- With order you force that the details will be returned ordered by serviceType.
- With postremove-calculator you indicate the logic to
execute just after a invoice detail is removed.
Let's look at the calculator code:
package org.openxava.test.calculators;
import java.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class DetailPostremoveCalculator implements IModelCalculator {
private IInvoice invoice;
public Object calculate() throws Exception {
invoice.setComment(invoice.getComment() + "DETAIL DELETED");
return null;
}
public void setEntity(Object model) throws RemoteException {
this.invoice = (IInvoice) model;
}
}
As you see this is a conventional calculator as it is used in calculated
properties. A thing to consider is that the calculator is applied to the
container entity (in this case
Invoice) and not to the
collection element. That is, if your calculator implements
IModelCalculator then it receives an
Invoice
and not an
InvoiceDetail. This is consistent because it is
executed after the detail is removed and the detail doesn't exist any
more.
You have full freedom to define how the collection data is obtained, with
condition you can overwrite the default condition generated by OpenXava:
<!-- Others carriers of same warehouse -->
<collection name="fellowCarriers">
<reference model="Carrier"/>
<condition>
${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND
${warehouse.number} = ${this.warehouse.number} AND
NOT (${number} = ${this.number})
</condition>
</collection>
If you have this collection within
Carrier, you can obtain with
this collection all carriers of the same warehouse but not himself, that
is the list of his fellow workers. As you see you can use this in the
condition in order to reference the value of a property of current object.
If with this you have not enough, you can write the logic that returns the
collection. The previous example can be written in the following way too:
<!--
The same that 'fellowCarriers' but implemented with a calculator
-->
<collection name="fellowCarriersCalculated">
<reference model="Carrier"/>
<calculator class="org.openxava.test.calculators.FellowCarriersCalculator"/>
</collection>
And here the calculator code:
package org.openxava.test.calculators;
import java.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class FellowCarriersCalculator implements IModelCalculator {
private ICarrier carrier;
public Object calculate() throws Exception {
// Using Hibernate
int warehouseZoneNumber = carrier.getWarehouse().getZoneNumber();
int warehouseNumber = carrier.getWarehouse().getNumber();
Session session = XHibernate.getSession();
Query query = session.createQuery("from Carrier as o where " +
"o.warehouse.zoneNumber = :warehouseZone AND " +
"o.warehouse.number = :warehouseNumber AND " +
"NOT (o.number = :number)");
query.setInteger("warehouseZone", warehouseZoneNumber);
query.setInteger("warehouseNumber", warehouseNumber);
query.setInteger("number", carrier.getNumber());
return query.list();
/* Using EJB3 JPA
EntityManager manager = XPersistence.getManager();
Query query = manager.createQuery("from Carrier c where " +
"c.warehouse.zone = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZone());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());
return query.getResultList();
*/
/* Using EJB2
return CarrierUtil.getHome().findFellowCarriersOfCarrier(
carrier.getWarehouseKey().getZoneNumber(),
carrier.getWarehouseKey().get_Number(),
new Integer(carrier.getNumber())
);
*/
}
public void setModel(Object model) throws RemoteException {
carrier = (ICarrier) model;
}
}
As you see this is a conventional calculator. Obviously it must return a
java.util.Collection
whose elements are of type
ICarrier.
The references in collections are bidirectional, this means that if in a
Seller you have a customers collection, then in
Customer you
must have a reference to
Seller. But if in
Customer
you have more than one reference to
Seller (for example, seller
and
alternateSeller) OpenXava does not know which to choose, for
this case you have the attribute role of reference. You can use it in this
way:
<collection name="customers">
<reference model="Customer" role="seller"/>
</collection>
To indicate that the reference seller and not alternateSeller will be used
in this collection.
In the case of a collection of entity references you have to define the
reference at the other side, but in the case of a collection of aggregate
references this is not necessary, because in the aggregates a reference to
this container is automatically generated.
Method
(7)
With
<method/> you can define a method that will be
included in the generated code as a Java method.
The syntax for method is:
<method
name="name" <!-- 1 -->
type="type" <!-- 2 -->
arguments="arguments" <!-- 3 -->
exceptions="exceptions" <!-- 4 -->
>
<calculator ... /> <!-- 5 -->
</method>
- name (required): Name of the method in Java,
therefore it must follow the Java rules to name members, like
beginning with lower-case.
- type (optional, by default void): Is the Java type
that the method returns. All Java types valid as return type for a
Java method are applicable here.
- arguments (optional): Argument list of the method
in Java format.
- exceptions (optional): Exception list that can be
thrown by this method, in Java format.
- calculator (required): Implements the logic of the
method.
Defining a method is easy:
<method name="increasePrice">
<calculator class="org.openxava.test.calculators.IncreasePriceCalculator"/>
</method>
And the implementation depends on the logic that you want to program. In
this case:
package org.openxava.test.calculators;
import java.math.*;
import java.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.model.*;
/**
* @author Javier Paniza
*/
public class IncreasePriceCalculator implements IModelCalculator {
private IProduct product;
public Object calculate() throws Exception {
product.setUnitPrice( // 1
product.getUnitPrice().
multiply(new BigDecimal("1.02")).setScale(2));
return null; // 2
}
public void setModel(Object model) throws RemoteException {
this.product = (IProduct) model;
}
}
All applicable things for calculators in properties are applicable to
methods too, with the next clarifications:
- A calculator for a method has moral authority to change the state of
the object.
- If the return type of the method is void the calculator must return
null.
Now you can use the method in the expected way:
IProduct product = ...
product.setUnitPrice(new BigDecimal(“100”));
product.increasePrice();
BigDecimal newPrice = product.getUnitPrice();
And in
newPrice you have 102.
Another example, now a little bit more complex:
<method name="getPrice" type="BigDecimal"
arguments="String country, BigDecimal tariff"
exceptions="ProductException, PriceException">
<calculator class="org.openxava.test.calculators.ExportPriceCalculator">
<set property="euros" from="unitPrice"/>
</calculator>
</method>
In this case you can notice that in arguments and exceptions the Java
format is used, since what you put there is inserted directly into the
generated code.
The calculator:
package org.openxava.test.calculators;
import java.math.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class ExportPriceCalculator implements ICalculator {
private BigDecimal euros;
private String country;
private BigDecimal tariff;
public Object calculate() throws Exception {
if ("España".equals(country) || "Guatemala".equals(country)) {
return euros.add(tariff);
}
else {
throw new PriceException("Country not registered");
}
}
public BigDecimal getEuros() {
return euros;
}
public void setEuros(BigDecimal decimal) {
euros = decimal;
}
public BigDecimal getTariff() {
return tariff;
}
public void setTariff(BigDecimal decimal) {
tariff = decimal;
}
public String getCountry() {
return country;
}
public void setCountry(String string) {
country = string;
}
}
Each argument is assigned to a property of the name in the calculator;
that is, the value of the first argument, country, is assigned to country
property, and the value of the second one, tariff, to the tariff property.
Of course, you can configure values for others calculator properties with
<set/> as usual in calculators.
And to use the method:
IProduct product = ...
BigDecimal price = product.getPrice(“España”, new BigDecimal(“100)); // works
product.getPrice(“El Puig”, new BigDecimal(“100”)); // throws PriceException
Methods are the sauce of the objects, without them the object only would
be a silly wrapper of data. When possible it is better to put the business
logic in methods (model layer) instead of in actions (controller layer).
Finder
(8)
A finder is a special method that allows you to find an object or a
collection of objects that follow some criteria. In POJO version a finder
method is a generated static method in the POJO class. In the EJB2 version
a finder matches with a finder in home.
The syntax for finder is:
<finder
name="name" <!-- 1 -->
arguments="arguments" <!-- 2 -->
collection="(true|false)" <!-- 3 -->
>
<condition ... /> <!-- 4 -->
<order ... /> <!-- 5 -->
</finder>
- name (required): Name of the finder method in Java,
hence it must follow the Java rules for member naming, e.g. beginning
with lower-case.
- arguments (required): Argument list for the method
in Java format. It is (most) advisable to use simple data types.
- collection (optional, by default false): Indicates
if the result will be a single object or a collection.
- condition (optional): A condition with SQL/EJBQL
syntax where you can use the names of properties inside ${}.
- order (optional): An order with SQL/EJBQL syntax
where you can use the names of properties inside ${}.
Some examples:
<finder name="byNumber" arguments="int number">
<condition>${number} = {0}</condition>
</finder>
<finder name="byNameLike" arguments="String name" collection="true">
<condition>${name} like {0}</condition>
<order>${name} desc</order>
</finder>
<finder
name="byNameLikeAndRelationWithSeller"
arguments="String name, String relationWithSeller"
collection="true">
<condition>${name} like {0} and ${relationWithSeller} = {1}</condition>
<order>${name} desc</order>
</finder>
<finder name="normalOnes" arguments="" collection="true">
<condition>${type} = 1</condition>
</finder>
<finder name="steadyOnes" arguments="" collection="true">
<condition>${type} = 2</condition>
</finder>
<finder name="all" arguments="" collection="true"/>
This generates a set of finder methods available from POJO class and EJB2
home. This methods can be used this way:
// POJO, both JPA and Hibernate
ICustomer customer = Customer.findByNumber(8);
Collection javieres = Customer.findByNameLike(“%JAVI%”);
// EJB2
ICustomer customer = CustomerUtil.getHome().findByNumber(8);
Collection javieres = CustomerUtil.getHome().findByNameLike(“%JAVI%”);
Postcreate
calculator (9)
With
<postcreate-calculator/> you can plug in your own
logic to execute just after creating the object as persistent object.
Its syntax is:
<postcreate-calculator
class="class"> <!-- 1 -->
<set ... /> ... <!-- 2 -->
</postcreate-calculator>
- class (required): Calculator class. This calculator
must implement ICalculator or some of its children.
- set (several, optional): To set the value of the
calculator properties before executing it.
A simple example is:
<postcreate-calculator
class="org.openxava.test.calculators.DeliveryTypePostcreateCalculator">
<set property="suffix" value="CREATED"/>
</postcreate-calculator>
And now the calculator class:
package org.openxava.test.calculators;
import java.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class DeliveryTypePostcreateCalculator implements IModelCalculator {
private IDeliveryType deliveryType;
private String suffix;
public Object calculate() throws Exception {
deliveryType.setDescription(deliveryType.getDescription() + " " + suffix);
return null;
}
public void setModel(Object model) throws RemoteException {
deliveryType = (IDeliveryType) model;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
}
In this case each time that a
DeliveryType is created, just
after it, a suffix to description is added.
As you see, this is exactly the same as in other calculators (as
calculated properties or method) but is executed just after creation.
Postmodify
calculator (11)
With
<postmodify-calculator/> you can plug in some logic
to execute after the state of the object is changed and just before it is
stored in the database, that is, just before executing UPDATE against
database.
Its syntax is:
<postmodify-calculator
class="class"> <!-- 1 -->
<set ... /> ... <!-- 2 -->
</postmodify-calculator>
- class (required): Calculator class. A calculator
that implements ICalculator or some of its children.
- set (several, optional): To set the value of the
calculator properties before execute it.
A simple example is:
<postmodify-calculator
class="org.openxava.test.calculators.DeliveryTypePostmodifyCalculator"/>
And now the calculator class:
package org.openxava.test.calculators;
import java.rmi.*;
import org.openxava.calculators.*;
import org.openxava.test.ejb.*;
/**
* @author Javier Paniza
*/
public class DeliveryTypePostmodifyCalculator implements IModelCalculator {
private IDeliveryType deliveryType;
public Object calculate() throws Exception {
deliveryType.setDescription(deliveryType.getDescription() + " MODIFIED");
return null;
}
public void setModel(Object model) throws RemoteException {
deliveryType = (IDeliveryType) model;
}
}
In this case whenever that a
DeliveryType is modified a suffix
is added to its description.
As you see, this is exactly the same as in other calculators (as
calculated properties or methods), but it is executed just after
modifying.
Postload
and preremove calculator (10, 12)
The syntax and behavior of postload and preremove calculators are the same
of the postcreate and and postmodify ones.
Validator
(13)
This validator allows to define a validation at model level. When you need
to make a validation on several properties at a time, and that validation
does not correspond logically with any of them, then you can use this type
of validation.
Its syntax is:
<validator
class="class" <!-- 1 -->
name="name" <!-- 2 -->
only-on-create="true|false" <!-- 3 -->
>
<set ... /> ... <!-- 4 -->
</validator>
- class (optional, required if name is not
specified): Class that implements the validation logic. It has to be
of type IValidator.
- name (optional, required if class is not
specified): This name is a validator name from xava/validators.xml
file of your project or of the OpenXava project.
- only-on-create (optional): If true the validator is
executed only when creating a new object, not when an existing object
is modified. The default value is false.
- set (several, optional): To set a value of the
validator properties before executing it.
An example:
<validator class="org.openxava.test.validators.CheapProductValidator">
<set property="limit" value="100"/>
<set property="description"/>
<set property="unitPrice"/>
</validator>
And the validator code:
package org.openxava.test.validators;
import java.math.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class CheapProductValidator implements IValidator { // 1
private int limit;
private BigDecimal unitPrice;
private String description;
public void validate(Messages errors) { // 2
if (getDescription().indexOf("CHEAP") >= 0
|| getDescription().indexOf("BARATO") >= 0 || getDescription().indexOf("BARATA") >= 0) {
if (getLimitBd().compareTo(getUnitPrice()) < 0) {
errors.add("cheap_product", getLimitBd()); // 3
}
}
}
public BigDecimal getUnitPrice() {
return unitPrice;
}
public void setUnitPrice(BigDecimal decimal) {
unitPrice = decimal;
}
public String getDescription() {
return description==null?"":description;
}
public void setDescription(String string) {
description = string;
}
public int getLimit() {
return limit;
}
public void setLimit(int i) {
limit = i;
}
private BigDecimal getLimitBd() {
return new BigDecimal(limit);
}
}
This validator must implement
IValidator (1), this forces you to write a
validate(Messages
messages) (2). In this method you add the error message ids (3)
(whose texts are in the
i18n files). And if the validation
process (that is the execution of all validators) produces some error,
then OpenXava does not save the object and displays the errors to the
user.
In this case you see how description and unitPrice properties are used to
validate, for that reason the validation is at model level and not at
individual property level, because the scope of validation is more than
one property.
Remove
validator (14)
The
<remove-validator/> is a level model validator too,
but in this case it is executed just before removing an object, and it has
the possibility to deny the deletion.
Its syntax is:
<remove-validator
class="validator" <!-- 1 -->
name="name" <!-- 2 -->
>
<set ... /> ... <!-- 3 -->
</remove-validator>
- class (optional, required if name is not
specified): Class that implements the validation logic. Must implement
IRemoveValidator.
- name (optional, required if class is not
specified): This name is a validator name from the xava/validators.xml
file of your project or from the OpenXava project.
- set (several, optional): To set the value of the
validator properties before executing it.
An example can be:
<remove-validator
class="org.openxava.test.validators.DeliveryTypeRemoveValidator"/>
And the validator:
package org.openxava.test.validators;
import java.util.*;
import org.openxava.test.ejb.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class DeliveryTypeRemoveValidator implements IRemoveValidator { // 1
private IDeliveryType deliveryType;
public void setEntity(Object entity) throws Exception { // 2
this.deliveryType = (IDeliveryType) entity;
}
public void validate(Messages errors) throws Exception {
if (!deliveryType.getDeliveries().isEmpty()) {
errors.add("not_remove_delivery_type_if_in_deliveries"); // 3
}
}
}
As you see this validator must implement
IRemoveValidator (1) this forces you to write
a
setEntity() (2) method that receives the object to remove. If
validation error is added to the Messages object sent to
validate()
(3) the validation fails. If after executing all validations there are
validation errors, then OpenXava does not remove the object and displays a
list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery
type before deleting it.
Aggregate
The aggregate syntax is:
<aggregate name="aggregate"> <!-- 1 -->
<bean class="beanClass"/> <!-- 2 -->
<ejb ... /> <!-- 3 -->
<implements .../>
<property .../> ...
<reference .../> ...
<collection .../> ...
<method .../> ...
<finder .../> ...
<postcreate-calculator .../> ...
<postmodify-calculator .../> ...
<validator .../> ...
<remove-validator .../> ...
</aggregate>
- name (required): Each aggregate must have a unique
name. The rules for this name are the same that for class names in
Java, that is, to begin with upper-case and each new word starting
with upper-case too.
- bean (one, optional): Allows to specify a class
written by you to implement the aggregate. The class has to be a
JavaBean, that is a plain Java class with getters and setters for
properties. Usually this is not used because it is much better that
OpenXava generates the code for you.
- ejb (one, optional): Allows to use existing EJB2 to
implement an aggregate. This can be used only in the case of a
collection of aggregates. Usually this is not used because it is much
better that OpenXava generates the code for you.
An OpenXava component can have whichever aggregates you want. And you can
reference it from the main entity or from another aggregate.
Reference
to aggregate
The first example is an aggregate Address that is referenced from the main
entity.
In the main entity you can write:
<reference name="address" model="Address" required="true"/>
And in the component level you define:
<aggregate name="Address">
<implements interface="org.openxava.test.ejb.IWithCity"/> <!.. 1 -->
<property name="street" type="String" size="30" required="true"/>
<property name="zipCode" type="int" size="5" required="true"/>
<property name="city" type="String" size="20" required="true"/>
<reference name="state" required="true"/> <!-- 2 -->
</aggregate>
As you see an aggregate can implement an interface (1) and contain
references (2), among other things, in fact all thing that you can use in
<entity/> are supported in an aggregate.
The resulting code can be used this way, for reading:
ICustomer customer = ...
Address address = customer.getAddress();
address.getStreet(); // to obtain the value
Or in this other way to set a new address:
// to set a new address
Address address = new Address(); // it's a JavaBean, never an EJB2
address.setStreet(“My street”);
address.setZipCode(46001);
address.setCity(“Valencia”);
address.setState(state);
customer.setAddress(address);
In this case you have a simple reference (not collection), and the
generated code is a simple JavaBean, whose life cycle is associated to its
container object, that is, the
Address is removed and created
through the
Customer. An Address never will have its own life
and cannot be shared by other
Customer.
Collection
of aggregates
Now an example of a collection of aggregates. In the main entity (for
example
Invoice) you can write:
<collection name="details" minimum="1">
<reference model="InvoiceDetail"/>
</collection>
And define the
InvoiceDetail aggregate:
<aggregate name="InvoiceDetail">
<property name="oid" type="String" key="true" hidden="true">
<default-value-calculator
class="org.openxava.test.calculators.InvoiceDetailOidCalculator"
on-create="true"/>
</property>
<property name="serviceType">
<valid-values>
<valid-value value="special"/>
<valid-value value="urgent"/>
</valid-values>
</property>
<property name="quantity" type="int"
size="4" required="true"/>
<property name="unitPrice"
stereotype="MONEY" required="true"/>
<property name="amount"
stereotype="MONEY">
<calculator
class="org.openxava.test.calculators.DetailAmountCalculator">
<set property="unitPrice"/>
<set property="quantity"/>
</calculator>
</property>
<reference model="Product" required="true"/>
<property name="deliveryDate" type="java.util.Date">
<default-value-calculator
class="org.openxava.calculators.CurrentDateCalculator"/>
</property>
<reference name="soldBy" model="Seller"/>
<property name="remarks" stereotype="MEMO"/>
<validator class="org.openxava.test.validators.InvoiceDetailValidator">
<set property="invoice"/>
<set property="oid"/>
<set property="product"/>
<set property="unitPrice"/>
</validator>
</aggregate>
As you see an aggregate is as complex as an entity, with calculators,
validators, references and so on. In the case of an aggregate used in a
collection a reference to the container is added automatically, that is,
although you have not defined it,
InvoiceDetail has a reference
to
Invoice.
In the generated code you can find an Invoice with a collection of
InvoiceDetail.
The difference between a collection of references and a collection of
aggregates is that when you remove a
Invoice its details are
removed too (because they are aggregates). Also there are differences at
user interface level (you can learn more on this in chapter 4).
(
New in v2.1.1 Reference Guide: Explanation of implicit
reference) Every aggregate has a implicit reference to its container
model. That is, InvoiceDetail has a reference named invoice, even if the
reference is not declared (although it can be optionally declared for
refining purpose, for example, for making it key). This reference can be
used in Java code, as following:
InvoiceDetail detail = ... ;
detail.getInvoice(); // For obtaining the parent
Or it can be used in XML code, in this way:
<property name="oid" type="String" key="true" hidden="true">
<default-value-calculator
class="org.openxava.test.calculators.InvoiceDetail2OidCalculator"
on-create="true">
<set property="invoiceYear" from="invoice.year"/> <!-- 1 -->
<set property="invoiceNumber" from="invoice.number"/> <!-- 1 -->
</default-value-calculator>
</property>
In this case we use invoice in the from attribute (1) although invoice is
not declared in InvoiceDetail.
New in v2.1.1: using reference to
parent model key properties in a from attribute (that is,
from="invoice.year")
Many-to-many
relationships
In OpenXava XML components there is no direct concept of a many-to-many
relationship, only collections are availables. Nevertheless modeling a
many-to-many relationship in OpenXava is easy. You only need to define
collections in both sides of the relationship.
For example, if you have customers and states, and a customer can work in
several states, and, obviously, in a state several customers can work,
then you have a many-to-many (using relational nomenclature) relationship.
Suppose that you have a table CUSTOMER (without reference to state), a
table STATE (without reference to customer) and a table CUSTOMER_STATE (to
link both tables). Then you can model this case in this way:
<component name="Customer">
<entity>
...
<collection name="states"> <!-- 1 -->
<reference model="CustomerState"/>
</collection>
...
</entity>
<aggregate name="CustomerState"> <!-- 2 -->
<reference name="customer" key="true"/> <!-- 3 -->
<reference name="state" key="true"/> <!-- 4 -->
</aggregate>
...
</component>
You define in
Customer a collection of aggregates (1), each
aggregate (
CustomerState) (2) contains a reference to a
State
(4), and, of course, a reference of its container entity (
Customer)
(3).
Then you map this collection in the usual way:
<component name="Customer">
...
<aggregate-mapping aggregate="CustomerState" table="CUSTOMER_STATE">
<reference-mapping reference="customer">
<reference-mapping-detail
column="CUSTOMER" <!-- 1 -->
referenced-model-property="number"/>
</reference-mapping>
<reference-mapping reference="state">
<reference-mapping-detail
column="STATE" <!-- 2 -->
referenced-model-property="id"/>
</reference-mapping>
</aggregate-mapping>
</component>
CustomerState is mapped to CUSTOMER_STATE, a table that only
contains two columns, one to link to CUSTOMER (1) and other to link to
STATE (2).
At model and mapping level all is right, but the User Interface generated
by default by OpenXava is somewhat cumbersome in this case. Although with
the next refinements to the view part your many-to-many collection will be
just fine:
<component name="Customer">
...
<view>
...
<collection-view collection="states">
<list-properties>state.id, state.name</list-properties> <!-- 1 -->
</collection-view>
<members>
...
states
...
</members>
</view>
<view model="CustomerState">
<reference-view reference="state" frame="false"/> <!-- 2 -->
</view>
...
</component>
In this view you can see how we define explicitly (1) the properties to be
shown in list of the collection states. This is needed because we have to
show the properties of the
State, not the
CustomerState
ones. Additionally, we define that reference to
State in
CustomerState
view to be showed without frames (2), this is to avoid two ugly nested
frames.
By this easy way you can define a collection to map a many-to-many
relationship in the database. If you want a bidirectional relationship
only need to create a customers collection in
State entity, this
collection may be of the aggregate type
StateCustomer and must
be mapped to the table CUSTOMER_STATE. All in analogy to the example here.