Project

General

Profile

Upgrading Hand-Written Java to FWD4

The main change when upgrading to FWD4 is moving the persistence engine for the generic Hibernate framework to FWD's custom ORM. If your application has some pieces of code which make use the Hibernate APIs, they must be adjusted to take advantage of the much faster persistence implementation. The next paragraph will guide you to porting the application to the new approach.

Drop all imports related to Hibernate

Usually, the Hibernate specific imports are similar to these:

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.HibernateException;
import org.hibernate.Query;

Just delete these lines. We are not using Hibernate at all. Instead, one or both of the below FWD specific import might be needed:

import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.orm.*;

Switching Transaction

Some pieces of the existing code use local Hibernate transactions. Example:

        Transaction transaction = null;
        try
        {
            transaction = session.beginTransaction();

            // business logic here

            transaction.commit();
        }
        catch (Exception e)
        {
            if (transaction.isActive() && !transaction.wasCommitted())
            {
                transaction.rollback();
            }
        } 

In FWD, the beginTransaction() method of Session returns a boolean instead, signalling a new transaction was opened at that point. Of course, the programmer is in charge of keeping the balance by closing the transactions he opens once the interaction with the database is over. The above code will be rewritten as:

        boolean transStarted = false;
        try
        {
            transStarted = session.beginTransaction();

            // business logic here

            if (transStarted)
            {
               session.commit();
            }
        }
        catch (Exception e)
        {
            if (session.isOpen() && session.inTransaction() && transStarted)
            {
                session.rollback();
            }
        } 

Working with DMOs

There are no Impl classes to write

Yes, that is the good news! In Hibernate approach, the programmer had to write two separate files for each table instance: a DMO interface in which to specify how the properties will be accessed by using setters and getters and an Impl class which was adding the implementation for each of those properties. Beside that, a .hbm.xml file was needed for describing mapping specific to respective table.

FWD4 takes another approach by creating the Impl classes on-the-fly, at runtime. There is no need for the .hbm.xml because all needed meta information for a field is available for each property as an Annotation. This greatly simplify the complexity and allows the properties to have supplementary 4GL-specific attributes declared in same place.

Let's assume we have the old hand-written interface/class pair for an entity named Employee:

package my_app.dmo.my_database;

import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.util.*;

public interface Employee
extends DataModelObject
{
   public character getName();

   public void setName(Text name);

   public integer getEmpNo();

   public void setEmpNo(Number empNo);

   public decimal getSalary();

   public void setSalary(Number salary);
}
package my_app.dmo.my_database.impl;

import java.io.Serializable;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.util.*;

public class EmployeeImpl
implements Serializable,
           Persistable,
           Employee
{
   private Long id = null;    /** Surrogate primary key */

   @LegacyField(name = "name", fieldId = 1)
   private character name;

   @LegacyField(name = "empNo", fieldId = 2)
   private integer empNo;

   @LegacyField(name = "salary", fieldId = 3)
   private decimal salary;

   public EmployeeImpl()
   {
      name = new character();
      empNo = new integer();
      salary = new decimal();
   }

   public Undoable deepCopy()
   {
      Undoable copy = new EmployeeImpl();
      copy.assign(this);
      return copy;
   }

   public void assign(Undoable other)
   {
      setName(other.getName());
      setEmpNo(other.getEmpNo());
      setSalary(other.getSalary());
   }

   public Long getId()
   {
      return id;
   }

   public character getName()
   {
      return new character(name);
   }

   public void setName(Text name)
   {
      this.name.assign(name);
   }

   public integer getEmpNo()
   {
      return new integer(empNo);
   }

   public void setEmpNo(Number empNo)
   {
      this.empNo.assign(empNo);
   }

   public decimal getSalary()
   {
      return new decimal(salary);
   }

   public void setSalary(Number salary)
   {
      this.salary.assign(salary);
   }
}

The FWD4 variant is much simpler: the Impl and .hbm.xml (which was dropped here) are gone, only the annotations from Impl are moved (and eventually enhanced) to the corresponding getter of the DMO interface:

package my_app.dmo.my_database;

import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.annotation.*;
import com.goldencode.p2j.util.*;

@Table(name = "employee", legacy = "employee")
@Indices(
{
   @Index(name = "idx__empNo", legacy = "idx-emp-no", primary = true, unique = true, components =
   {
      @IndexComponent(name = "empNo", legacy = "emp-no")
   }),
})
public interface Employee
extends DataModelObject
{
   @Property(id = 1, name = "name", column="name", legacy = "name", format = "x(16)", order = 10)
   public character getName();

   public void setName(Text name);

   @Property(id = 2, name = "empNo", column="emp_no", legacy = "emp-no", format = ">>>>>9", order = 20)
   public integer getEmpNo();

   public void setEmpNo(Number empNo);

   @Property(id = 3, name = "salary", column="salary", legacy = "sal", format = "->,>>>,>>9.99", scale = 2, order = 30)
   public decimal getSalary();

   public void setSalary(Number salary);
}
To note:
  • the interface has two new annotations: @Table and @Indices. The former allows to specify meta data related to the table, and the latter, if present, lists the indices the table may have.
  • the legacy attribute allow to specify the legacy name (original name in ABL code) of the table, index or field. This is mandatory.
  • the @Property annotation defines the property name in Java, its legacy name and column name in database and other 4GL specific attributes like the format or scale here present. They are taken directly from the table definitions of the permanent database (the .df file) or temp-table. Other attributes include: help, initial, label, columnLabel, validateMessage, validateExpression, to name only the most important.

Creation of DMO instances.

With Hibernate, the programmer was required to provide an implementation for each DMO interface where he was in charge of the management of the properties of the DMO. To create a record, the constructor of the implementing class was invoked directly:

   EmployeeImpl newEmp = new EmployeeImpl();

In FWD, all DMOs behaves in a well known manner because the implementation is dynamic and automatic. All fields are treated the same and the programmer cannot interfere in the process, but at the same time this simplifies his work. The instantiation of these objects is performed using the DataObjectFactory, as in following example:

   DataObjectFactory doFactory = DataObjectFactory.getInstance(session);
   Employee newEmp = doFactory.createDMO(Employee.class);

The factory will use the provided Class interface to identify the dynamically generated implementation, use its constructor to create the object and return the new instance. The same factory can be used for multiple records which will be persisted to same database. You may use any of the multiple createDMO methods overloaded, depending on the context.

Accessing DMO properties

All accesses to the properties of the record are performed via the DMO interface rather than the implementation class. There should not be any reason to break the object encapsulation and FWD API does not provide any means to access the internal data directly.

The only constraint is that the properties are of types which extend BaseDataTypes, not Java objects (wrapped or not). Example:

   newEmp.setName(new character("John Doe"));
   integer age1 = newEmp.getAge();
   integer age2 = new integer();
   age2.assign(newEmp.getAge());

Here are some examples of incorrect constructs which will cause compile errors:

   newEmp.setName("John Doe");   // the setter does not take a java.lang.String argument
   int age = newEmp.getAge();    // the returned value is an @integer@, which cannot be assigned to scalar variable age

Storing and deleting records

Just like in Hibernate, these operations are performed using the Session instance. To save a record to database use:

   session.save((Record) newEmp, true);

and for delete operation:

   session.delete((Record) newEmp);

Of course, while doing any of these a transaction must be opened.

Extending DMO (DTOs)

There are cases when an existing DMO interface needs to be extended to accommodate additional properties. We encountered such cases in which a DTO required supplementary properties in order to transfer additional data. In this case, the responsibility for writing the code belongs to the programmer. He must add the new properties as fields and write the setters and getters for accessing them. For these, it is not required to use only BaseDataTypes. Here is a simple example:

public abstract class EmployeeDTO
implements Employee
{
    boolean manager;

    boolean isManager()
    {
       return manager;
    }

    void setManager(boolean on)
    {
       manager = on;
    }
}

As you noted, the new class is not complete (it still lacks the implementation for the properties inherited from Employee DTO) so it is marked as abstract. All declared methods must be also implemented and must not implement the methods declared in the DMO interface. There is still one more constraint: the object is created by adding the new property to an existing instance of its DMO type (in this case an Employee). To create an instance of such class the DataObjectFactory is used:

   Employee empDmo = ...;

   EmployeeDTO empDTO = DataObjectFactory.createDTO(EmployeeDTO.class, empDmo);

   // usage:
   empDTO.setName("Jane Done");
   empDTO.setManager(true);

Notice that the properties from both definitions are accessed for the same object.
How does this work? The actual object returned by createDTO is a proxy. The methods declared and implemented in the abstract class are accessed directly, while those belonging to DMO interface are delegated to the equivalent methods from the DMO object used as parameter.

FQL and queries

From HQL to FQL

FQL is a query language based on Hibernate's HQL with some minor differences. Converting from old HQL strings to FQL is usually straightforward.
For example, with Hibernate we had:

   String empNo = ...; // or some kind of parameter

   String hql = "from " + Employee.class.getName() + " where empNo = :empNo";
   Query query = session.createQuery(hql);
   query.setParameter("empNo", P2jTypeConversionHelper.convertStringToInteger(empNo));

   List<Employee> customerOrderList = query.list();

In FWD, some things are a bit changed: the named parameters are replaced with positional parameters and the creation of the query is a static method, so we write:

   String empNo = ...; // or some kind of parameter

   String hql = "from " + Employee.class.getName() + " where empNo = ?0";
   Query query = Session.createQuery(fql);
   query.setParameter(0, P2jTypeConversionHelper.convertStringToInteger(empNo));

   List<Employee> customerOrderList = query.list(session);

In both cases, the list() method returns a list of record objects from the table associated with Employee with specific properties.

Criteria and Restrictions to FQL

FWD does not have (yet) alternative support for creating queries like Criteria API of Hibernate which allows to create Criterion objects which would drive the record filtering instead of the HQL string. If the old application has occurrences of these, they must be converted to FQL.

Example assuming we have the following code:

   integer sal = ... ; // or some kind of parameter
   character patt = ... ; // or some kind of parameter

   Criteria cr = session.createCriteria(Employee.class);

   Criterion salary = Restrictions.gt("salary", sal.intValue());
   Criterion name = Restrictions.ilike("firstNname", patt.javaValue());

   // To get records matching with OR conditions
   LogicalExpression orExp = Restrictions.or(salary, name);
   cr.add(orExp);

   cr.setMaxResults(10);
   List results = cr.list();

We need to create a FQL string which contains every Criterion converted to a substring and the applied logical operation to match the same predicate

   integer sal = ... ; // or some kind of parameter
   character patt = ... ; // or some kind of parameter

   String fql = "from " + Employee.class.getName() + " where " +
                   "salary > ?0 or " +       // Criterion salary and orExp LogicalExpression
                   "firstNname like ?1 " +   // Criterion name
                   "limit 10";               // equivalent of setMaxResults()

   Query query = Session.createQuery(fql);
   query.setParameter(0, sal);
   query.setParameter(1, patt);

   List<Employee> results = query.list(session);