2012年7月25日星期三

ADF_173:如何在Table组件中使用Active Data Service?

运行环境:JDeveloper 11.1.2.2.0 + Oracle Database 10g Express Edition 10.2.0.1。

Active Data Service是ADF中的一个高级特性,本文介绍如何在Table中使用ADS。

重点步骤说明:

1. 页面代码
<af:table value="#{stockManager}" var="row" rowBandingInterval="0" id="t1">
    <af:column sortable="false" headerText="Ticket" align="start" id="c1">
        <af:outputText value="#{row.ticket}" id="ot1"/>
    </af:column>
    <af:column sortable="false" headerText="Value" align="start" id="c2">
        <af:outputText value="#{row.value}" id="ot2"/>
    </af:column>
</af:table>

说明:Table的value属性绑定到一个Managed Bean。

2. adfc-config.xml
<managed-bean id="__1">
  <managed-bean-name>stockManager</managed-bean-name>
  <managed-bean-class>view.StockManager</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<managed-bean id="__2">
  <managed-bean-name>stockBackend</managed-bean-name>
  <managed-bean-class>view.StockBackEnd</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
  <managed-property>
    <property-name>listener</property-name>
    <value>#{stockManager}</value>
  </managed-property>
</managed-bean>

说明:注册Managed Bean:StockManager和StockBackEnd。
其中,StockBackEnd有一个属性:listener,指向StockManager。

3. 完整的StoreManager的代码
package view;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;

import javax.faces.context.FacesContext;

import oracle.adf.view.rich.activedata.ActiveDataEventUtil;
import oracle.adf.view.rich.event.ActiveDataEntry;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;
import oracle.adf.view.rich.model.ActiveCollectionModelDecorator;
import oracle.adf.view.rich.model.ActiveDataModel;

import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.SortableModel;


public class StockManager extends ActiveCollectionModelDecorator implements IBackendListener {
    @Override
    public ActiveDataModel getActiveDataModel() {
        return stockModel;
    }

    @Override
    protected CollectionModel getCollectionModel() {
        if (collectionModel == null) {
            FacesContext ctx = FacesContext.getCurrentInstance();
            ExpressionFactory ef = ctx.getApplication().getExpressionFactory();
            ValueExpression ve = ef.createValueExpression(ctx.getELContext(), "#{stockBackend}", StockBackEnd.class);
            StockBackEnd context = (StockBackEnd)ve.getValue(ctx.getELContext());
            collectionModel = new SortableModel(context.getStocks());
        }
        return collectionModel;

        //        if (collectionModel == null) {
        //            // connect to a backend system to get a Collection
        //            List stocks = FacesUtil.loadBackEnd().getStocks();
        //            // make the collection become a (Trinidad) CollectionModel
        //            collectionModel = new SortableModel(stocks);
        //        }
        //        return collectionModel;
    }

    /**
     * Callback from the backend to push new data to our decorator.
     * The decorator itself notifies the ADS system that there was a data change.
     *
     * @param key the rowKey of the updated Stock
     * @param updatedStock the updated stock object
     */
    @Override
    public void onStockUpdate(Integer rowKey, Stock stock) {
        if (rowKey != null) {
            System.out.println("Stock " + stock.getTicket() + " Changed is : " +  stock.getValue() );
            ActiveStockModel asm = getActiveStockModel();
            asm.prepareDataChange();
            ActiveDataUpdateEvent event =
                ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
                                                               asm.getCurrentChangeCount(), new Object[] { rowKey },
                                                               null, new String[] { "value" },
                                                               new Object[] { stock.getValue() });
            // Deliver the new Event object to the ADS framework
            asm.notifyDataChange(event);
        }
    }

    /**
     * Typesafe caller for getActiveDataModel()
     * @return
     */
    protected ActiveStockModel getActiveStockModel() {
        return (ActiveStockModel)getActiveDataModel();
    }

    private CollectionModel collectionModel;
    private ActiveStockModel stockModel = new ActiveStockModel();
}

说明:
(1)StockManager继承了ActiveCollectionModelDecorator,重写了方法getCollectionModel,该方法返回集合对象。这里使用了ValueExpression动态绑定到StockBackEnd,这样不用自己构造CollectionModel对象。
(2)StockManager实现了IBackendListener接口,重写了方法onStockUpdate,该方法向ADS framework发送UpdateEvent。

4. 完整的StockBackEnd的代码
package view;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;


public class StockBackEnd {

    public StockBackEnd() {
        stocks.add(new Stock("中国联通", 1.0));
        stocks.add(new Stock("中国石油", 2.0));
        stocks.add(new Stock("盐湖钾肥", 3.0));
    }

    public void setListener(IBackendListener listener) {
        this.listener = listener;
    }

    public IBackendListener getListener() {
        return listener;
    }

    private IBackendListener listener;

    private final List<Stock> stocks = new CopyOnWriteArrayList<Stock>();

    public List<Stock> getStocks() {
        return stocks;
    }

    public void changeData() {
        List<Stock> rows = getStocks();
        Stock dataRow = null;
        for (int rowKey = 0; rowKey < rows.size(); rowKey++) {
            dataRow = rows.get(rowKey);
            dataRow.setValue(dataRow.getValue() + 1.0);
            listener.onStockUpdate(rowKey, dataRow);
            Long stoptime = 2000L;
            try {
                Thread.sleep(stoptime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @PostConstruct
    private void startUpdateProcess() {
        Runnable dataChanger = new Runnable() {
            public void run() {
                // wait 10 seconds before we start to update our stocks...
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                while (listener != null) {
                    try {
                        Thread.sleep(3000);
                        changeData();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        Thread newThread = new Thread(dataChanger);
        newThread.start();
    }

    @PreDestroy
    private void stopUpdateProcess() {
        if (listener != null) {
            System.out.println("listener " + listener);
            listener = null;
        }
    }
}

说明:
(1)StockBackEnd定义了数据源,实际情况中,你应该在这里重新定义你获取数据的方式。
(2)StockBackEnd定义了触发ChangeData的方式:这里是在执行完毕构造函数后,启动一个线程,每隔10秒,执行一次ChangeData方法。
实际情况中,你应该在这里重新定义触发方式,比如使用JMS。

6. 完整的ActiveStockModel的代码
package view;

import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;

import oracle.adf.view.rich.activedata.BaseActiveDataModel;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;


public class ActiveStockModel extends BaseActiveDataModel {

    @Override
    protected void startActiveData(Collection<Object> rowKeys, int startChangeCount) {
    }

    @Override
    protected void stopActiveData(Collection<Object> rowKeys) {
    }

    @Override
    public int getCurrentChangeCount() {
        return changeCounter.get();
    }

    /**
     * Increment the change counter.
     */
    public void prepareDataChange() {
        changeCounter.incrementAndGet();
    }

    /**
     * Deliver an ActiveDataUpdateEvent object to the ADS framework.
     *
     * @param event the ActiveDataUpdateEvent object
     */
    public void notifyDataChange(ActiveDataUpdateEvent event) {
        fireActiveDataUpdate(event);
    }
    private final AtomicInteger changeCounter = new AtomicInteger();
}

说明:该类的作用是把ActiveDataUpdateEvent对象发送给ADS framework。

7. 运行效果
每行数据依次被更新(背景色显示为蓝色),而整个Table并没有被刷新。


Project 下载:ADF_ADS_Table.7z

参考文献:
1.《Web User Interface Developer’s Guide for ADF》之 Using the Active Data Service with an Asynchronous Backend
2.《Fusion Developer's Guide for ADF》之 Using the Active Data Service
3. http://biemond.blogspot.com/2009/12/adf-data-push-with-active-data-service.html
4. http://matthiaswessendorf.wordpress.com/2010/01/07/adf%E2%80%99s-active-data-service-and-scalar-data-like-activeoutputtext/
5. http://matthiaswessendorf.wordpress.com/2009/12/05/adf-faces-and-server-side-push/
6. http://matthiaswessendorf.wordpress.com/2009/12/11/adfs-active-data-service-and-multiple-push-windows/
7. http://technology.amis.nl/2011/10/19/adf-faces-handle-task-in-background-process-and-show-real-time-progress-indicator-for-asynchronous-job-using-server-push-in-adf/

2012年7月24日星期二

ADF_172:如何使用ActiveImage组件?

运行环境:JDeveloper 11.1.2.2.0 + Oracle Database 10g Express Edition 10.2.0.1。

我们常用的组件是Image,它是用来显示图片的。那么,ActiveImage比Image特殊在哪里呢?
当内容发生改变时,ActiveImage组件可以帮助我们从后台“动态刷新”内容,用户无需做任何操作。
同样的功能如果使用Image组件来实现,就需要使用PPR的功能。

ActiveImage适用于一些滚动的新闻图片,比如本实验就是每隔2秒钟自动切换不同的图片。



重点步骤说明:

1. ActiveImage组件页面代码
<af:activeImage id="ai1" source="#{imageBean.path}"/>

2. 在adfc-config.xml中注册Managed Bean: ImageBean

<?xml version="1.0" encoding="UTF-8" ?>
<adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
<managed-bean id="__1">
<managed-bean-name>imageBean</managed-bean-name>
<managed-bean-class>view.ImageBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</adfc-config>


3. 完整的Managed Bean代码

package view;

import java.util.Collection;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

import oracle.adf.view.rich.activedata.ActiveModelContext;
import oracle.adf.view.rich.activedata.BaseActiveDataModel;
import oracle.adf.view.rich.event.ActiveDataEntry;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;
import oracle.adf.view.rich.activedata.ActiveDataEventUtil;
//import oracle.adfinternal.view.faces.activedata.ActiveDataEventUtil;


public class ImageBean extends BaseActiveDataModel {
public ImageBean() {
ActiveModelContext context = ActiveModelContext.getActiveModelContext();
Object[] keyPath = new String[0];
context.addActiveModelInfo(this, keyPath, "path");

timer.schedule(new UpdateTask(), 2000, 2000);
}

public String getPath() {
return image1;
}

// not needed as we do not need to connect to a (real) active data source...

protected void startActiveData(Collection<Object> rowKeys, int startChangeCount) {
}

// not needed as we do not need to disconnect from a (real) active data source...

protected void stopActiveData(Collection<Object> rowKeys) {
}

public int getCurrentChangeCount() {
return counter.get();
}

protected class UpdateTask extends TimerTask {
public void run() {
counter.incrementAndGet();

ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE, counter.get(),
new String[0], null, new String[] { "path" },
new Object[] { (counter.get() % 2) == 0 ? image1 :
image2 });
fireActiveDataUpdate(event);
}
}

private static final String image1 = "/images/free-climber-yosemite.jpg";
private static final String image2 = "/images/rutherford-yosemite-falls.jpg";
private static final Timer timer = new Timer();
private final AtomicInteger counter = new AtomicInteger(0);
}

说明:
(1)ImageBean必须实现ActiveDataModel接口,这里是继承BaseActiveDataModel,后者实现了ActiveDataModel接口。
(2)使用ActiveModelContext注册该ActiveDataModel(即ImageBean),并把keyPath与"path"属性关联起来,这样当"path"属性发生变化时,Model层可以监听到,并通知视图层,视图层将会随之改变。
(3)使用Timer来定时启动任务UpdateTask,即执行UpdateTask类的run方法。
其中第1个参数2000表示第一次执行时延迟2秒钟, 第2个参数2000表示每2秒钟执行一次。
(4)UpdateTask类是一个多线程任务:增加计数器值,并且“引爆”数据改变事件。
(5)使用ActiveDataEventUtil创建事件,更新Model。
其中参数type表示改变事件的类型,可以设置为UPDATE, INSERT, DELETE, REFRESH,等等;
参数changeCount用来保证读取一致性;
参数key表示当前记录的key,适用于Model是CollectionModel类型的情况;
参数insertKey表示插入的位置,适用于Model是CollectionModel类型的情况;
参数names表示已改变的属性名称;
参数values表示已改变的属性值。

Project 下载:ADF_ActiveImage.7z

ADF_171:如何使用ActiveOutputText组件?

运行环境:JDeveloper 11.1.2.2.0 + Oracle Database 10g Express Edition 10.2.0.1。

我们常用的组件是OutputText,它是用来显示Label的。那么,ActiveOutputText比OutputText特殊在哪里呢?
当内容发生改变时,ActiveOutputText组件可以帮助我们从后台“动态刷新”内容,用户无需做任何操作。
同样的功能如果使用OutputText组件来实现,就需要使用PPR的功能。

本实验取材于ADF Faces Rich Client Components Demo。
关于Demo的详细说明,比如下载、安装、源代码等等,请参考《发布与运行ADF Faces Rich Client Components Demo》。

访问http://jdevadf.oracle.com/adf-richclient-demo/faces/visualDesigns/activeCounter.jspx,可以看到运行效果:页面上的计数器会不断增长并自动刷新。



为了彻底搞清楚ActiveOutputText使用方法,我重新开发了一个应用,模仿Demo中的实现。
重点步骤说明:

1. ActiveOutputText组件页面代码
<af:activeOutputText value="#{counterBean.state}" id="aot1"
inlineStyle="color:brown;font-size:100px;font-weight:bold;text-align:center;"></af:activeOutputText>

2. 在adfc-config.xml中注册Managed Bean: CounterBean

<?xml version="1.0" encoding="UTF-8" ?>
<adfc-config xmlns="http://xmlns.oracle.com/adf/controller" version="1.2">
<managed-bean id="__1">
<managed-bean-name>counterBean</managed-bean-name>
<managed-bean-class>view.CounterBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
</adfc-config>


3. 完整的Managed Bean代码

package view;

import java.util.Collection;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

import oracle.adf.view.rich.activedata.ActiveModelContext;
import oracle.adf.view.rich.activedata.BaseActiveDataModel;
import oracle.adf.view.rich.event.ActiveDataEntry;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;
import oracle.adf.view.rich.activedata.ActiveDataEventUtil;
//import oracle.adfinternal.view.faces.activedata.ActiveDataEventUtil;

public class CounterBean extends BaseActiveDataModel {
public String getState() {
ActiveModelContext context = ActiveModelContext.getActiveModelContext();
Object[] keyPath = new String[0];
context.addActiveModelInfo(this, keyPath, "state");
timer.schedule(new UpdateTask(), 2000, 2000);
return String.valueOf(counter);
}

// not needed as we do not need to connect to a (real) active data source...
protected void startActiveData(Collection<Object> rowKeys, int startChangeCount) {
}

// not needed as we do not need to disconnect from a (real) active data source...
protected void stopActiveData(Collection<Object> rowKeys) {
}

public int getCurrentChangeCount() {
return counter.get();
}

protected class UpdateTask extends TimerTask {
public void run() {
counter.incrementAndGet();
ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE, counter.get(),
new String[0], null, new String[] { "state" },
new Object[] { counter.get() });
fireActiveDataUpdate(event);
}
}

private static final Timer timer = new Timer();
private final AtomicInteger counter = new AtomicInteger(0);
}

说明:
(1)CounterBean必须实现ActiveDataModel接口,这里是继承BaseActiveDataModel,后者实现了ActiveDataModel接口。
(2)使用ActiveModelContext注册该ActiveDataModel(即CounterBean),并把keyPath与"state"属性关联起来,这样当"state"属性发生变化时,Model层可以监听到,并通知视图层,视图层将会随之改变。
(3)使用Timer来定时启动任务UpdateTask,即执行UpdateTask类的run方法。
其中第1个参数2000表示第一次执行时延迟2秒钟, 第2个参数2000表示每2秒钟执行一次。
(4)UpdateTask类是一个多线程任务:增加计数器值,并且“引爆”数据改变事件。
(5)使用ActiveDataEventUtil创建事件,更新Model。
其中参数type表示改变事件的类型,可以设置为UPDATE, INSERT, DELETE, REFRESH,等等;
参数changeCount用来保证读取一致性;
参数key表示当前记录的key,适用于Model是CollectionModel类型的情况;
参数insertKey表示插入的位置,适用于Model是CollectionModel类型的情况;
参数names表示已改变的属性名称;
参数values表示已改变的属性值。

Project 下载:ADF_ActiveOutputText.7z

2012年7月19日星期四

ADF_162:使用Contextual Event在Region之间传递事件

运行环境:JDeveloper 11.1.2.2.0 + Oracle Database 10g Express Edition 10.2.0.1。

页面上的每个Region是一个独立的区域,除了通过传递参数给Region,与该Region进行交互之外,还可以使用Contextual Event。
相对于传递参数的方式,使用Contextual Event不必重新初始化TaskFlow,无需了解每个Region的实现细节,不会“干扰”Region自身的逻辑。
Contextual Event由两部分组成:事件的生产者和事件的消费者。

参考文献1是一个不错的入门级的例子,本实验参照该文重新做一遍,对重要步骤进行说明。

1. 分别定义两个Bounded Task Flow:Department和Employee

2. 拖放两个Bounded Task Flow到页面生成两个Region

3. 为departmentPageDef.xml增加Events
定义表明department.jsff页面中的First等Build-in Operation,将会产生FirstEvent等操作。


4. 为employeePageDef.xml增加Events
定义表明employee.jsff页面中的First等Build-in Operation,将会产生FirstEvent等操作。


5. 在两个Region所在的主页面定义Event Mapping关系
(1)选择主页面的PageDef文件,在Structure面板中,右键根节点,选择Edit Event Map:

(2)增加Event Mapping

(3)Producer是department中的First Event等,Consumer是employee中的First Event等

(4)完成所有事件的Mapping关系


6. 运行
点击左边Department表单下的导航按钮,会发现右边的Employee表单也跟着导航了。


Project 下载:ADF_Region_ContextualEvent.7z

参考文献:
1. http://oracleseeker.com/2009/09/08/adf_pas_event_regions/

ADF_161:如何传递参数给Dynamic Region?

开发环境:JDeveloper 11.1.2.2.0 + Oracle XE Database 10gR2。

使用Page Fragment的Bounded TaskFlow可以拖放到其它页面的某个Region,这是在设计时实现的,是静态的。
Dynamic Region特性可以在运行时决定使用哪个Bounded TaskFlow,是动态的。
在《Tree组件使用指南之四:点击不同的树节点显示不同的表单》中,我使用了switcher实现了显示不同的表单。
如果使用Dynamic Region实现同样的功能,该如何做呢?

重点步骤说明:

1. 设计并实现Employee Bounded TaskFlow
(1)点击Employee节点时,要传入EmployeeId,这里使用了SetCurrentRowWihtKeyValue操作

(2)设置SetCurrentRowWihtKeyValue操作的参数rowKey=#{pageFlowScope.employeeId}

(3)设置Employee Bounded TaskFlow的输入参数employeeId=#{pageFlowScope.employeeId}


2. 设计并实现Department Bounded TaskFlow
(1)点击Department节点时,要传入DepartmentId,这里使用了SetCurrentRowWihtKeyValue操作

(2)设置SetCurrentRowWihtKeyValue操作的参数rowKey=#{pageFlowScope.departmentId}

(3)设置Department Bounded TaskFlow的输入参数departmentId=#{pageFlowScope.departmentId}


3. 拖放DeparmentsView1生成Tree
具体设置请参考《Tree组件使用指南之四:点击不同的树节点显示不同的表单》。

4. 拖放Department Bounded TaskFlow到主页面,选择Dynamic Region
(1)会提示你创建一个Managed Bean,并在Managed Bean中创建一个dynamicTaskFlowId属性。
注意,Managed Bean的Scope最好设置成ViewScope,保证在当前页面不用重新实例化Managed Bean。
(2)选择该Region,切换到Bindings Tab,设置taskFlowId,将其绑定到Managed Bean中的dynamicTaskFlowId属性。
(3)设置Region的参数,因为不同的TaskFlow参数不同,我这里使用parameterMap来设置参数,将其绑定到。
Managed Bean中的parameters属性。

(4)定制TreeSelectionListener,并在点击不同的节点时,给parameters赋值。
完整的Managed Bean的代码如下:
package view;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import oracle.adf.controller.TaskFlowId;
import oracle.adf.view.rich.component.rich.data.RichTree;

import oracle.jbo.Row;
import oracle.jbo.uicli.binding.JUCtrlHierBinding;
import oracle.jbo.uicli.binding.JUCtrlHierNodeBinding;
import oracle.jbo.uicli.binding.JUCtrlHierTypeBinding;

import org.apache.myfaces.trinidad.event.SelectionEvent;
import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.RowKeySet;

import view.util.JSFUtils;


public class MyBackingBean {
    private String departmentTaskFlowId = "/WEB-INF/department-btf-definition.xml#department-btf-definition";
    private String employeeTaskFlowId = "/WEB-INF/employee-btf-definition.xml#employee-btf-definition";
    private TaskFlowId dynamicTaskFlowId = TaskFlowId.parse(departmentTaskFlowId);
    private HashMap parameters = new HashMap();

    public MyBackingBean() {
    }

    public void treeSelectionListener(SelectionEvent selectionEvent) {
        JSFUtils.invokeMethodExpression("#{bindings.DepartmentsView1.treeModel.makeCurrent}", Object.class,
                                        SelectionEvent.class, selectionEvent);

        RichTree tree = (RichTree)selectionEvent.getSource();
        RowKeySet rowKeySet = selectionEvent.getAddedSet();
        Iterator rksIterator = rowKeySet.iterator();
        while (rksIterator.hasNext()) {
            List key = (List)rksIterator.next();
            JUCtrlHierBinding treeBinding = null;
            CollectionModel collectionModel = (CollectionModel)tree.getValue();
            treeBinding = (JUCtrlHierBinding)collectionModel.getWrappedData();
            JUCtrlHierNodeBinding nodeBinding = treeBinding.findNodeByKeyPath(key);

            Row rw = nodeBinding.getRow();
            String rowType = rw.getStructureDef().getDefName();
            System.out.println("########################### rowType " + rowType);

            JUCtrlHierTypeBinding typeBinding = nodeBinding.getHierTypeBinding();
            String nodeStuctureDefname = typeBinding.getStructureDefName();
            System.out.println("########################### nodeStuctureDefname " + nodeStuctureDefname);

            if (nodeStuctureDefname.equalsIgnoreCase("model.DepartmentsView")) {
                setDynamicTaskFlowId(TaskFlowId.parse(departmentTaskFlowId));
                parameters.clear();
                parameters.put("departmentId", rw.getAttribute("DepartmentId"));  
                System.out.println("########################### DepartmentId " + rw.getAttribute("DepartmentId"));
            } else {
                setDynamicTaskFlowId(TaskFlowId.parse(employeeTaskFlowId));
                parameters.clear();
                parameters.put("employeeId", rw.getAttribute("EmployeeId"));  
                System.out.println("########################### EmployeeId " + rw.getAttribute("EmployeeId"));
            }
        }
    }

    public void setDynamicTaskFlowId(TaskFlowId dynamicTaskFlowId) {
        this.dynamicTaskFlowId = dynamicTaskFlowId;
    }

    public TaskFlowId getDynamicTaskFlowId() {
        return dynamicTaskFlowId;
    }

    public void setParameters(HashMap parameters) {
        this.parameters = parameters;
    }

    public HashMap getParameters() {
        return parameters;
    }
}


5. 运行
效果和《Tree组件使用指南之四:点击不同的树节点显示不同的表单》一样,不过这里使用的技术是带参数的Dynamic Region。

Project 下载:ADF_Region_Dynamic.7z

1. http://www.baigzeeshan.com/2010/06/working-with-dynamic-regions-in-oracle.html
2. https://cn.forums.oracle.com/forums/thread.jspa?threadID=2270511

2012年7月5日星期四

ADF_160:AM使用指南之七:Configuration参数说明(4)

AM的参数中与数据库有关的有两个重要的参数:jbo.doconnectionpooling和jbo.txn.disconnect_level。
因为与数据库有关,所以这两个参数与性能有直接的关系。

1. jbo.doconnectionpooling
(1)=false,默认值,表明每个AM实例保留一个数据库连接,即使用户请求结束之后,依然保留,直到AM实例消亡。
优点:用户下一次请求时,不用再获取数据库连接。
缺点:大量的数据库连接被AM保留,即使这些连接未真正使用,导致服务器资源紧张。
(2)=true,表明每个AM实例只在请求期间保留一个数据库连接,用户请求结束之后,就释放数据库连接。
优点:及时释放数据库连接,节省服务器资源。
缺点:因为释放掉了数据库连接,需要保存当前cursor的位置(ADF-BC的原理如此),即Passivation,然后重新获取一个新的连接后再Activation,会导致性能下降。
(3)小结:如果最佳性能为首要目标,选择=false;如果以降低数据库连接数为首要目标,选择=true。

2. jbo.txn.disconnect_level
(1)=0,默认值,表明当AM释放掉数据库连接时,AM的状态(VO、RowSet、Cursor)会持久化到数据库中。
(2)=1,表明,表明当AM释放掉数据库连接时,AM的状态(VO、RowSet、Cursor)会持久化到内存中。

3. jbo.doconnectionpooling=true + jbo.txn.disconnect_level=1
既然单独jbo.doconnectionpooling=true,会由于Passivation和Activation而导致性能下降,那么作为补偿的手段之一设置jbo.txn.disconnect_level=1,可以弥补一些性能的下降。

结论
(1)如果用户并发数不是很高,并且降低数据库连接数不作为首要目标,那么可以选择jbo.doconnectionpooling =false,因为这样应用的性能比较好(在每次请求时不用重新获取数据库连接和AM状态的持久化)。
这是一种牺牲数据库链接资源保证应用性能的作法。
(2)如果用户并发数很高,并且降低数据库连接数作为首要目标,那么可以选择jbo.doconnectionpooling =true,同时设置jbo.txn.disconnect_level=1,这样在减少数据库连接的同时,AM的状态持久化到内存中。
这是一种节省数据库资源的同时,尽量不降低应用性能的作法。
(3)参数的选择最终要根据应用压力测试的结果,没有哪一种配置是适合所有应用的。

ADF_159:AM使用指南之七:Configuration参数说明(3)

经常听到很多开发人员抱怨ADF应用性能差,究其原因大概都与AM的Passivation(“钝化”)和Activation(“激活”)有关。
Passivation操作要向数据库表PS_TXN写入数据,Activation操作要从数据库表PS_TXN读取数据,它将直接影响应用的性能。
那么Passivation和Activation与AM的哪些配置参数有关呢?最直接相关的参数就是Referenced Pool Size。
如果并发用户数小于该值,AM不会做Passivation和Activation,因为AM池中有足够的AM实例供用户使用。
如果并发用户数大于该值,AM将会做Passivation和Activation,因为AM池中没有足够的AM实例供用户使用。
比如,Referenced Pool Size=20,并发用户数=21,当第21个用户发起请求时,因为没有AM实例可用,只好Passivation某个AM实例,待第21个用户请求完成后,再Activation该AM实例,恢复到之前的状态。

因此想要提高应用性能,就需要知道应用的最佳并发用户数和最大并发用户数,关于最佳并发用户数和最大并发用户数,请参考《性能调优概述》。
根据这两个值来设置Referenced Pool Size和其它相关参数,目的只有一个:尽量避免和减少Passivation和Activation的次数。

1. 在给ADF应用做压力测试时,如何确定最佳并发用户数?
当并发用户数小于Referenced Pool Size时,肯定不会有Passivation和Activation。
因此要让并发用户数大于Referenced Pool Size,运行24小时,然后通过EM Console监控应用Passivation和Activation的实例峰值。
如果峰值没有超过Referenced Pool Size,继续增加并发用户数(10%),直到发现峰值超过Referenced Pool Size,则最佳并发用户数就是前一个测试的并发用户数的大小。
该测试过程是一个不断寻找的过程,在这个过程中,可能要修改Referenced Pool Size以及其它相关参数,然后修改并发用户数,直到找到最佳的数值。

2. 在给ADF应用做压力测试时,如何确定最大并发用户数?
确定最佳并发用户数后,继续增加并发用户数(10%),直到到达Maximum Pool Size,说明已经接近了应用性能的极限。
继续测试,直到出现异常:无法获得AM实例,则最大并发用户数就是前一个测试的并发用户数的大小。
该测试过程也是一个不断寻找的过程,在这个过程中,可能要同时调整Referenced Pool Size和Maximum Pool Size以及其它相关参数,然后修改并发用户数,直到找到最大的数值。

3. 在给ADF应用做压力测试时,发现实际使用的数据库连接数超过了活动的AM实例,这是为什么?
一般来说,一个AM实例会使用一个数据库连接,但是在Passivation和Activation时会使用额外的数据库连接。
可以通过设置jbo.server.internal_connection属性,指向另外一个Data Source,与AM实例使用的Data Source分开。
然后再进行压力测试,观察的另一个Data Source的使用情况,这里就是额外的数据库连接。

4. 结论
(1)AM池能够支持大大超过池子大小的并发用户数。
(2)为了减少Passivation和Activation,尽可能地让Referenced Pool Size接近最佳并发用户数。

参考文献:

1. http://andrejusb.blogspot.com/2010/12/oracle-adf-bc-11g-tuning-for-immediate.html
2. http://andrejusb.blogspot.com/2010/02/optimizing-oracle-adf-application-pool.html
3. http://andrejusb.blogspot.com/2011/10/experimenting-with-adf-bc-application.html
4. http://andrejusb.blogspot.com/2011/10/adf-bc-tuning-with-do-connection.html
5. http://andrejusb.blogspot.com/2011/11/stress-testing-oracle-adf-bc.html
6. http://andrejusb.blogspot.com/2011/11/stress-testing-oracle-adf-bc_08.html
7. http://andrejusb.blogspot.com/2011/11/stress-testing-oracle-adf-bc_16.html
8. http://andrejusb.blogspot.com/2012/01/adf-performance-marathon-22-hours.html
9. http://andrejusb.blogspot.jp/2012/08/adf-bc-passivationactivation-and-sql.html
10. http://www.oracle.com/technetwork/developer-tools/jdev/index-097578.html

ADF_158:AM使用指南之七:Configuration参数说明(2)

Application Module Pool 是用来存放有同一类型的AM 实例的池子,多个浏览器客户端可以“共享使用”少量的Application Module实例,这样就可以提高应用的性能。
只有根一级的Application Module才可以建立Pool,换句话说,内嵌的Application Module也将“共享使用”根一级的Application Module Pool,包括数据库连接,事务,缓存。
Application Module实例分为状态和无状态两种。
对于有状态的AM实例,它保存用户session的相关信息,用户做下一步操作时,将会继续使用该AM实例。
如果用户请求很多,AM实例已经接近峰值,那么将会“钝化”这些有状态的AM实例:即把有状态信息持久化。
然后把这些AM实例腾出来供其它用户使用,等到该用户继续做下一个有状态操作时,再用一个新的AM实例,并匹配“激活”刚才“钝化”的信息。

AM Pool主要参数说明如下:
1. Pool Behavior Parameters
(1)Failover Transaction State Upon Managed Release:对应jbo.dofailover属性,默认false,建议设置为true。
带有未决事务的AM实例被释放回池中时是否执行“钝化”,即处于受管状态,以保证激活时可以继续事务。
(2)Disconnect Application Module Upon Release:对应jbo.doconnectionpooling属性,默认false,建议设置为false。
当AM实例每次释放回池中时,是否释放掉其对应的JDBC连接。不释放的好处是不用再从数据库连接池获取连接,并且prepared statements也可以直接拿来就用。
(3)Support Dynamic JDBC Credentials:默认true,建议设置为true。
允许开发人员程序在用户的Session开始的时候通过代码来修改数据库的连接的用户和口令。
(4)Reset Non-Transactional State Upon Unmanaged Release:默认true,建议设置为true。
当AM实例以无状态的方式释放回池中时,是否重置所有的Non-Transactional State,比如VO的运行时设置,JDBC 的Prepared Statements,绑定变量等等,保证放回池中的AM实例是“干净”的。
(5)Enable Application Module pooling:对应jbo.ampool.doampooling属性,默认true,建议设置为true。
是否使用AM池。建议生产环境设置为true,测试环境中为了明确与AM有关的问题时可以设置为false。
(6)Row-Level Locking Behavior Upon Release:默认false,建议设置为true。
强制AM 实例被释放回池时不去在数据库中创建一个pending transaction state。
ADF web application 应该设置锁定模式为乐观锁“optimistic”(默认为悲观锁“pessimistic”),以避免创建行级锁。

2. AM实例池大小参数介绍(Pool Sizing Parameters)
(1)Initial Pool Size:默认值0,建议设置为AM实例最佳并发数+10%,即AM实例最佳并发数乘上(1+10%)。
初始化AM实例池时创建的AM实例的个数,在初始化AM实例池一开始创建一定数量的AM实例,这样不必在用户请求时再临时创建。
(2)Maximum Pool Size:默认值5000,建议设置为AM实例最大并发数+10%,即AM实例最大并发数乘上(1+10%)。
AM实例池中最多允许的AM实例的个数,也是最多允许的数据库连接数。如果请求的AM实例数超过该值,用户将会收到类似“没有可获得的AM实例或数据库连接”错误。
(3)Referenced Pool Size:默认值10,建议设置为有状态的并发的用户数。
AM实例池中最多允许的有状态(preserve session affinity)的AM实例的个数。
预估一下这样的并发用户有多少:做完某个操作后,短暂思考一下,继续下一个操作。
其好处是:一个有状态的AM实例将一直为一个用户服务,省去了钝化再激活的过程,节省了切换AM的时间。

3. Pool Cleanup Parameters
(1)Minimum Available Size:默认值5。
AM实例池中最小可用的AM实例个数,即空闲的AM实例。
当AM实例不活动时间超过Idle Instance Timeout时,清理首先到达Maximum Available Size,然后如果还有满足条件的,继续清理,直到Minimum Available Size。
(2)Maximum Available Size:默认值25,建议设置为AM实例最佳并发数+10%,即AM实例最佳并发数乘上(1+10%)。
正常情况下,AM池中最大可用的AM实例个数,即处于工作待命的AM实例数。
当AM实例不活动时间超过Idle Instance Timeout时,清理首先到达Maximum Available Size,然后如果还有满足条件的,继续清理,直到Minimum Available Size。
(3)Idle Instance Timeout:默认值600000(10分钟),建议大于Session Timeout的时间,防止频繁做Passivation和Activation。
AM实例不活动时间,单位毫秒。当AM实例不活动时间超过此值后,将被标记为“可以清除”,并在下次AM池“大扫除”时被清除。
值得注意的是,即使AM实例被清除了,它依然关联着一个数据库连接,直到经过了AM的最大生存时间:Maximum Instance Time to Live。
(4)Pool Polling Interval:默认值600000(10分钟),建议设置为30000(30秒)。
AM实例池进行“大扫除”的时间间隔,单位毫秒。
(5)Maximum Instance Time to Live:对应属性jbo.ampool.timetolive,默认值3600000(1小时),建议大于Session Timeout的时间,防止频繁做Passivation和Activation。
AM 实例池中每个实例允许存活的最大毫秒值,当AM实例超过该值以后,将被标记“可以清除”,并在下次AM池“大扫除”时被清除。
默认值为1小时,实际上是不太合适的,因为在此期间用户的访问量可能很多,导致AM的实例数也很高,而每个AM又关联着一个数据库连接,最终数据库连接耗尽。

重要说明:
(1)所有的参数均是针对在一个JVM上运行的Application Server(如WebLogic Server)上的设置,如果是在集群的情况下,要除以Application Server的个数。
因为AM Pool是建立在每一个Application Server上的。
(2)WebLogic Server上数据库连接池有一个参数:Inactive Connection Timeout,应该设置为0。这样做是为了不与参数Disconnect Application Module Upon Release冲突。
即完全由AM来决定是否把数据库连接释放回数据库连接池。
(3)参数Idle Instance Timeout、Pool Polling Interval、Maximum Instance Time to Live 是相关联的。
Maximum Instance Time to Live(2分钟) 大于 Idle Instance Timeout(1分钟) + Pool Polling Interval(30秒):每经过30秒,AM池清除那些不活动时间超过1分钟的AM实例;每个AM实例2分钟后,释放其关联的数据库连接。

参考文献:
1. http://www.oracle.com/technology/products/jdev/howtos/10g/adfbc_perf_and_tuning.html
2. http://www.oracle.com/technology/products/jdev/tips/muench/ampooling/index.html
3. http://www.avromroyfaderman.com/2008/10/adf-bc-tuning-i-entity-objects/
4. http://huraky.javaeye.com/blog/577791
5. http://andrejusb.blogspot.com/2010/02/optimizing-oracle-adf-application-pool.html

ADF_157:AM使用指南之七:Configuration参数说明(1)

开发环境:JDeveloper 11.1.2.2.0 + Oracle XE Database 10gR2。

在AM的配置项中,有Connection Type一项。
Connection Type分为两种:JDBC Datasource和JDBC URL。

1. JDBC Datasource
JDBC Datasource一般用在生产环境,由应用服务器管理Connection Pool,与ADF无关。
此时,AM也将从应用服务器管理的Connection Pool中获取数据库连接。
使用JDBC Datasource的好处是不用在开发端配置数据库具体信息,将来数据库变了,只要JNDI名字不变,程序就不用修改。

可以看出,当Connection Type=JDBC Datasource时,Connection Pool的所有参数都被忽略。


2. JDBC URL
JDBC URL一般用在开发环境,由ADF管理Connection Pool。

这时,你可以修改相关的的参数。
可以看出,当Connection Type=JDBC URL时,你可以修改Connection Pool相关的的参数。

一般来说,开发时不会关心数据库连接池的优化,因此,一般开发时不修改这些参数。

小结:一般不需要修改Connection Pool的配置,因为这个是由应用服务器管理的,不同的应用服务器配置不同,需要查相关手册。

2012年7月4日星期三

ADF_156:AM使用指南之六:使用Shared AM提高性能

开发环境:JDeveloper 11.1.2.2.0 + Oracle XE Database 10gR2。

默认情况下,每个用户发出一个请求,都会实例化一个新的AM来响应该请求。
与一般的AM不同,Shared AM可以只由一个AM实例来满足多次请求,适用于一些共享和公共的资源,比如公共的LOV:DepartemntLOV,ManagerLOV。
当然,由Shared AM管理这些LOV的前提是:这些LOV的数据基本很少改变,相对是“静态的”。
我们可以把这些公共的VO集中起来,统一交由Shared AM来管理,这样这些LOV将被一直存在于Application或Session范围中,大大减少访问数据库的次数,从而提高性能。

重点步骤说明:

1. 创建要Shared AM的Model Project
先后分别创建两个ADF Model Project:

(1)common.model.hrHrCommonModel:包括HrCommonAppModule AM、Jobs EO、JobsView VO。
(2)common.model.org.OrgCommonModel:包括OrgCommonAppModule AM、Departments EO、DepartmentsView VO。
特别要注意的是:一定要修改默认的Packge路径,不要和其它Package路径发生冲突,因为这些对象都是要被别的Project引用的。

2. 以ADF Library Jar File的形式发布要Shared AM的Model Project

3. 新建一个ADF应用,选择Employees Table
(1)为Model Project导入其它两个Model Project的ADF Library Jar File。

(2)为Model Project添加两个Shared AM,即HrCommonAppModule和OrgCommonAppModule。

注意,Shared AM将自动使用Shared Configuration,即HrCommonAppModuleShared和OrgCommonAppModuleShared,无论默认的配置是否设置的是Local Configuration。
(3)为EmployessView的DepartmentId和JobId分别增加LOV,分别指向OrgCommonAppModule中的DepartmentsView和HrCommonAppModule中的JobsView。
注意:这里只能使用Choice List作为展现形式,而不能使用Input Text with LOV。
因为Shared AM中的LOV是为所有用户服务的,因此必须是只读形式,而不能允许查询,否则会影响其它用户使用。
(4)创建页面,拖放Employee。


4. 为HrCommonAppModuleShared和OrgCommonAppModuleShared的jbo.shared.txn设置同样的值
这样设置后,HrCommonAppModuleShared和OrgCommonAppModuleShared将会共享一个数据库连接。


我觉得不设置这个属性也可以,因为除了第一次使用以外,以后都是从内存中读取LOV,不会再访问数据库。

5. 运行
使用SQL语句查询数据库连接数:select count(sid),username from v$session group by username;
(1)页面第一次Load出来,显示连接数:3。
解释:AppModule、HrCommonAppModule、OrgCommonAppModule各自使用了一个数据库连接。
(2)重新打开一个浏览器,再次访问页面,显示连接数:4。
解释:只有AppModule新增了一个AM实例,该实例使用了一个数据库连接,其它两个AM没有访问数据库。
即两个LOV的数据是从内存读取的,说明Shared AM的确起作用了。

Project 下载:ADF_AM_Shared.7z

参考文献:
1. http://www.oracle.com/technetwork/developer-tools/adf/learnmore/87-lov-using-shared-am-444769.pdf
2. 《Fusion Developer's Guide for ADF》之 Sharing Application Module View Instances
3. http://jobinesh.blogspot.jp/2011/05/two-root-application-modules-sharing.html
4. http://www.avromroyfaderman.com/2008/09/shared-application-module-instance-tricks-part-i-service-methods/
5. http://www.avromroyfaderman.com/2008/09/shared-application-module-instance-tricks-part-ii-displaying-data/