Features of Athena Framework for Flex

In addition to distinguished features of Athena for Java, Athena Framework for Flex offers the following features for Flash/Flex/AIR development:

Unified Object Model on the Server and on the Client

Traditionally, developers need to develop two different sets of classes to represent the same object model for the server side (Java) and for the client side (Flex). Such mismatch results confusion and inefficiency. With Athena Framework, you get unified object model on the server and on the client. During code generation, Athena Console generates entity classes for both Java and Flex. For an entity class, its Java class and Flex class are essentially the same - the only difference is the language syntax. To perform the same operation, you can either do it on the server side or on the client side. For example, to create a Department object and an Employee object, you may use the following code in Java on the server side:

Department dept = (Department) uow.createNewInstance(Department_EO.SYSTEM_NAME);
dept.setNameFull("R & D");
Employee emp = (Employee) uow.createNewInstance(Employee_EO.SYSTEM_NAME);
emp.setFirstName("Alan");
emp.setLastName("Turing");
emp.setDepartment(dept);

uow.flush();

Alternatively, you may creates the objects on the client side and remotely save them on the server side:

// -- Client side code (AS3) --
var dept:Department = Department.createNewInstance();
dept.nameFull = "R & D";
var emp:Employee = Employee.createNewInstance();
emp.firstName = "Alan";
emp.lastName = "Turing";
emp.department = dept;

eoService.invokeService("empService", "save", [emp], onSaveSuccess, onSaveError, null);

// -- Server side code (Java) --
/** On employee saved success */
protected function onSavedSuccess(e:EventRemoteOperationSuccess):void { 
  var savedEmployee:Employee = e.data as Employee;
  trace("Employee saved: ID: " + savedEmployee.employee_ID + ", department: " + savedEmployee.department.nameFull);
}

// --- The corresponding server side Java code: ---
public class EmpService extends AbstractService {
  ...
  public Object saveEmployee(Employee employee) {
     return doPerisist(employee, false);
  }
}

In case of editing multiple related objects, manipulating objects on the client side will greatly simplifies the whole process.

Loading Objects through EJBQL to the Client Side

Loading objects through EJQBL on the client side is the same as on the server side except that loading on the client side is done asynchronously. Sample code:

var ejbql:String = "SELECT d FROM Department d ORDER BY d.nameFull";
eoService.invokeService("empService", "executeQuery", [ejbql], onQuerySuccess, onQueryError, null);

protected function onQuerySuccess(e:EventRemoteOperationSuccess):void { // listener (success)
	var depts:ArrayCollection = e.data as ArrayCollection;
	trace("Departments loaded, total: " + depts.length);
}

protected function onQueryError(e:EventRemoteOperationError):void { // listener (error)
	trace("Failed to load departments: " + e.message);
}

The corresponding server side code:

public class EmpService extends AbstractService { 
  ...
  public List executeQuery(String ejbql) {
    return eoContext.createSelectQuery(ejbql).getResultList();
  }
  ...
}

Once the objects are loaded, you can manipulate them as you wish, e.g., binding them to the UI, modifying them, etc.

Partial Object Loading

Partial objects are objects with only some of their properties loaded. For example, we only need to load the name and other essential properties of the Employee class if we want to display a list of employees on the UI. Only the when user clicks on a certain Employee object, we then fully load the selected object. Partial object loading significantly reduces CPU, memory and network usage. Before diving into the details, you need to understand how unit of work works.

Unit of Work Basics

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. org.athenasource.framework.eo.core.UnitOfWork is Athena's implementation of the Unit of Work pattern. UnitOfWork ensures uniqueness of EOObject. Each database record results maximum one enterprise object in a UnitOfWork.

By default, enterprise objects (instances of entity classes) returned from the server are being put into a UnitOfWork. If no UnitOfWork is specified when you make the remote call, a new instance of UnitOfWork is created for the enterprise objects returned. If the same database record is loaded again to a UnitOfWork, the existing corresponding enterprise object will be returned instead of creating a new object. Existing enterprise objects will updated if they are outdated (version lower than db). A partial enterprise object will be updated with full properties when a complete loading is performed.

Partial Object Loading Through EJBQL

To load partial objects, you simple execute EJBQLs with the special po_ attribute loading properties. For example,

var uow:UnitOfWork = new UnitOfWork("myuow");
var ejbql:String = SELECT e FROM Employee e [e.department:S]{po_e='employee_ID, firstName, lastName, department_ID'}";
eoService.invokeService("empService", "executeQuery", [ejbql], onQuerySuccess, onQueryError, uow); // returned objects will be merged into the specified unit of work.

Loading Full Object

If the user selects an employee to view the details, we need to load the full properties of the object:

eoService.invokeService("empService", "executeQuery", ["SELECT e FROM Employee e WHERE e.employee_ID = " + selectedEmp.employee_ID], onLoadFullEmpSuccess, null, uow); // specifies the same unit of work.

Once the remote call returns, a full Employee object is returned and merged into the existing UnitOfWork. Once all the properties are available, the user can see the full details of the employee.

Automatic Relationship Target Objects Loading (Auto Resolution/Faulting)

Relationship target objects can be loaded together with the source objects in EJBQL. For example, "SELECT d FROM Department d [d.employees:S]" loads all deparments with Department.employees relationship target objects (Employees). Suppose an object deparment with its employees relationships unresolved, any access to deparment.employees will trigger automatic resolution of the relationship target objects. On the server side, the resolution is performed synchronously. Due to the nature of the Flex application, the resolution is performed asynchronously.

For example, when the user selects a department from the data grid on the left, its employees will be listed on the other data grid through automatic relationship target object loading:

The corresponding code:

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
  xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" 
  creationComplete="application1_creationCompleteHandler(event)">

	<fx:Script>
		<![CDATA[
			import com.test.Department;
			import com.test.Employee;
			import mx.collections.ArrayCollection;
			import mx.events.FlexEvent;
			import org.athenasource.framework.eo.core.EOService;
			import org.athenasource.framework.eo.core.ioc.EOServiceLocator;
			import org.athenasource.framework.eo.remoting.event.EventEOService;
			import org.athenasource.framework.eo.remoting.event.EventRemoteOperationSuccess;

			var eoService:EOService; // EO service

			[Bindable]
			public var departments:ArrayCollection = new ArrayCollection(); // all departments.

			protected function application1_creationCompleteHandler(event:FlexEvent):void {
				// Initialize eoService
				eoService = new EOService("http://localhost:8080/JavaEmployeeDir/messagebroker/amf", "eo", 2, true, onEoServiceEvent);
				// Set Service Locator
				EOServiceLocator.getInstance().eoService = eoService;
			}

			protected function onEoServiceEvent(event:EventEOService):void { // Called when eo service is ready.
				if(event.kind == EventEOService.KIND_LOGIN_SUCCESS) {
					trace("Metadata loaded successfully.");
				    trace("Loading employees and departments ...");
				    eoService.invokeService("empService", "loadDepts", [], onLoadDeptsSuccess, null);
				}else if(event.kind == EventEOService.KIND_LOGIN_ERROR || event.kind == EventEOService.KIND_META_LOAD_ERROR) {
					trace("Failed to load metadata: " + event.errorMessage);
				}
			}

			protected function onLoadDeptsSuccess(e:EventRemoteOperationSuccess):void {
				trace("Departments loaded successfully.");
				departments = e.data as ArrayCollection;;
			}
		]]>
	</fx:Script>

	<mx:DataGrid x="10" y="37" id="gridDepts" dataProvider="{departments}" width="195" height="141">
		<mx:columns>
			<mx:DataGridColumn headerText="Full name" dataField="nameFull"/>
		</mx:columns>
	</mx:DataGrid>

	<mx:DataGrid x="252" y="36" id="gridEmps" dataProvider="{gridDepts.selectedItem.employees}" width="248" height="142">
		<mx:columns>
			<mx:DataGridColumn dataField="firstName"/>
			<mx:DataGridColumn dataField="lastName"/>
		</mx:columns>

	</mx:DataGrid>
	<s:Label x="10" y="10" text="Departments" fontSize="20" width="302"/>
	<s:Label x="252" y="16" text="Employees in selected department:"/>
</s:Application>

Server side code:

public class EmpService extends AbstractService {
  public List loadDepts() {
    EOContext eoContext = createEOContext();
    return eoContext.createSelectQuery("SELECT d FROM Department d ORDER BY d.nameFull").getResultList();
  }
}

When the user selects a department from the datagrid on the left, gridDepts.selectedItem is set. As the data provider of the data grid for displaying employee list is bound to gridDepts.selectedItem.employees, the employees relationship of the selected Department object is trigger to resolve. Once the target Employee objects of the relationship is returned from the server, they are displayed on the data grid.

For additional features of Athena Framework, please click here.

http://directory.flashflex.com