2013年5月27日星期一

ADF_232:使用HTTP Basic Authentication Server作为ADF Mobile 应用的Login Server

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

目前,ADF Mobile应用可以使用Oracle Access
Management (OAM)作为验证服务器,也可以使用HTTP Basic Authentication Server作为验证服务器。
本文介绍后一种的配置方法。

1. 开发并部署运行一个简单的Enable ADF Security的ADF Web 应用:SampleBasicAuthenticationServer,作为HTTP Basic Authentication Server
(1)该应用只有一个welcome.jsf页面。
(2)Enable ADF Security时,选择HTTP Basic Authentication。
(3)把welcome.jsf授予角色authenticated-role。注意,为了能够授权,需要为welcome.jsf生成pageDef。

2. 创建ADF Mobile 应用,并配置安全,这里使用ADF Mobile自带的例子HelloWorld。
(1)打开adfmf-feature.xml,为Feature配置安全


(2)打开adfmf-application.xml,为应用配置Login Server


这里的Login/Logout URL指向的就是SampleBasicAuthenticationServer应用中的受保护页面:
http://10.191.4.237:7101/SampleBasicAuthenticationServer/faces/welcome.jsf。
增加Cookies:JSESSIONID,这是WebLogic Server要用到的,所以要配置。


配置完成后的样子


3. 部署并运行HelloWorld应用
(1)首先显示的是登录画面


 (2)登录成功后,才会显示Feature画面。



Project 下载:ADF_Mobile_Auth(HTTP Basic).7z

参考文献:
1. http://andrejusb.blogspot.jp/2012/10/adf-mobile-login-functionality.html

Tips_017:Win7的账户策略设置

公司的设置的密码级别要求很高,经常容易输错。
最近发现,如果连续输入的不正确,账户会被锁定:“引用账户当前已锁定,且可能无法登录”。
一开始没搞清楚是什么情况,以为机器出故障了,后来才发现与Win7的账户策略设置有关。
解决办法如下:
命令行输入:gpedit.msc,然后从“本地计算机策略”一路展开节点:
计算机配置->windows设置->安全设置->帐户策略->帐户锁定策略,找到“帐户锁定阀值”。


默认是5,表示5次输入失败后,将锁定账户。


设置为0,0表示永远不锁定账户。



参考文章:
1. http://blog.sina.com.cn/s/blog_5ad2899601017m2n.html

2013年5月23日星期四

ADF_231:ADF Mobile 11.1.2.4 Samples 介绍(17):JSExtend

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

JSExtend演示了如何在.amx页面中调用定制的Javascript方法。
这种技术非常有用,特别是在调用那些没有暴露在DeviceFeatures DataControl中的Cordova方法。
你也可以增加自己的Javascript方法,然后用这种方式去调用。
JSExtend还演示了如何在Javascript中回调Java方法。

1. MyClass.java 代码

package mobile;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.IOException;

import javax.el.ValueExpression;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.framework.api.AdfmfContainerUtilities;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class MyClass {
    public MyClass() {
    }

    // This method calls the "doAlert" javascript function in the "Javascript" feature and passes in a variable number of params
    public void FireAlerts(ActionEvent actionEvent) {
        AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Javascript", "doAlert", new Object[] {});

        AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Javascript", "doAlert", new Object[] {"arg1"});

        AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Javascript", "doAlert", new Object[] {"arg1", "arg2"});

    }

    // This method calls the "fetchPic" javascript function in the "Javascript" feature with no params
    public void FetchPic(ActionEvent actionEvent) {
        AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Javascript", "fetchPic", new Object[] {});
    }

    // This method calls the "fetchVideo" javascript function in the "Javascript" feature with no params
    public void FetchVideo(ActionEvent actionEvent) {
        AdfmfContainerUtilities.invokeContainerJavaScriptFunction("Javascript", "fetchVideo", new Object[] {});
    }


    // This method will be called by the Javascript method so we show 2-way communication  
    public void FetchCallback(String path) {
        /* Now you have the full path to the file so you can use code like the following to read it
        FileInputStream file;
        try {
        file = new FileInputStream(path);
            int bytesread = 0;
            byte[] b = new byte[1000];
            do {
                bytesread = file.read(b);
                // now do something with the byte array like copy it somewhere, stream it over a web service, etc
            } while (bytesread < 1000);
         
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        */

        // We'll simply set a scoped variable that we are displaying on the page
        ValueExpression ve = AdfmfJavaUtilities.getValueExpression("#{pageFlowScope.picpath}", String.class);
        ve.setValue(AdfmfJavaUtilities.getAdfELContext(), path);
     
    }
}

2. methods.js 代码

(function () {

    // This method shows you how to use variable args and prints out the results
    doAlert = function () {
        var args = arguments;

        var str = "doAlert, argCount:" + args.length + ", arguments:";

        for (x = 0;x < args.length;x++) {
            if (x > 0) {
                str += ", ";
            }
            str += arguments[x];
        }

        alert(str);
    };

    // This method uses PhoneGap and calls the getPicture method to get a picture from the photo library
    fetchPic = function () {
        navigator.camera.getPicture(onSuccess, onFail,{quality : 50, destinationType : navigator.camera.DestinationType.FILE_URI, sourceType : navigator.camera.PictureSourceType.PHOTOLIBRARY});
    };

    // Once a valid picture returns, it calls back to java with the result
    function onSuccess(URI) {
        adf.mf.api.invokeMethod("mobile.MyClass", "FetchCallback", URI, onInvokeSuccess, onFail);
    };

    function onFail() {
        alert("It failed");
    };

    function onInvokeSuccess(param) {
    };

    // This method uses PhoneGap and calls the getPicture method to get a picture from the photo library
    fetchVideo = function () {
        navigator.device.capture.captureVideo(captureSuccess, captureFail, {limit : 1});
    };

    function captureSuccess(mediaFiles) {
        var i, len;
        for (i=0, len=mediaFiles.length; i
            adf.mf.api.invokeMethod("mobile.MyClass", "FetchCallback", mediaFiles[i].fullPath, onInvokeSuccess, onFail);
        }
    };

    function captureFail() {
        alert("It failed.  Note: This is not supported on the simulator");
    };


})();

ADF_230:ADF Mobile 11.1.2.4 Samples 介绍(16):RESTDemo


开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

RESTDemo演示了如何使用REST风格的Web Service。
这里展示了两种使用方法:REST-XML和REST-JSON,去调用一个公共的Web Service:http://freegeoip.net/,获取当前机器的地理位置。
REST-XML使用了一个XSD,并创建了一个URL Service Data Control去访问结构化的数据,UI层直接绑定到这个DC上。
REST-JSON使用了一个帮助类:RESTServiceAdapter去获取Web Service的URL connection,然后使用帮助类:JSONSerializationHelper解析返回结果(非结构化的数据)。UI层是间接绑定到一个Managed Bean生成的DC。



1. RESTJSONBean.java 代码

package mobile;

import oracle.adfmf.dc.ws.rest.RestServiceAdapter;
import oracle.adfmf.framework.api.JSONBeanSerializationHelper;
import oracle.adfmf.framework.api.Model;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;

public class RESTJSONBean {

    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public RESTJSONBean() {
    }

    private String searchIp = "oracle.com";
    private String jsonResponse = "";
    private RESTJSONResponse response = null;

    public void setSearchIp(String searchIp) {
        String oldSearchIp = this.searchIp;
        this.searchIp = searchIp;
        propertyChangeSupport.firePropertyChange("searchIp", oldSearchIp, searchIp);
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public String getSearchIp() {
        return searchIp;
    }

    public void setJsonResponse(String response) {
        String oldResponse = this.jsonResponse;
        this.jsonResponse = response;
        propertyChangeSupport.firePropertyChange("jsonResponse", oldResponse, response);
    }

    public String getJsonResponse() {
        return jsonResponse;
    }

    public void setResponse(RESTJSONResponse response) {
        RESTJSONResponse oldResponse = this.response;
        this.response = response;
        propertyChangeSupport.firePropertyChange("response", oldResponse, response);
    }

    public RESTJSONResponse getResponse() {
        return response;
    }

    public void loadData() {
        RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();

        // Clear any previously set request properties, if any
        restServiceAdapter.clearRequestProperties();

        // Set the connection name
        restServiceAdapter.setConnectionName("GeoIP");

        // Specify the type of request
        restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);

        // Specify the number of retries
        restServiceAdapter.setRetryLimit(0);

        // Set the URI which is defined after the endpoint in the connections.xml.
        // The request is the endpoint + the URI being set
        restServiceAdapter.setRequestURI("/json/" + getSearchIp());

        setJsonResponse("");

        // Execute SEND and RECEIVE operation
        try {
            // For GET request, there is no payload
            setJsonResponse(restServiceAdapter.send(""));
            
            // Now create a new RESTJSONResponse object and parse the JSON string returned into this class
            RESTJSONResponse res = new RESTJSONResponse();
            res = (RESTJSONResponse)JSONBeanSerializationHelper.fromJSON(RESTJSONResponse.class, getJsonResponse());
            setResponse(res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

代码中的连接"GeoIP" 定义来自于URL Connection:



参考文献:
1. https://blogs.oracle.com/mobile/entry/adf_mobile_rest_json_xml
2. http://www.youtube.com/watch?v=HOesFpjBz2M
3. http://biemond.blogspot.com/2012/10/using-json-rest-in-adf-mobile.html

2013年5月22日星期三

ADF_229:ADF Mobile 11.1.2.4 Samples 介绍(15):Weather3

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

Weather3与Weather2基本实现相同,除了点击查询按钮时,实现了异步调用。

知识点:

1. 新增ForecastWorker.java,作为多线程任务类

package mobile;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class ForecastWorker implements Runnable {
    CityInformation ci = null;
    String zip = "";

    public ForecastWorker() {
        super();
    }

    public ForecastWorker(CityInformation ci, String zip) {
        this.ci = ci;
        this.zip = zip;
    }

    public void run() {
        ci.retrieveForecastAsync(zip);
        AdfmfJavaUtilities.flushDataChangeEvent();
    }
}

注意这行代码:AdfmfJavaUtilities.flushDataChangeEvent();,有了这行代码,界面才能刷新。

2. CityInformation.java中的相关方法

    public synchronized boolean retrieveForecast(String zip) {
        // First lets clear the cityForecast
        cityForecast.setSuccess((Boolean)Boolean.FALSE);
        cityForecast.setResponseText((String)"Running...");
        cityForecast.setCity((String)"");
        cityForecast.setState((String)"");
        cityForecast.setWeatherStationCity((String)"");
        cityForecast.clearForecast();
     
        ForecastWorker fw = new ForecastWorker(this, zip);
        Thread t = new Thread(fw);
        t.start();
     
        return true;
    }
 
    public synchronized boolean retrieveForecastAsync(String zip) {
        // Before we get any forecast, we get the WeatherInfo if it's not retrieved yet
        weatherInfo.retreiveWeatherInfo();

        boolean ret = false;

        List pnames = new ArrayList();
        List params = new ArrayList();
        List ptypes = new ArrayList();

        pnames.add("ZIP");
        ptypes.add(String.class);
        params.add(zip);

        try {
            // This calls the DC method and gives us the Return
            GenericType result =
                (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("WeatherDC", null, "GetCityForecastByZIP",
                                                                        pnames, params, ptypes);

            // This will give us the CityForeCast object from the result
            GenericType cfgt = (GenericType)result.getAttribute(0);

            // Read the attributes from the GenericType returend from the getCityForecastByZip call          
            cityForecast.setSuccess((Boolean)cfgt.getAttribute("Success"));
            cityForecast.setResponseText((String)cfgt.getAttribute("ResponseText"));
            cityForecast.setCity((String)cfgt.getAttribute("City"));
            cityForecast.setState((String)cfgt.getAttribute("State"));
            cityForecast.setWeatherStationCity((String)cfgt.getAttribute("WeatherStationCity"));

            // This will give us the ForecastResult which is a collection of Forecast objects
            GenericType frgt = (GenericType)cfgt.getAttribute("ForecastResult");


            // fcgt is a collection of Forecast objects, get all those in a loop
            int count = frgt.getAttributeCount();
            for (int i = 0; i < count; i++) {
                // Get each individual WeatherDescription object
                GenericType fgt = (GenericType)frgt.getAttribute(i);

                // Now make a real WeatherDescription java object out of this GenericType
                Forecast f = (Forecast)GenericTypeBeanSerializationHelper.fromGenericType(Forecast.class, fgt);
                f.weatherInfo = weatherInfo;

                // Now get the Temperature subobject
                GenericType tempgt = (GenericType)fgt.getAttribute("Temperatures");

                // Now set the high and low temps
                f.setDaytimeHigh((String)tempgt.getAttribute(0));
                f.setMorningLow((String)tempgt.getAttribute(1));
                // Now add this to our holder of all WeatherDescriptions
                cityForecast.addForecast(f);
             
            }
            ret = true;
        } catch (AdfInvocationException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, CityInformation.class, "retrieveForecastAsync",
                      ">>>>>> AdfInvocationException=" + e.getMessage());
            AdfException ex = new AdfException("Error Invoking Web Service.  Please try later", AdfException.WARNING);
            throw ex;

        } catch (Exception e2) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, CityInformation.class, "retrieveForecastAsync",
                      ">>>>>> Exception=" + e2.getMessage());
            AdfException ex = new AdfException("Error Invoking Web Service.  Please try later", AdfException.WARNING);
            throw ex;
        }
        return ret;
    }

3. 部署,运行
(1)可以看到点击查询按钮后,会马上进入到下一个页面,但是数据并没有完全得到。
这时,并不会Block用户可以做其他事情。


(2)获取数据后,界面自动刷新。



参考文献:
1. https://blogs.oracle.com/mobile/entry/web_service_example_part_3

ADF_228:ADF Mobile 11.1.2.4 Samples 介绍(14):Weather2

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

与Weather1不同,Weather2演示了如何使用Java调用Web Service,并且通过解析 "GenericType" 返回对象生成最终的Java对象。用户界面组件是绑定到Java Bean上,而不是直接绑定到Web Service上。
这样做最大的好处是:可以尽可能地控制服务的访问,比如缓存结果数据,这样,即使服务不在线,仍然可以快速提供响应。
这种实现方式的目的让应用与网络尽可能地“绝缘”。



知识点:

1. 点击天气查询按钮,调用的是WeatherBean.java中的方法callForecast,后者会调用CityInformation.java中的方法retrieveForecast

    public boolean retrieveForecast(String zip) {
        // Before we get any forecast, we get the WeatherInfo if it's not retrieved yet
        weatherInfo.retreiveWeatherInfo();

        boolean ret = false;

        Trace.log(Utility.ApplicationLogger, Level.INFO, WeatherBean.class, "retrieveForecast",
                  ">>>>>> Inside retrieveForecast");

        List pnames = new ArrayList();
        List params = new ArrayList();
        List ptypes = new ArrayList();

        pnames.add("ZIP");
        ptypes.add(String.class);
        params.add(zip);

        // First lets clear the cityForecast
        cityForecast.setSuccess((Boolean)Boolean.FALSE);
        cityForecast.setResponseText((String)"");
        cityForecast.setCity((String)"");
        cityForecast.setState((String)"");
        cityForecast.setWeatherStationCity((String)"");
        cityForecast.clearForecast();

        try {
            Trace.log(Utility.ApplicationLogger, Level.INFO, WeatherBean.class, "retrieveForecast",
                      ">>>>>> Before invokeDataControlMethod");

            // This calls the DC method and gives us the Return
            GenericType result =
                (GenericType)AdfmfJavaUtilities.invokeDataControlMethod("WeatherDC", null, "GetCityForecastByZIP",
                                                                        pnames, params, ptypes);

            // This will give us the CityForeCast object from the result
            GenericType cfgt = (GenericType)result.getAttribute(0);

            // Read the attributes from the GenericType returend from the getCityForecastByZip call          
            cityForecast.setSuccess((Boolean)cfgt.getAttribute("Success"));
            cityForecast.setResponseText((String)cfgt.getAttribute("ResponseText"));
            cityForecast.setCity((String)cfgt.getAttribute("City"));
            cityForecast.setState((String)cfgt.getAttribute("State"));
            cityForecast.setWeatherStationCity((String)cfgt.getAttribute("WeatherStationCity"));

            // This will give us the ForecastResult which is a collection of Forecast objects
            GenericType frgt = (GenericType)cfgt.getAttribute("ForecastResult");


            // fcgt is a collection of Forecast objects, get all those in a loop
            int count = frgt.getAttributeCount();
            for (int i = 0; i < count; i++) {
                // Get each individual WeatherDescription object
                GenericType fgt = (GenericType)frgt.getAttribute(i);

                // Now make a real WeatherDescription java object out of this GenericType
                Forecast f = (Forecast)GenericTypeBeanSerializationHelper.fromGenericType(Forecast.class, fgt);
                f.weatherInfo = weatherInfo;

                // Now get the Temperature subobject
                GenericType tempgt = (GenericType)fgt.getAttribute("Temperatures");

                // Now set the high and low temps
                f.setDaytimeHigh((String)tempgt.getAttribute(0));
                f.setMorningLow((String)tempgt.getAttribute(1));
                // Now add this to our holder of all WeatherDescriptions
                cityForecast.addForecast(f);
            }
            ret = true;

            Trace.log(Utility.ApplicationLogger, Level.INFO, WeatherBean.class, "retrieveForecast",
                      ">>>>>> After invokeDataControlMethod");
        } catch (AdfInvocationException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, WeatherBean.class, "retrieveForecast",
                      ">>>>>> AdfInvocationException=" + e.getMessage());
            AdfException ex = new AdfException("Error Invoking Web Service.  Please try later", AdfException.WARNING);
            throw ex;

        } catch (Exception e2) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, WeatherBean.class, "retrieveForecast",
                      ">>>>>> Exception=" + e2.getMessage());
            AdfException ex = new AdfException("Error Invoking Web Service.  Please try later", AdfException.WARNING);
            throw ex;
        }
        return ret;
    }

说明:
(1)使用AdfmfJavaUtilities.invokeDataControlMethod调用Data Control中的方法,也就是真正的Web Service。
(2)返回值的类型是GenericType,后面是解析GenericType的逻辑,其中包括城市信息和城市未来5天的天气预报信息。
(3)异常的处理使用的是AdfException,如果调用出错,在界面会显示该错误。

2. 查询结果页面也不是绑定到Web Servcie Data Control上的,而是CityInformation.java生成的Data Control。

3. 这个例子中,模型层的基本设计是这样的
(1)CityInformation->CityForecast->Forecast,生成CityInformation Data Control,用于城市天气查询结果页面绑定。
(2)WeatherInformation->WeatherDescription,生成WeatherInformation Data Control,用于天气类型页面绑定。
(3)Web Service Data Control依然通过WSDL生成,不过不直接绑定到页面按钮上,而是在代码中,通过AdfmfJavaUtilities.invokeDataControlMethod调用。
(4)更进一步设想,如果需要缓存数据,可以修改相应的CityInformation.java和WeatherInformation.java,这正是这种模型层设计的好处。

参考文献:
1. https://blogs.oracle.com/mobile/entry/web_services_example_part_2

ADF_227:ADF Mobile 11.1.2.4 Samples 介绍(13):Weather1

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

Weather1演示了如何调用Web Service。这里通过Web Service Data Control访问一个公共的天气预报Web Service:http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL。

该Web Service提供了很多方法,这里使用GetCityForecastByZIP和GetWeatherInformation。

直接使用Web Service Data Control生成Data Control的方式好处是无需写任何Java代码,但是不好的地方也很明显:就是用户无法在调用Web Service之前或之后加入自己的逻辑。

所以这种方式只适合于简单的Demo演示。

实际使用中,还要考虑到异常处理:比如网络断了,服务无法访问。





参考文献:
1. https://blogs.oracle.com/mobile/entry/web_services_example_part_1

ADF_226:ADF Mobile 11.1.2.4 Samples 介绍(12):PrefDemo


开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

PrefDemo演示了如何使用应用级的和Feature级的用户设置。

知识点:

1. 应用级的Preference设置



2. Feature级的Preference设置



 3. 运行效果


ADF_225:ADF Mobile 11.1.2.4 Samples 介绍(11):Skinning

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

Skinning演示了如何使用Skin显示不同风格的界面。

知识点:

1. 在adfmf-skins.xml中定义了所有的皮肤
<?xml version="1.0" encoding="UTF-8" ?>
<adfmf-skins xmlns="http://xmlns.oracle.com/adf/mf/skin">
    <skin>
        <id>mobileFusionFx.iOS</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx</extends>
        <style-sheet-name>css/myiOS.css</style-sheet-name>
    </skin>
    <skin>
        <id>mobileFusionFx-v1.iOS</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.iOS</extends>
        <style-sheet-name>css/v1.css</style-sheet-name>
        <version>
            <name>v1</name>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx-v2.iOS</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.iOS</extends>
        <style-sheet-name>css/v2.css</style-sheet-name>
        <version>
            <name>v2</name>
            <default>true</default>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx.iPhone</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.iOS</extends>
        <style-sheet-name>css/myiPhone.css</style-sheet-name>
    </skin>
    <skin>
        <id>mobileFusionFx-v1.iPhone</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx-v1.iOS</extends>
        <style-sheet-name>css/myiPhone.css</style-sheet-name>
        <version>
            <name>v1</name>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx-v2.iPhone</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx-v2.iOS</extends>
        <style-sheet-name>css/myiPhone.css</style-sheet-name>
        <version>
            <name>v2</name>
            <default>true</default>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx.iPad</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.iOS</extends>
        <style-sheet-name>css/myiPad.css</style-sheet-name>
    </skin>
    <skin>
        <id>mobileFusionFx-v1.iPad</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx-v1.iOS</extends>
        <style-sheet-name>css/myiPad.css</style-sheet-name>
        <version>
            <name>v1</name>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx-v2.iPad</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx-v2.iOS</extends>
        <style-sheet-name>css/myiPad.css</style-sheet-name>
        <version>
            <name>v2</name>
            <default>true</default>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx.Android</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx</extends>
        <style-sheet-name>css/myAndroid.css</style-sheet-name>
    </skin>
    <skin>
        <id>mobileFusionFx-v1.Android</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.Android</extends>
        <style-sheet-name>css/v1.css</style-sheet-name>
        <version>
            <name>v1</name>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx-v2.Android</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.Android</extends>
        <style-sheet-name>css/v2.css</style-sheet-name>
        <version>
            <name>v2</name>
            <default>true</default>
        </version>
    </skin>
    <!-- This is the generic entry for Android models, it is just a placeholder-->
    <skin>
        <id>mobileFusionFx.Model</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.Android</extends>
        <style-sheet-name>css/myAndroidModel.css</style-sheet-name>
    </skin>
    <skin>
        <id>mobileFusionFx-v1.Model</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.Android</extends>
        <style-sheet-name>css/myAndroidModel.css</style-sheet-name>
        <version>
            <name>v1</name>
        </version>
    </skin>
    <skin>
        <id>mobileFusionFx-v2.Model</id>
        <family>mobileFusionFx</family>
        <extends>mobileFusionFx.Android</extends>
        <style-sheet-name>css/myAndroidModel.css</style-sheet-name>
        <version>
            <name>v2</name>
            <default>true</default>
        </version>
    </skin>
    <!-- This is the skin addition to add new styles instead of extending-->
    <skin-addition>
        <skin-id>mobileFusionFx</skin-id>
        <style-sheet-name>css/myaddedcss.css</style-sheet-name>
    </skin-addition>
</adfmf-skins>

ADF Mobile的皮肤是可以继承的,继承的顺序如下:
(1)首先是设备一级的皮肤定义,比如:mobileFusionfx.iPhone or mobileFusionFx.iPad
(2)其次是平台一级的皮肤定义,比如:mobileFusionFx.iOS or mobileFusion.Android
(3)最后是基本的皮肤定义,比如:mobileFusionFx

2. 在adfmf-config.xml中定义了要使用的皮肤
<?xml version="1.0" encoding="UTF-8" ?>

<adfmf-config xmlns="http://xmlns.oracle.com/adf/mf/config">
  <skin-family>mobileFusionFx</skin-family>
  <skin-version>v2</skin-version>
</adfmf-config>

3. 部署,发现按钮上的文字颜色为白色。



4. 修改adfmf-skins.xml,把.Model改为sdk
<skin>
    <id>mobileFusionFx-v1.sdk</id>
    <family>mobileFusionFx</family>
    <extends>mobileFusionFx.Android</extends>
    <style-sheet-name>css/myAndroidModel.css</style-sheet-name>
    <version>
        <name>v1</name>
    </version>
</skin>
<skin>
    <id>mobileFusionFx-v2.sdk</id>
    <family>mobileFusionFx</family>
    <extends>mobileFusionFx.Android</extends>
    <style-sheet-name>css/myAndroidModel.css</style-sheet-name>
    <version>
        <name>v2</name>
        <default>true</default>
    </version>
</skin>

5. 重新部署,发现按钮上的文字颜色变为洋红色。

ADF_224:ADF Mobile 11.1.2.4 Samples 介绍(10):HR

开发运行环境:JDeveloper 11.1.2.4 + Android SDK r21.1

HR演示了实际应用中最常用的CRUD功能的实现,它使用了一个本地的SQLite数据库保存数据。
HR还展示了iPhone和iPad两种界面,它们使用的是同一个数据模型层。

知识点:

1. List功能的实现
(1)按lastname的首字母分段显示
(2)并且在每个首字母项上有数量统计显示
(3)不是一次Load所有的行,会在最下面显示:Load More Rows.....

List.amx主要代码:
<amx:listView id="listView1" var="row" value="#{bindings.employees.collectionModel}"
            fetchSize="#{bindings.employees.rangeSize}" collapsibleDividers="true" showDividerCount="true"
            dividerMode="firstLetter" dividerAttribute="lastName"
            inlineStyle="position:absolute;top:50px;bottom:0;left:0;right:0">
  <amx:listItem id="listItem1" action="#{viewScope.ListBean.GetAction}" showLinkIcon="#{!row.showDelete}"
                shortDesc="Employee List Item">
      <amx:tableLayout id="tld1" width="100%" shortDesc="Emp Table Layout">
          <amx:rowLayout id="rld1">
              <amx:cellFormat id="cfdi" rowSpan="2" width="44px" halign="center" shortDesc="Emp Cell format">
                  <amx:image id="imaged1" source="#{row.pic}"
                             inlineStyle="height:34px;width:34px;margin-top:4px" shortDesc="Employee Image"/>
              </amx:cellFormat>
              <amx:cellFormat id="cfd1" width="100%" height="28px" shortDesc="Emp Cell Format">
                  <amx:outputText value=" #{row.firstName} #{row.lastName}" id="outputText2"/>
              </amx:cellFormat>
          </amx:rowLayout>
          <amx:rowLayout id="rld2">
              <amx:cellFormat id="cfd2" width="100%" height="12px" shortDesc="Emp Cell Format">
                  <amx:outputText id="title" value="#{row.job.title}"
                                  styleClass="adfmf-listItem-captionText"/>
              </amx:cellFormat>
          </amx:rowLayout>
      </amx:tableLayout>
      <amx:commandButton id="commandButton1" text="Delete" styleClass="adfmf-commandButton-alert"
                         rendered="#{row.showDelete}" actionListener="#{bindings.DeleteEmployee.execute}"
                         inlineStyle="position:absolute;margin:5px;right:0;top:0;" shortDesc="Delete Button"/>
      <amx:setPropertyListener from="#{row.rowKey}" to="#{bindings.deleteEmpNo.inputValue}" type="swipeRight"
                               id="spl3"/>
      <amx:actionListener id="al1" type="swipeRight" binding="#{bindings.ShowDeleteButton.execute}"/>
      <amx:setPropertyListener from="#{row.rowKey}" to="#{bindings.currentEmp.inputValue}" id="spl4"/>
      <amx:actionListener id="al2" binding="#{bindings.ClearDeleteButton.execute}"/>
      <amx:setPropertyListener from="#{false}" to="#{bindings.newEmp.inputValue}" id="spl5"/>
  </amx:listItem>
</amx:listView>

可以看出:
(1)集合数据是来自EmployeeList.java中的employees对象,即getEmployees()返回的对象。
(2)点击某一项,传递当前行的rowkey给下个页面。
(3)向右滑动某一项,显示删除按钮,组件的显示与隐藏逻辑如下:

<amx:cellFormat id="cf4" width="48px" halign="center" shortDesc="Cell">
    <amx:commandButton id="cb1" actionListener="#{bindings.Execute.execute}" shortDesc="Search Link"
                       icon="/images/search_36.png">
        <amx:setPropertyListener id="spl6" from="#{true}" to="#{pageFlowScope.HRBean.searchOn}"/>
    </amx:commandButton>
</amx:cellFormat>
<amx:cellFormat id="cf1" shortDesc="ClearFilter Cell" width="48px" halign="center"
                rendered="#{bindings.filter.inputValue ne '' and pageFlowScope.HRBean.searchOn}">
    <amx:commandButton id="cb2" shortDesc="ClearFilter Button"
                       actionListener="#{bindings.Execute.execute}" icon="/images/clear_36.png"
                       styleClass="adfmf-commandButton-alert">
        <amx:setPropertyListener id="spl2" from="#{}" to="#{bindings.filter.inputValue}"/>
        <amx:setPropertyListener id="spl7" from="#{false}" to="#{pageFlowScope.HRBean.searchOn}"/>
    </amx:commandButton>
</amx:cellFormat>


2. Filter功能的实现
(1)输入查询关键字后,点击查询按钮会调用EmployeeList.java中的Execute方法。
(2)同时会显示,清除查询关键字按钮。
(3)当查询结果为0时,会使用rendered="#{bindings.employeeCount.inputValue eq 0}",显示“没有符合条件的数据”。


4. Metris功能的实现
这种布局方式的实现如下:
(1)使用tableLayout显示Tab。
(2)点击Tab,传递当前Tab值到ManagedBean的相应属性中。
(3)使用panelSplitter显示每个Tab中的内容,属性selectedItem="#{pageFlowScope.HRBean.metrictab}",即指向ManagedBean的相应属性。


5. Detail功能的实现


(1)Directs的右上角的数字是使用 styleClass="hr-reportcount"实现的。
其在hr.css的定义如下:
.hr-reportcount {
    color: white;
    background-color: red;
    position: absolute;
    top: 45px;
    right: 2px;  
    font-size: 11px;
    font-weight: bold;
    padding: 0 2px !important;
    border-radius: 5px;
}
(2)点击当前员工,向左滑动看前一个员工,向右滑动看后一个员工
(3)点击当前员工,按住不动,或者点击右上角的按钮,都会在底部弹出一个popup窗口。
你可以给这个员工打电话,发短信......


6. EmployeeList.java 代码,全部的CRUD的操作就定义在这个类中。

package HR;

import application.DBConnectionFactory;

import application.LifeCycleListenerImpl;

import com.sun.util.logging.Level;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

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

import oracle.adf.model.datacontrols.device.DeviceManager;

import oracle.adf.model.datacontrols.device.DeviceManagerFactory;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.framework.exception.AdfException;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
import oracle.adfmf.java.beans.ProviderChangeListener;
import oracle.adfmf.java.beans.ProviderChangeSupport;
import oracle.adfmf.util.Utility;
import oracle.adfmf.util.logging.Trace;


public class EmployeeList {
    private static List s_employees = null;
    private Employee editEmployee = new Employee();
    private transient ProviderChangeSupport providerChangeSupport = new ProviderChangeSupport(this);
    private boolean newEmp = false;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    private int employeeCount = -1;
    private int deleteEmpNo = 0;
    private int currentEmp = 0;
    private String filter = "";
    private String apppath = AdfmfJavaUtilities.getDirectoryPathRoot(AdfmfJavaUtilities.ApplicationDirectory);
    private static final String NOPIC = "/images/missingEmployee_120.png";
    private static final int ALLRECORDS = -1;
    private static final String SORTLNAME = "LAST_NAME";
    private static final String SORTIDDESC = "EMPLOYEE_ID DESC";
    private static final String SORTID = "EMPLOYEE_ID";

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void addProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.addProviderChangeListener(l);
    }

    public void removeProviderChangeListener(ProviderChangeListener l) {
        providerChangeSupport.removeProviderChangeListener(l);
    }

    public EmployeeList() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "EmployeeList",
                  "!!!!!!!!!!!!!!!!!In the EmployeeList constructor!!!!!!!!!!!!!!!!!!!!!!!!!");
        if (s_employees == null) {
            s_employees = new ArrayList();
            Execute();
        }
    }

    public Employee[] getEmployees() {
        Employee e[] = null;

        e = (Employee[])s_employees.toArray(new Employee[s_employees.size()]);

        return e;
    }

    public void setEmployeeCount(int employeeCount) {
        int oldEmployeeCount = this.employeeCount;
        this.employeeCount = employeeCount;
        propertyChangeSupport.firePropertyChange("employeeCount", oldEmployeeCount, employeeCount);
    }

    public int getEmployeeCount() {
        return s_employees.size();
    }

    public void setFilter(String filter) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "setFilter",
                  "##############Filter = " + filter);
        String oldFilter = this.filter;
        this.filter = filter;
        propertyChangeSupport.firePropertyChange("filter", oldFilter, filter);
    }

    public String getFilter() {
        return filter;
    }

    public void setCurrentEmp(int currentEmp) {
        int oldCurrentEmp = this.currentEmp;
        this.currentEmp = currentEmp;
        propertyChangeSupport.firePropertyChange("currentEmp", oldCurrentEmp, currentEmp);
    }

    public int getCurrentEmp() {
        return currentEmp;
    }

    public void setNewEmp(boolean newEmp) {
        this.newEmp = newEmp;
    }

    public boolean getNewEmp() {
        return newEmp;
    }

    public void setEditEmployee(Employee editEmployee) {
        this.editEmployee.copy(editEmployee);
    }

    public Employee getEditEmployee() {
        return editEmployee;
    }

    public void setDeleteEmpNo(int deleteEmpNo) {
        int oldDeleteEmpNo = this.deleteEmpNo;
        this.deleteEmpNo = deleteEmpNo;
        propertyChangeSupport.firePropertyChange("deleteEmpNo", oldDeleteEmpNo, deleteEmpNo);
    }

    public int getDeleteEmpNo() {
        return deleteEmpNo;
    }


    ///////////////////////////////////
    //      Utility Methods - Begin
    ///////////////////////////////////

    public Employee getEmployeeById(int empId) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "getEmployeeById",
                  "##############Inside getEmployeeById");
        Employee e = null;

        ArrayList tempEmps = new ArrayList();
        tempEmps = selectEmpsFromDB("EMPLOYEE_ID = " + empId, SORTLNAME, 1);
        if (tempEmps.size() > 0) {
            e = (Employee)tempEmps.get(0);
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "getEmployeeById",
                  "##############getEmployeeById completed");
        return e;
    }

    public ArrayList getEmployeeReports(int empId) {
        ArrayList reports = null;
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "getEmployeeReports",
                  "##############Inside getEmployeeReports");

        reports = selectEmpsFromDB("MANAGER_ID = " + empId, SORTLNAME, ALLRECORDS);
        if(reports == null) {
            reports = new ArrayList();
        }
     
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "getEmployeeReports",
                  "##############getEmployeeReports completed");

        return reports;
    }

    public int getEmployeeReportCount(int empId) {
        int reportCount = 0;

        ArrayList reports = getEmployeeReports(empId);
        reportCount = reports.size();
     
        return reportCount;
    }

    private boolean UpdateReportingToMgr(Employee emp) {
        boolean ret = true;
        Employee[] reports = emp.getReports();
        int mgrID = emp.getManagerId();
        int count = reports.length;
        for (int x = 0; x < count; x++) {
            reports[x].setManagerId(mgrID);
            ret = UpdateEmpToDB(reports[x]);
            if (!ret) {
                break;
            }
        }
        return ret;
    }


    public void calculateMinMaxValues(Employee currEmp) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "calculateMinMaxValues",
                  "##############Inside calculateMinMaxValues");

        ArrayList empsInOrg = new ArrayList();
        empsInOrg = selectEmpsFromDB("DEPARTMENT_ID = " + currEmp.getDepartmentId(), SORTLNAME, ALLRECORDS);

        int count = empsInOrg.size();
        for (int x = 0; x < count; x++) {
            Employee e = (Employee)empsInOrg.get(x);
            double salary = e.getSalary();
            if (e.getDepartmentId() == currEmp.getDepartmentId()) {
                if (salary > currEmp.maxOrgSalary) {
                    currEmp.maxOrgSalary = salary;
                }
                if (salary < currEmp.minOrgSalary) {
                    currEmp.minOrgSalary = salary;
                }
            }
        }
        if (currEmp.maxOrgSalary == currEmp.minOrgSalary) {
            currEmp.minOrgSalary = 0;
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "calculateMinMaxValues",
                  "##############calculateMinMaxValues completed");
    }
    ///////////////////////////////////
    //      Utility Methods - End
    ///////////////////////////////////


    ///////////////////////////////////
    //        DC Methods - Begin
    ///////////////////////////////////

    public void Execute() {
        s_employees.clear();
        String filter = getFilter();
        String whereClause = "";
        if (filter.length() > 0) {
            whereClause += "FIRST_NAME LIKE '%" + filter + "%' OR LAST_NAME LIKE '%" + filter + "%'";
        }
        s_employees = selectEmpsFromDB(whereClause, SORTLNAME, ALLRECORDS);

        providerChangeSupport.fireProviderRefresh("employees");
    }

    public void AddEmployee() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "AddEmployee",
                  "##############Adding Employee");
        int maxId = 0;
        ArrayList lastEmp = new ArrayList();
        lastEmp = selectEmpsFromDB("", SORTIDDESC, 1);
        if (lastEmp.size() > 0) {
            Employee e = (Employee)lastEmp.get(0);
            maxId = e.getId();
        }

        int minId = 0;
        ArrayList firstEmp = new ArrayList();
        firstEmp = selectEmpsFromDB("", SORTID, 1);
        if (firstEmp.size() > 0) {
            Employee e = (Employee)firstEmp.get(0);
            minId = e.getId();
        }
        setEditEmployee(new Employee(maxId + 1, "", "", "", "", new Date(System.currentTimeMillis()), "AD_ASST", 0, 0,
                                     minId, 10, NOPIC, false));
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "AddEmployee",
                  "##############Adding completed");
    }

    public void EditEmployee(int id) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "EditEmployee",
                  "##############Editing Employee");
        int count = getEmployeeCount();
        for (int x = 0; x < count; x++) {
            Employee e = (Employee)s_employees.get(x);
            if (e.id == id) {
                Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "EditEmployee",
                          "##############Found Employee to edit");
                setEditEmployee(e);
                break;
            }
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "EditEmployee",
                  "##############Editing completed");
    }

    public void SaveEmployee() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                  "##############Saving Employee");
        if (newEmp) {
            if (AddEmpToDB()) {
                Employee newEmployee = new Employee(editEmployee);
                Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                          "##############Inserting Employee");
                s_employees.add(0, newEmployee);
                providerChangeSupport.fireProviderCreate("employees", newEmployee.getKey(), newEmployee);
                Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                          "##############Employee inserted");
            }
        } else {
            int count = getEmployeeCount();
            for (int x = 0; x < count; x++) {
                Employee e = (Employee)s_employees.get(x);
                if (e.id == editEmployee.id) {
                    Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                              "##############Found Employee to update");
                    if (UpdateEmpToDB(editEmployee)) {
                        e.copy(editEmployee);
                        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                                  "##############Employee" + e.getFirstName() + " " + e.getLastName() + " Updated");
                        setEditEmployee(e);
                        break;
                    }
                }
            }
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "SaveEmployee",
                  "##############Saving completed");
    }

    public void DeleteEmployee(int id) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmployee",
                  "##############Inside DeleteEmployee");
        int count = getEmployeeCount();
        for (int x = 0; x < count; x++) {
            Employee e = (Employee)s_employees.get(x);
            if (e.id == id) {
                Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmployee",
                          "##############Found Employee to delete");
                if (UpdateReportingToMgr(e)) {
                    if (DeleteEmpFromDB(id)) {
                        setDeleteEmpNo(0);
                        s_employees.remove(x);
                        providerChangeSupport.fireProviderDelete("employees", e.getKey());

                        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmployee",
                                  "##############Employee deleted");
                        break;
                    }
                }
            }
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmployee",
                  "##############DeleteEmployee completed");
    }

    public void fetchPicture() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "savePicture",
                  "##############Inside savePicture");

        DeviceManager dm = DeviceManagerFactory.getDeviceManager();
        int source = dm.CAMERA_SOURCETYPE_CAMERA;

        // If we're in the simulator on iOS, then we'll use the photolibrary
        if (dm.getName().indexOf("Simulator") != -1) {
            source = dm.CAMERA_SOURCETYPE_PHOTOLIBRARY;
        }

        String inFile =
            dm.getPicture(25, dm.CAMERA_DESTINATIONTYPE_FILE_URI, source, false, dm.CAMERA_ENCODINGTYPE_PNG, 56, 55);

        if (inFile != null && inFile.length() > 0) {
            editEmployee.setPic(inFile);
        }
    }


    public void clearPicture() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "clearPicture",
                  "##############Inside clearPicture");

        String picpath = apppath + "/" + editEmployee.getId() + ".png";
        File f = new File(picpath);
        if (f.exists()) {
            f.delete();
        }

        editEmployee.setPic(NOPIC);
    }

    // This method iterates though the collection and turns off any delete buttons except the row swiped

    public void ShowDeleteButton(int id) {
        Employee e = null;
        int count = getEmployeeCount();
        for (int x = 0; x < count; x++) {
            e = (Employee)s_employees.get(x);
            if (e.getId() == id) {
                e.setShowDelete(true);
            } else if (e.isShowDelete()) {
                e.setShowDelete(false);
            }
        }
    }

    // This method clears all delete buttons.  We do this when a delete button exists but someone taps on another row

    public void ClearDeleteButton() {
        Employee e = null;
        int count = getEmployeeCount();
        for (int x = 0; x < count; x++) {
            e = (Employee)s_employees.get(x);
            if (e.isShowDelete()) {
                e.setShowDelete(false);
            }
        }
    }
 
    public void exportContact() {
        AdfException e = new AdfException("Function Not Implemented",AdfException.WARNING);
        throw e;
    }

    ///////////////////////////////////
    //        DC Methods - End
    ///////////////////////////////////


    ///////////////////////////////////
    //    Database Methods - Begin
    ///////////////////////////////////

    private ArrayList selectEmpsFromDB(String filter, String order, int maxRecords) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "selectEmpsFromDB",
                  "##############Inside selectEmpsFromDB");
        ArrayList empList = new ArrayList();

        try {
            Connection conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String select = "SELECT * FROM EMPLOYEES";
            if (filter.length() > 0) {
                select += " WHERE " + filter;
            }
            if (order.length() > 0) {
                select += " ORDER BY " + order;
            }
            if (maxRecords != ALLRECORDS) {
                select += " lIMIT " + maxRecords;
            }
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "selectEmpsFromDB",
                      "##############" + select);
            PreparedStatement pStmt = conn.prepareStatement(select);
            ResultSet rs = pStmt.executeQuery();
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "selectEmpsFromDB",
                      "!!!!!!!!!!!!!!!!!Query Executed!!!!!!!!!!!!!!!!!!!!!!!!!");
            while (rs.next()) {
                int id = rs.getInt("EMPLOYEE_ID");
                String first = rs.getString("FIRST_NAME");
                String last = rs.getString("LAST_NAME");
                String email = rs.getString("EMAIL");
                String phone = rs.getString("PHONE_NUMBER");
                Date hireDate = rs.getDate("HIRE_DATE");
                if (hireDate == null) {
                    hireDate = new Date(0);
                }
             
             
                String jobId = rs.getString("JOB_ID");
                double salary = getDouble(rs, "SALARY");
                double commPct = getDouble(rs, "COMMISSION_PCT");
                int mgrId = rs.getInt("MANAGER_ID");
                int deptId = rs.getInt("DEPARTMENT_ID");
                String emppic = NOPIC;

                String picpath = apppath + "/" + id + ".png";
                File f = new File(picpath);
                if (f != null && f.exists()) {
                    emppic = "file://" + picpath + "?" + System.currentTimeMillis();
                } else {
                    byte[] b = rs.getBytes("PIC");
                    if (b != null && b.length > 0) {
                        FileOutputStream out = new FileOutputStream(picpath);
                        out.write(b);
                        out.close();
                        emppic = "file://" + picpath + "?" + System.currentTimeMillis();
                    }
                }
             
                Employee e =
                    new Employee(id, first, last, email, phone, hireDate, jobId, salary, commPct, mgrId, deptId, emppic,
                                 false);
                empList.add(e);
            }
            rs.close();
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "selectEmpsFromDB",
                      "##############selectEmpsFromDB completed");
        } catch (SQLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "selectEmpsFromDB",
                      "##############Exception:  " + e.getMessage());
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "selectEmpsFromDB",
                      "##############Exception:  " + e.getMessage());
        }
     
        return empList;
    }

    public boolean AddEmpToDB() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "AddEmpToDB",
                  "##############Inside AddEmpToDB");
        boolean ret = false;

        try {
            Connection conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String insertSQL =
                "Insert into EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,COMMISSION_PCT,MANAGER_ID,DEPARTMENT_ID) values (?,?,?,?,?,?,?,?,?,?,?)";

            PreparedStatement pStmt = conn.prepareStatement(insertSQL);
            pStmt.setInt(1, editEmployee.getId());
            pStmt.setString(2, editEmployee.getFirstName());
            pStmt.setString(3, editEmployee.getLastName());
            pStmt.setString(4, editEmployee.getEmail());
            pStmt.setString(5, editEmployee.getPhoneNumber());
            pStmt.setDate(6, editEmployee.getHireDate());
            pStmt.setString(7, editEmployee.getJobId());
            pStmt.setDouble(8, editEmployee.getSalary());
            pStmt.setDouble(9, editEmployee.getCommissionPct());
            pStmt.setInt(10, editEmployee.getManagerId());
            pStmt.setInt(11, editEmployee.getDepartmentId());
            pStmt.execute();
            conn.commit();
            ret = true;
            savePictureToDB();
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "AddEmpToDB",
                      "##############AddEmpToDB completed");
        } catch (SQLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "AddEmpToDB",
                      "##############Exception:  " + e.getMessage());
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "AddEmpToDB",
                      "##############Exception:  " + e.getMessage());
        }

        return ret;
    }

    public boolean UpdateEmpToDB(Employee emp) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "UpdateEmpToDB",
                  "##############Inside UpdateEmpToDB");
        boolean ret = false;

        try {
            Connection conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String updateSQL =
                "UPDATE EMPLOYEES SET FIRST_NAME=?,LAST_NAME=?,EMAIL=?,PHONE_NUMBER=?,HIRE_DATE=?,JOB_ID=?,SALARY=?,COMMISSION_PCT=?,MANAGER_ID=?,DEPARTMENT_ID=? WHERE EMPLOYEE_ID=?";

            PreparedStatement pStmt = conn.prepareStatement(updateSQL);
            pStmt.setString(1, emp.getFirstName());
            pStmt.setString(2, emp.getLastName());
            pStmt.setString(3, emp.getEmail());
            pStmt.setString(4, emp.getPhoneNumber());
            pStmt.setDate(5, emp.getHireDate());
            pStmt.setString(6, emp.getJobId());
            pStmt.setDouble(7, emp.getSalary());
            pStmt.setDouble(8, emp.getCommissionPct());
            pStmt.setInt(9, emp.getManagerId());
            pStmt.setInt(10, emp.getDepartmentId());
            pStmt.setInt(11, emp.getId());
            pStmt.execute();
            conn.commit();
            ret = true;
            savePictureToDB();
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "UpdateEmpToDB",
                      "##############UpdateEmpToDB completed");
        } catch (SQLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "UpdateEmpToDB",
                      "##############Exception:  " + e.getMessage());
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "UpdateEmpToDB",
                      "##############Exception:  " + e.getMessage());
        }

        return ret;
    }

    public boolean DeleteEmpFromDB(int id) {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmpFromDB",
                  "##############Inside DeleteEmpFromDB");
        boolean ret = false;

        try {
            Connection conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String updateSQL = "DELETE FROM EMPLOYEES WHERE EMPLOYEE_ID=?";

            PreparedStatement pStmt = conn.prepareStatement(updateSQL);
            pStmt.setInt(1, id);
            pStmt.execute();
            conn.commit();
            ret = true;
            Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "DeleteEmpFromDB",
                      "##############DeleteEmpFromDB completed");
        } catch (SQLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "DeleteEmpFromDB",
                      "##############Exception:  " + e.getMessage());
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "DeleteEmpFromDB",
                      "##############Exception:  " + e.getMessage());
        }

        return ret;
    }

    public void savePictureToDB() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, EmployeeList.class, "savePicture",
                  "##############Inside savePicture");
        try {

            Connection conn;
            conn = DBConnectionFactory.getConnection();
            conn.setAutoCommit(false);
            String sql = "UPDATE EMPLOYEES SET PIC=? WHERE EMPLOYEE_ID=?";
            PreparedStatement pStmt = conn.prepareStatement(sql);

            String inFile = editEmployee.getPic();
            if (inFile.compareTo(NOPIC) == 0) {
                pStmt.setNull(1, Types.NULL);
            } else {
                URL u = new URL(inFile);
                FileInputStream in = new FileInputStream(u.getPath());
                byte[] b = new byte[in.available()];
                in.read(b);
                pStmt.setBytes(1, b);
                in.close();
            }
            pStmt.setInt(2, editEmployee.getId());
            pStmt.execute();
            conn.commit();

        } catch (MalformedURLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "savePictureToDB",
                      "##############Exception:  " + e.getMessage());
        } catch (IOException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "savePictureToDB",
                      "##############Exception:  " + e.getMessage());
        } catch (SQLException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "savePictureToDB",
                      "##############Exception:  " + e.getMessage());
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "savePictureToDB",
                      "##############Exception:  " + e.getMessage());
        }
    }

    ///////////////////////////////////
    //    Database Methods - End
    ///////////////////////////////////
    private double getDouble(ResultSet rs, String column) throws SQLException {
        if(rs != null) {
            String temp = rs.getString(column);
         
            if(Utility.isNotEmpty(temp)) {
                return Double.parseDouble(temp);
            }
        }
        return 0.0d;
    }

}

7. HRBean.java 代码

package mobile;

import application.LifeCycleListenerImpl;

import com.sun.util.logging.Level;

import javax.el.MethodExpression;

import javax.el.ValueExpression;

import oracle.adfmf.amx.event.ActionEvent;
import oracle.adfmf.amx.event.SelectionEvent;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.java.beans.PropertyChangeListener;
import oracle.adfmf.java.beans.PropertyChangeSupport;
import oracle.adfmf.util.Utility;
import oracle.adfmf.util.logging.Trace;

public class HRBean {
    private String detailtab = "profile";
    private String metrictab = "salary";
    private String orgtab = "byname";
    private boolean searchOn = false;
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    public HRBean() {
    }

    public void DeleteHandler(ActionEvent actionEvent) {
        try {
        ValueExpression ve1 =
            AdfmfJavaUtilities.getValueExpression("#{bindings.currentEmp.inputValue}", Integer.class);
        Integer currentEmp = (Integer)ve1.getValue(AdfmfJavaUtilities.getAdfELContext());

        ValueExpression ve2 =
            AdfmfJavaUtilities.getValueExpression("#{bindings.deleteEmpNo.inputValue}", Integer.class);
        Integer deleteNo = (Integer)ve2.getValue(AdfmfJavaUtilities.getAdfELContext());

        MethodExpression me1 =
            AdfmfJavaUtilities.getMethodExpression("#{bindings.DeleteEmployee.execute}", Object.class,
                                                   new Class[] { });
        me1.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[] { });

        if (currentEmp.intValue() == deleteNo.intValue()) {
            MethodExpression me2 =
                AdfmfJavaUtilities.getMethodExpression("#{bindings.Previous.execute}", Object.class, new Class[] { });
            me2.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[] { });
        }
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "savePictureToDB",
                      "##############Exception:  " + e.getMessage());
        }

    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void setDetailtab(String detailtab) {
        String oldDetailtab = this.detailtab;
        this.detailtab = detailtab;
        propertyChangeSupport.firePropertyChange("detailtab", oldDetailtab, detailtab);
    }

    public String getDetailtab() {
        return detailtab;
    }

    public void setMetrictab(String metrictab) {
        String oldMetrictab = this.metrictab;
        this.metrictab = metrictab;
        propertyChangeSupport.firePropertyChange("metrictab", oldMetrictab, metrictab);
    }

    public String getMetrictab() {
        return metrictab;
    }

    public void setOrgtab(String orgtab) {
        String oldOrgtab = this.orgtab;
        this.orgtab = orgtab;
        propertyChangeSupport.firePropertyChange("orgtab", oldOrgtab, orgtab);
    }

    public String getOrgtab() {
        return orgtab;
    }

    public void setSearchOn(boolean searchOn) {
        boolean oldSearchOn = this.searchOn;
        this.searchOn = searchOn;
        propertyChangeSupport.firePropertyChange("searchOn", oldSearchOn, searchOn);
    }

    public boolean isSearchOn() {
        return searchOn;
    }

    public void orgMapSelectionHandler(SelectionEvent selectionEvent) {
        MethodExpression me = AdfmfJavaUtilities.getMethodExpression("#{bindings.setCurrentRowWithKeyValue.execute}", Object.class, new Class[] {});
        me.invoke(AdfmfJavaUtilities.getAdfELContext(), new Object[] {});
    }

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

8. LifeCycleListenerImpl.java 代码,在应用首次启动时,调用copyDB()拷贝数据库文件HR.db

package application;

import com.sun.util.logging.Level;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

import java.sql.Statement;

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

import javax.el.ValueExpression;

import oracle.adfmf.application.LifeCycleListener;
import oracle.adfmf.framework.api.AdfmfJavaUtilities;
import oracle.adfmf.util.Utility;
import oracle.adfmf.util.logging.Trace;

public class LifeCycleListenerImpl implements LifeCycleListener {
    public LifeCycleListenerImpl() {
    }

    /**
     * The start method will be called at the start of the application. Only the
     * Application Lifecycle Event listener will be called on start up.
     */
    public void start() {
        Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "start",
                  "##############App Start");
        try {
            Statement stat = DBConnectionFactory.getConnection().createStatement();
            ResultSet rs = stat.executeQuery("SELECT PIC FROM EMPLOYEES;");
        } catch (SQLException e) {
            // if the error message is "out of memory",
            // it probably means no database file is found
            Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "start",
                      "##############Database does not exist, creating it");
            //            InitDB();
            copyDB();
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "start",
                      "##############Exception:  " + e.getMessage());
        }
        Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "start",
                  "##############App Start Complete");
    }

    /**
     * The stop method will be called at the termination of the application. Only
     * the Application Lifecycle Event listener will be called on start up.
     *
     * NOTE: Depending on how the application is being shutdown, this method may
     * or may not be called. Features should save off their state in the deactivate
     * handler.
     */
    public void stop() {
        // Add code here...
    }

    /**
     * The activate method will be called when the feature is activated. The
     * Application Lifecycle Event listener will be called on application
     * being started and resumed.
     */
    public void activate() {
        // Add code here...
    }

    /**
     * The deactivate method will be called when the feature is deactivated. The
     * Application Lifecycle Event listener will be called on application
     * being hibernated.
     */
    public void deactivate() {
        // Add code here...
    }

    private void copyDB() {
        try {
            Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "copyDB",
                      "##############copyDB Start");
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            InputStream in = cl.getResourceAsStream(".adf/META-INF/HR.db");
            String outFile =
                AdfmfJavaUtilities.getDirectoryPathRoot(AdfmfJavaUtilities.ApplicationDirectory) + "/HR.db";

            // blank out the DB file if its already there before we copy it.
            // We do this in a scoped section so that the File goes out of scope and releases itself
            {
                File f = new File(outFile);
                if( f!= null && f.exists() ) {
                    f.createNewFile();
                }
            }
             
            FileOutputStream out = new FileOutputStream(outFile);

            int b;
            do {
                b = in.read();
                if (b != -1) {
                    out.write(b);
                }
            } while (b != -1);
            in.close();
            out.close();

            Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "InitDB",
                      "##############InitDB Complete");
        } catch (FileNotFoundException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "copyDB",
                      "##############Exception:  " + e.getMessage());
        } catch (IOException e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "copyDB",
                      "##############Exception:  " + e.getMessage());
        }

    }

    private void InitDB() {
        try {
            Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "InitDB",
                      "##############initDB Start");
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            InputStream is = cl.getResourceAsStream(".adf/META-INF/hr.sql");
            if (is == null) {
                Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "InitDB",
                          "##############Could not look up : /META-INF/hr.sql");
                return;
            }

            BufferedReader bReader = new BufferedReader(new InputStreamReader(is));
            List stmts = new ArrayList();
            String strstmt = "";
            String ln = bReader.readLine();
            while (ln != null) {
                if (ln.startsWith("REM") || ln.startsWith("COMMIT")) {
                    ln = bReader.readLine();
                    continue;
                }
                strstmt = strstmt + ln;
                if (strstmt.endsWith(";")) {
                    Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "InitDB",
                              "##############" + strstmt);
                    stmts.add(strstmt);
                    strstmt = "";
                    ln = bReader.readLine();
                    continue;
                }
                ln = bReader.readLine();

            }

            DBConnectionFactory.getConnection().setAutoCommit(false);
            for (int i = 0; i < stmts.size(); i++) {
                Statement pStmt = DBConnectionFactory.getConnection().createStatement();
                pStmt.executeUpdate((String)stmts.get(i));

            }
            DBConnectionFactory.getConnection().commit();
            Trace.log(Utility.ApplicationLogger, Level.INFO, LifeCycleListenerImpl.class, "InitDB",
                      "##############InitDB Complete");
        } catch (Exception e) {
            Trace.log(Utility.ApplicationLogger, Level.SEVERE, LifeCycleListenerImpl.class, "InitDB",
                      "##############Exception:  " + e.getMessage());
        }

    }

}

9. DBConnectionFactory.java 代码,数据库连接工厂,获取数据库连接

package application;

import java.sql.Connection;
import java.sql.SQLException;

import oracle.adfmf.framework.api.AdfmfJavaUtilities;

public class DBConnectionFactory {
    public DBConnectionFactory() {
        super();
    }
    
    protected static Connection conn = null;

    public static Connection getConnection() throws Exception {
        if (conn == null) {
            try {
                // create a database connection
                String Dir = AdfmfJavaUtilities.getDirectoryPathRoot(AdfmfJavaUtilities.ApplicationDirectory);
                String connStr = "jdbc:sqlite:" + Dir + "/HR.db";
                conn = new SQLite.JDBCDataSource(connStr).getConnection();
            } catch (SQLException e) {
                // if the error message is "out of memory",
                // it probably means no database file is found
                System.err.println(e.getMessage());
            }
        }
        return conn;
    }
    
}