Pages

Thursday, July 31, 2008

Try the new Oracle Metalink site build in Flex

James Ward already announced it that Oracle is using Flex for their console websites like the Enterprise Manager website but now you can try the new Oracle Metalink in Flex. The new Metalink looks really cool. Try it yourself just use your own metalink account.






Flex CRUD application with drag and drop and LifeCycle

I remade my old Drag and Drop Employee application by using Adobe LifeCycle Data Services( which is free for one cpu ). In the first edition I used BlazeDS, but with LifeCycle it is a little bit easier to do CRUD operations on an Employees table. In BlazeDS I had to made different java methods which does the CRUD operations. Now I only have to add an new Employee object to the ArrayCollection or update an Employee oject in the ArrayCollection and LifeCycle does the transaction. But LifeCycle has something extra it can push the changes to the other Flex clients which are using the same Data Service.
This are the features of the demo application. I can drag an employee in the Tree to an other department. Click on an employee and a new update Tab is shown where I can update the employee or I can create an new employee in a create employee tab.
Here are some screenshots.




To let this example work you need to do the following things.

setup lifecycle in jdeveloper

configure lifecycle for the employees table

download Flexlib for the dragable and closeable Tabs. These guys did a perfect job.

Here is the source code of java side and here is the Flex code

These are the things I needed to do.

<mx:DataService id="ds" destination="employee" message="messageHandler(event.message)"/>

<mx:ArrayCollection id="employeeArray" />

Step 1 fill the employeeArray


public function init():void{
ds.autoCommit=false;
ds.autoSyncEnabled = true;
ds.addEventListener(ResultEvent.RESULT, resultHandler);
ds.addEventListener(DataServiceFaultEvent.FAULT, faultHandler);
ds.addEventListener(DataConflictEvent.CONFLICT, conflictHandler);
var token:AsyncToken = AsyncToken(ds.fill(employeeArray));
token.kind = "fill";
}

Step 2 wait for the employee retrieving result, sort this result on departmentId and Firstname and build a new xml from the Arraycollection for the Tree.

private function resultHandler(event:ResultEvent):void
{
if (event.token.kind == "fill")
{
// fill tree
sortEmployees();
refreshTree();
}
}

private function sortEmployees():void {
var deptSortField:SortField = new SortField();
deptSortField.name = "departmentId";
deptSortField.numeric = true;
var firstNameSortField:SortField = new SortField();
firstNameSortField.name = "firstName";
firstNameSortField.numeric = false;

/* Create the Sort object and add the SortField object created earlier to the array of fields to sort on. */
var employeeDataSort:Sort = new Sort();
employeeDataSort.fields = [deptSortField,firstNameSortField];

/* Set the ArrayCollection object's sort property to our custom sort, and refresh the ArrayCollection. */
employeeArray.sort = employeeDataSort;
employeeArray.refresh();
}
public function refreshTree():void{
mainXML = new XMLDocument();
var rootXML:XMLNode = mainXML.createElement("root");
rootXML.attributes.type = "root";

for ( var i:int = 0 ; i < employeeArray.length ; i++ ) {

var department:int = employeeArray.getItemAt(i).departmentId;
var search:Array = rootXML.childNodes;
var deptXML:XMLNode = null;

if ( search.length > 0 ) {
for ( var b:int =0 ; b < search.length ; b++ ) {
if ( search[b].attributes.id == department.toString()) {
deptXML = search[b];
break;
}
}
}
if ( deptXML==null ) {
deptXML = mainXML.createElement("department");
deptXML.attributes.label = department;
deptXML.attributes.type = "dept";
deptXML.attributes.id = department.toString();
rootXML.appendChild(deptXML);
}
var firstName:String = employeeArray.getItemAt(i).firstName;
var lastName:String = employeeArray.getItemAt(i).lastName;
var employee:int = employeeArray.getItemAt(i).employeeId;

var elementXML:XMLNode = mainXML.createElement("employee");
elementXML.attributes.label = firstName+" "+lastName;
elementXML.attributes.type = "emp";
elementXML.attributes.id = employee;
deptXML.appendChild(elementXML);
}
mainXML.appendChild(rootXML);
myXml = new XML(mainXML.toString());

tree.dataProvider = myXml;
openAllNodes();
}


Step 3 When we want to change an employee we have to find this employee, we will use a filter function and I add the code for an update and an insert

private function filterEmployees():void {
employeeArray.filterFunction = processFilter;
employeeArray.refresh();
}

private function removeFilterEmployees():void {
employeeArray.filterFunction = null;
employeeArray.refresh();
}


private function processFilter(employee:Object):Boolean {
return parseInt(employee.employeeId) == searchEmployeId;
}

// update
var employeeLocal:Object = null;
// search the employee
searchEmployeId = employeeId;
filterEmployees();
// if found we can update the department.
if ( employeeArray.length == 1 ) {
employeeLocal = employeeArray.getItemAt(0);
}
removeFilterEmployees();

employeeLocal.departmentId = departmentId;
employeeLocal.email = email;
employeeLocal.firstName = firstName;
employeeLocal.lastName = lastName;
employeeLocal.jobId = jobId ;
employeeLocal.hireDate = hireDate;


ds.commit();
var token:AsyncToken = AsyncToken(ds.fill(employeeArray));
token.kind = "fill";

// insert
employee = new Employee();
employee.employeeId = employeeId;
employee.departmentId = departmentId;
employee.email = email;
employee.firstName = firstName;
employee.lastName = lastName;
employee.jobId = jobId ;
employee.hireDate = hireDate;
employeeArray.addItem(employee);
ds.commit();
var token:AsyncToken = AsyncToken(ds.fill(employeeArray));
token.kind = "fill";

Wednesday, July 23, 2008

Load balancing with AS 10.1.3 and with Web Cache

This week I had to setup load balancing on three 10.1.3 OC4J containers so the load is spread is over the servers and when an oc4j container spontaneous reboots the others servers can handle the load until the container is back. We had a little problem where the production server (windows 32bits) gave a out of memory exception, an out of swap error and the application users had to wait for 5 minutes before they can work again.
I first used Oracle Web Cache which is a 10.1.2 Application Server product. This was pretty easy but very stupid in 10.1.3. ( the second part of this blog I will show you how easy this is in 10.1.3
The first step in Web Cache is to configure the listening ports for the load balancer.


In my case Web cache will listen on port 80 and 7778.

Now we can add the application servers ( original servers)

Define a website url for which you want to load balance, I will use the Enterprise Manager Website /em

The site is /em/
Add a site to server mapping where we connect the site em to the availible AS server
Now we have to make sure that a new web session is always run on the same original server else we cannot log in.

Use for the em website as session JSESSIONID and OC4J-based as binding mechanism. We are now ready with Web Cache. The only thing still to do is to add a virtual hsot to the httpd.conf of the Application Servers. We have to do this else the em website is redirected to one of the application server instead of the load balancer adress.


NameVirtualHost XPCND7010XMP.work.local:7777
<VirtualHost XPCND7010XMP.work.local:7777>
ServerName XPCND7010XMP.work.local
Port 80
</VirtualHost>
XPCND7010XMP.work.local:7777 is the application server on port 7777.
inside VirtualHost we use the adress of the load balancer.
That's all in webcache.

You don't have to do this in 10.1.3. Just install on the load balancer a http server ( custom install in the application server installer ).
Add in every AS Home the following topology lines to the opmn.xml and you have a cluster.

<notification-server interface="ipv4">
<port local="6100" remote="6200" request="5003"/>
<ssl enabled="true" wallet-file="$ORACLE_HOME/opmn/conf/ssl.wlt/default"/>
<topology>
<discover list="*233.0.0.2:1500"/>
</topology>
</notification-server>
On the application servers you don't need the http servers anymore so you can disable.
In this cluster you can have only one Enterprise Manager application so you have to disable it on all the application servers except one. ( server.xml and default-web-site.xml )
That's all in 10.1.3

Monday, July 21, 2008

OOW08 Mix presentations and ADF & SOA

The result of the oracle openworld 2008 mix competition are published. It really surprises me they are not so much ADF (not any) or SOA presentations selected as I would expect. The new ADF Rich Faces and Soa suite 11g are really cool stuff. So why is that, is ADF too difficult ? or are there already too much ADF presentations at OOW08. I think that the Oracle customers are now using the database, forms/designer and the BI tool of Oracle. I believe that the customers want to use ADF but the learning curve is too high that's why APEX and BI are so popular. The Oracle customers cannot transform their classic developers to ADF developer so easily.
The ADF developer community is not so large, it has to be a lot bigger to survive. To make this happen, Oracle has to promote and invest into JHeadstart ( it has to be more like APEX ) so the customer will make the step to go from APEX or Forms to Jheadstart. The second step Oracle has to do is to make sure that the BEA java developers will use ADF. So the ADF community will be large and important else ADF will only be used in the Oracle Apps.
By the way here are the SOA or Portal presentations which are selected by the Oracle mix people for OOW08.
BEA Aqualogic versus Oracle Fusion Middleware shoot out by Lonneke Dikmans
Oracle Portal, WebCenter and Stellent – which one should you use ? by by Eric Marcoux
How to Effectively use Web 2.0 Technologies within a Portal by Howard Block.

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.

Tuesday, July 15, 2008

Using Adobe LifeCycle in the embedded OC4J server

Using Adobe LifeCycle Data Services ES instead of BlazeDS gives you some extra features like security, WSRP portlets, data push or data synchronisation between the connected flex clients and you can generate PDF documents with LifeCycle. With BlazeDS you can only retrieve the data from the application server. With LifeCycle you can also do CRUD operations on this data and the best part is LiveCycle Data Services ES Express has been free for a single CPU server.
To use LifeCycle in jdeveloper you have to create a new web project and copy the lib and flex folder of the flex.war to the WEB-INF folder of the jdeveloper web project. Do the same with the web.xml. We only have to add some java options to the run configuration of the web project and we are ready to run it. Add -Doc4j.jmx.security.proxy.off=true -Doc4j.userThreads=true to the java options
Now you can run LifeCycle from jdeveloper and please try the Data services features with java , hibernate or SQL adapter.

Friday, July 11, 2008

Using EJB in Flex with blazeds

This blog explains how you can use ejb in Adobe Flex with blazeds. First thing I did is creating a application with the jsf, ejb template.

In the model project we can create a new entity bean. I use the CMP entity bean from tables.


For the EJB version I use Enterprise JavaBeans 2.1 option. The Oracle table I will use in this entity bean is the employee table of the HR schema.

Now we can create a Session Bean
If we look at the properties of the session bean we see the retrieveAllEmployees method. We will use this to display all the employees in flex.

We are ready to add the blazeds libraries to the viewcontroller project. Because we want to use EJB, we have to download an extra library. Here is the link of the ejb library which is made by Ryan J. Norris. Add also this library to the viewcontroller project.
Because we use ejb we have to add the embedded oc4j client and j2ee library to viewcontroller project.


Now we can configure blazeds. We have to create two files (remoting-config.xml and services-config.xml ) in WEB-INF/flex/ folder
Here is the remoting-config.xml file. In this file we define a destination with the name of the session bean in the model project.


<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service" class="flex.messaging.services.RemotingService">
<adapters>
<adapter-definition id="java-object"
class="flex.messaging.services.remoting.adapters.JavaAdapter"
default="true"/>
</adapters>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
<destination id="EmployeeEJB">
<properties>
<factory>ejb</factory>
<source>SessionEJB</source>
</properties>
</destination>
</service>

Here is the services-config.xml. In this file we define the ejb factory in my case I have to use com.adobe.ac.ejb.EJBFactory for ejb3 we have to use com.adobe.ac.ejb.EJB3Factory as factory class.

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
<services>
<service-include file-path="remoting-config.xml"/>
<default-channels>
<channel ref="my-amf"/>
</default-channels>
</services>
<factories>
<factory id="ejb" class="com.adobe.ac.ejb.EJBFactory" />
</factories>
<channels>
<channel-definition id="my-amf"
class="mx.messaging.channels.AMFChannel">
<endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
class="flex.messaging.endpoints.AMFEndpoint"/>
<properties>
<polling-enabled>false</polling-enabled>
</properties>
</channel-definition>
</channels>

<logging>
<target class="flex.messaging.log.ConsoleTarget" level="Error">
<properties>
<prefix>[Flex] </prefix>
<includeDate>false</includeDate>
<includeTime>false</includeTime>
<includeLevel>false</includeLevel>
<includeCategory>false</includeCategory>
</properties>
</target>
</logging>
</services-config>

Add the blazeds servlet to the web.xml

<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>

<listener>
<listener-class>flex.messaging.HttpFlexSession</listener-class>
</listener>

The last step in jdeveloper is to add -Doc4j.jmx.security.proxy.off=true to the run options of the viewcontroller project.

Let's open Flex builder 3 and create a new project.

We have to select j2ee server technology.

The second step is to add the webapp url's.
The mxml is very simple. First we add a remoteobject, then we are calling theretrieveAllEmployees method when the application is loaded and at last we display the result in a datagrid.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
width="1155" height="206.21213">
<mx:applicationComplete>
srv.retrieveAllEmployees()
</mx:applicationComplete>

<mx:RemoteObject id="srv" showBusyCursor="true"
destination="EmployeeEJB"/>
<mx:DataGrid width="1104.6212"
dataProvider="{srv.retrieveAllEmployees.lastResult}"
height="140.98485"/>

</mx:Application>

Here the result. That's all.

Wednesday, July 9, 2008

Save your searches in an ADF Query panel

Probably your already know the ADF Search form in Jdeveloper 11G . You can get this choice when you drag a viewobject from the datacontrol to the page. This is nice but it can be lot cooler and better. In 11g you can use Query panels. This panel not only looks better but the user can personalize this panel and save its searches. So the next time the user can use them again. The query panel also give you more search options like greater than or between.

In this blog I will show you what you need to do to make this work. You need to know a litte bit about MDS and ADF security, for more info read my previous blog.
The ADF Query panel not only displays the attributes of the viewobject but also the viewobject criteria's. For this I created a new criteria called SalaryCriteria. To do this yourself we have to open the viewobject, go to the query part and there we see the defined criteria's.
In this editor you can create a complex where clause. The bind variables you use in this criteria are displayed as search attributes in the ADF Query Panel.


When you open the viewobject in the datacontrol there is a sub folder called Named Criteria. You can drag one of the criteria's to the page. If you use All Queriable Attributes then you get as default, a query panel with all the queriable attributes of the viewobject. When we use the SalaryCriteria then only the salary bind variable is displayed, off course you can still use the other attributes for a search operation.
When you drag a named criteria on the jsf page you get a chioce to select a particular query panel. I now use the ADF Query Panel with Table. With Table means with a search result table.

Here you see result when we drag the all queriable attributes named criteria from the datacontrol. Now if we want to save the search we have to use the ADF Security wizard and add security to the page definition of this search page. The last step is to configure MDS in this web application. We have to enable MDS on the viewcontroller project and pages and of course. configure adf-config.xml for MDS, you can use the adf-config of my previous blog, you only have to change the folders of the metadata path.


When we press save in the Query Panel we get a dialog where we can save the search

In the top right of the Query Panel you can select your own saved searches or the view criteria's




Here you see what is happening when you save a search. ADF creates a persdef folder and in this folder ADF makes a copy of the used viewobject and adds the new search criteria to it. ADF does the same thing as we did manually in the viewobject.

Saturday, July 5, 2008

Customize and personalize your jsf pages with MDS

This large blog entry is about MDS (Metadata Services) which was introduced jdev 10.1.3.2 but for this MDS example I use JDeveloper 11g TP4. In 11g you can use mds in your jsf applications. In this blog I will show you, how to customize a table in a jsf page for a particular user. This is called seeded customization. For this you have to start jdeveloper in the customize mode and change the properties of the jsf components for this particular user. The changes are saved in a separate xml file. Off course this should never be done for a user ( use a role ). When this particular user start the web application the customized page is shown. Other users will only see the original page.
We can also use MDS to personalize the jsf pages. The user can change the jsf pages at runtime. This is called User Customization or change persistence. In the web application we have to define what the user can change. In this blog I will allow the user to change the order of columns in a table and the user can change the width of a column. The changes are saved in a MDS repository ( I will use a file based mds repository).
Step 1 is to create a fusion web application. Add database table to the bc4j model project. So we can use this as table in a jsf page then we have to change the mds properties of the viewcontroller project. See the picture what to change.
  • We have to enable Enable User customations then Across Sessions using MDS and at last Enable Seeded Customizations. Across sessions will store the changes in the mds repository, so the next time the user still sees the changes.
    Step 2 is the configuration of the adf-config.xml file

    In this file we have to add three things. The first thing we have to do is to define a mds repository. Make sure that you add the following folders ViewController/public_html , ViewController/adfmsrc and Model/src to the metadata-path property.
    The second thing are the customizations classes. You can use different classes at the same time. I use in this blog only one ( UserCC ) . You can also use SiteCC or SecurityRoleCC.

  • Now we only have to define which changes are stored in the mds repository


      In my case you can change the order of culumns in a table and the width of the column.
      Here is my adf-config.xml

      <?xml version="1.0" encoding="windows-1252" ?>
      <adf-config xmlns="http://xmlns.oracle.com/adf/config"
      xmlns:sec="http://xmlns.oracle.com/adf/security/config">
      <sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
      <CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
      credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
      <sec:JaasSecurityContext initialContextFactoryClass="oracle.adf.share.security.JAASInitialContextFactory"
      jaasProviderClass="oracle.adf.share.security.providers.jps.JpsSecurityContext"
      authorizationEnforce="true"
      authenticationRequire="true"/>
      </sec:adf-security-child>
      <app-config type="MDS" name="default"
      xmlns="http://xmlns.oracle.com/adf/mds/config">
      <mds-config version="11.1.1.000" xmlns="http://xmlns.oracle.com/mds/config">
      <persistence-config>
      <metadata-namespaces>
      <namespace path="/" metadata-store-usage="one"/>
      </metadata-namespaces>
      <metadata-store-usages>
      <metadata-store-usage id="one">
      <metadata-store name="mymetadatastore"
      class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
      <property name="metadata-path"
      value="D:/projecten/workspace/11g/mds_user_cc/ViewController/public_html;D:/projecten/workspace/11g/mds_user_cc/ViewController/adfmsrc;D:/projecten/workspace/11g/mds_user_cc/Model/src"/>
      </metadata-store>
      </metadata-store-usage>
      </metadata-store-usages>
      </persistence-config>
      <cust-config>
      <match>
      <customization-class name="oracle.adf.share.config.UserCC"/>
      </match>
      </cust-config>
      </mds-config>
      </app-config>
      <adf-faces-config xmlns="http://xmlns.oracle.com/adf/faces/config">
      <persistent-change-manager>
      <persistent-change-manager-class>
      oracle.adf.view.rich.change.MDSDocumentChangeManager
      </persistent-change-manager-class>
      </persistent-change-manager>
      <taglib-config>
      <taglib uri="http://xmlns.oracle.com/adf/faces/rich">
      <tag name="column">
      <attribute name="displayIndex">
      <persist-changes>true</persist-changes>
      </attribute>
      <attribute name="width">
      <persist-changes>true</persist-changes>
      </attribute>
      </tag>
      </taglib>
      </taglib-config>
      </adf-faces-config>
      </adf-config>

      We are ready with the MDS settings. Now we can add security to the project and add some example users. I use the ADF Security wizard for this. The wizard is located in the tools menu. This are the wizard steps. 1. enforce authorization 2. no identity store 3. Enable Credential store 4. No policy store 5. No anomymous providor 6. Select idstore.loginmodule 7. HTTP Basic authentication with jazn.com as realm and press finish.
      Let's create two test users named test and test1. Go the tools menu and select the preferences menu item.

      Open the embedded oc4j server preferences and add the user test and test1 to the jazn.com identity store. Create a new role users and those users to this role.
      At last we can create the jsf page with a table ( drag a viewobject from the datacontrol and drop this on the page select a readonly table). You have to add a button with no action to the page too. This is necessary else the changes are not submitted to mds repository.

      We have to check if the customization is enabled on the page. Select the jsp:root in the structure window and see in the property window if customization is allowed. The second thing we have to check if there is a unique customizationId on every table column. Select the table column, go to the property window -> behavior -> advanced -> customizationId
      Now we can go the page definition to add security to this page. Select the page definition permission value by the permission class combobox and select the view action to all the operations.

      We have to add the view permission to the users role. go to the structure window of the pagedef and the select the pagedef. Use the right button to select edit authorization menu item.

      Select all the options by the users role.

      Finally we can run the web application. If we log in as test or test1 and change the size of a column ( press the submit button) or the change the column order we will see that these setting are stored in the public_html folder. Every user has it's own folder and page xml.

      This is how a mds xml looks like for a particular user.


      The second part is about seeded customizations. Here can the developer customize the page for a particular user. In this blog I will make sure the salary column has a red background with white characters for only the user test. First we have to change the CustomizationLayerValues.xml in the jdev folder. Here we have to add the users test and test1 to the user custimatization layer.

      Startup jdeveloper and select the customization developer. If you don't get this option you have to go the preferences windows ( tools menu) and go to the roles entry. Here you can change the role.

      We can see a new window called Customizations. In this window we can select the test user.

      If we open the jsf page and change the properties of the jsf components then these changes are stored in the mds repository under the selected user in the customizations window.
      We can start the web application again. First we use the test user.

      Now we use the test1 user.

      I hope you got a good impression what MDS can do for you.