Pages

Wednesday, February 25, 2009

Change language / Locale in ADF

Sometimes you need to support multiple languages in your ADF application. This probably means that you need to change labels, listbox values and maybe need to change the number format layout like the decimal and number grouping separator. In this example I will show you how you can achieve this. Here are some pics of the JDeveloper 11g example application I made. You can change the language by selecting an language in the listbox and press the refresh button or you can use a language.
Here we go from dutch to english.
In this example I am using resourcebundles to change the values / labels to the right language. You can use properties or java files for the resourcebundles. To use these resourcebundles in our application we have to configure this in the faces-config.xml or do this in the JSF page.
The JSF page I made for this demo

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<c:set var="viewcontrollerBundle"
value="#{adfBundle['nl.whitehorses.locale.view.ViewControllerBundle']}"/>
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<af:document>
<af:messages/>
<af:form>
<af:panelFormLayout>
<af:panelGroupLayout layout="horizontal">
<af:selectOneChoice label="Locale" id="choice"
valueChangeListener="#{UserPreferences.localeChangeListener}"
value="#{UserPreferences.language}"
autoSubmit="true">
<af:selectItem label="Dutch" value="nl"/>
<af:selectItem label="English" value="en"/>
</af:selectOneChoice>
<af:commandButton text="Refresh"/>
<af:spacer width="10" height="10"/>
<af:commandButton text="Dutch" disabled="#{UserPreferences.language == 'nl'}"
actionListener="#{UserPreferences.localeChangeListener}">
<af:setActionListener to="#{UserPreferences.language}" from="nl"/>
</af:commandButton>
<af:commandButton text="English" disabled="#{UserPreferences.language == 'en'}"
actionListener="#{UserPreferences.localeChangeListener}">
<af:setActionListener to="#{UserPreferences.language}" from="en"/>
</af:commandButton>
</af:panelGroupLayout>
<af:spacer width="10" height="20"/>
<af:outputLabel value="#{viewcontrollerBundle.OUTPUT}"/>
<af:outputLabel value="#{msg.OUTPUT}"/>
<af:outputLabel value="#{jmsg.java_output}"/>
<af:outputLabel value="100000">
<af:convertNumber/>
</af:outputLabel>
<af:selectOneChoice label="Label" value="2">
<af:forEach items="#{bindings.lovDataResult.rangeSet}" var="li">
<af:selectItem label="#{li.lovLabel}" value="#{li.lovValue}"/>
</af:forEach>
</af:selectOneChoice>
</af:panelFormLayout>
</af:form>
</af:document>
</f:view>
</jsp:root>

I use the c:set component in the JSF page to define the resourcebundle, but when you want to use this resourcebundle in more then one page you also can define this in the faces-config.xml.
Here is my faces-config.xml which I use in this demo.

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<application>
<default-render-kit-id>oracle.adf.rich</default-render-kit-id>
<locale-config>
<default-locale>nl</default-locale>
<supported-locale>nl</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>nl.whitehorses.locale.view.locale</base-name>
<var>msg</var>
</resource-bundle>
<resource-bundle>
<base-name>nl.whitehorses.locale.view.JavaLocale</base-name>
<var>jmsg</var>
</resource-bundle>
</application>
</faces-config>

In the faces-config we can define what our default locale is. The resourcebundle without an underscore and country code should contain the labels which matches the default locale. In this example I also support english so I need to create a xxxx_en.properties or xxx_en.java file which contain the english values. After this we need define the main resourcebundles ( without _en ) and under which variable name it is known.

Here you see an project overview of the different resourcebundles I used in this example

To format number values with the right decimal and number grouping separator we can use the trinidad-config.xml. Here we can use an EL expression to define the separator for the right Locale.

<?xml version="1.0" encoding="windows-1252"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
<skin-family>blafplus-rich</skin-family>
<number-grouping-separator>#{UserPreferences.language=='nl' ? '.' : ','}</number-grouping-separator>
<decimal-separator>#{UserPreferences.language=='nl' ? ',' : '.'}</decimal-separator>
</trinidad-config>

To see the effect of these separators we need to use af:convertNumber on an inputtext or an outputtext.

In the JSF and the trinidad-config.xml I use this backing bean to set or read the selected language.
In this backing bean I also call an ADF MethodAction which refreshes the listbox with the right locale values.

package nl.whitehorses.bean;

import java.io.Serializable;

import java.util.Locale;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;

import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.binding.OperationBinding;

public class UserPreferences implements Serializable {

private String language;

public UserPreferences() {
language = Locale.getDefault().getLanguage();
}

private void changeLocale(String language){
System.out.println("changeLocale "+language);
this.language = language;
Locale newLocale = new Locale(this.language);
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(newLocale);

// refresh lov method action
DCBindingContainer bc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
OperationBinding lovOper = bc.getOperationBinding("getLovData");
lovOper.execute();

}

public void localeChangeListener(ValueChangeEvent valueChangeEvent) {
changeLocale(valueChangeEvent.getNewValue().toString());
}

public void localeChangeListener(ActionEvent actionEvent) {
changeLocale(this.language);
}

public void setLanguage(String language) {
System.out.println("setLanguage "+language);

this.language = language;
}

public String getLanguage() {
return language;
}
}

Here an example of a java resourcebundle.

package nl.whitehorses.locale.view;

import java.util.ListResourceBundle;

public class JavaLocale_en extends ListResourceBundle {

public Object[][] getContents() {
return contents;
}
private Object[][] contents =
{ { "java_output", "java output" }
, { "java_output2","java output 2" }, };
}

Here you can download my 11g workspace

Wednesday, February 18, 2009

High Available WebLogic Cluster

For one of my customer I am building a WebLogic cluster to provide high availible JMS services and web applications. In this blog I will give you some hints how to achieve this.
First a picture of the WebLogic Cluster configuration.

On the left site I have an normal Apache http server version 2.2 with a weblogic plugin. This plugin does the http forwarding to one of WLS server instances. Then I have two servers where the WLS are running on. On the first WLS server I am running the admin server with one of the cluster server instances and on the second WLS server is the other Cluster server instance running. Every server instance has its own JMS server and the WLS cluster has one JMS module (in here you define the distributed queues).
The JMS messages of the JMS servers, HTTP session data and WLS security realm configuration are all stored in the MySQL cluster database. This memory database ( a bit like coherence then with tables instead of java objects) provides me a high available WLS data. Off course you can use Oracle RAC or a single database for this data, but I prefer the MySQL cluster because I just need a few tables and no PLSQL + the MySQL cluster is up and running in a few hours. Or you can use file persistence but then you need to do something with scripts or cluster file systems when there is a failover.
This cluster configuration has the following failover features.
  1. The Apache redirects the http session to one of the servers. Session data is stored in the database so each server can handle the http request.
  2. WLS Server instance failover. Each server has a dynamic ip address when a server instance fails, the nodemanager will add the ip address to the other server and start the WLS server instance so you always have 2 WLS server instances.
  3. WLS Service failover, when for example a JMS server fails then this JMS server will be started in the other WLS server instance so you always have two JMS servers.
  4. MySQL cluster node failure. WebLogic uses for all their configuration an multi jdbc datasource so when one MySQL cluster node fails the other datasource is used to connect to the other MySQL cluster node
To setup a WLS domain see my previous blogpost. or see this blogpost for how to setup a MySQL cluster .

Here some pics how to configure high availability in WebLogic. First we need to configure the cluster for failover. In my case I select all the servers as migratable servers and using Consensus as migration basis.

Every WLS server instance need to have its own migratable target. On this migratable target we add the JMS servers

Set automatic migrate recovery services on these migratable targets.

After this we need to set an dynamic ip address to every Weblogic Server instance. Just select an server and set the ip address. Now we need to configure the WLS nodemanager on every server and put in the netmask and interface parameter in nodemanager.properties.
NetMask=255.255.255.0
Interface=eth0
Now the wlsifconfig script can add the ip address to the network device and start the WLS server instance.

The server configuration is ready we can go on with the JMS part.

Step 1 configure the JDBC persistence stores and target these to the migratable targets.

Step 2, create the JMS servers on a migratable target and every JMS server has its own jdbc persistence store

Create a JMS systemmodule

Create an connection factory and a distributed queue both targeted on the cluster.

That's all for the JMS part.

The last part is to configure Apache and the enterprise applications for High Availability.
Install apache 2.2 and download the weblogic module for apache here.
edit the httpd.conf add the following lines

LoadModule weblogic_module modules/mod_wl_22.so

<ifmodule>
WebLogicCluster node1_ip:port,node2_ip:port
</ifmodule>

<location>
SetHandler weblogic-handler
</location>


Step 2 is to configure every enterprise application ( ADF app or a WS) with a weblogic deployment descriptor so it stores its session data in the database.

<?xml version="1.0" encoding="ISO-8859-1"?>
<weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application.xsd" xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
<session-descriptor>
<persistent-store-type>jdbc</persistent-store-type>
<persistent-store-pool>JDBCMultiDataSourceMHS</persistent-store-pool>
<sharing-enabled>true</sharing-enabled>
<persistent-store-table>WL_SERVLET_SESSIONS</persistent-store-table>
<jdbc-connection-timeout-secs>120</jdbc-connection-timeout-secs>
</session-descriptor>
</weblogic-application>


that's all

Sunday, February 15, 2009

FaceBook ADF Task Flow

This is an example of a Facebook Task Flow which you can use in every ADF 11G application. Just an little example how powerfull and reusable Task Flow are. I used for this example the facebook-java-api, which is an opensource project. Great work guys.

Here some screenshots of the facebook TF. First we have to authenticate on the facebook site. This is only necessary the first time after this it will use a cookie to authenticate. I use the af:inlineFrame component to open the special facebook url with my application id.Now we can open the second accordion page. This will update my panelbox with my photo and show my friends in an ADF table.

We can email some friends by selecting these in the adf table and go to the email accordion and send the email.


Here is the facebook backing bean where it all happens

package nl.whitehorses.tf.facebook.beans;

import com.google.code.facebookapi.FacebookException;
import com.google.code.facebookapi.FacebookJaxbRestClient;
import com.google.code.facebookapi.ProfileField;
import com.google.code.facebookapi.schema.FriendsGetResponse;

import com.google.code.facebookapi.schema.User;
import com.google.code.facebookapi.schema.UsersGetInfoResponse;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import javax.faces.event.ActionEvent;

import oracle.adf.view.rich.component.rich.data.RichTable;
import oracle.adf.view.rich.component.rich.input.RichInputText;
import oracle.adf.view.rich.component.rich.layout.RichPanelBox;
import oracle.adf.view.rich.component.rich.layout.RichShowDetailItem;
import oracle.adf.view.rich.context.AdfFacesContext;

import org.apache.myfaces.trinidad.event.DisclosureEvent;
import org.apache.myfaces.trinidad.model.RowKeySet;


public class FaceBookBean {

private String APIKEY;
private String SECRET;

private List<User> modelFriends = null;
private int friendSize;

private String sessionID;
private String token;
private FacebookJaxbRestClient client;
private String facebookUser;
private String facebookUserImage;
private RichTable resultTable;
private RichPanelBox panelBox;
private String facebookLoginUrl;
private RichShowDetailItem loginTab;
private RichShowDetailItem resultTab;
private Long loggedUser;
private RichInputText emailSubject;
private RichInputText emailBody;

public FaceBookBean() {

}

private void refresh() {
try {
if ( sessionID==null){
sessionID = client.auth_getSession(token);
facebookLoginUrl = "http://www.facebook.com";
}
if (sessionID != null) {

loggedUser = client.users_getLoggedInUser();
List<Long> loggedUsers = new ArrayList<Long>(1);
loggedUsers.add(loggedUser);
client.users_getInfo(loggedUsers,
EnumSet.of( ProfileField.NAME
, ProfileField.PIC_SMALL));

UsersGetInfoResponse userResponse = (UsersGetInfoResponse)client.getResponsePOJO();
for (User user : userResponse.getUser()) {
facebookUser = user.getName();
facebookUserImage = user.getPicSmall();
}

FriendsGetResponse response =
(FriendsGetResponse)client.friends_get(loggedUser);
List<Long> friends = response.getUid();
client.users_getInfo(friends,
EnumSet.of( ProfileField.STATUS
, ProfileField.NAME
, ProfileField.BIRTHDAY
, ProfileField.PIC_SMALL));

userResponse =
(UsersGetInfoResponse)client.getResponsePOJO();

modelFriends = userResponse.getUser();
friendSize = modelFriends.size();

resultTable.setRows(friendSize);

// disable the login tab, if we are here we have a session object
loginTab.setDisabled(true);
loginTab.setVisible(false);

// refresh panelbox and the result table
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
adfFacesContext.addPartialTarget(resultTable);
adfFacesContext.addPartialTarget(panelBox);
}
} catch (FacebookException ex) {
ex.printStackTrace();
}


}

public void sendEmail(ActionEvent actionEvent) {
try {
RowKeySet selection = resultTable.getSelectedRowKeys();
Object[] keys = selection.toArray();
List<Long> receivers = new ArrayList<Long>(keys.length);
for ( Object key : keys ) {
User user = this.modelFriends.get((Integer)key);
receivers.add(user.getUid());
}
Object succes = client.notifications_sendTextEmail( receivers
,(String)emailSubject.getValue()
,(String)emailBody.getValue());
System.out.println(succes.toString());
} catch (FacebookException e) {
e.printStackTrace();
}
}

public void afterFaceBookLogin(ActionEvent actionEvent) {
refresh();
}


public void resultTableDisclosure(DisclosureEvent disclosureEvent) {
refresh();
}

public String getFacebookUser() {
return facebookUser;
}

public String getFacebookUserImage() {
return facebookUserImage;
}

public void setModelFriends(List<User> modelFriends) {
this.modelFriends = modelFriends;
}

public List<User> getModelFriends() {
return modelFriends;
}


public void setResultTable(RichTable resultTable) {
this.resultTable = resultTable;
}

public RichTable getResultTable() {
return resultTable;
}

public void setPanelBox(RichPanelBox panelBox) {
this.panelBox = panelBox;
}

public RichPanelBox getPanelBox() {
return panelBox;
}

public String getFacebookLoginUrl() {
if (sessionID==null){
try {
client = new FacebookJaxbRestClient(APIKEY, SECRET);
token = client.auth_createToken();
facebookLoginUrl= "http://api.new.facebook.com/login.php?api_key=" + APIKEY + "&v=1.0&auth_token=" + token;
} catch (FacebookException ex) {
ex.printStackTrace();
}
}
return facebookLoginUrl;
}

public void setLoginTab(RichShowDetailItem loginTab) {
this.loginTab = loginTab;
}

public RichShowDetailItem getLoginTab() {
return loginTab;
}

public void setResultTab(RichShowDetailItem resultTab) {
this.resultTab = resultTab;
}

public RichShowDetailItem getResultTab() {
return resultTab;
}


public void setAPIKEY(String APIKEY) {
this.APIKEY = APIKEY;
}

public String getAPIKEY() {
return APIKEY;
}

public void setSECRET(String SECRET) {
this.SECRET = SECRET;
}

public String getSECRET() {
return SECRET;
}

public void setEmailSubject(RichInputText emailSubject) {
this.emailSubject = emailSubject;
}

public RichInputText getEmailSubject() {
return emailSubject;
}

public void setEmailBody(RichInputText emailBody) {
this.emailBody = emailBody;
}

public RichInputText getEmailBody() {
return emailBody;
}
}

Here is the code of my Facebook page fragment

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:f="http://java.sun.com/jsf/core">
<af:panelBox text="Facebook Task Flow of #{FaceBook.facebookUser}"
icon="#{FaceBook.facebookUserImage}" id="box"
inlineStyle="height:500px; width:700px;"
binding="#{FaceBook.panelBox}">
<af:panelAccordion inlineStyle="height:480px; width:680px;">
<af:showDetailItem text="Facebook Login" binding="#{FaceBook.loginTab}">
<af:inlineFrame source="#{FaceBook.facebookLoginUrl}"
inlineStyle="width:99%; height:99%;"
shortDesc="Loginpage"/>
<f:facet name="toolbar"/>
</af:showDetailItem>
<af:showDetailItem text="Facebook Results" binding="#{FaceBook.resultTab}"
disclosureListener="#{FaceBook.resultTableDisclosure}">
<af:panelCollection id="col" inlineStyle="width:99%; height:98%;">
<f:facet name="toolbar">
<af:toolbar>
<af:commandToolbarButton text="Refresh"
actionListener="#{FaceBook.afterFaceBookLogin}"/>
</af:toolbar>
</f:facet>
<af:table value="#{FaceBook.modelFriends}" id="result" var="row"
rowSelection="multiple" emptyText="#{'No rows yet.'}"
binding="#{FaceBook.resultTable}" columnStretching="blank"
inlineStyle="height:97%; width:100%;">
<af:column sortable="false" headerText="Name" width="200">
<af:outputText value="#{row.name} "/>
</af:column>
<af:column sortable="false" headerText="Birthdate" width="100">
<af:outputText value="#{row.birthday}"/>
</af:column>
<af:column sortable="false" headerText="Image" width="60">
<af:image source="#{row.picSmall}"/>
</af:column>
<af:column sortable="false" headerText="Status" width="120"
noWrap="false">
<af:outputText value="#{row.status.message}"/>
</af:column>
<af:column sortable="false" headerText="Id" width="60">
<af:outputText value="#{row.uid}"/>
</af:column>
</af:table>
</af:panelCollection>
</af:showDetailItem>
<af:showDetailItem text="Email">
<af:panelFormLayout>
<af:inputText label="Subject" columns="52"
binding="#{FaceBook.emailSubject}"/>
<af:inputText label="Body" rows="3" columns="50" wrap="soft"
binding="#{FaceBook.emailBody}"/>
</af:panelFormLayout>
<f:facet name="toolbar">
<af:commandButton text="Send Email"
actionListener="#{FaceBook.sendEmail}"/>
</f:facet>
</af:showDetailItem>
</af:panelAccordion>
</af:panelBox>
</jsp:root>


When you drop this task flow on an page you have to fill the api and secret input parameters. I can't give you mine keys. You can do this in the pagedef of the page.

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="11.1.1.51.88" id="startPageDef" Package="pages">
<parameters/>
<executables>
<taskFlow id="facebooktaskflowdefinition1"
taskFlowId="/WEB-INF/facebook-task-flow-definition.xml#facebook-task-flow-definition"
xmlns="http://xmlns.oracle.com/adf/controller/binding">
<parameters>
<parameter id="API" xmlns="http://xmlns.oracle.com/adfm/uimodel"
value="'0eff-----------------1'"/>
<parameter id="SECRET" xmlns="http://xmlns.oracle.com/adfm/uimodel"
value="'e1cee430d8599bc3be2e42fc19fe9d68'"/>
</parameters>
</taskFlow>
</executables>
<bindings/>
</pageDefinition>

To use this Task Flow in your ADF application, we need to go to http://developers.facebook.com/get_started.php where you can register your own application. Now you will have your own applicationId and secret which you can use for the pagedef task flow parameters. And please fill in callback url to a website.



Here is the 11g workspace.

Thursday, February 12, 2009

Reset / Clear an ADF page

Sometimes you want to reset or clear the jsf page without leaving and reloading the page. This can be a little tricky, it depends if you are using ADF inputtext items with ADF pagedef bindings or use it values from a backing bean.

First we need to add an button on the page with an actionlistener

<af:commandButton text="Revert Changes" actionListener="#{MainBean.revertCategories}"/>

In this backing bean method we can reset the ADF bindings. Use this code to requery all the iterators in the pagedef.
   
public void revertCategories(ActionEvent actionEvent) {
dc.refreshControl();
DCIteratorBinding iterBind= (DCIteratorBinding)dc.get("queryCategoryFindAllIterator");
iterBind.executeQuery();
iterBind.refresh(DCIteratorBinding.RANGESIZE_UNLIMITED);
}


Resetting inputtext items based on values in a backing bean can be tricky especially when a user has changed an inputtext component. In that case you can try to reset this value from the backing bean, but it may not work because the submittedValue of the uicomponent with the user input is still set. We need to reset the inputtext uicomponent and add partial trigger to it. Here is the code to do this. This method has as input parameter the backing bean binding of a form or an other higher uicomponent. This method will reset all the childern uicomponents who are not disabled.


private void resetValueInputItems(AdfFacesContext adfFacesContext, UIComponent component){
List<UIComponent> items = component.getChildren();
for ( UIComponent item : items ) {

resetValueInputItems(adfFacesContext,item);

if ( item instanceof RichInputText ) {
RichInputText input = (RichInputText)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
} else if ( item instanceof RichInputDate ) {
RichInputDate input = (RichInputDate)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
}
}
}

public void resetForm(ActionEvent actionEvent) {
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
resetValueInputItems(adfFacesContext,vvFormBinding );
testMap.put("TEST", "initial value");
}

Friday, February 6, 2009

Display detail records in a dynamic ADF FormLayout

Sometimes you have to make a CRUD page on master detail tables which don't have fixed columns like the tables in the HR demo schema. The detail table has for example only a foreign key to the master table and a type and value column. The detail values are now stored vertically and not horizontal in columns. In this blog I will show how to make a dynamic custom form with inputtexts for each master.
Here is an picture of the category and category_items table I use in this blog. In this example I got for example a category U2 with the some items records like Style and Country and a category week with the weekdays as items.
I will use an local EJB as model because this gives me the Category and CategoryItems classes which I can use in the backing bean and the EJB session bean has a nice merge method which I can use when I send back the changed category.
On the EJB Session bean I created an ADF datacontrol so I can call method actions in the page definition and these methods actions will fill the method iterators.

In the left site of the page I will display all the categories and on the right I have a dynamic task flow region. You can click on a Id this will display the detail form.

When I click on my car then I will call an method action to retrieve this category class. With the items of this category I will open the category items task flow and use af:forEach to display all the items

If I select an other category I first remove the previous inputtext components and display the new items
When I change an item I can press the save button. This will call the merge method action and passes the category with the changed items to the ejb session bean, EJB will update the items values.

Here is the ddl of my examples tables.


create table CATEGORY
(
ID NUMBER not null,
NAME VARCHAR2(30) not null
)
;
alter table CATEGORY
add constraint CATEGORY_PK primary key (ID);

insert into CATEGORY (ID, NAME)
values (1, 'U2');
insert into CATEGORY (ID, NAME)
values (2, 'SEAT LEON');
insert into CATEGORY (ID, NAME)
values (3, 'WEEK');
commit;

create table CATEGORY_ITEMS
(
CAT_ID NUMBER(4) not null,
NAME VARCHAR2(40) not null,
VALUE VARCHAR2(40) not null
)
;
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_PK primary key (CAT_ID, NAME);
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_CATEGORY_FK1 foreign key (CAT_ID)
references CATEGORY (ID);

insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Style', 'Pop');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Country', 'Ireland');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Albums', 'War,Rattle and hum');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Color', 'Black');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, '4WD', 'No');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'HorsePower', '140');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Transmission', 'Automatic');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day1', 'Monday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day2', 'Tuesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day3', 'Wednesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day4', 'Thursday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day5', 'Friday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day6', 'Saturday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day7', 'Sunday');
commit;


My backing which calls the method action and retrieves the Category from the method iterator.

package nl.whitehorses.dynamic.beans;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.event.ActionEvent;

import nl.whitehorses.dynamic.form.model.entities.Category;

import nl.whitehorses.dynamic.form.model.entities.CategoryItems;

import oracle.adf.controller.TaskFlowId;
import oracle.adf.model.BindingContext;
import oracle.adf.model.bean.DCDataRow;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.adf.model.binding.DCIteratorBinding;

import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;

import oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding;

import oracle.jbo.Row;

public class Main {

private Long catId;
private DCBindingContainer dc;
private Category category;
private String emptyTaskFlowId = "/WEB-INF/empty-task-flow-definition.xml#empty-task-flow-definition";
private String categoryTaskFlowId = "/WEB-INF/category-task-flow-definition.xml#category-task-flow-definition";
private String taskFlowId;
List catItemList;
private RichPanelFormLayout categoryItemForm;

public Main() {
dc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
}

public void categoryTableLink(ActionEvent actionEvent) {
// Add event code here...

// the category pk is already set
// now get the category
FacesCtrlActionBinding categoryFindByKey = (FacesCtrlActionBinding)dc.getControlBinding("queryCategoryFindByKey");
categoryFindByKey.execute();

// adf executed the method action
// now get the result and put this in the category variable
DCIteratorBinding categoryBinding= (DCIteratorBinding)dc.get("queryCategoryFindByKeyIter");
Row[] categoryRows = categoryBinding.getAllRowsInRange();
for (Row row : categoryRows) {
category = (Category)((DCDataRow)row).getDataProvider();
}


// remove old childern when a other category is selected
if ( categoryItemForm != null ) {
int size = categoryItemForm.getChildren().size();
for ( int i = 0 ; i < size ; i++ ) {
categoryItemForm.getChildren().remove(0);
}
}
catItemList = category.getCategoryItemsList();
// set category taskflow
taskFlowId = categoryTaskFlowId;
}

public void saveCategoryItems(ActionEvent actionEvent) {
// Add event code here...
FacesCtrlActionBinding merge = (FacesCtrlActionBinding)dc.getControlBinding("mergeEntity");
merge.execute();
}

public TaskFlowId getDynamicTaskFlowId() {
if ( taskFlowId == null ) taskFlowId = emptyTaskFlowId;
return TaskFlowId.parse(taskFlowId);
}


public void setCatId(Long catId) {
this.catId = catId;
}

public Long getCatId() {
return catId;
}

public String getCategoryFragment() {
return category.getName();
}

public void setCatItemsList(List catItemList) {
this.catItemList = catItemList;
}

public List getCatItemsList() {
return catItemList;
}

public void setCategoryItemForm(RichPanelFormLayout categoryItemForm) {
this.categoryItemForm = categoryItemForm;
}

public RichPanelFormLayout getCategoryItemForm() {
return categoryItemForm;
}

public void setCategory(Category category) {
this.category = category;
}

public Category getCategory() {
return category;
}
}

and as last the category items jsf page code

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:f="http://java.sun.com/jsf/core">
<af:subform>
<af:panelHeader text="Category #{MainBean.categoryFragment}">
<af:panelFormLayout binding="#{MainBean.categoryItemForm}">
<af:forEach var="row" items="#{MainBean.catItemsList}">
<af:inputText label="#{row.name}" value="#{row.value}"/>
</af:forEach>
<f:facet name="footer">
<af:commandButton text="Save"
actionListener="#{MainBean.saveCategoryItems}"/>
</f:facet>
</af:panelFormLayout>
</af:panelHeader>
</af:subform>
</jsp:root>


Here is my 11g example workspace