2012年3月28日星期三

ADF_117:VO高级使用技巧之二:编程实现基于Ref Cursor的VO

开发运行环境:JDevloper 11.1.2.4.0+ Oracle Database 11gR2 XE

本文最后一次修改日期:2013-07-01

在Fusion Order Demo的StandaloneExamples中的AdvancedViewObjectsExamples中的ViewObjectOnRefCursor,介绍了如何使用ResultSet查询结果集实现View Object。
其中,Ref Cursor是使用PL-SQL实现的。
关于Fusion Order Demo,请参考《发布与运行 Oracle Fusion Order Demo》。
关于VO中的重要方法的介绍,请参考《VO高级使用技巧之一:可重写的重要的方法》。

1. PL-SQL代码

REM  For security reasons, the FOD user is not given create procedure privileges
REM  To run this script, issue the following statement as system:
REM  grant create procedure to fod;

REM  To back out this change, issue the following as FOD:
REM  drop package RefCursorExample;
REM  And run the following as system:
REM  revoke create procedure from fod;

CREATE OR REPLACE PACKAGE RefCursorExample IS
  TYPE ref_cursor IS REF CURSOR;
  FUNCTION get_orders_for_customer(p_email VARCHAR2) RETURN ref_cursor;
  FUNCTION count_orders_for_customer(p_email VARCHAR2) RETURN NUMBER;
END RefCursorExample;
.
/
show errors
CREATE OR REPLACE PACKAGE BODY RefCursorExample IS
  FUNCTION get_orders_for_customer(p_email VARCHAR2) RETURN ref_cursor IS
    the_cursor ref_cursor;
  BEGIN
    OPEN the_cursor FOR
      SELECT o.order_id, o.order_status_code, o.order_total
        FROM orders o, persons p
     WHERE o.customer_id = p.person_id
       AND p.email = p_email;
    RETURN the_cursor;
  END get_orders_for_customer;

  FUNCTION count_orders_for_customer(p_email VARCHAR2) RETURN NUMBER IS
    the_count NUMBER;
  BEGIN
    SELECT COUNT(*)
      INTO the_count
      FROM orders o, persons p
     WHERE o.customer_id = p.person_id
       AND p.email = p_email;
    RETURN the_count;
  END count_orders_for_customer;
END RefCursorExample;
.
/
show errors

2. OrdersForCustomerImpl.java代码

package devguide.advanced.refcursor;

import java.math.BigDecimal;

import java.sql.CallableStatement;
import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Types;
import java.sql.Timestamp;

import oracle.jbo.JboException;
import oracle.jbo.domain.Date;
import oracle.jbo.domain.Number;
import oracle.jbo.server.SQLBuilder;
import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;

import oracle.jdbc.OracleTypes;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class OrdersForCustomerImpl extends ViewObjectImpl {
    /**This is the default constructor (do not remove).
     */
    public OrdersForCustomerImpl() {
    }

    /**
     * Overridden framework method.
     *
     * Executed when the framework needs to issue the database query for
     * the query collection based on this view object. One view object
     * can produce many related result sets, each potentially the result
     * of different bind variable values. If the rowset in query is involved
     * in a framework-coordinated master/detail viewlink, then the params array
     * will contain one or more framework-supplied bind parameters. If there
     * are any user-supplied bind parameter values, they will *PRECEED* the
     * framework-supplied bind variable values in the params array, and the
     * number of user parameters will be indicated by the value of the
     * numUserParams argument.
     */
    protected void executeQueryForCollection(Object qc, Object[] params, int numUserParams) {
        storeNewResultSet(qc, retrieveRefCursor(qc, params));
        super.executeQueryForCollection(qc, params, numUserParams);
    }

    /**
     * Overridden framework method.
     *
     * Wipe out all traces of a built-in query for this VO
     */
    protected void create() {
        getViewDef().setQuery(null);
        getViewDef().setSelectClause(null);
        setQuery(null);
    }

    /**
     * Overridden framework method.
     *
     * The role of this method is to "fetch", populate, and return a single row
     * from the datasource by calling createNewRowForCollection() and populating
     * its attributes using populateAttributeForRow().
     */
    protected ViewRowImpl createRowFromResultSet(Object qc, ResultSet rs) {
        /*
       * We ignore the JDBC ResultSet passed by the framework (null anyway) and
       * use the resultset that we've stored in the query-collection-private
       * user data storage
       */
        rs = getResultSet(qc);

        /*
       * Create a new row to populate
       */
        ViewRowImpl r = createNewRowForCollection(qc);

        try {
            /*
         * Populate new row by attribute slot number for current row in Result Set
         */
            populateAttributeForRow(r, 0, rs.getLong(1));
            populateAttributeForRow(r, 1, rs.getString(2));
            populateAttributeForRow(r, 2, rs.getString(3));
        } catch (SQLException s) {
            throw new JboException(s);
        }
        return r;
    }

    /**
     * Overridden framework method.
     *
     * Return true if the datasource has at least one more record to fetch.
     */
    protected boolean hasNextForCollection(Object qc) {
        ResultSet rs = getResultSet(qc);
        boolean nextOne = false;
        try {
            nextOne = rs.next();
            /*
         * When were at the end of the result set, mark the query collection
         * as "FetchComplete".
         */
            if (!nextOne) {
                setFetchCompleteForCollection(qc, true);
                /*
           * Close the result set, we're done with it
           */
                rs.close();
            }
        } catch (SQLException s) {
            throw new JboException(s);
        }
        return nextOne;
    }

    /**
     * Overridden framework method.
     *
     * The framework gives us a chance to clean up any resources related
     * to the datasource when a query collection is done being used.
     */
    protected void releaseUserDataForCollection(Object qc, Object rs) {
        /*
       * Ignore the ResultSet passed in since we've created our own.
       * Fetch the ResultSet from the User-Data context instead
       */
        ResultSet userDataRS = getResultSet(qc);
        if (userDataRS != null) {
            try {
                userDataRS.close();
            } catch (SQLException s) {
                /* Ignore */
            }
        }
        super.releaseUserDataForCollection(qc, rs);
    }

    /**
     * Overridden framework method
     *
     * Return the number of rows that would be returned by executing
     * the query implied by the datasource. This gives the developer a
     * chance to perform a fast count of the rows that would be retrieved
     * if all rows were fetched from the database. In the default implementation
     * the framework will perform a SELECT COUNT(*) FROM (...) wrapper query
     * to let the database return the count. This count might only be an estimate
     * depending on how resource-intensive it would be to actually count the rows.
     */
    public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
        Long result =
            (Long)callStoredFunction(NUMBER, "RefCursorExample.count_orders_for_customer(?)", viewRowSet.getParameters(true));
        return result.longValue();
    }
    // ------------- PRIVATE METHODS ----------------


    /**
     * Return a JDBC ResultSet representing the REF CURSOR return
     * value from our stored package function.
     */
    private ResultSet retrieveRefCursor(Object qc, Object[] params) {
        ResultSet rs =
            (ResultSet)callStoredFunction(OracleTypes.CURSOR, "RefCursorExample.get_orders_for_customer(?)", new Object[] { getNamedBindParamValue("CustEmail",
                                                                                                                                                   params) });
        return rs;
    }

    private Object getNamedBindParamValue(String varName, Object[] params) {
        Object result = null;
        if (getBindingStyle() == SQLBuilder.BINDING_STYLE_ORACLE_NAME) {
            if (params != null) {
                for (Object param : params) {
                    Object[] nameValue = (Object[])param;
                    String name = (String)nameValue[0];
                    if (name.equals(varName)) {
                        return (String)nameValue[1];
                    }
                }
            }
        }
        throw new JboException("No bind variable named '" + varName + "'");
    }

    /**
     * Store a new result set in the query-collection-private user-data context
     */
    private void storeNewResultSet(Object qc, ResultSet rs) {
        ResultSet existingRs = getResultSet(qc);
        // If this query collection is getting reused, close out any previous rowset
        if (existingRs != null) {
            try {
                existingRs.close();
            } catch (SQLException s) {
            }
        }
        setUserDataForCollection(qc, rs);
        hasNextForCollection(qc); // Prime the pump with the first row.
    }

    /**
     * Retrieve the result set wrapper from the query-collection user-data
     */
    private ResultSet getResultSet(Object qc) {
        return (ResultSet)getUserDataForCollection(qc);
    }

    /**
     * Return either null or a new oracle.jbo.domain.Date
     */
    private static Date nullOrNewDate(Timestamp t) {
        return t != null ? new Date(t) : null;
    }

    /**
     * Return either null or a new oracle.jbo.domain.Number
     */
    private static Number nullOrNewNumber(BigDecimal b) {
        try {
            return b != null ? new Number(b) : null;
        } catch (SQLException s) {
        }
        return null;
    }
    //----------------[ Begin Helper Code ]------------------------------
    public static int NUMBER = Types.NUMERIC;
    public static int DATE = Types.DATE;
    public static int VARCHAR2 = Types.VARCHAR;

    /**
     * Simplifies calling a stored function with bind variables
     *
     * You can use the NUMBER, DATE, and VARCHAR2 constants in this
     * class to indicate the function return type for these three common types,
     * otherwise use one of the JDBC types in the java.sql.Types class.
     *
     * NOTE: If you want to invoke a stored procedure without any bind variables
     * ====  then you can just use the basic getDBTransaction().executeCommand()
     *
     * @param sqlReturnType JDBC datatype constant of function return value
     * @param stmt stored function statement
     * @param bindVars Object array of parameters
     * @return function return value as an Object
     */
    protected Object callStoredFunction(int sqlReturnType, String stmt, Object[] bindVars) {
        CallableStatement st = null;
        try {
            st = getDBTransaction().createCallableStatement("begin ? := " + stmt + "; end;", 0);
            st.registerOutParameter(1, sqlReturnType);
            if (bindVars != null) {
                for (int z = 0; z < bindVars.length; z++) {
                    st.setObject(z + 2, bindVars[z]);
                }
            }
            st.executeUpdate();
            return st.getObject(1);
        } catch (SQLException e) {
            throw new JboException(e);
        }
    }

    /**getEstimatedRowCount - overridden for custom java data source support.
     */
    public long getEstimatedRowCount() {
        long value = super.getEstimatedRowCount();
        return value;
    }

    /**Gets the bind variable value for CustEmail
     */
    public String getCustEmail() {
        return (String)getNamedWhereClauseParam("CustEmail");
    }

    /**Sets <code>value</code> for bind variable Email
     */
    public void setCustEmail(String value) {
        setNamedWhereClauseParam("CustEmail", value);
    }
}

3. 运行
(1)以system用户连接数据库,然后执行:grant create procedure to fod;
(2)以fod用户连接数据库,然后执行数据库脚本:CreateRefCursorPackage.sql
(3)运行AM



说明:本例还是一个典型的使用ResultSet实现VO的例子,你只需要修改私有方法retrieveRefCursor返回你的ResultSet就可以了。

参考文献:
1. Fusion Developer's Guide for ADF 11.1.2.4 之 42.8节:Using Programmatic View Objects for Alternative Data Sources
2. http://codingwithpassion.blogspot.jp/2010/10/oracle-adf-advanced-techniques-custom.html
3. http://adfpractice-fedor.blogspot.jp/2011/01/adf-bc-programmatically-populated-vo.html
4. http://adfpractice-fedor.blogspot.jp/2010/12/adf-bc-plsql-procedure-params.html

2012年3月27日星期二

ADF_116:VO高级使用技巧之一:可重写的重要的方法

开发运行环境:JDevloper 11.1.2.4.0+ Oracle Database 11gR2 XE

本文最后一次修改日期:2013-07-01

ViewObjectImpl.java中有如下可重写的重要的方法:

1. create()
当VO实例被创建时调用,可用于初始化VO的状态信息。
重写此方法时,如果想要去掉与SQL查询的关联关系,可以调用如下语句:
// Wipe out all traces of a query for this VO
getViewDef().setQuery(null);
getViewDef().setSelectClause(null);
setQuery(null);

2. executeQueryForCollection()
当执行VO查询时调用。

3. hasNextForCollection()
用于支持 row set iterator上的hasNext()方法。
重写此方法时,如果有下一条记录,返回true,否则返回false。

4. createRowFromResultSet()
用于populate取回的数据,变为Row对象保存。
重写此方法时,需要调用方法createNewRowForCollection()创建一个空行,然后调用方法populateAttributeForRow()生成Row对象及其属性。

5. getQueryHitCount()
用于支持方法getEstimatedRowCount()。
重写此方法时,返回满足查询条件的记录数。

6. releaseUserDataForCollection()
用于释放与Row Set关联的上下文对象。

7. getUserDataForCollection() 和 setUserDataForCollection()
上面介绍的方法中,很多都带有一个参数:qc,该参数即是Row Set对象。
重写上面的方法时,经常会需要重新设定或获取Row Set对象,这时,就可以调用getUserDataForCollection() 和 setUserDataForCollection()。

参考文献:
1. Fusion Developer's Guide for ADF 11.1.2.4 之 42.8节:Using Programmatic View Objects for Alternative Data Sources

2012年3月26日星期一

ADF_115:如何使用Dialog Framework?

运行环境:JDeveloper 11.1.2.2.0 + Oracle Database 10g Express Edition 10.2.0.1。
以前如果想要打开一个窗口,需要使用JavaScript;如果窗口中有多个页面,需要自己写代码管理。
使用ADF Dialog Framework,无需使用JavaScript,就可以很方便地打开一个窗口并管理窗口中的页面。
使用ADF Dialog Framework的方法非常简单:
(1)在导航规则中,以dialog:开头,导航到要在窗口中显示的页面。
(2)在命令组件上,action属性选择以dialog:开头的规则。
(3)在命令组件上,useWindow属性设置为true。

举例说明:一个用户注册的页面流如下:
(1)login.jsf
登录页面,登录成功,进入orders.jsf;登录失败,弹出一个窗口,显示error.jsf。
新用户可以点击New User?链接创建账户,弹出一个窗口,显示newAccount.jsf。
(2)error.jsf
登录失败页面,点击Back按钮,关闭弹出窗口,控制权返回login.jsf。
(3)orders.jsf
登录成功页面。
(4)newAccount.jsf
输入用户信息,点击Detail按钮,进入accountDetail.jsf,注意,此时仍在弹出的窗口中。
(5)accountDetail.jsf
输入用户账户信息,点击Done按钮提交,关闭弹出窗口, 控制权返回login.jsf,并把账户和口令传递给login.jsf,并显示出来 。
或点击Cancel按钮取消, 关闭弹出窗口, 控制权返回login.jsf。

重点步骤说明:
1. login.jsf

<af:form id="f1">
  <af:inputText label="User Name:" id="it1" value="#{backing_login.username}" partialTriggers="cl1" />
  <af:inputText label="Password:" id="it2" value="#{backing_login.password}" partialTriggers="cl1" />
  <af:commandButton text="Login" id="cb1" action="#{backing_login.handleLogin}" useWindow="true"/>
  <af:spacer width="10" height="10" id="s1"/>
  <af:commandLink text="New user?" id="cl1" action="dialog:new_account" useWindow="true" partialSubmit="true"
                  launchListener="#{backing_login.handleLaunch}"
                  returnListener="#{backing_login.handleReturn}" windowHeight="300" windowWidth="500"/>
</af:form>

说明:
(1)action=下一个导航目的地。必须以dialog:打头,这样ADF Faces才知道要把下一个页面以dialog形式打开。
(2)useWindow="true"。 表明下一个导航目的地,将使用dialog展现。
(3)windowHeight和windowWidth
(4)windowEmbedStyle
=inlineDocument,popup dialog与原始页面是同一个view id。
=window,popup dialog 单独使用一个view id。
(5)windowModalityType
=applicationModal,modal模式。
=modeless,非modal模式。
(6)launchListener=某个Backing Bean中的一个方法。
弹出窗口显示时调用的方法,可以用来传递参数给弹出窗口。
(7)returnListener=某个Backing Bean中的一个方法。
弹出窗口关闭时调用的方法,可以用来接收弹出窗口的返回参数。
(8)partialSubmit="true"。
防止popup dialogx显示时,原始页面被reloading。
(9)设置username 和password的patialTriggers指向commandLink
这样,当弹出窗口关闭时,会自动刷新这两个字段。

2. accountDetail.jsf

<af:form id="f1">
    <af:inputText label="Username:" id="it1"  value="#{pageFlowScope.firstName}"/>
    <af:inputText label="Password:" id="it2"  value="#{pageFlowScope.password}"/>
    <af:inputText label="Confirm Password:" id="it3"  value="#{pageFlowScope.confirmPassword}"/>
    <af:commandButton text="Cancel" id="cb1">
        <af:returnActionListener/>
    </af:commandButton>
    <af:commandButton text="Done" id="cb2" actionListener="#{backing_login.done}" />
</af:form>

说明:
(1)对于Cancel按钮,可以设置immediate="true",更重要的是要设置Return Action Listener,它会调用returnFromDialog方法,即关闭弹出窗口,并返回null。
(2)对于Done按钮,在Managed Bean的方法中调用returnFromDialog方法,该方法可以返回一个java.lang.Object或java.util.Map对象。即关闭弹出窗口,并返回参数值。

3. Managed Bean的完整代码

package view;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import oracle.adf.view.rich.context.AdfFacesContext;

import org.apache.myfaces.trinidad.event.LaunchEvent;
import org.apache.myfaces.trinidad.event.ReturnEvent;


public class LoginBackingBean {
    public LoginBackingBean() {
    }
    // Use by inputText value binding
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public String handleLogin() {
        String retValue = "dialog:error";
        if (password != null && password.equals("oracle")) {
            retValue = "success";
        }
        return retValue;
    }

    public void handleLaunch(LaunchEvent launchEvent) {
        //Pass the current value of the field into the dialog
        Object usr = username;
        launchEvent.getDialogParameters().put("hello", "maping");      
    }

    public void handleReturn(ReturnEvent returnEvent) {
        if (returnEvent.getReturnValue() != null) {
            Customer cst = (Customer)returnEvent.getReturnValue();
            username = cst.getFirstName();
            password = cst.getPassword();
        }
    }

    public void done(ActionEvent e) {
        AdfFacesContext afContext = AdfFacesContext.getCurrentInstance();
        System.out.println("################## " + afContext.getPageFlowScope().get("hello"));
        String firstName = afContext.getPageFlowScope().get("firstName").toString();
        String lastName = afContext.getPageFlowScope().get("lastName").toString();
        String street = afContext.getPageFlowScope().get("street").toString();
        String zipCode = afContext.getPageFlowScope().get("zipCode").toString();
        String password = afContext.getPageFlowScope().get("password").toString();
        String confirmPassword = afContext.getPageFlowScope().get("confirmPassword").toString();
        if (!password.equals(confirmPassword)) {
            FacesMessage fm = new FacesMessage();
            fm.setSummary("Confirm Password");
            fm.setDetail("You've entered an incorrect password. Please verify that you'veentered a correct password!");
            FacesContext.getCurrentInstance().addMessage(null, fm);
        } else {
            //Get the return value
            Customer cst = new Customer();
            cst.setFirstName(firstName);
            cst.setLastName(lastName);
            cst.setStreet(street);
            cst.setZipCode(zipCode);    
            cst.setPassword(password);
            // And return it
            afContext.getCurrentInstance().returnFromDialog(cst, null);
            afContext.getPageFlowScope().clear();
        }
    }
}

说明:
(1)当窗口弹出时,命令组件上的launchListener将被调用,通过调用getDialogParameters把参数/参数值放到一个Map中,该Map可以把通过pageFlowScope访问到。
(2)当弹出窗口关闭时,命令组件上的returnListener将被调用,返回参数值可以通过returnEvent.getReturnValue()获得。

Project 下载:ADF_Dialog_Framework.7z

参考文献:

1.《Fusion Developer's Guide for Oracle ADF》之 Using the ADF Faces Dialog Framework
2. http://andrejusb.blogspot.com/2011/08/adf-dialog-framework-and-adf-task-flow.html
3. http://www.oracle.com/technetwork/developer-tools/jdev/index-095233.html
4. http://www.oracle.com/technetwork/cn/testcontent/index-101017-zhs.html
5. https://forums.oracle.com/forums/thread.jspa?threadID=550661

6. http://www.yaosansi.com/post/605.html
7. http://oracleseeker.com/2009/12/30/adf_dialog_inlinepopup_11g/

2012年3月25日星期日

ADF_114:使用EL表达式向Managed Bean传递参数

这是开发人员经常遇到的问题,不过回答可能令人沮丧,简单的回答是不行。
是的,你无法通过使用EL表达式向Managed Bean传递参数,至少目前不行,JSF1.2不行。
不对吧,你也许会说,因为EL表达式:#{securityContext.userInRole['admin']}是可以工作的。
这不就是向向一个Managed Bean(securityContext)的方法(userInRole)传递了一个参数值('admin')吗?
且慢!事情不是你看上去的那样!
EL表达式分两种,一种是ValueExpression,一种是MethodExpression。

1. ValueExpression
ValueExpression是绑定到Managed Bean的属性上的。
ValueExpression的解析原理是从左到右、按先后的部分解析,表达式的后面的部分是作为前面部分的属性。
比如,#{securityContext.userInRole['admin']}其实是和#{securityContext.userInRole.admin}等价的。
其含义都是:查找 #{securityContext.userInRole}对象的admin属性,由于#{securityContext.userInRole}是一个Map类型的对象,因此获取其admin属性,就是在Map中查找key='admin'的value值。
现在,你应该注意到'admin'是被[]包围的,而不是(),所以这不是方法调用,而是获取Map对象中的key='admin'的value值。

2. MethodExpression
MethodExpression是绑定到Managed Bean的方法上的。
MethodExpression的解析原理也是从左到右、按先后的部分解析,表达式的后面的部分是作为前面部分的方法的上下文环境。
但是目前不支持使用EL表达式向Managed Bean传递参数。

难道说,就没有办法向Managed Bean传递参数了吗?当然不是,我们可以不使用EL,通过其它方式也能达到目的。

3. 使用其它方式向Managed Bean传递参数
基本思路都是参数绑定到Managed Bean的某个属性,也就是说,方法的定义中不包括参数,但是可以在方法体中获取参数值。
(1)直接把参数绑定到Managed Bean的某个属性
比如,在页面中使用#{myBackingBean.firstname},在Managed Bean中定义firstname属性,以及getter和setter方法。
(2)把变量值放到Memory Scope中,然后在Managed Bean中获取,详见《使用Managed Bean访问各个Scope变量》。
(3)通过ADF Binding保存变量,然后在Managed Bean中获取,详见《ADF工具类:JSFUtil.java 源代码》和《ADF工具类:ADFUtil.java 源代码》。
(4)通过在faces-config.xml或adfc-config.xml中的Managed Bean的定义传递参数。

<managed-bean>
<managed-bean-name>userbean</managed-bean-name>
<managed-bean-class>com.oracle.sample.User</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>userType</property-name>
<value>#{reference['USER_TYPES'].['EMPLOYEE']}</value>
</managed-property>
</managed-bean>


当然,我承认,如果EL表达式能够支持,是最简洁一种实现方式。

参考文献:
1. https://blogs.oracle.com/jdevotnharvest/entry/passing_parameters_to_managed_bean_method_using_el
2. https://forums.oracle.com/forums/thread.jspa?messageID=3963557
3. https://forums.oracle.com/forums/thread.jspa?threadID=1039258

2012年3月24日星期六

ADF_113:使用Managed Bean访问各个Scope变量

开发环境:JDevloper 11.1.2.1.0+ Oracle Database 10g Express Edition 10.2.0.1。

开发中经常会遇到访问不同Scope变量的需求。本文就这一问题作了一个实验来说明如何使用Manged Bean访问。

1. 页面代码
在页面中,使用了6个InputText来保存ADF中不同Scope的变量:RequestScope、ViewScope、PageFlowScope、BackingBeanScope、SessionScope、ApplicationScope。

<af:form id="f1">
<af:inputText label="ADF RequestScope:" id="it1" value="#{requestScope.requestVar}"/>
<af:inputText label="ADF View Scope:" id="it2" value="#{viewScope.viewVar}"/>
<af:inputText label="ADF PageFlow Scope:" id="it3" value="#{pageFlowScope.pageFlowVar}"/>
<af:inputText label="ADF BackingBean Scope:" id="it4" value="#{backingBeanScope.backingBeanVar}"/>
<af:inputText label="ADF Session Scope: " id="it5" value="#{sessionScope.sessionVar}"/>
<af:inputText label="ADF Application Scope: " id="it6" value="#{applicationScope.applicationVar}"/>
<af:commandButton text="Submit" id="cb1" actionListener="#{myBackingBean.submitButton_action}"/>
</af:form>


2. Managed Bean代码

package view;

import java.util.Map;

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

import oracle.adf.share.ADFContext;

import org.apache.myfaces.trinidad.context.RequestContext;

import view.util.JSFUtils;

public class MyBackingBean {
public MyBackingBean() {
}

public void submitButton_action(ActionEvent evt) {
System.out.println("############## Get Scope Vaiables from ADFContext ################");
ADFContext ac = ADFContext.getCurrent();
Map requestScope = ac.getRequestScope();
Map viewScope = ac.getViewScope();
Map pageFlowScope = ac.getPageFlowScope();
//Map backingBeanScope = ac.getBackingBeanScope();
Map sessionScope = ac.getSessionScope();
Map applicationScope = ac.getApplicationScope();

String requestVar = (String)requestScope.get("requestVar");
System.out.println("############## requestVar: " + requestVar);
String viewVar = (String)viewScope.get("viewVar");
System.out.println("############## viewVar: " + viewVar);
String pageFlowVar = (String)pageFlowScope.get("pageFlowVar");
System.out.println("############## pageFlowVar: " + pageFlowVar);
String backBeanVar = (String)JSFUtils.getManagedBeanValue("backingBeanScope.backingBeanVar");
System.out.println("############## backBeanVar: " + backBeanVar);
String sessionVar = (String)sessionScope.get("sessionVar");
System.out.println("############## sessionVar: " + sessionVar);
String applicationVar = (String)applicationScope.get("applicationVar");
System.out.println("############## applicationVar: " + applicationVar);

//requestScope.clear();
//viewScope.clear();
//pageFlowScope.clear();
//sessionScope.clear();
//applicationScope.clear();

System.out.println("############## Get Scope Vaiables from RequestContext ################");
RequestContext rc = RequestContext.getCurrentInstance();
String pageFlowVar2 = (String)rc.getPageFlowScope().get("pageFlowVar");
System.out.println("############## pageFlowVar2: " + pageFlowVar2);

System.out.println("############## Get Scope Vaiables from FacesContext ################");
FacesContext fc = FacesContext.getCurrentInstance();
ExternalContext ec = fc.getExternalContext();
Map requestMap = ec.getRequestMap();
Map sessionMap = ec.getSessionMap();
Map applicationMap = ec.getApplicationMap();

String initVar = ec.getInitParameter("administrator.email");
System.out.println("############## initVar: " + initVar);
String requestVar3 = (String)requestMap.get("requestVar");
System.out.println("############## requestVar3: " + requestVar3);
String sessionVar3 = (String)sessionMap.get("sessionVar");
System.out.println("############## sessionVar3: " + sessionVar3);
String applicationVar3 = (String)applicationMap.get("applicationVar");
System.out.println("############## applicationVar3: " + applicationVar3);
}
}

(1)使用ADFContext访问不同Scope中的变量
可以看出,除了BackingBeanScope之外,其它Scope的变量都提供了相应的API。输出结果如下:
############## Get Scope Vaiables from ADFContext ################
############## requestVar: 1
############## viewVar: 2
############## pageFlowVar: 3
############## backBeanVar: 4
############## sessionVar: 5
############## applicationVar: 6

(2)使用RequestContext访问PageFlowScope中的变量
个人感觉这种方式不太标准,因为我只找到访问PageFlowScope变量的API。输出结果如下:
############## Get Scope Vaiables from RequestContext ################
############## pageFlowVar2: 3

(3)使用FacesContext访问不同Scope中的变量
FacesContext是JSF标准的API,JSF不包括ViewScope、PageFlowScope和BackingBeanScope。
如果你开发纯JSF应用的话,就要使用FacesContext,而不能使用ADFContext。输出结果如下:
############## Get Scope Vaiables from FacesContext ################
############## initVar: admin@oracle.com
############## requestVar3: 1
############## sessionVar3: 5
############## applicationVar3: 6

另外,我还展示了如何获取应用的Init Parameter。
首先要在web.xml中,添加一个context-param定义,比如:
<context-param>
<param-name>administrator.email</param-name>
<param-value>admin@oracle.com</param-value>
</context-param>
然后使用getInitParameter("administrator.email")方法获取参数值。
注意,context-param是应用级的参数,与定义在Servlet或Filter中定义的init parameter不同。
后者是属于该Servlet或Filter的,因此要在相应的Servlet或Filter代码中获取。比如:
String value = getServletConfig().getInitParameter("param1");
String value = getServletContext().getInitParameter("param1");


Project 下载:ADF_GetScopeVariables.7z

参考文献:
1. http://biemond.blogspot.jp/2011/01/some-handy-code-for-your-managed-beans.html
2. http://biemond.blogspot.jp/2009/03/some-handy-code-for-backing-beans-adf.html

2012年3月1日星期四

ADF_112:使用SetCurrentRowWithKeyValue

ADF_112:使用SetCurrentRowWithKeyValue

开发环境:JDevloper 11.1.2.1.0+ Oracle Database 10g Express Edition 10.2.0.1。

每个View Object都有两个个默认的Operation:setCurrentRowWithKey和setCurrentRowWithKeyValue。
关于setCurrentRowWithKey使用方法请参考《收藏带参数的编辑页面》,本文介绍如何使用SetCurrentRowWithKeyValue
该方法可以用来设置VO的当前行,参数就是主键,也就是给主键赋值。
考虑如下场景:两个页面都使用了Table,分别绑定各自的VO,点击第1个页面中的某行,导航到第2个页面。
希望能够显示第2个页面中当前选中的行。

本实验也可以作为两个页面之间如何传递参数的例子。

重点步骤说明:
1. 设置VO:DepartmentHasEmployeeView的主键
该VO选择所有有员工的部门,使用SQL语句方式创建,因此需要手工设置主键:DepartmentId。


2. 设置VO:DepartmentSalaryView的主键
该VO计算所有有员工的部门的工资总和,使用SQL语句方式创建,因此需要手工设置主键:DepartmentId。


3. 创建页面:departmentList.jsf
拖放DepartmentHasEmployeeView 生成表格,把DepartmentId列使用Link方式展现。
注意,这里要拖放DepartmentSalaryView中的SetCurrentRowWithKey生成ADF Link组件。

也就是说,点击DepartmentId列,会执行DepartmentSalaryView中的SetCurrentRowWithKey方法,即把当前行的DepartmentId,传递给下一个页面:departmentSalary.jsf。
注意,如果行数比较多,出现了滚动条,需要设置属性DisplayRow=selected。
这样,如果选中的行比较靠后,滚动条会自动滚到合适的位置,不用人工再拖。
页面代码如下:
<af:commandLink actionListener="#{bindings.setCurrentRowWithKeyValue.execute}"
text="#{row.DepartmentId}" disabled="#{!bindings.setCurrentRowWithKeyValue.enabled}"
id="cl1" action="toSalary"/>

4. 创建页面:departmentSalary.jsf
拖放DepartmentSalaryView 生成表格,把DepartmentId列使用Link方式展现。
注意,这里要拖放DepartmentHasEmployeeView中的SetCurrentRowWithKey生成ADF Link组件。

也就是说,点击DepartmentId列,会执行DepartmentSalaryView中的SetCurrentRowWithKey方法,即把当前行的DepartmentId,传递回上一个页面:departmentList.jsf。
注意,如果行数比较多,出现了滚动条,需要设置属性DisplayRow=selected。
这样,如果选中的行比较靠后,滚动条会自动滚到合适的位置,不用人工再拖。
页面代码如下:
<af:commandLink actionListener="#{bindings.setCurrentRowWithKeyValue.execute}"
text="#{row.DepartmentId}"
disabled="#{!bindings.setCurrentRowWithKeyValue.enabled}" id="cl1" action="back"/>

5. 运行效果
(1)在departmentList.jsf页面中,点击DepartmentId=110的行,

(2)导航到departmentSalary.jsf页面,发现自动选中的当前行就是DepartmentId=110的那行。

(3)在departmentSalary.jsf页面中,点击DepartmentId=70的行,

(4)导航到departmentList.jsf页面,发现自动选中的当前行就是DepartmentId=70的那行。

这就是SetCurrentRowWithKeyValue起到的作用。

Project 下载:ADF_SetCurrentRowWithKeyValue.7z

参考文献:
1. https://blogs.oracle.com/shay/entry/selecting_a_row_in_a_table_to
2. https://blogs.oracle.com/shay/entry/passing_value_between_pages_to
3. http://eleven-china.blogspot.kr/2009/03/task-flow.html

ADF_111:Popup组件的属性ContentDelivery各个值的的含义

选中ADF pop组件时,细心的人会注意到有一个属性:ContentDelivery。
该属性有三个值可以设置:immediate、lazy、lazyUncached。

这三个值的含义和使用场景如下:

1. immediate
当页面被首次装载时即装载popup组件,无论该popup组件是否被真正使用到。
如果你确定该popup组件在该页面中至少会使用一次,那么你可以使用该选项,否则建议设置为另外两个值。

2. lazy
当popup组件被第一次使用时才装载,然后将popup实例缓存。
因此第一次装载有些慢,以后再使用时,会从缓存中读取popup实例,速度较快。
当popup组件显示的是静态文本时,比如警告信息,可以使用该选项。
该选项为默认选项。

3. lazyUncached
当popup组件被第一次使用时才装载,但是不缓存popup实例。
也就是说,每次使用popup组件,都会创建一个新实例。
当popup组件显示的是动态内容时,比如点击表格某行,弹出popup窗口编辑该行记录,可以使用该选项。

参考文献:
1. http://www.gebs.ro/blog/oracle/oracle-adf-tips/
2. http://jdevadf.oracle.com/adf-richclient-demo/docs/tagdoc/af_popup.html