Saturday, July 19, 2008

CRUD operations in Flex with ADF BC

With Adobe LifeCycle Data Services we can do CRUD operations in Flex. For this example I am using ADF Business Components ( I am an Oracle Dude), but you can always use Hibernate or a SQL datasource instead. Because I choose ADF BC I have to do a lot. Maybe someday there will be a standard adf bc adapter for LifeCycle and then I only have to some configuration work (like in hibernate).
The great thing is that LifeCycle keeps all the clients in sync which are using the same data service, even in different flex projects ( Data Push).

First you have to setup a jdeveloper LifeCycle project, see my previous post
For my example I am using the employee table in the HR schema. Make sure you the java types instead the Oracle java types else you have to do some extra casting

To get this working in LifeCycle we have to create three classes and do some configuration work.
Create an employee object class, I already tried the use the RowImpl of the employee viewobject but this does not work in all cases. When I update a record in Flex the updated RowImpl is send back to the lifecycle server where I got a dead viewobject row error. So we have to do it manually. To make life a little bit simplier I use two methods which can transform this employee object to EmployeeRowImpl and back.

package nl.ordina.flex.adfbc;

import java.math.BigDecimal;
import java.sql.Date;

import nl.ordina.flex.model.dataaccess.EmployeesViewImpl;
import nl.ordina.flex.model.dataaccess.EmployeesViewRowImpl;

public class Employee {

private Integer employeeId;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
private Date hireDate;
private String jobId;
private Double salary;
private Integer commissionPct;
private Integer managerId;
private Integer departmentId;


public Employee() {
}



public Object getAttribute(String attribute) {
if ( attribute.equalsIgnoreCase("firstname")) {
return firstName;
}
if ( attribute.equalsIgnoreCase("lastname")) {
return lastName;
}
if ( attribute.equalsIgnoreCase("departmentId")) {
return departmentId;
}
if ( attribute.equalsIgnoreCase("managerId")) {
return managerId;
}

return null;
}

public Employee transform( EmployeesViewRowImpl row) {
Employee emp = new Employee();
emp.setEmployeeId(row.getEmployeeId().intValue());
emp.setFirstName(row.getFirstName());
emp.setLastName(row.getLastName());
if ( row.getCommissionPct() != null) emp.setCommissionPct(row.getCommissionPct().intValue());
if ( row.getDepartmentId() != null) emp.setDepartmentId(row.getDepartmentId().intValue());
if ( row.getEmail() != null) emp.setEmail(row.getEmail());
if ( row.getHireDate() != null) emp.setHireDate(row.getHireDate());
if ( row.getJobId() != null) emp.setJobId(row.getJobId());
if ( row.getManagerId() != null) emp.setManagerId(row.getManagerId().intValue());
if ( row.getPhoneNumber() != null) emp.setPhoneNumber(row.getPhoneNumber());
if ( row.getSalary() != null) emp.setSalary(row.getSalary().doubleValue());
return emp;
}

public EmployeesViewRowImpl transform(EmployeesViewImpl view, Employee employee) {
EmployeesViewRowImpl row = (EmployeesViewRowImpl)view.createRow();
row.setEmployeeId(new BigDecimal(employee.getEmployeeId()));
if ( employee.getFirstName() != null) row.setFirstName(employee.getFirstName());
row.setLastName(employee.getLastName());
if ( employee.getCommissionPct() != 0) row.setCommissionPct(new BigDecimal(employee.getCommissionPct()));
if ( employee.getDepartmentId() != null) row.setDepartmentId(new BigDecimal(employee.getDepartmentId()));
if ( employee.getEmail() != null) row.setEmail(employee.getEmail());
row.setHireDate(employee.getHireDate());
row.setJobId(employee.getJobId());
if ( employee.getManagerId() != 0) row.setManagerId(new BigDecimal(employee.getManagerId()));
if ( employee.getPhoneNumber() != null) row.setPhoneNumber(employee.getPhoneNumber());
if ( !employee.getSalary().isNaN()) row.setSalary(new BigDecimal(employee.getSalary()));
return row;
}




public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}

public Integer getEmployeeId() {
return employeeId;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getFirstName() {
return firstName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getLastName() {
return lastName;
}

public void setEmail(String email) {
this.email = email;
}

public String getEmail() {
return email;
}

public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public String getPhoneNumber() {
return phoneNumber;
}

public void setHireDate(Date hireDate) {
this.hireDate = hireDate;
}

public Date getHireDate() {
return hireDate;
}

public void setJobId(String jobId) {
this.jobId = jobId;
}

public String getJobId() {
return jobId;
}

public void setSalary(Double salary) {
this.salary = salary;
}

public Double getSalary() {
return salary;
}

public void setCommissionPct(Integer commissionPct) {
this.commissionPct = commissionPct;
}

public Integer getCommissionPct() {
return commissionPct;
}

public void setManagerId(Integer managerId) {
this.managerId = managerId;
}

public Integer getManagerId() {
return managerId;
}

public void setDepartmentId(Integer departmentId) {
this.departmentId = departmentId;
}

public Integer getDepartmentId() {
return departmentId;
}
}


Create an EmployeeService which does the CRUD operations in ADF BC. Make sure this is a singleton else you can get a lot of open conflicting connections to the database

package nl.ordina.flex.adfbc;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import nl.ordina.flex.model.dataaccess.EmployeesViewImpl;
import nl.ordina.flex.model.dataaccess.EmployeesViewRowImpl;
import nl.ordina.flex.model.service.EmployeeModuleImpl;

import oracle.jbo.client.Configuration;

public class EmployeeService {

private EmployeeModuleImpl am;
private static EmployeeService instance = null;

public EmployeeService() {
am = (EmployeeModuleImpl)Configuration.createRootApplicationModule("nl.ordina.flex.model.service.EmpoyeeModule", "EmployeeModuleLocal");
}

public static EmployeeService getInstance() {
if(instance == null) {
instance = new EmployeeService();
}
return instance;
}


public List getEmployees() {
List list = new ArrayList();
Employee emp = new Employee();
EmployeesViewImpl empView = am.getEmployeesView1();
empView.executeQuery();
while (empView.hasNext()) {
EmployeesViewRowImpl row = (EmployeesViewRowImpl) empView.next();
list.add(emp.transform(row));
}
return list;
}


public Employee getEmployee(Integer employeeId) {
EmployeesViewImpl view = am.getEmployeesView1();
Employee emp = new Employee();
view.setid(new BigDecimal(employeeId));
view.executeQuery();

if ( view.hasNext()) {
EmployeesViewRowImpl row = (EmployeesViewRowImpl)view.next();
view.setid(null);
view.executeQuery();
return emp.transform(row);
} else {
view.setid(null);
view.executeQuery();
return null;
}
}

public Employee create(Employee employee) {
EmployeesViewImpl view = am.getEmployeesView1();
Employee emp = new Employee();

view.insertRow(emp.transform(view,employee));
am.getDBTransaction().commit();
return employee;
}

public static String capitalize(String s) {
if (s.length() == 0) return s;
return s.substring(0, 1).toUpperCase() + s.substring(1);
}


public boolean update(Employee employee, List changes) {
EmployeesViewImpl view = am.getEmployeesView1();
view.setid(new BigDecimal(employee.getEmployeeId()));
view.executeQuery();
if ( view.hasNext()) {
EmployeesViewRowImpl row = (EmployeesViewRowImpl)view.next();

for (int i = 0 ; i < changes.size(); i++ ) {
String attribute = capitalize(changes.get(i).toString());
Object value = employee.getAttribute(attribute);
row.setAttribute(attribute,value);
}
am.getDBTransaction().commit();
view.setid(null);
view.executeQuery();

return true;
} else {
view.setid(null);
view.executeQuery();
return false;
}
}

public boolean delete(Employee employee) {
EmployeesViewImpl view = am.getEmployeesView1();
view.setid(new BigDecimal(employee.getEmployeeId()));
view.executeQuery();
view.setid(null);
if ( view.hasNext()) {
EmployeesViewRowImpl row = (EmployeesViewRowImpl)view.next();
row.remove();
am.getDBTransaction().commit();
view.setid(null);
view.executeQuery();
return true;
} else {
view.setid(null);
view.executeQuery();
return false;
}
}
}

We have to create Assembler class which is used by the LifeCycle server

package nl.ordina.flex.adfbc;

import java.util.List;
import java.util.Collection;
import java.util.Map;

import flex.data.DataSyncException;
import flex.data.assemblers.AbstractAssembler;

public class EmployeeAssembler extends AbstractAssembler {

public Collection fill(List fillArgs) {
EmployeeService service = EmployeeService.getInstance();
return service.getEmployees();
}

public Object getItem(Map identity) {
EmployeeService service = EmployeeService.getInstance();
return service.getEmployee((Integer)identity.get("employeeId"));
}

public void createItem(Object item) {
EmployeeService service = EmployeeService.getInstance();
service.create((Employee) item);
}

public void updateItem(Object newVersion, Object prevVersion, List changes) {
EmployeeService service = EmployeeService.getInstance();
boolean success = service.update((Employee) newVersion, changes);
if (!success) {
Integer employeeId = ((Employee) newVersion).getEmployeeId();
throw new DataSyncException(service.getEmployee(employeeId), changes);
}
}

public void deleteItem(Object item) {
EmployeeService service = EmployeeService.getInstance();
boolean success = service.delete((Employee) item);
if (!success) {
Integer employeeId = ((Employee) item).getEmployeeId();
throw new DataSyncException(service.getEmployee(employeeId), null);
}
}

}

Now the last step in jdeveloper is to add a new data service entry to the lifecycle configuration ( data-management-config.xml located in the flex folder)

<?xml version="1.0" encoding="UTF-8"?>
<service id="data-service"
class="flex.data.DataService">

<adapters>
<adapter-definition id="actionscript" class="flex.data.adapters.ASObjectAdapter" default="true"/>
<adapter-definition id="java-dao" class="flex.data.adapters.JavaAdapter"/>
</adapters>

<default-channels>
<channel ref="my-rtmp"/>
</default-channels>

<destination id="employee">
<adapter ref="java-dao" />
<properties>
<cache-items>false</cache-items>
<use-transactions>false</use-transactions>
<source>nl.ordina.flex.adfbc.EmployeeAssembler</source>
<scope>application</scope>
<metadata>
<identity property="employeeId" type="java.math.BigDecimal" />
</metadata>
<network>
<session-timeout>20</session-timeout>
<paging enabled="false" pageSize="100" />
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="REPLACE" max-frequency="500"/>
</network>
</properties>
</destination>

</service>


Now we can create a Flex lifecycle project ( For more information on creating a flex j2ee project see one of my BlazeDS examples. But here we have to create the employee object too. Create an new Class called Employee

package
{
import flash.display.InteractiveObject;

[Managed]
[RemoteClass(alias="nl.ordina.flex.adfbc.Employee")]

public class Employee
{
public function Employee()
{
}
public var employeeId:int;
public var firstName:String;
public var lastName:String;
public var email:String;
public var phoneNumber:String;
public var hireDate:Date;
public var jobId:String;
public var salary:Number;
public var commissionPct:Number;
public var managerId:int;
public var departmentId:int;
}
}

Add the employee destination to mx:DataService. Import the employee object by adding xmlns="*" to the mx:Application element. Now you can use <Employee/> in Flex.



<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
width="1155" height="276"
applicationComplete="ds.fill(employeeArray)" >

<mx:ArrayCollection id="employeeArray"/>
<mx:DataService id="ds" destination="employee"/>

<Employee/>

<mx:DataGrid dataProvider="{employeeArray}" editable="true"
width="100%" height="100%"/>

</mx:Application>

Start the flex project twice so you can see the data push in action.

2 comments:

  1. Hi,
    Could you also prepare small blog about Flex, ADF BC deployed in Oracle WLS with security enabled? I like to know how that can be achieved so i can see which authenticated user in BC is providing operation. How to setup Flex and if it is possible.
    Thanks in advance.
    P.S. Your blogs are very usefull - thanks for sharing them via blogs

    Regards
    Robert

    ReplyDelete
  2. how do I get this line "view.setid (new BigDecimal (employeeId));". not locate the setid.

    ReplyDelete