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/

没有评论: