Since v6.6 you can use Java
annotations instead of stereotypes (that is you can use @File
instead of @Stereotype("FILE")), so the compiler assures that the
code is well write and moreover each annotation can include its specific
attributes.
Since v6.6 you can use a Java
annotation instead of a stereotype, in this way:
Or if you use a version previous
to 6.6 using the DISCUSSION stereotype, thus:
The user can mark in any part of
the map then the coordinates change. Also if he types or pastes the
coordinates in the field the map and the mark are repositioned.
OpenTopoMap is free to use even
for commercial projects with CC-BY-SA license.
Concurrency
and version property
Concurrency is the ability of the application to allow several users to
save data at same time without losing data. OpenXava uses the optimistic
concurrency of JPA. When you use optimistic concurrency the records are
not locked allowing high concurrency without losing data integrity.
For example, if a user A reads a record and then a user B reads the same
record, modifies it and saves the changes, when the user A tries to save
the record he receives an error, then he needs to refresh the data and to
retry his modification.
For activating concurrency support for an entity you only need to declare
a property using
@Version, in this way:
@Version
private Integer version;
This property is persistence engine use, your application or your user
must not use this property directly. If you don't use automatic schema
evolution remember to add the column VERSION to the table.
Enums
OpenXava supports Java 5 enums. An enum allows you to define a property
that can hold one of the indicated values only .
It's easy to use, let's see this example:
private Distance distance;
public enum Distance { LOCAL, NATIONAL, INTERNATIONAL };
The
distance property only can have the following values: LOCAL,
NATIONAL or INTERNATIONAL, and as
@Required is not specified, no
value (null) is allowed too. Since v5.3 if
@Required is
specified, the first option is the default value and an empty value is not
available. If a different default value is required, use
@DefaultValueCalculator.
Since v5.6.1 the enums annotated with
@Required in an
embeddable class will
display empty value if it is used in a collection of elements.
At the user interface level the current implementation uses a combo. The
label for each value is obtained from the
i18n files.
At the database level, the value is by default saved as an integer (0 for
LOCAL, 1 for NATIONAL, 2 for INTERNATIONAL and null for no value), but
another field type can easily be configured in order to work correctly
with legacy databases. See more about this in the
mapping
chapter.
Enums with icon (new in
v6.3)
You can associate an icon to each enum option using
org.openxava.model.IIconEnum:
public enum Priority implements IIconEnum {
LOW("transfer-down"), MEDIUM("square-medium"), HIGH("transfer-up");
private String icon;
private Priority(String icon) {
this.icon = icon;
}
public String getIcon() {
return icon;
}
};
private Priority priority;
Just make your enum to implement
IIconEnum that forces you
to have a
getIcon() method. This method has to return an icon id
from
Material Design Icons.
OpenXava can use these icons in several parts of the UI, for example in
list:
Calculated
properties
The calculated properties are read only (only have
getter) and
are not persistent (they do not match with any column of database table).
A calculated property is defined in this way:
@Depends("unitPrice") // 1
@Max(9999999999L) // 2
public BigDecimal getUnitPriceInPesetas() {
if (unitPrice == null) return null;
return unitPrice.multiply(new BigDecimal("166.386")).setScale(0, BigDecimal.ROUND_HALF_UP);
}
According to the above definition now you can use the code in this way:
Product product = ...
product.setUnitPrice(2);
BigDecimal result = product.getUnitPriceInPesetas();
And
result will hold 332.772.
When the property
unitPriceInPesetas is displayed to the user
it's not editable, and its editor has a length of 10, indicated using
@Max(9999999999L)
(2). Also, because of you use
@Depends("unitPrice") (1) when the
user will change the value of the
unitPrice property in the user
interface the
unitPriceInPesetas property will be recalculated
and its value will be refreshed to the user.
From a calculated property you have direct access to JDBC connections,
here is an example:
@Max(999)
public int getDetailsCount() {
// An example of using JDBC
Connection con = null;
try {
con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection(); // 1
String table = MetaModel.get("InvoiceDetail").getMapping().getTable();
PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
" 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;
}
catch (Exception ex) {
log.error("Problem calculating details count of an Invoice", ex);
// You can throw any runtime exception here
throw new SystemException(ex);
}
finally {
try {
con.close();
}
catch (Exception ex) {
}
}
}
Yes, the JDBC code is ugly and awkward, but sometimes it can help to solve
performance problems. The
DataSourceConnectionProvider class
allows you to obtain a connection associated to the same data source that
the indicated entity (
Invoice in this case). This class is for
your convenience, but you can access to a JDBC connection using JNDI or
any other way you want. In fact, in a calculated property you can write
any code that Java allows you.
If you are using property-based access, that means you annotate the
getters or setters of your class, then you have to add the
@Transient annotation to your calculated
property, in this way:
private long number;
@Id @Column(length=10) // You annotated the getter,
public long getNumber() { // so JPA will use property-base access for your class
return number;
}
public void setNumber(long number) {
this.number = number;
}
@Transient // You have to annotate as Transient your calculated property
public String getZoneOne() { // because you are using property-based access
return "In ZONE 1";
}
Formula
(new in v3.1.4)
Using
@Formula from
Hibernate Annotations you can define a calculation
for your property. This calculation is expressed using SQL, and it is
executed by the database, not by Java. You only need to write a valid SQL
fragment:
@org.hibernate.annotations.Formula("UNITPRICE * 1.16")
private BigDecimal unitPriceWithTax;
public BigDecimal getUnitPriceWithTax() {
return unitPriceWithTax;
}
The use is simple. Put your calculation exactly in the same way that you
would put it in a SQL statement.
Usually the properties with
@Formula are read only properties,
that is, they have only getter, not setter. When the object is read from
database the calculation is done by the database and the property is
populate with it.
This is an alternative to calculated properties. It has the advantage that
the user can filter by this property in list mode, and the disadvantage
that you have to use SQL instead of Java, and you cannot use
@Depends for live recalculation of the value.
Default
value calculator
With
@DefaultValueCalculator you can associate
logic to a property, in this case the property is readable and writable.
This calculator is for calculating its initial value. For example:
@DefaultValueCalculator(CurrentYearCalculator.class)
private int year;
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 it
if he wants to. The logic for generating this value is in the
CurrentYearCalculator
class, that it's:
package org.openxava.calculators;
import java.util.*;
/**
* @author Javier Paniza
*/
public class CurrentYearCalculator implements ICalculator {
public Object calculate() throws Exception {
Calendar cal = Calendar.getInstance();
cal.setTime(new java.util.Date());
return new Integer(cal.get(Calendar.YEAR));
}
}
It's possible to customize the behaviour of a calculator setting the value
of its properties, as following:
@DefaultValueCalculator(
value=org.openxava.calculators.StringCalculator.class,
properties={ @PropertyValue(name="string", value="GOOD") }
)
private String relationWithSeller;
In this case for calculating the default value OpenXava instances
StringCalculator
and then injects the value "GOOD" in the property
string of
StringCalculator,
and finally it calls to the
calculate() method in order to
obtain the default value for
relationWithSeller. As you see, the
use of
@PropertyValue annotation allows you to create
reusable calculators.
@PropertyValue allows to inject the value from other displayed
properties, in this way:
@DefaultValueCalculator(
value=org.openxava.test.calculators.CarrierRemarksCalculator.class,
properties={
@PropertyValue(name="drivingLicenceType", from="drivingLicence.type")
}
)
private String remarks;
In this case before to execute the calculator OpenXava fills the
drivingLicenceType
property of
CarrierRemarksCalculator with the value of the
displayed property
type from the reference
drivingLicence.
As you see the
from attribute supports qualified properties
(reference.property). Moreover, each time that
drivingLicence.type
changes
remarks is recalculated (
new in v5.1, with
previous versions it was recalculated only the first time).
Also you can use
@PropertyValue without
from nor
value:
@DefaultValueCalculator(value=DefaultProductPriceCalculator.class, properties=
@PropertyValue(name="familyNumber")
)
In this case OpenXava takes the value of the displayed property
familyNumber
and inject it in the property
familyNumber of the calculator;
that is
@PropertyValue(name="familiyNumber") is equivalent to
@PropertyValue(name="familiyNumber",
from="familyNumber").
From a calculator you have direct access to JDBC connections, here is an
example:
@DefaultValueCalculator(value=DetailsCountCalculator.class,
properties= {
@PropertyValue(name="year"),
@PropertyValue(name="number"),
}
)
private int detailsCount;
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 an
IConnectionProvider (2) that you can
use within the
calculate() method.
OpenXava comes with a set of predefined calculators, you can find them in
org.openxava.calculators.
Default
values on create
You can indicate that the value will be calculated just before creating
(inserting into database) an object for the first time.
Usually for the key case you use the JPA standard. For example, if you
want to use an
identity (auto increment) column as key:
@Id @Hidden
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
You can use other generation techniques, for example, a database
sequence
can be defined in this JPA standard way:
@SequenceGenerator(name="SIZE_SEQ", sequenceName="SIZE_ID_SEQ", allocationSize=1 )
@Hidden @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SIZE_SEQ")
private Integer id;
If you want to generate a unique identifier of type String and 32
characters, you can use a Hibernate extesion of JPA:
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String oid;
Look at section 9.1.9 of JPA 1.0 specification (part of JSR-220) for
learning more about
@GeneratedValues.
If you want to use your own logic for generating the value on creating, or
you want a generated new value for a non-key property then you cannot use
the JPA
@GeneratedValue, although it's easy to solve these cases
using JPA. You only need to add this code to your class:
@PrePersist
private void calculateCounter() {
counter = new Long(System.currentTimeMillis()).intValue();
}
The JPA
@PrePersist annotation does that this method will be
executed before inserting the data the first time in database, in this
method you can calculate the value for your key or non-key properties with
your own logic.
Property
validator
A
@PropertyValidator executes validation logic
on the value assigned to the property just before storing. A property may
have several validators:
@PropertyValidator(value=ExcludeStringValidator.class, properties=
@PropertyValue(name="string", value="MOTO")
)
@PropertyValidator(value=ExcludeStringValidator.class, properties=
@PropertyValue(name="string", value="COCHE"),
onlyOnCreate=true
)
private String description;
With an OpenXava older than 6.1 you have to wrap the
@PropertyValidator
annotations with
@PropertyValidators:
@PropertyValidators ({ // Only needed until v6.0.2
@PropertyValidator(value=ExcludeStringValidator.class, properties=
@PropertyValue(name="string", value="MOTO")
),
@PropertyValidator(value=ExcludeStringValidator.class, properties=
@PropertyValue(name="string", value="COCHE"),
onlyOnCreate=true
)
})
private String description;
The technique to configure the validator (with
@PropertyValue, though
from
attribute does not work, you must use
value always) is exactly
the same than in
calculators.
With the attribute
onlyOnCreate=”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 the calculator to have a
validate() method where the
validation of property is executed. The arguments of
validate()
method are:
- Messages errors: An 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.
- Object value: The value to validate.
- String objectName: Object name of the container of
the property to validate. Useful to use in error messages.
- 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 displays them and does
not save the object.
Since v4.6.1 is also possible to use in the validator the message of
@PropertyValidator.
That is, you can write:
@PropertyValidator(value=BookTitleValidator.class, message="{rpg_book_not_allowed}")
private String title;
If the message is between braces is get from i18n files, if not is used as
is.
Moreover, you have to implement the
IWithMessage interface in your validator:
public class BookTitleValidator implements IPropertyValidator, IWithMessage {
private String message;
public void setMessage(String message) throws Exception {
this.message = message; // This message is from @PropertyValidator
}
public void validate(Messages errors, Object value, String propertyName, String modelName) {
if (((String)value).contains("RPG")) {
errors.add(message); // You can add it directly
}
}
}
The message specified in the
@PropertyValidator annotation,
rpg_book_not_allowed,
is injected in the validator calling
setMessage(). This message
can be added directly as an error.
The package
org.openxava.validators contains some common
validators.
@PropertyValidator is defined as a
Bean
Validation constraint since v5.3 and as a
Hibernate
Validator constraint until v5.2.x.
If you need to use JPA in your validator, please see
Using JPA from a Validator or
Callback.
Default
validator (new in v2.0.3)
You can define a default validator for properties depending on its type or
stereotype. In order to do it you have to use the file
validators.xml
in
src/main/resources/xava (just
xava before v7) of your
project to define in it the default validators.
For example, you can define in your
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:
@Required @Stereotype("PERSON_NAME")
private String name;
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).
Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining
openxava/src/main/resources/xava/default-validators.xml
and
openxavatest/src/main/resources/xava/validators.xml.
Default validators do not apply when you use directly the JPA api for
saving your entities.
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:
@Calculation("((hours * worker.hourPrice) + tripCost - discount) * vatPercentage / 100")
private BigDecimal total;
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. All the
properties used in
@Calculation (the operands) must be displayed
in the user interface in order
@Calculation works, if it is not
the case you should use a regular calculated property instead.
References
A reference allows access from an entity to another entity. 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:
Customer customer = ...
customer.getSeller().getName();
to access to the name of the seller of that customer.
The syntax of reference is:
@Required // 1
@Id // 2
@SearchKey // 3 New in v3.0.2
@DefaultValueCalculator // 4
@ManyToOne( // 5
optional=false // 1
)
private type referenceName; // 5
public type getReferenceName() { ... } // 5
public void setReferenceName(type newValue) { ... } // 5
- @ManyToOne(optional=false) (JPA), @Required (OX) (optional, the JPA is the preferred one):
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.
- @Id (JPA, 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.
- @SearchKey (OX, 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 to type its value for searching. OpenXava uses the @Id
members for searching by default, and if the id members are hidden
then it uses the first property in the view. With @SearchKey
you can choose explicitly references for searching.
- @DefaultValueCalculator
(OX, 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).
- Reference declaration: A regular Java reference
declaration with its getters and setters. The reference is marked with
@ManyToOne (JPA) and the type must be
another entity.
A little example of references:
@ManyToOne
private Seller seller; // 1
public Seller getSeller() {
return seller;
}
public void setSeller(Seller seller) {
this.seller = seller;
}
@ManyToOne(fetch=FetchType.LAZY)
private Seller alternateSeller; // 2
public Seller getAlternateSeller() {
return alternateSeller;
}
public void setAlternateSeller(Seller alternateSeller) {
this.alternateSeller = alternateSeller;
}
- A reference called seller to the entity of Seller
entity.
- A reference called alternateSeller to the entity Seller.
In this case we use fetch=FetchType.LAZY, in this way the
data is read from database on demand. This is the most efficient
approach, but it's not the JPA default, therefore it's advisable to use
always fetch=FetchType.LAZY when declaring the
references.
If you assume that this is in an entity named
Customer, you
could write:
Customer customer = ...
Seller seller = customer.getSeller();
Seller alternateSeller = customer.getAlternateSeller();
Default
value calculator in references
In a reference
@DefaultValueCalculator works
like in a property,
only that it has to return the value of the reference key.
For example, in the case of a reference with simple key, you can write:
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
@PropertyValue(name="value", value="2")
)
private Family family;
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 composite key:
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({
@JoinColumn(name="ZONE", referencedColumnName="ZONE"),
@JoinColumn(name="WAREHOUSE", referencedColumnName="NUMBER")
})
@DefaultValueCalculator(DefaultWarehouseCalculator.class)
private Warehouse warehouse;
And the calculator code:
package org.openxava.test.calculators;
import org.openxava.calculators.*;
/**
* @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;
}
}
Returns an object of type
Warehouse but filling only the key
properties.
Using
references as key
You can use references as key, or as part of the key. You have to declare
the reference as
@Id, and use an id class, as following:
@Entity
@IdClass(AdditionalDetailKey.class)
public class AdditionalDetail {
// JoinColumn is also specified in AditionalDetailKey because
// a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Id @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")
private Service service;
@Id @Hidden
private int counter;
...
}
Also, you need to write your key class:
public class AdditionalDetailKey implements java.io.Serializable {
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")
private Service service;
@Hidden
private int counter;
// equals, hashCode, toString, getters and setters
...
}
You need to write the key class although the key would be only a reference
with only a join column.
It's better to use this feature only when you are working against legacy
databases, if you have control over the schema use an autogenerated id
instead.
Embedded
reference
You can reference an
embeddable
class using the
@Embedded annotation. For example, in your
entity you can write:
@Embedded
private Address address;
And you have to define the
Address class as embeddable:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
*
* @author Javier Paniza
*/
@Embeddable
public class Address implements IWithCity { // 1
@Required @Column(length=30)
private String street;
@Required @Column(length=5)
private int zipCode;
@Required @Column(length=20)
private String city;
// ManyToOne inside an Embeddable is not supported by JPA 1.0 (see at 9.1.34),
// but Hibernate implementation supports it.
@ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")
private State state; // 2
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getZipCode() {
return zipCode;
}
public void setZipCode(int zipCode) {
this.zipCode = zipCode;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
As you see an embeddable class can implement an interface (1) and contain
references (2), among other things, but it can't use JPA callbacks
methods.
This code can be used this way, for reading:
Customer 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();
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, which 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.
Collections
Entity
collections
You can define a collection of references to entities. A collection is a
Java property of type
java.util.Collection.
Here syntax for collection:
@Size // 1
@Condition // 2
@OrderBy // 3
@XOrderBy // 4
@OrderColumn // 5 New in v5.3
@OneToMany/@ManyToMany // 6
private Collection<YourEntity> collectionName; // 5
public Collection<YourEntity> getCollectionName() { ... } // 5
public void setCollectionName(Collection<YourEntity> newValue) { ... } // 5
- @Size (BV, HV, optional): Minimum (min) and/or
maximum (max) number of expected elements. This is validated
just before saving.
- @Condition (OX, optional): Restricts the elements that appear
in the collection. Doesn't work with @ManyToMany.
- @OrderBy (JPA, optional): The elements in collections will
be in the indicated order.
- @XOrderBy (OX, optional): The @OrderBy of JPA does
not allow to use qualified properties (properties of references). @XOrderBy
does allow it.
- @OrderColumn
(JPA, optional): (New in v5.3) The order
of the elements in the collection is persisted in database. A special
column is created in the table to keep this order. The collection must
be a java.util.List. The user interface allows the user to
reorder the collection elements.
- Collection declaration: A regular Java collection
declaration with its getters and setters. The collection is marked
with @OneToMany (JPA) or @ManyToMany (JPA) and the type must be
another entity.
Let's have a look at some examples. First a simple one:
@OneToMany (mappedBy="invoice")
private Collection<Delivery> deliveries;
public Collection<Delivery> getDeliveries() {
return deliveries;
}
public void setDeliveries(Collection<Delivery> deliveries) {
this.deliveries = deliveries;
}
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.You use
mappedBy="invoice" to indicate that the
reference
invoice of
Delivery is used to mapping this
collection.
Now you can write a code like this:
Invoice invoice = ...
for (Delivery delivery: invoice.getDeliveries()) {
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:
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE) // 1
@OrderBy("serviceType desc") // 2
@org.hibernate.validator.Size(min=1) // 3
private Collection<InvoiceDetail> details;
- Using REMOVE as cascade type produces that when the user removes an
invoice its details are also removed.
- With @OrderBy you force that the details will be returned
ordered by serviceType.
- The restriction @Size(min=1) requires at least one detail
for the invoice to be valid.
You have full freedom to define how the collection data is obtained, with
@Condition you can overwrite the default condition:
@Condition(
"${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
"${warehouse.number} = ${this.warehouse.number} AND " +
"NOT (${number} = ${this.number})"
)
public Collection<Carrier> getFellowCarriers() {
return null;
}
If you have this collection within
Carrier, you can obtain with
this collection all the 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 refer to the value of a property of the
current object.
@Condition only applied to the user interface
generated by OpenXava, if you call directly to
getFellowCarriers()
it will return null.
If with this you don't have enough, you can write the logic that returns
the collection. The previous example can be written in the following way
too:
public Collection<Carrier> getFellowCarriers() {
Query query = XPersistence.getManager().createQuery("from Carrier c where " +
"c.warehouse.zoneNumber = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZoneNumber());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());
return query.getResultList();
}
As you see this is a conventional getter method. Obviously it must return
a
java.util.Collection whose elements are of type
Carrier.
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 it's possible that in
Customer
you have more than one reference to
Seller (for example,
seller
and
alternateSeller) JPA does not know which one to choose,
because of this you have the attribute
mappedBy of
@OneToMany.
You can use it in this way:
@OneToMany(mappedBy="seller")
private Collection<Customer> customers;
To indicate that the reference
seller and not
alternateSeller
will be used in this collection.
The
@ManyToMany (JPA) annotation allows to define
a collection with many-to-many multiplicity. As following:
@Entity
public class Customer {
...
@ManyToMany
private Collection<State> states;
...
}
In this case a customer has a collection of states, but a state can be
present in several customers.
Embedded
collections
Collections of embeddable objects were not available in early JPA
versions, so we used to simulate them using collections to entities with
cascade type REMOVE or ALL. OpenXava manages these collections in a
special way, and we still call them
embedded collections.
Now an example of an embedded collection. In the main entity (for example
Invoice) you can write:
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection details;
Note that you use
CascadeType.REMOVE, and
InvoiceDetail
is an entity, not an embeddable class:
package org.openxava.test.model;
import java.math.*;
import javax.persistence.*;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.GenericGenerator;
import org.openxava.annotations.*;
import org.openxava.calculators.*;
import org.openxava.test.validators.*;
/**
*
* @author Javier Paniza
*/
@Entity
@EntityValidator(value=InvoiceDetailValidator.class,
properties= {
@PropertyValue(name="invoice"),
@PropertyValue(name="oid"),
@PropertyValue(name="product"),
@PropertyValue(name="unitPrice")
}
)
public class InvoiceDetail {
@ManyToOne // Lazy fetching produces a fails on removing a detail from invoice
private Invoice invoice;
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String oid;
private ServiceType serviceType;
public enum ServiceType { SPECIAL, URGENT }
@Column(length=4) @Required
private int quantity;
@Stereotype("MONEY") @Required
private BigDecimal unitPrice;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
private Product product;
@DefaultValueCalculator(CurrentDateCalculator.class)
private java.util.Date deliveryDate;
@ManyToOne(fetch=FetchType.LAZY)
private Seller soldBy;
@Stereotype("MEMO")
private String remarks;
@Stereotype("MONEY") @Depends("unitPrice, quantity")
public BigDecimal getAmount() {
return getUnitPrice().multiply(new BigDecimal(getQuantity()));
}
public boolean isFree() {
return getAmount().compareTo(new BigDecimal("0")) <= 0;
}
@PostRemove
private void postRemove() {
invoice.setComment(invoice.getComment() + "DETAIL DELETED");
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
public ServiceType getServiceType() {
return serviceType;
}
public void setServiceType(ServiceType serviceType) {
this.serviceType = serviceType;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
public BigDecimal getUnitPrice() {
return unitPrice==null?BigDecimal.ZERO:unitPrice;
}
public void setUnitPrice(BigDecimal unitPrice) {
this.unitPrice = unitPrice;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public java.util.Date getDeliveryDate() {
return deliveryDate;
}
public void setDeliveryDate(java.util.Date deliveryDate) {
this.deliveryDate = deliveryDate;
}
public Seller getSoldBy() {
return soldBy;
}
public void setSoldBy(Seller soldBy) {
this.soldBy = soldBy;
}
public String getRemarks() {
return remarks;
}
public void setRemarks(String remarks) {
this.remarks = remarks;
}
public Invoice getInvoice() {
return invoice;
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
}
}
As you see this is a complex entity, with calculators, validators,
references and so on. Also you have to define a reference to the container
class (
invoice). In this case when an
Invoice is
removed all its details are removed too. Moreover there are differences at
user interface level (you can learn more on the
view
chapter).
Element
collections (new in v5.0)
Since JPA 2.0 you can define a collection of real
embeddable
objects. We call these collections
element collections.
This is the syntax for element collections:
@Size // 1
@OrderBy // 2
@OrderColumn // 3 New in v5.3
@ElementCollection // 4
private Collection<YourEmbeddableClass> collectionName; // 3
public Collection<YourEmbeddableClass> getCollectionName() { ... } // 3
public void setCollectionName(Collection<YourEmbeddableClass> newValue) { ... } // 3
- @Size (BV, HV, optional): Minimum (min) and/or
maximum (max) number of expected elements. This is validated
just before saving.
- @OrderBy (JPA, optional): The elements in collections will
be in the indicated order.
- @OrderColumn
(JPA, optional): (New in v5.3) The order
of the elements in the collection is persisted in database. A special
column is created in the table to keep this order. The collection must
be a java.util.List. The user interface allows the user to
reorder the collection elements.
- Collection declaration: A regular Java collection
declaration with its getters and setters. The collection is marked
with @ElementCollection (JPA). The elements
must be embeddable
classes.
The elements in the collection are saved all at once at the same time of
the main entity. Moreover, the generated user interface allows the user to
modify all the elements of the collection at the same time.
An embeddable class that is contained within an element collection must
not contain collections of any type.
Let's see an example. First you have to define the collection in the main
entity:
@Entity
public class Quote extends Identifiable {
...
@ElementCollection
private Collection<QuoteDetail> details;
public Collection<QuoteDetail> getDetails() {
return details;
}
public void setDetails(Collection<QuoteDetail> details) {
this.details = details;
}
...
}
Then define your embeddable class:
@Embeddable
public class QuoteDetail {
@ManyToOne(fetch=FetchType.LAZY, optional=false) // 1
private Product product;
@Required // 2
private BigDecimal unitPrice;
@Required
private int quantity;
private Date availabilityDate;
@Column(length=30)
private String remarks;
@Column(precision=10, scale=2)
@Depends("unitPrice, quantity")
public BigDecimal getAmount() { // 3
return getUnitPrice().multiply(new BigDecimal(getQuantity()));
}
...
}
As you can see, an embeddable class used in an element collection can
contain references(1), validations(2) and calculated properties(3) among
other things.
Lists
with @OrderColumn (new in v5.3)
To have a collection that keeps the order of its elements use
java.util.List
instead of
java.util.Collection and annotate the collection with
@OrderColumn. That is, if you define a
collection in this way:
@OneToMany(mappedBy="project", cascade=CascadeType.ALL)
@OrderColumn
private List<ProjectTask> tasks;
The user interface allows the user to change the order of the elements and
this order is persisted in database. Moreover, if you change the order of
the elements programmatically this order is persisted in database too.
To persist the order, JPA uses a special column in the database table,
this column is for internal use only, you have not a property to access it
from your code. You can use
@OrderColumn(name="MYCOLUMN") to
specify the column name you want, if
name is not specified the
collection name plus "_ORDER" is used. If you use the
updateSchema
tool it will create the column for you. Otherwise, if you control the
database schema by yourself you should add the column to your table, for
example for the above collection you should add the next column to your
table:
ALTER TABLE PROJECTTASK
ADD TASKS_ORDER INTEGER
In the current implementation drag and drop is used by the user to change
the order, with
@OneToMany collections the order is persisted
just after drop, while in
@ElementCollection the order is
persisted after saving the container entity.
Methods
Methods are defined in an OpenXava entity (really a JPA entity) as in a
regular Java class. For example:
public void increasePrice() {
setUnitPrice(getUnitPrice().multiply(new BigDecimal("1.02")).setScale(2));
}
Methods are the sauce of the objects, without them the object would only
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).
Finders
A finder is a special static method that allows you to find an object or a
collection of objects that follow some criteria.
Some examples:
public static Customer findByNumber(int number) throws NoResultException {
Query query = XPersistence.getManager().createQuery(
"from Customer as o where o.number = :number");
query.setParameter("number", number);
return (Customer) query.getSingleResult();
}
public static Collection findAll() {
Query query = XPersistence.getManager().createQuery("from Customer as o");
return query.getResultList();
}
public static Collection findByNameLike(String name) {
Query query = XPersistence.getManager().createQuery(
"from Customer as o where o.name like :name order by o.name desc");
query.setParameter("name", name);
return query.getResultList();
}
This methods can be used this way:
Customer customer = Customer.findByNumber(8);
Collection javieres = Customer.findByNameLike(“%JAVI%”);
As you see, using finder methods creates a more readable code than using
the verbose query API of JPA. But this is only a style recommendation, you
can choose not to write finder methods and to use directly JPA queries.
Entity validator
An
@EntityValidator 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:
@EntityValidator(
value=class, // 1
onlyOnCreate=(true|false), // 2
properties={ @PropertyValue ... } // 3
)
- value (required): Class that implements the
validation logic. It has to be of type IValidator.
- onlyOnCreate (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.
- properties (several @PropertyValue, optional): To set a value
of the validator properties before executing it.
An example:
@EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
@PropertyValue(name="limit", value="100"),
@PropertyValue(name="description"),
@PropertyValue(name="unitPrice")
})
public class Product {
And the validator code:
package org.openxava.test.validators;
import java.math.*;
/**
* @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 (getLimiteBd().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(Integer.toString(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.
Since v4.6.1 the validator can implement
IWithMessage to inject the message from
@EntityValidator,
it works like in the
property
validator case.
@EntityValidators({ // Only needed until v6.0.2
@EntityValidator(value=org.openxava.test.validators.CheapProductValidator.class, properties= {
@PropertyValue(name="limit", value="100"),
@PropertyValue(name="description"),
@PropertyValue(name="unitPrice")
}),
@EntityValidator(value=org.openxava.test.validators.ExpensiveProductValidator.class, properties= {
@PropertyValue(name="limit", value="1000"),
@PropertyValue(name="description"),
@PropertyValue(name="unitPrice")
}),
@EntityValidator(value=org.openxava.test.validators.ForbiddenPriceValidator.class,
properties= {
@PropertyValue(name="forbiddenPrice", value="555"),
@PropertyValue(name="unitPrice")
},
onlyOnCreate=true
)
})
public class Product {
@EntityValidator is defined as a
Bean
Validation constraint since v5.3 and as a
Hibernate
Validator constraint until v5.2.x.
If you need to use JPA in your validator, please see
Using JPA from a Validator or
Callback.
Remove validator
The
@RemoveValidator 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:
@RemoveValidator(
value=class, // 1
properties={ @PropertyValue ... } // 2
)
- class (required): Class that implements the
validation logic. Must implement IRemoveValidator.
- properties (several @PropertyValue, optional): To set the
value of the validator properties before executing it.
An example can be:
@RemoveValidator(value=DeliveryTypeRemoveValidator.class,
properties=@PropertyValue(name="number")
)
public class DeliveryType {
And the validator:
package org.openxava.test.validators;
import org.openxava.test.model.*;
import org.openxava.util.*;
import org.openxava.validators.*;
/**
* @author Javier Paniza
*/
public class DeliveryTypeRemoveValidator implements IRemoveValidator { // 1
private DeliveryType deliveryType;
private int number; // We use this (instead of obtaining it from deliveryType)
// for testing @PropertyValue for simple properties
public void setEntity(Object entity) throws Exception { // 2
this.deliveryType = (DeliveryType) entity;
}
public void validate(Messages errors) throws Exception {
if (!deliveryType.getDeliveries().isEmpty()) {
errors.add("not_remove_delivery_type_if_in_deliveries", new Integer(getNumber())); // 3
}
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
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.
As in the case of
@EntityValidator you can use several
@RemoveValidator
for entity (with
@RemoveValidators annotation for versions older
than 6.1).
@RemoveValidator is executed when you remove entities from
OpenXava (using
MapFacade or standard OX actions), but not
when you use directly JPA. If you want to create a restriction on remove
which is recognized by JPA, just use
@PreRemove JPA call method.
JPA callback
methods
With
@PrePersist you can plug in your own logic to
execute just before creating the object as persistent object.
As following:
@PrePersist
private void prePersist() {
setDescription(getDescription() + " CREATED");
}
In this case each time that a
DeliveryType is created a suffix
to description is added.
As you see, this is exactly the same as in other methods but is
automatically executed just before creation.
With
@PreUpdate 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.
As following:
@PreUpdate
private void preUpdate() {
setDescription(getDescription() + " MODIFIED");
}
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 methods, but it is
executed just before modifying.
You can use all the JPA callback annotations:
@PrePersist,
@PostPersist,
@PreRemove,
@PostRemove,
@PreUpdate,
@PostUpdate and
@PostLoad.
OX
callback methods (new in V4.0.1)
Using @PreCreate you can perform logic that will be executed before
persisting the object. It allows to perform entity manager operations and
queries that are not allowed in JPA callbacks.
For example, if we need to create a customer and assign it to an invoice
when the customer is not specified:
@PreCreate
public void onPreCreate() {
// Automatically create a new customer
if (getCustomer() == null) {
Customer cust = new Customer();
cust.setName(getName());
cust.setAddress(getAddress());
cust = XPersistence.getManager().merge(cust);
setCustomer(cust);
}
}
The entity manager operation will not affect the callbacks behavior. Along
with @PreCreate is @PostCreate and @PreDelete the methods decorated with
these annotations are part of the transaction, therefore the use of these
annotations keeps the integrity of the information without further effort
from the developer. When combined with JPA annotations the order for each
callback is as follows:
For creating an entity: @PreCreate, @PrePersist(JPA), @PostPersist(JPA)
and @PostCreate.
For deleting and entity: @PreDelete, @PreRemove(JPA), @PostRemove(JPA).
The methods annotated with these annotations should not return any value
and must not have any parameters. These annotations are intended for the
entities, and are ignored when used on entity listeners.
Inheritance
OpenXava supports Java and
JPA inheritance.
For example you can define a
@MappedSuperclass in this way:
package org.openxava.test.model;
import javax.persistence.*;
import org.hibernate.annotations.*;
import org.openxava.annotations.*;
/**
* Base class for defining entities with a UUID oid. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
public class Identifiable {
@Id @GeneratedValue(generator="system-uuid") @Hidden
@GenericGenerator(name="system-uuid", strategy = "uuid")
private String oid;
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
}
You can define another
@MappedSuperclass that extends from this
one, for example:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
* Base class for entities with a 'name' property. <p>
*
* @author Javier Paniza
*/
@MappedSuperclass
public class Nameable extends Identifiable {
@Column(length=50) @Required
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now you can use
Identifiable or
Nameable for defining
your entities, as following:
package org.openxava.test.model;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorColumn(name="TYPE")
@DiscriminatorValue("HUM")
@Table(name="PERSON")
@AttributeOverrides(
@AttributeOverride(name="name", column=@Column(name="PNAME"))
)
public class Human extends Nameable {
@Enumerated(EnumType.STRING)
private Sex sex;
public enum Sex { MALE, FEMALE };
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
And now, the real entity inheritance, an entity that extends other entity:
package org.openxava.test.model;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Entity
@DiscriminatorValue("PRO")
public class Programmer extends Human {
@Column(length=20)
private String mainLanguage;
public String getMainLanguage() {
return mainLanguage;
}
public void setMainLanguage(String mainLanguage) {
this.mainLanguage = mainLanguage;
}
}
You can create an
OpenXava
module for
Human and
Programmer (not for
Identifiable
or
Nameble directly). In the
Programmer module the
user can only access to programmers, in the other hand using
Human
module the user can access to
Human and
Programmer
objects. Moreover when the user tries to view the detail of a Programmer
from the
Human module the
Programmer view will be
show. True polymorphism.
Since v4.5 OpenXava supports all inheritance features of JPA, including
single table per class hierarchy, joined and table per class mapping
strategies, before v4.5 only
@AttributeOverrides and single table per class
hierarchy mapping strategy was supported.
Composite key
The preferred way for defining the key of an entity is a single
autogenerated key (annotated with
@Id and
@GeneratedValue), but sometimes, for example
when you go against a legacy database, you need to have an entity mapped
to a table that uses several column as key. This case can be solved with
JPA (therefore with OpenXava) in two ways, using
@IdClass or using
@EmbeddedId
Id class
In this case you use
@IdClass in your entity to indicate a key
class, and you mark the key properties as
@Id in your entity:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.jpa.*;
/**
*
* @author Javier Paniza
*/
@Entity
@IdClass(WarehouseKey.class)
public class Warehouse {
@Id
// Column is also specified in WarehouseKey because a bug in Hibernate, see
// http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Column(length=3, name="ZONE")
private int zoneNumber;
@Id @Column(length=3)
private int number;
@Column(length=40) @Required
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getZoneNumber() {
return zoneNumber;
}
public void setZoneNumber(int zoneNumber) {
this.zoneNumber = zoneNumber;
}
}
You also need to declare your id class, a serializable regular class with
all key properties from the entity:
package org.openxava.test.model;
import java.io.*;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
public class WarehouseKey implements Serializable {
@Column(name="ZONE")
private int zoneNumber;
private int number;
@Override
public boolean equals(Object obj) {
if (obj == null) return false;
return obj.toString().equals(this.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
return "WarehouseKey::" + zoneNumber+ ":" + number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getZoneNumber() {
return zoneNumber;
}
public void setZoneNumber(int zoneNumber) {
this.zoneNumber = zoneNumber;
}
}
Embedded id
In this case you have a reference to a
@Embeddable object marked as
@EmbeddedId:
package org.openxava.test.model;
import javax.persistence.*;
import org.openxava.annotations.*;
/**
*
* @author Javier Paniza
*/
@Entity
public class Warehouse {
@EmbeddedId
private WarehouseKey key;
@Column(length=40) @Required
private String name;
public WarehouseKey getKey() {
return key;
}
public void setKey(WarehouseKey key) {
this.key = key;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And you key is an embeddable class that holds the key properties:
package org.openxava.test.model;
import javax.persistence.*;
/**
*
* @author Javier Paniza
*/
@Embeddable
public class WarehouseKey implements java.io.Serializable {
@Column(length=3, name="ZONE")
private int zoneNumber;
@Column(length=3)
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public int getZoneNumber() {
return zoneNumber;
}
public void setZoneNumber(int zoneNumber) {
this.zoneNumber = zoneNumber;
}
}
Bean Validation
OpenXava has full support for the Java standard for
Bean Validation
(1.1 JSR-349 since v5.3 and 1.0 JSR-303 since v4.1). You can define your
own constraints for your entities as explained in Bean Validation
specification, and OpenXava will recognize them, showing the corresponding
validation messages to the user. Consult the latest
Hibernate Validator documentation to learn how to
write a JSR-349 validator, since the current version of Hibernate
Validator implements JSR-349.
Moreover, since v5.3 the OpenXava annotations
@Required,
@PropertyValidator and
@EntityValidator are defined as Bean
Validation constraints, that means that when you save an entity using
directly JPA these validations will apply.
On the other hand,
@RemoveValidator,
@PropertyValidator(onlyOnCreate=true),
EntityValidator(onlyOnCreate=true) and the
default
validator feature of OpenXava are not recognized by Bean Validation
or JPA, but only by OpenXava.
@AssertTrue
Since v4.9 OpenXava allows to inject properties and qualified properties
(reference properties) that belong to validated bean, into the message
identified by means of the message element of
@AssertTrue. Example:
In this case we have an
@AssertTrue annotating a field of the
entity:
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.model.*
import javax.validation.constraints.*;
@Entity
public class Driver extends Identifiable{
@Required
@Column(length=40)
private String name;
@AssertTrue(message="{disapproved_driving_test}")
private boolean approvedDrivingTest;
@OneToMany(mappedBy="driver")
private Collection<Vehicle> vehicles = new ArrayList<Vehicle>();
//getters and setters...
}
{disapproved_driving_test} is the message identifier that is
declared under i18n file like this:
disapproved_driving_test=Driver {name} can not be registered: must approve the driving test
If we try to create an entity with
name=MIGUEL GRAU and
approvedDrivingTest=false
the next error message will be shown:
Driver MIGUEL GRAU can not be registered: must approved the driving
test
In this case we have an
@AssertTrue annotating a method of the
entity:
import javax.persistence.*;
import org.openxava.annotations.*;
import org.openxava.model.*;
import javax.validation.constraints.*;
@Entity
public class Vehicle extends Identifiable{
@Required
@Column(length=15)
private String type;
@Required
@Column(length=7)
private String licensePlate;
private boolean roadworthy;
@ManyToOne
private Driver driver;
@AssertTrue(message="{not_roadworthy}")
private boolean isRoadworthyToAssignTheDriver(){
return driver == null || roadworthy;
}
//getters and setters...
}
{not_roadworthy} is the message identifier that is declared under
i18n file like this:
not_roadworthy={type} plate {licensePlate} is not roadworthy. It can not be assigned to the driver {driver.name}
If we have an entity:
type=AUTO,
licensePlate=A1-0001
and
roadworthy=false, and try to assign
driver (name =
MIGUEL GRAU), the validation method will fail and display the error
message:
AUTO plate A1-0001 is not roadworthy. It can not be assigned to the
driver MIGUEL GRAU
Hibernate
Validator (new in v3.0.1)
OpenXava has full support for
Hibernate Validator with Bean Validation support.
Hibernate Validator 3.x (with the old API) was supported until v5.2.x. You
can define your own constraints for your entities as explained in
Hibernate
Validator documentation, and OpenXava will recognize them,
showing the corresponding validation messages to the user.
Moreover, the OpenXava annotations
@Required,
@PropertyValidator and
@EntityValidator are defined as Hibernate
Validator 3.x constraints until v5.2.x and as Bean Validation constraints
since v5.3.