Flex Data Services 2 - Notes

What are Flex Data Services 2?

FDS 2 is a web application, not a server plugin. It also provides both server-side and client-side library. It can deploy on webshere, tomcat, jrun, etc.

New features and changes for Flex Data Services 2:

Data synchronization between client and server, Publish/subscribe messaging, Data paging, Data push, In-context collaboration(Data synchronization between clients).

See Flex 2.0.1 release notes, it includes details about new features, how to integrate log4j, etc. ( http://www.adobe.com/support/documentation/en/flex/2/releasenotes_flex2_fds.html)

Channels

Channels are communication method between flex clients (flash player, etc.) and FDS, which is transparent to developers.

There are three basic channels: http, amf, rtmp. Amf is based on http, can be configured with polling, to simulate rtmp. All channels can be configured in secure way. So there are really eight channels: http, http-secure, amf, amf-secure, amf-polling, amf-secure-polling, rtmp, rtmp-secure.

Each kind of flex data services can use some or all channels above. Which channel it uses is XML-based configurable, so developers don’t need to worry about it when they are coding. But they should know only rtmp is really real-time, which uses non-standard http port which may be blocked by firewall. Others all use http port, and amf-polling can simulate push but with a configurable delay.

RTMP

http://www.cnbruce.com/blog/showlog.asp?cat_id=24&log_id=828

http://www.nettime.net.cn/itedu/news/2005128/2005128112617790.htm

http://www.nshen.net/blog/default.asp?cat=16

http://www.blueidea.com/computer/net/2005/2778.asp

RTMP(the Real-time Messaging Protocol)協議作爲客戶端和服務器端的傳輸協議,這是一個專門爲高效傳輸視頻、音頻和數據而設計的 TCP/IP 協議,使用 RTMP 協議傳輸的數據是未經加密的,包括用戶名和密碼等認證信息。(http://www.javvin.net/ProtocolsTerms/RTMP2.php)

(http://ria.richtechmedia.com/2006/06/21/flex-2-%E6%87%89%E7%94%A8%E8%AA%AA%E6%98%8E%E8%A8%AA%E5%95%8F/)In Flex we actually support two protocols. There is HTTP and RTMP. The HTTP of course now is a one-way protocol where the client always initiates the call so for that we do use a polling mechanism. With RTMP, it’s a protocol that has been used by the Flash Player for quite a while to do audio, video and data synchronization and it provides a persistent socket that the client keeps open with the server, to analyze the server it just naturally push messages to the client in real time. Flex Data Services provides an abstraction layer, so the client does not really need to know, which protocol is he using and it actually fails over from one to the other, so that technique actually makes it easy to build applications that support push even when the user might be applying a firewall and can’t use RTMP.

(http://livedocs.adobe.com/flex/201/html/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Book_Parts&file=security2_117_01.html#137544)Flash Player uses the Real-Time Messaging Protocol (RTMP) for client-server communication. This is a TCP/IP protocol designed for high-performance transmission of audio, video, and data messages. RTMP sends unencrypted data, including authentication information (such as a name and a password). Although RTMP in and of itself does not offer security features, Flash communications applications can perform secure transactions and secure authentication through an SSL-enabled web server. Flash Player also provides support for versions of RTMP that are tunneled through HTTP and HTTPS. RTMP refers to RTMP transmitted within an HTTP wrapper, and RTMPS is RTMP transmitted within an HTTPS wrapper.

RTMP remains a permanent connection between client and server, so the maximum number of connections is limited. 我小測了一下, 在Websphere test environment中使用RTMP, 沒有Workmanage只支持9個連接, 第十個一連就拋異常了:

J2CA0020E: The Connection Pool Manager could not allocate a Managed Connection: com.ibm.websphere.ce.j2c.ConnectionWaitTimeoutException: Connection not available

Three flex server data access features

RPC (Remote Process Call) services. Asynchronous. 包括HTTPService – HTTP URL, WebService – a SOAP-compliant web service, RemoteObject – POJO (Plain Old Java Object), 其中前兩者可以不需要FDS的支持, 只要Flex SDK 2就夠了, 因爲這兩者可以直接通過URL來進行相應的調用, 但這樣就無法利用FDS2提供的服務, 如Proxy等等. 而RemoteObject需要FDS才能使用. Flex HTTPService與AJAX中的xmlhttp類似. WebService支持調用SOAP-based web services. RemoteObject能直接遠程調用server端的java objects. 此三者都是通過Http端口進行數據傳輸 (RO使用AMF協議是基於HTTP協議的), 都是客戶端發起.

Data management service. 需FDS支持. 支持Clients和Server數據同步, 支持分佈式數據, 支持push.

Message service. 需FDS支持. 支持server和clients的異步通訊. 支持JMS provider等FDS之後的message provider.

從client-side代碼來看, 所有這些方式都是類似的, 都是通過destination訪問, 而不是URL. 因爲在FDS2中, 這些services都通過XML配置文件定義唯一的destination.

RPC services

Data management service

Adapter

Used for server-side additional data operation when automatically data synchronization happens.

ActionScript adapter -- persists data in server memory and is useful for applications that require transient distributed data that is not persisted to a data store.
Used for automatic clients synchronization, not server push.

Java adapter -- passes data changes to methods available on an arbitrary Java class, referred to as the Java assembler. This adapter lets Java developers employ the Data Transfer Object (DTO) design pattern.
Automatic clients synchronization, also server push with flex Server-Side API.
Developers implement java assembler to do custom action for every data synchronization, like data persistent, etc.
Server-side java code can begin a data synchronization use flex server-side API, which is really a server push.

DMS在server端做的是根據某一個client的更新, 更新自身數據, 同時更新其他clients數據, 如果採用Java adapter則可以進行持久化. 更新其他clients數據對developer來說幾乎是透明的, 而持久化則需要developer自己顯式實現. 所以實際上server內存中的數據起到一個樞紐作用. FDS與Clients的數據同步可自動完成, 但需要手動把數據持久化.

每一次client更新通知server, 傳到server的是一個更新列表, 包括create, update, delete, 每個更新元素保存在flex.data.ChangeObject中, 可用isCreate(), isUpdate(), isDelete()進行判斷, 並通過getNewVersion()和getPreviousVersion()得到完整的數據元素. 每個更新元素有一個identity值, 就是用來標誌是哪一個元素, 這在 <metadata></metadata> 中設定. 客戶端傳給server的每個ChangeObject中都包含這個值, 並來標誌該元素, 以及指定各個客戶端將被動態更新的元素.

可以有兩種方式來在Server端Java代碼中對客戶端和服務端數據同步時做一些自己的操作, 比如數據持久化. 其一是Using the fill-method and sync-method approach, 就是在XML文件中配置fill和synchronize的方法; 另一種是Using the Assembler interface approach, 只要自己定義的Assembler繼承類flex.data.assemblers.AbstractAssembler 即可. Assembler是指在XML配置的能與Java Adapter交互的類.

“You can implement a Data Management Services Assembler by extending this AbstractAssembler class. When you extend this class, you do not need to specify the methods via the XML tags in the Data Services configuration (if you also specify the XML tags, the methods specified in the XML tags will be used instead of the methods in this interface).”

當某一個客戶端數據變化, 或者通用Server API (flex.data.DataServiceTransaction) 調用都可以同步各個客戶端與server的數據. 也就是說, 使用flex.data.DataServiceTransaction 是可以實現server data push的. An instance of the DataServiceTransaction class is created for each operation that modifies the state of objects that the Data Management Service manages.

It also support paging feature.

Difficult to push specific data to a specific user??

DMS可以配置使用rtmp或amf polling (both secure or not) channel.

上文提到的兩種方法共有的代碼如下:

Server-side (java codes)

WebContent/WEB-INF/flex/remoting-config.xml 中相關配置

<destination id="tony_contact">
        
<properties>
            
<source>tony.contact.ContactRO</source>
            
<scope>application</scope>
        
</properties>
    
</destination>

 

ContactVO.java

/**
 * ContactVO.java
*/


package tony.contact;

/**
 * 
@author Tony
 
*/

public class ContactVO
{
    
private int contactId;
    
private String firstName;
    
private String lastName;
    
    
public int getContactId()
    
{
        
return contactId;
    }


    
public void setContactId(int contactId)
    
{
        
this.contactId = contactId;
    }


    
public String getFirstName()
    
{
        
return firstName;
    }


    
public void setFirstName(String firstName)
    
{
        
this.firstName = firstName;
    }


    
public String getLastName()
    
{
        
return lastName;
    }


    
public void setLastName(String lastName)
    
{
        
this.lastName = lastName;
    }

    
    
public ContactVO()
    
{
    }

    
    
public ContactVO(int contactId, String firstName, String lastName)
    
{
        
this.contactId = contactId;
        
this.firstName = firstName;
        
this.lastName = lastName;
    }

}

 

ContactDAO.java

/**
 * ContactDAO.java
*/


package tony.contact;

import java.util.List;
import flex.messaging.io.ArrayList;

/**
 * 
@author Tony
 
*/

public class ContactDAO
{
    
static List myData = null;
    
static int count = 10;
    
static
    
{
        myData 
= new ArrayList();
        ContactVO cvo;
        
for (int i=0; i<count; i++)
        
{
            cvo 
= new ContactVO();
            cvo.setContactId(i);
            cvo.setFirstName(
"firstName" + i);
            cvo.setLastName(
"lastName" + i);
            myData.add(cvo);
        }

    }

    
    
public List getContacts()
    
{
        
return myData;
    }

    
    
public List getContacts(String name)
    
{
        
return getContacts();
    }

    
    
public ContactVO create(ContactVO contact)
    
{
        contact.setContactId(count
++);
        myData.add(contact);
        
return contact;
    }

    
    
public void update(ContactVO newContact, ContactVO preContact)
    
{
        myData.set(preContact.getContactId(), newContact);
    }

    
    
public void delete(ContactVO contact)
    
{
        myData.remove(contact.getContactId());
    }

}

 

ContactRO.java

/**
 * ContactRO.java
*/


package tony.contact;

import flex.data.DataServiceTransaction;

/**
 * 
@author Tony
 
*/

public class ContactRO
{
    
public void modDataByServerPush()
    
{
        DataServiceTransaction dtx 
= DataServiceTransaction.begin(false);
        
try
        
{
            ContactVO ct 
= new ContactVO(1"test""serverAPI");
            dtx.updateItem(
"tony.dms.contact", ct, nullnull);
            
new ContactDAO().update(ct, ct);
        }

        
finally
        
{
            dtx.commit();
        }

    }

}

Client-side (AS, mxml codes)

ContactVO.as

// ContactVO.as

package tony.contact
{
    [Managed]
    [RemoteClass(alias
="tony.contact.ContactVO")]
    public class ContactVO
    
{
        public 
var contactId:int;
        public 
var firstName:String = "";
        public 
var lastName:String = "";
    }

}

 

contact.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    creationComplete
="initApp();">
    
<mx:Script>
        
<![CDATA[
            import mx.data.DataService;
            import mx.collections.ArrayCollection;
            import tony.contact.ContactVO;
            
            public var ds:DataService;
            [Bindable]
            public var contacts:ArrayCollection;
            private var cont:ContactVO;
            
            public function initApp():void
            {
                contacts = new ArrayCollection();
                ds = new DataService("tony.dms.contact");
                ds.fill(contacts);
            }
        
]]>
    
</mx:Script>
    
    
<mx:RemoteObject id="contactRO" destination="tony_contact" 
           showBusyCursor
="false"/>
           
    
<mx:DataGrid id="dg" dataProvider="{contacts}" editable="true">
        
<mx:columns>
            
<mx:DataGridColumn dataField="contactId" headerText="Id"/>
            
<mx:DataGridColumn dataField="firstName" headerText="First Name"/>
            
<mx:DataGridColumn dataField="lastName" headerText="Last Name"/>
        
</mx:columns>
    
</mx:DataGrid>
    
<mx:HBox width="{dg.width}">
        
<mx:Button label="DataService.fill" click="ds.fill(contacts)"/> 
        
<mx:Button label="Show Server Push API" click="contactRO.modDataByServerPush()"/>
    
</mx:HBox>
    
</mx:Application>

不同的代碼

Server-side

First one:

WebContent/WEB-INF/flex/data-management-config.xml 中相關配置

    <destination id="tony.dms.contact">
        
<adapter ref="java-dao"/>
        
<properties>
            
<use-transactions>false</use-transactions>
        
            
<source>tony.contact.ContactAssembler</source>
            
<scope>application</scope>
            
<metadata>
                
<identity property="contactId"/>
            
</metadata>
            
<network>
                
<session-timeout>20</session-timeout>
                
<paging enabled="false" pageSize="10"/>
                
<throttle-inbound policy="ERROR" max-frequency="500"/>
                
<throttle-outbound policy="REPLACE" max-frequency="500"/>
            
</network>
            
<server>
                
<fill-method>
                    
<name>loadContacts</name>
                
</fill-method>
                
<fill-method>
                    
<name>loadContacts</name>
                    
<params>java.lang.String</params>
                
</fill-method>
                
<sync-method>
                    
<name>syncContacts</name>
                
</sync-method>
            
</server>
        
</properties>
        
<channels>
            
<channel ref="my-rtmp"/>
        
</channels>
    
</destination>

 

ContactAssembler.java

/**
 * ContactAssembler.java
*/


package tony.contact;

import java.util.Iterator;
import java.util.List;
import flex.data.ChangeObject;

/**
 * 
@author Tony
 
*/

public class ContactAssembler
{
    
public List loadContacts()
    
{
        ContactDAO dao 
= new ContactDAO();
        
return dao.getContacts();
    }

    
    
public List loadContacts(String name)
    
{
        ContactDAO dao 
= new ContactDAO();
        
return dao.getContacts(name);
    }

    
    
public List syncContacts(List changes)
    
{
        Iterator it 
= changes.iterator();
        ChangeObject co;
        
while (it.hasNext())
        
{
            co 
= (ChangeObject)it.next();
            
if (co.isCreate())
            
{
                co 
= doCreate(co);
            }

            
else if (co.isUpdate())
            
{
                doUpdate(co);
            }

            
else if (co.isDelete())
            
{
                doDelete(co);
            }

        }

        
return changes;
    }


    
private void doDelete(ChangeObject co)
    
{
        ContactDAO dao 
= new ContactDAO();
        dao.delete((ContactVO)co.getPreviousVersion());
    }


    
private void doUpdate(ChangeObject co)
    
{
        ContactDAO dao 
= new ContactDAO();
        
//Contact cont = (Contact)co.getNewVersion();
        
//cont.setFirstName("ZhuA");
        dao.update((ContactVO)co.getNewVersion(), (ContactVO)co.getPreviousVersion());
    }


    
private ChangeObject doCreate(ChangeObject co)
    
{
        ContactDAO dao 
= new ContactDAO();
        ContactVO contact 
= dao.create((ContactVO)co.getNewVersion());
        co.setNewVersion(contact);
        
return co;
    }

}

 

Second one:

WebContent/WEB-INF/flex/data-management-config.xml 中相關配置

    <destination id="tony.dms.contact">
        
<adapter ref="java-dao"/>
        
<properties>
            
<use-transactions>false</use-transactions>

            
<source>tony.contact.AnotherContactAssembler</source>
            
<scope>application</scope>
            
<metadata>
                
<identity property="contactId"/>
            
</metadata>
            
<network>
                
<session-timeout>20</session-timeout>
                
<paging enabled="false" pageSize="10"/>
                
<throttle-inbound policy="ERROR" max-frequency="500"/>
                
<throttle-outbound policy="REPLACE" max-frequency="500"/>
            
</network>
        
</properties>
        
<channels>
            
<channel ref="my-rtmp"/>
        
</channels>
    
</destination>

 

AnotherContactAssembler.java

/**
 * AnotherContactAssembler.java
*/


package tony.contact;

import java.util.Collection;
import java.util.List;
import flex.data.assemblers.AbstractAssembler;

/**
 * 
@author Tony
 
*/

public class AnotherContactAssembler extends AbstractAssembler
{
    
public void createItem(Object newItem)
    
{
        
new ContactDAO().create((ContactVO)newItem);
    }

    
    
public void deleteItem(Object preItem)
    
{
        
new ContactDAO().delete((ContactVO)preItem);
    }

    
    
public Collection fill(List fillParameters)
    
{
        ContactDAO dao 
= new ContactDAO();
        
return dao.getContacts();
    }


    
public void updateItem(Object newItem, Object preItem, List changes)
    
{
        
// DataServiceTransaction dtx = DataServiceTransaction.getCurrentDataServiceTransaction();
        new ContactDAO().update((ContactVO)newItem, (ContactVO)preItem);
        
//((Contact)newItem).setFirstName("CCCC");
    }

}

 

Flex message service

Concepts

Producers -- Applications that send messages.
Consumers -- Applications that receive messages.
Message channel -- Connects producers and consumers to message destinations.
Message endpoint -- The code responsible for encoding data into messages, and decoding messages into a format that consumers can use.
Message adapter -- The code acting as a conduit between the Flex Message Service and other messaging system.

Supported messaging

Flex messaging
Provided by FDS 2, producers and customers are flex clients or Java objects which use Flex server-side API to send or receive messages.
Only support topic; and the only one supporting subtopic.
Use ActionScript adapter.
Java Message Service (JMS) messaging
Support all JMS messaging like IBM MQ, Sonic MQ, etc. Serve-side java objects use JMS API, and flex clients use client API.
Support topic and queue.
Use JMS adapter.
ColdFusion Component (CFC) messaging
Similar with JMS messaging, but with ColdFusion Event Gateway Adapter.
Custom messaging
Custom message adapter to support other messaging, should Inherit from flex.messaging.services.ServiceAdapter.

如何實現把message只發給特定的用戶?

properties + selector

可以使用Cusumer類中的selector屬性來接收特定的message, 可以傳給selector一個字符串, 字符串中包含基於SQL92語法(http://www.linuxforum.net/books/postgresNEW/syntax.htm)的表達式.(Flex2 developer’s guide / p1194). 如:

<mx:consumer id="consumer" message="messageHandler(event)" selector="myName='CC'" destination="tony_message_chat"></mx:consumer>

selector是否有長度限制?? 好像沒有.

Implements custom java adapter to set selectors in server-side??

Subtopic

“The subtopic feature lets you divide the messages that a Producer component sends to a destination into specific categories at the destination. You can configure a Consumer component that subscribes to the destination to receive only messages sent to a specific subtopic or set of subtopics. You use wildcard characters (*) to send or receive messages from more than one subtopic.”

“You cannot use subtopics with a JMS destination. However, you can use message headers and Consumer selector expressions to achieve similar functionality when using JMS.”

Cluster

當用Flex message service自己的message provider時, 它支持cluster. Flex 內配了對JGroup的支持來實現cluster. (Flex 2 Developer’s Guide / p1111). JGroups is an open-source program (http://www.jgroups.org/javagroupsnew/docs/index.html). FDS supports JGroups internally, and developers just need to do some configurations to use cluster with JGroups.

不過FDS的expression版只支持單CPU, 只有序列號才能支持cluster.

用戶退出後怎麼unsubscribe??

Flash Player also provides support for versions of RTMP that are tunneled through HTTP and HTTPS, 但Flex目前不支持. Flash Communication Server supports HTTP tunneling to get around this issue and extends services to the users behind proxy servers.

Flex Message Service也可以配置使用rtmp或amf polling (both secure or not) channel.

Flex messaging, JMS messaging (Topic/Queue)代碼, 三者客戶端部分相同, 服務端使用同一個VO, 如下:

Server-side (java codes)

MyMessageVO.java

/**
 * MyMessageVO.java
*/


package tony.tonyMesChat;

import java.io.Serializable;

/**
 * 
@author Tony
 
*/

public class MyMessageVO implements Serializable
{
    
private String name;
    
private String words;
    
    
public String getName()
    
{
        
return name;
    }


    
public void setName(String name)
    
{
        
this.name = name;
    }


    
public String getWords()
    
{
        
return words;
    }


    
public void setWords(String words)
    
{
        
this.words = words;
    }

    
    
public MyMessageVO(String name, String words)
    
{
        
this.name = name;
        
this.words = words;
    }

}

 

Client-side (AS, mxml codes)

MyMessageVO.as

// MyMessageVO.as

package tony.tonyMesChat
{
    
public class MyMessageVO
    
{
        
private var mname:String = "";
        
private var mwords:String = "";
        
        
public function get name():String
        
{
            
return mname;
        }

        
        
public function set name(name:String):void
        
{
            
this.mname = name;
        }

        
        
public function get words():String
        
{
            
return mwords;
        }

        
        
public function set words(words:String):void
        
{
            
this.mwords = words;
        }

    }

}

 

TonyMesChat.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
    creationComplete
="initChat()">
    
<mx:Script>
        
<![CDATA[
            import mx.messaging.*;
            import mx.messaging.messages.*;
            import mx.messaging.events.*;
            import tony.tonyMesChat.MyMessageVO;
            
            private function initChat():void
            {
                consumer.subscribe();
                robotChat.talkOn();
            }
            
            private function sendMessage():void
            {
                var message:AsyncMessage = new AsyncMessage();
                message.headers = new Array();
                //message.headers["myName"] = "CC";
                message.body = new MyMessageVO();
                message.body.name = txtName.text;
                message.body.words = txtWords.text;
                producer.send(message);
                
                if (txtWords.text == "shutup")
                {
                    robotChat.shutUp();
                }
                else if (txtWords.text == "hibaby")
                {
                    robotChat.talkOn();
                }
                
                txtWords.text = "";
            }
            
            private function messageHandler(e:MessageEvent):void
            {
                var obj:Object = e.message.body;
                if (txtAllwords != null && obj.name != undefined)
                {
                    var temp:String = obj.name + ": " + obj.words + " ";
                    
                    txtAllwords.text += temp;
                    txtAllwords.validateNow();
                    txtAllwords.verticalScrollPosition=txtAllwords.maxVerticalScrollPosition;
                }
            }
            
            private function faultHandler(e:Event):void
            {
                trace("Something wrong");
                trace(e.toString());
            }
        
]]>
    
</mx:Script>
    
<mx:Producer id="producer" destination="tony_message_chat"/>
    
<mx:Consumer id="consumer" destination="tony_message_chat" message="messageHandler(event)"/>
    
    
<mx:RemoteObject id="robotChat" destination="tony_message_chat_robot" 
           showBusyCursor
="false" fault="faultHandler(event)" />
    
    
<mx:TextArea id="txtAllwords" height="247" width="282" editable="false"/>
    
<mx:HBox width="{txtAllwords.width}">
        
<mx:TextInput id="txtName" width="54"/>
        
<mx:TextInput id="txtWords" width="{txtAllwords.width-txtName.width-10}" enter="sendMessage()"/>
    
</mx:HBox>
    
<mx:Button label="Send" click="sendMessage()"/>
</mx:Application>

 

不同的代碼

Server-side

Flex messaging:

WebContent/WEB-INF/flex/messaging-config.xml 中相關配置

    <destination id="tony_message_chat">
        
<properties>
            
<!--
            <network>
                <cluster ref="default-cluster"/>
            </network>
            
-->
            
<server>
                
<max-cache-size>1000</max-cache-size>
                
<message-time-to-live>0</message-time-to-live>
                
<durable>false</durable>
            
</server>
        
</properties>

        
<channels>
            
<channel ref="my-rtmp"/>
        
</channels>
    
</destination>

 

WebContent/WEB-INF/flex/remoting-config.xml 中相關配置

<destination id="tony_message_chat_robot">
        
<properties>
            
<source>tony.tonyMesChat.RobotChat</source>
            
<scope>application</scope>
        
</properties>
    
</destination>

 

RobotChat.java

/**
 * RobotChat.java
*/


package tony.tonyMesChat;

import java.util.Random;

import flex.messaging.MessageBroker;
import flex.messaging.messages.AsyncMessage;
//import flex.messaging.util.UUIDUtils;

/**
 * 
@author p465890
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 
*/

public class RobotChat
{
    
private static Thread robot = null;
    
private static boolean talktalk = true;
    
private static String[] robotWords = {
            
"I'm robot CC.""Who are you?""Come on!""Oh shit!",
            
"I'm unhappy now...""????""!!!!"}
;
    
    
public void shutUp()
    
{
        talktalk 
= false;
    }

    
    
public void talkOn()
    
{
        
if (robot != null && talktalk)
        
{
            
return;
        }

        talktalk 
= true;
        robot 
= new Thread(new Runnable()
        
{
            
public void run()
            
{
                MessageBroker msgBroker 
= MessageBroker.getMessageBroker(null);
                
//String clientID = UUIDUtils.createUUID(false);;

                Random random 
= new Random();
                
while (talktalk)
                
{
                    AsyncMessage msg 
= new AsyncMessage();

                    msg.setDestination(
"tony_message_chat");
                    
// msg.setClientId(clientID);
                    
// msg.setMessageId(UUIDUtils.createUUID(false));
                    
// msg.setTimestamp(System.currentTimeMillis());
                    msg.setBody(new MyMessageVO("CCRobot", robotWords[random.nextInt(robotWords.length)]));
                    msgBroker.routeMessageToService(msg, 
null);
                    
try
                    
{
                        Thread.sleep((random.nextInt(
10000% 8 + 1* 1000);
                    }

                    
catch (InterruptedException e)
                    
{
                        e.printStackTrace();
                    }

                }

            }

        }
);
        robot.start();
    }

}

 

JMS messaging (topic):

WebContent/WEB-INF/flex/messaging-config.xml 中相關配置

<destination id="tony_message_chat">
        
<properties>
            
<server>
                
<durable>false</durable>
                
<durable-store-manager>flex.messaging.durability.FileStoreManager</durable-store-manager>
            
</server>

            
<jms>
                
<destination-type>Topic</destination-type>
                
<message-type>javax.jms.ObjectMessage</message-type>
                
<connection-factory>jms/flex/tony_message_chat_tcf</connection-factory>
                
<destination-jndi-name>jms/topic/flex/tony_message_chat</destination-jndi-name>
                
<destination-name>tony_message_chat</destination-name>
                
<delivery-mode>NON_PERSISTENT</delivery-mode>
                
<message-priority>DEFAULT_PRIORITY</message-priority>
                
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
                
<transacted-sessions>false</transacted-sessions>
            
</jms>
        
</properties>

        
<channels>
            
<channel ref="my-rtmp"/>
        
</channels>
        
        
<adapter ref="jms"/>
    
</destination>

 

WebContent/WEB-INF/flex/remoting-config.xml 中相關配置

<destination id="tony_message_chat_robot">
        
<properties>
            
<source>tony.tonyMesChat.JMSRobotChat</source>
            
<scope>application</scope>
        
</properties>
    
</destination>

 

JMSRobotChat.java

/**
 * JMSRobotChat.java
*/


package tony.tonyMesChat;

import java.util.Properties;
import java.util.Random;

import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;

/**
 * 
@author Tony
 
*/

public class JMSRobotChat
{
    
private static Thread robot = null;
    
private static boolean talktalk = true;
    
private static String[] robotWords = {
            
"I'm robot CC.""Who are you?""Come on!""Oh shit!",
            
"I'm unhappy now...""????""!!!!"}
;
    
    
public void shutUp()
    
{
        talktalk 
= false;
    }

    
    
public void talkOn()
    
{
        
if (robot != null && talktalk)
        
{
            
return;
        }

        talktalk 
= true;
        robot 
= new Thread(new Runnable()
        
{
            
public void run()
            
{
                TopicSession pubSession;
                TopicPublisher publisher;
                TopicConnection connection;

                String _providerurl 
= "iiop://127.0.0.1:2809";
                
//adjust for your app server
                String _ctxtFactory = "com.ibm.websphere.naming.WsnInitialContextFactory";

                
try {
                    
// Obtain JNDI Context
                    Properties p = new Properties();
                    p.put(Context.PROVIDER_URL, _providerurl);
                    p.put(Context.INITIAL_CONTEXT_FACTORY, _ctxtFactory);
                    
//specific to your app server setup
                    
//p.put(Context.SECURITY_PRINCIPAL, "admin");
                    
//p.put(Context.SECURITY_CREDENTIALS , "admin");
                    Context context = new InitialContext(p);
                    TopicConnectionFactory factory 
= (TopicConnectionFactory) context.lookup("jms/flex/tony_message_chat_tcf");
                    
// Create a JMS connection
                    connection = factory.createTopicConnection();
                    
// Create publisher session
                    pubSession = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
                    Topic topic 
= (Topic) context.lookup("jms/topic/flex/tony_message_chat");
                    
// Create a publisher
                    publisher = pubSession.createPublisher(topic);

                    Random random 
= new Random();
                    
while (talktalk)
                    
{
                        ObjectMessage message 
= pubSession.createObjectMessage();
                        message.setStringProperty(
"myName""CC");
                        message.setObject(
new MyMessageVO("CCRobot", robotWords[random.nextInt(robotWords.length)]));
                        publisher.publish(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, 
5 * 60 * 1000);
                        Thread.sleep((random.nextInt(
10000% 8 + 1* 1000);
                    }

                }
 
                
catch (Exception e) 
                
{
                    e.printStackTrace();
                }

            }

        }
);
        robot.start();
    }

}

 

JMS messaging (Queue):

WebContent/WEB-INF/flex/messaging-config.xml 中相關配置

<destination id="tony_message_chat">

        
<properties>

            
<server>
                
<durable>false</durable>
                
<durable-store-manager>flex.messaging.durability.FileStoreManager</durable-store-manager>
            
</server>

            
<jms>
                
<destination-type>Queue</destination-type>
                
<message-type>javax.jms.ObjectMessage</message-type>
                
<connection-factory>jms/flex/tony_message_chat_qcf</connection-factory>
                
<destination-jndi-name>jms/queue/flex/tony_message_chat_queue</destination-jndi-name>
                
<destination-name>tony_message_chat</destination-name>
                
<delivery-mode>NON_PERSISTENT</delivery-mode>
                
<message-priority>DEFAULT_PRIORITY</message-priority>
                
<acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>
                
<transacted-sessions>false</transacted-sessions>
            
</jms>
        
</properties>

        
<channels>
            
<channel ref="my-rtmp"/>
        
</channels>

        
<adapter ref="jms"/>
    
</destination>

 

WebContent/WEB-INF/flex/remoting-config.xml 中相關配置

<destination id="tony_message_chat_robot">
        
<properties>
            
<source>tony.tonyMesChat.JMSQueueRobotChat</source>
            
<scope>application</scope>
        
</properties>
    
</destination>

 

JMSQueueRobotChat.java

/**
 * JMSQueueRobotChat.java
*/


package tony.tonyMesChat;

import java.util.Properties;
import java.util.Random;

import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;

/**
 * 
@author Tony
 
*/

public class JMSQueueRobotChat
{
    
private static Thread robot = null;
    
private static boolean talktalk = true;
    
private static String[] robotWords = {
            
"I'm robot CC.""Who are you?""Come on!""Oh shit!",
            
"I'm unhappy now...""????""!!!!"}
;
    
    
public void shutUp()
    
{
        talktalk 
= false;
    }

    
    
public void talkOn()
    
{
        
if (robot != null && talktalk)
        
{
            
return;
        }

        talktalk 
= true;
        robot 
= new Thread(new Runnable()
        
{
            
public void run()
            
{
                QueueSession sendSession;
                QueueSender sender;
                QueueConnection connection;

                String _providerurl 
= "iiop://127.0.0.1:2809";
                String _ctxtFactory 
= "com.ibm.websphere.naming.WsnInitialContextFactory";

                
try {
                    Properties p 
= new Properties();
                    p.put(Context.PROVIDER_URL, _providerurl);
                    p.put(Context.INITIAL_CONTEXT_FACTORY, _ctxtFactory);
                    
//specific to your app server setup
                    
//p.put(Context.SECURITY_PRINCIPAL, "admin");
                    
//p.put(Context.SECURITY_CREDENTIALS , "admin");
                    Context context = new InitialContext(p);
                    QueueConnectionFactory factory 
= (QueueConnectionFactory) context.lookup("jms/flex/tony_message_chat_qcf");
                    connection 
= factory.createQueueConnection();
                    sendSession 
= connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
                    Queue queue 
= (Queue) context.lookup("jms/queue/flex/tony_message_chat_queue");
                    sender 
= sendSession.createSender(queue);

                    Random random 
= new Random();
                    
while (talktalk)
                    
{
                        ObjectMessage message 
= sendSession.createObjectMessage();
                        message.setObject(
new MyMessageVO("CCRobot", robotWords[random.nextInt(robotWords.length)]));
                        sender.send(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, 
5 * 60 * 1000);
                        Thread.sleep((random.nextInt(
10000% 8 + 1* 1000);
                    }

                }

                
catch (Exception e) 
                
{
                    e.printStackTrace();
                }

            }

        }
);
        robot.start();
    }

}

 

JMS messaging + property of message header + selector代碼

Server-side (java codes)

WebContent/WEB-INF/flex/remoting-config.xml 中相關配置

<destination id="tony_alert_demo_robot">
        
<properties>
            
<source>tony.alertDemo.JMSAlertRobot</source>
            
<scope>application</scope>
        
</properties>
    
</destination>

 

AlertVO.java

/**
 * AlertVO.java
*/


package tony.alertDemo;

import java.io.Serializable;

/**
 * 
@author Tony
 
*/

public class AlertVO implements Serializable
{
    
public String words;
    
    
public AlertVO(String words)
    
{
        
this.words = words;
    }

}

 

JMSAlertRobot.java

/**
 * JMSAlertRobot.java
*/


package tony.alertDemo;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Random;

import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.TopicConnection;
import javax.jms.TopicConnectionFactory;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.naming.Context;
import javax.naming.InitialContext;

import tony.alertDemo.AlertVO;

/**
 * 
@author Tony
 
*/

public class JMSAlertRobot
{
    
private static Thread robot = null;
    
private static boolean talktalk = true;
    
private static int[] alertType = {1234};
    
private static String[] robotWords = {
            
"......""``````""~~~~~~""******",
            
"------""??????""!!!!!"}
;
    
private static List userIds = new ArrayList();
    
private static String[] clientIds = {"AAA""BBB"};
    
private static String[] appIds = {"CCC""DDD"};
     
    
public void getout(String userId)
    
{
        
if (!userIds.isEmpty())
        
{
            userIds.remove(userId);
        }

        
        
if (userIds.isEmpty())
        
{
            talktalk 
= false;
        }

    }

    
    
public void alertOn(String userId)
    
{
        userIds.add(userId);
        
        
if (robot != null && talktalk)
        
{
            
return;
        }

        talktalk 
= true;
        robot 
= new Thread(new Runnable()
        
{
            
public void run()
            
{
                TopicSession pubSession;
                TopicPublisher publisher;
                TopicConnection connection;

                String _providerurl 
= "iiop://127.0.0.1:2809";
                String _ctxtFactory 
= "com.ibm.websphere.naming.WsnInitialContextFactory";

                
try {
                    Properties p 
= new Properties();
                    p.put(Context.PROVIDER_URL, _providerurl);
                    p.put(Context.INITIAL_CONTEXT_FACTORY, _ctxtFactory);
                    Context context 
= new InitialContext(p);

                    TopicConnectionFactory factory 
= (TopicConnectionFactory) context.lookup("jms/flex/tony_alert_demo_tcf");
                    connection 
= factory.createTopicConnection();
                    pubSession 
= connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
                    Topic topic 
= (Topic) context.lookup("jms/topic/flex/tony_alert_demo");
                    publisher 
= pubSession.createPublisher(topic);

                    Random random 
= new Random();
                    
while (talktalk)
                    
{
                        ObjectMessage message 
= pubSession.createObjectMessage();
                        
int num = random.nextInt(100000% 6;
                        
                        
switch (num)
                        
{
                        
case 0:
                            message.setIntProperty(
"mt"1);
                            
break;
                        
case 1:
                            message.setIntProperty(
"mt"2);
                            message.setStringProperty(
"md", (String)(userIds.get(random.nextInt(10000% userIds.size())));
                            
break;
                        
case 2:
                        
case 3:
                            message.setIntProperty(
"mt"3);
                            message.setStringProperty(
"md", clientIds[random.nextInt(10000% clientIds.length]);
                            
break;
                        
case 4:
                        
case 5:
                            message.setIntProperty(
"mt"4);
                            message.setStringProperty(
"md", appIds[random.nextInt(10000% appIds.length]);
                            
break;
                        }


                        message.setObject(
new AlertVO(robotWords[random.nextInt(10000% robotWords.length]));
                        publisher.publish(message, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, 
5 * 60 * 1000);
                        Thread.sleep(
2000);
                    }

                }
 
                
catch (Exception e) 
                
{
                    e.printStackTrace();
                }

            }

        }
);
        robot.start();
    }

}

 

Client-side (mxml codes)

AlertDemo.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
    currentState
="logon" creationComplete="initApp()">
    
    
<mx:Script>
        
<![CDATA[
            import mx.binding.utils.BindingUtils;
            import mx.messaging.events.MessageEvent;
            import mx.controls.Alert;
            import mx.messaging.Consumer;
            
            private var consumer:Consumer;
            private var userId:String;
            private var clientId:String;
            private var appId:String;
            
            [Bindable]
            private var clientIds:Array = ["AAA", "BBB"];
            
            [Bindable]
            private var appIds:Array = ["CCC", "DDD"];
                
            private function initApp():void
            {
                clientId = (String)(cbbClientIds.selectedItem);
                appId = (String)(cbbAppIds.selectedItem);
            }
            
            private function logon():void
            {
                userId = txtUserId.text;
                txtUserId.text = "";
                
                if (userId == "")
                {
                    Alert.show("Input user id first");
                    return;
                }
                
                consumer = new Consumer();
                consumer.destination = "tony_alert_demo";
                var strSelector:String = "mt=1 OR (mt=2 AND md='" + userId + 
                    "') OR (mt=3 AND md='" + clientId + 
                    "') OR (mt=4 AND (md='" + appId + "'";
                var i:int = 0;
                //for (i=0; i<600; i++)
                //{
                //    strSelector += (" OR md='app" + i + "'");
                //}
                strSelector += "))";
                //Alert.show(strSelector);
                consumer.selector = strSelector;
                consumer.addEventListener(MessageEvent.MESSAGE, messageHandler);
                consumer.subscribe();
                
                robotAlert.alertOn(userId);
                currentState = "alertView";
            }
            
            private function logout():void
            {
                consumer.unsubscribe();
                robotAlert.getout(userId);
                txtAlerts.text = "";
                cbbClientIds.selectedIndex = 0;
                cbbAppIds.selectedIndex = 0;
                currentState = "logon";
            }
            
            private function messageHandler(e:MessageEvent):void
            {
                var body:Object = e.message.body;
                var header:Object = e.message.headers;

                var alert:String = ";   Content: " + body.words + " ";
                switch (header.mt)
                {
                    case 1:
                        alert = "System message" + alert;
                        break;
                    case 2:
                        alert = "User message to " + header.md + alert;
                        break;
                    case 3:
                        alert = "Client message to " + header.md + alert;
                        break;
                    case 4:
                        alert = "Appliction message to " + header.md + alert;
                        break;
                }
                
                txtAlerts.text += alert;
                txtAlerts.validateNow();
                txtAlerts.verticalScrollPosition=txtAlerts.maxVerticalScrollPosition;
            }
        
]]>
    
</mx:Script>
    
    
<mx:RemoteObject id="robotAlert" destination="tony_alert_demo_robot" 
           showBusyCursor
="false"/>
    
    
<mx:states>
        
<mx:State name="logon">
            
<mx:AddChild position="lastChild">
                
<mx:VBox height="100%" horizontalAlign="center" verticalAlign="middle">
                    
<mx:HBox width="100%">
                        
<mx:Label text="User Id" width="54"/>
                        
<mx:TextInput id="txtUserId"/>
                    
</mx:HBox>
                    
<mx:HBox width="100%">
                        
<mx:Label text="Client Id" width="54"/>
                        
<mx:ComboBox id="cbbClientIds" dataProvider="{clientIds}" close="clientId=(String)(ComboBox(event.target).selectedItem)" width="159"/>
                    
</mx:HBox>
                    
<mx:HBox width="100%">
                        
<mx:Label text="App Id" width="54"/>
                        
<mx:ComboBox id="cbbAppIds" dataProvider="{appIds}" close="appId=(String)(ComboBox(event.target).selectedItem)" width="158"/>
                    
</mx:HBox>
                    
<mx:Button label="Logon" click="logon()"/>
                
</mx:VBox>
            
</mx:AddChild>    
        
</mx:State>
        
<mx:State name="alertView">
            
<mx:AddChild position="lastChild">
                
<mx:VBox height="100%" horizontalAlign="center" verticalAlign="middle">
                    
<mx:TextArea id="txtAlerts" height="263" width="352"/>
                    
<mx:Button label="Logout" click="logout()"/>
                
</mx:VBox>
            
</mx:AddChild>
        
</mx:State>
    
</mx:states>
</mx:Application>

 

 

與J2EE某些框架整合

FDS可與spring, hibernate很好的結合在一起.

“In Flex Data Services we’ve added a factory facility so that when you want a component, you use this facroty facility and we’ve integrated that with Spring and we integrated that with EJB, so that will allow a client to directly call a remote object kind of Spring component without having the right glue or wrapper code.”

“For Hibernate Flex’s data management layer has a Hibernate adapter and this allows you to directly expose Hibernate object models directly to Flex clients. So without any coding, Flex clients can execute queries on Hibernate, get back lists or graphs of objects. Using Flex’s data binding support, we listen for changes that you make on the client to those objects, which queue up changes up to the server, commit them to do conflict detection. If there are any conflicts, report those conflicts back to the client, so the client can do either accept server or accept client and then we will also push those changes out to other clients that happen to be looking at that same data. So, this is a very easy way to quickly get your Hibernate models published to Flex clients. Now a lot of people don’t want to expose their Hibernate models directly to the client, so there is a way you can kind of extend that and filter it to restrict the model or add your own security constraints on top of the security constraints which Flex provides out of the box.”

使用文中代碼

http://www.adobe.com/support/documentation/en/flex/2/install.html

安裝Flex Data Service, 安裝包中提供了JRun Server, 使用它可以直接把FDS自帶的例子跑起來.

如果跑在其他Server或Container中, 比如Tomcat, Websphere等, 則需要有一點點的配置或相關代碼的改動.

以RAD6作IDE, 跑在Websphere上爲例, 要使用上文提到的代碼:

導入FDS安裝目錄下的sample.war爲新的工程, 把WebContent/WEB-INF/flex/services-config.xml中有關Websphere內容註釋掉的地方把註釋去掉即可. 如果本機的Websphere Server沒有WorkManager的功能, 就不用管WorkManager那部分. 實際上FDS在使用RTMP時有專門利用Websphere的WorkManager作的性能優化, 不使用WorkManager程序跑起來是沒問題的.

在相關位置添加上面那些codes, 具體位置見各codes中的註釋. 對於需要JMS provider, 根據具體情況配置, 只要注間JNDI引用與代碼中的一致. 代碼在Websphere Test Environment 5.1 + Webshpere Embedded MQ下運行通過.JMS配置如截圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章