This guide is for helping to an OpenXava 2 developer to start rapidly with
OpenXava 3.
OX3 will be fully compatible with OX2 and you will can run your OX2
applications using OX3, also you can develop using XML with OX3.
Component
definition
The main difference between OX2 and OX3 is the format for defining
components.
In OX2 we use XML, as following:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE component SYSTEM "dtds/component.dtd">
<component name="Teacher">
<entity>
<property name="id" type="String" key="true"
size="5" required="true"/>
<property name="name" type="String"
size="40" required="true"/>
</entity>
<entity-mapping table="MYSCHOOL@separator@TEACHER">
<property-mapping property="id" column="ID"/>
<property-mapping property="name" column="NAME"/>
</entity-mapping>
</component>
In OX3 we use Java with annotations:
package org.openxava.school.model;
import javax.persistence.*;
import org.openxava.annotations.*;
@Entity
public class Teacher {
@Id @Column(length=5) @Required
private String id;
@Column(length=40) @Required
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now you write the logic directly in your Java class, therefore you does
not need calculator for calculated properties nor method.
The object-relational mapping is done by JPA annotations. And the rest
(tab and view) is a literal translation of OpenXava XML to Java
annotations.
Aggregates
Aggregates do not longer exist on OX2. But you can simulate the aggregate
using embeddable objects for single references, and collection of entities
with cascade remove for collections.
That is, in OX2 you writes:
<entity>
<reference name="address" model="Address"/>
</entity>
<aggregate name="Address">
...
</aggregate>
And in OX3 you write:
// Customer.java
@Entity
public class Customer {
@Embedded
private Address address;
}
// Address.java
@Embeddable
public class Address {
}
As you can see, we use the JPA Embeddable object that behaves exactly as
an aggregate of OpenXava for the case of single reference.
For collection of aggregate you write using OX2:
<component name="Invoice">
<entity>
<collection name="details">
<reference model="InvoiceDetail"/>
</collection>
</entity>
<aggregate name="InvoiceDetail">
...
</aggregate>
</component>
But in OX3 you write:
// Invoice.java
@Entity
public class Invoice {
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
}
// InvoiceDetail.java
@Entity
public class InvoiceDetail {
}
That is, you use a collection of entities with cascade type remove, and
this behaves as the collection of aggregates of OX2.
Valid
values
The OX2
<valid-values/> are implemented in OX3 with Java 5
enums.
That is, in OX2 you write:
<property name="distance">
<valid-values>
<valid-value value="local"/>
<valid-value value="national"/>
<valid-value value="international"/>
</valid-values>
</property>
Its equivalent in OX3 is:
private Distance distance;
public enum Distance { LOCAL, NATIONAL, INTERNATIONAL };
It has the same effect, except that:
- The data type is not int, is just Distance.
- On saving to database the valid value saves by default 0 for no
value, 1 for local, 2 for national and 3 for interantional, while the
enum saves null for no value, 0 for LOCAL, 1 for NATIONAL and 2 for
INTERNATIONAL.
That is, if you want to use a database of OX2 with OX3 you need to use a
Hibernate Type, as following:
@org.hibernate.annotations.Type(type="org.openxava.types.Base1EnumType",
parameters={
@Parameter(name="enumType", value="org.openxava.test.model.InvoiceDetail$ServiceType")
}
)
private ServiceType serviceType;
public enum ServiceType { SPECIAL, URGENT }
Base1EnumType is a Hibernate Type included in OpenXava for saving
a Java 5 enum with base 1 index. In this way you go against the database
of your OX2 application using OX3.
Default
value calculators on create
The
default-value-calculator of OX2 is available in OX3 by means
of
@DefaultValueCalculator annotation. But
@DefaultValueCalculator
does not support
on-create="true" of its XML counterpart. You
have to simulate the effect of this type of calculator using the JPA
equivalent.
For an autogenerated id you have to use the
@GeneratedValue JPA
annotation. For example, in OX2 you write:
<property name="number" type="Integer" key="true" size="5" required="false">
<default-value-calculator class="org.openxava.calculators.IdentityCalculator" on-create="true"/>
</property>
In OX3 you write:
@Id @Column(length=5)
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer number;
That is, plain JPA code.
But
<default-value-calculator ... on-create="true"/> is
more flexible than
@GeneratedValue because it admits any logic,
and it can be used for not key properties. In this case you can use a
@PrePersist
JPA method. Let's look an example. When in OX2 you write:
<property name="oid" type="String" key="true" hidden="true">
<default-value-calculator
class="org.openxava.test.calculators.InvoiceDetailOidCalculator"
on-create="true"/>
</property>
Where
InvoiceDetailOidCalculator has a custom logic for
generated the value. This can be easily translate to OX3 adding the next
method to your POJO:
@PrePersist
private void calculateOID() {
oid = invoice.getYear() + ":" + invoice.getNumber() + ":" + System.currentTimeMillis();
}
In this case the logic of
calculatedOID method is the same of
the
InvoiceDetailOIDCalculator. Again, you can see as we are
using plain JPA code.
Callback
calculators: postcreate, postmodify, postload, preremove and postremove
Instead of
<postcreate-calculator/>,
<postmodify-calculator/>,
<postload-calculator/>,
<preremove-calculator/>
and
<postremove-calculator/> in OX3 you use the standard
JPA callback methods, that is, methods in your entity annotated with
@PrePersist,
@PostPersist,
@PreRemove,
@PostRemove,
@PreUpdate,
@PostUpdate and
@PostLoad.
For example, if you have a collection as this one in an
Invoice
component:
<collection name="details">
<reference model="InvoiceDetail"/>
<postremove-calculator
class="org.openxava.test.calculators.DetailPostremoveCalculator"/>
</collection>
With this calculator class:
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;
}
}
In OX3 you only need to have a method as this one in the
InvoiceDetail
class:
@PostRemove
private void postRemove() {
invoice.setComment(invoice.getComment() + "DETAIL DELETED");
}
In this case easier than in OX2.
Converters
The useful OpenXava converters are not available in OX3. But, don't worry,
we can use the Type of Hibernate, that provides almost all functionality
of OpenXava converters.
Look at Hibernate
Reference about Type,
Hibernate Type has some little drawback over classic OX converter:
- Converter by defaults (from defaults-converter.xml) not supported.
- Converters for references not supported.
persistence.xml
instead of configuration
In the build.xml the property 'configuration' is not longer used. Now
instead of properties files we can comment and uncomment code in
persistence.xml in order to work with several configuration, in this way:
<!-- Tomcat + Hypersonic -->
<persistence-unit name="default">
<non-jta-data-source>java:comp/env/jdbc/MySchoolDS</non-jta-data-source>
<class>org.openxava.session.GalleryImage</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
</properties>
</persistence-unit>
<!-- JBoss + Hypersonic
<persistence-unit name="default">
<non-jta-data-source>java:/MiEscuelaDS</non-jta-data-source>
<class>org.openxava.session.GalleryImage</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
</properties>
</persistence-unit>
-->
<!-- WebSphere + AS/400
<persistence-unit name="default">
<non-jta-data-source>jdbc/MiEscuelaDS</non-jta-data-source>
<class>org.openxava.session.GalleryImage</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.DB2400Dialect"/>
<property name="hibernate.show_sql" value="false"/>
</properties>
</persistence-unit>
-->
Here we have 3 configuration and we can active one of them easily only
commenting and uncommenting.
JUnit
test: Setting value to a combo for a composite key reference
In OX3 this is the way to put value to a combo with composite key:
Shipment shipment = (Shipment) Shipment.findAll().iterator().next();
setValue("shipment.KEY", toKeyString(shipment));
As you see you have to use toKeyString() method (included in
ModuleTestBase), and not the toString() method of the POJO.
This technique also works with OX2.
View
property
View properties does not exist in OX3. But it's easy to simulate them
using
transient
properties in your model.
That is, in OX2 you write:
<view>
<property name="deliveredBy">
<valid-values>
<valid-value value="employee"/>
<valid-value value="carrier"/>
</valid-values>
<default-value-calculator
class="org.openxava.calculators.IntegerCalculator">
<set property="value" value="0"/>
</default-value-calculator>
</property>
<property-view property="deliveredBy">
<on-change class="org.openxava.test.actions.OnChangeDeliveryByAction"/>
</property-view>
...
</view>
And now using OX3 you will write:
@Transient
@DefaultValueCalculator(value=EnumCalculator.class,
properties={
@PropertyValue(name="enumType", value="org.openxava.test.model.Delivery$DeliveredBy")
@PropertyValue(name="value", value="CARRIER")
}
)
@OnChange(OnChangeDeliveryByAction.class)
private DeliveredBy deliveredBy;
public enum DeliveredBy { EMPLOYEE, CARRIER }
With the same effect.