本文翻譯自 – http://www.tigase.org/content/writing-plugin-code
上一篇文章描述了XMPP stanza如何在session manager當中被處理。處理分爲四個步驟,每一個步驟都有相對應類型的插件負責處理。 java
若是你已經看過這四個接口的代碼,你會發現每一個接口都只有一個方法須要實現。沒錯,這個方法就是處理packet的地方它們具備很是類似的入口參數,下面對這些參數進行介紹: node
若是仔細得看一下上面的這些接口,會發現它們還extend XMPPImplIfc接口。XMPPImplIfc定義了一些能夠得到插件基礎meta信息的接口。請參考下面的源碼: 數據庫
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
/**
* 須要添加XMPPImplIfc接口的描述
*/
public interface XMPPImplIfc {
int concurrentQueuesNo();
@Deprecated
int concurrentThreadsPerQueue();
/**
* id()返回插件的惟一ID。每個插件都擁有惟一ID:它在配置文件中用來指定哪一個插件須要加載,哪一個不須要。
* 在大多數狀況,ID就是該插件感興趣的packet的XMLNS。
*
*
@return id,字符串格式
*/
String id();
/**
* init()方法在插件被加載到內存以後當即被執行,檢查數據庫是否可用和其餘的初始化過程均可以寫到這個方法裏。
* 這對於那些經過非標準存儲方式訪問數據庫或須要對數據庫scheme進行升級的插件來講很是有用。
*
* @param settings 初始化配置信息
*
@throws TigaseDBException
*/
void init(Map settings)throwsTigaseDBException;
//~--- get methods ----------------------------------------------------------
/**
* isSupporting方法傳入元素的名稱和命名空間,返回這個元素是否被該插件「感興趣」
*
* @param elem 元素名稱,字符串格式
* @param ns 命名空間,字符串格式
*
@return 一個布爾類型,true:感興趣;false:不感興趣
*/
boolean isSupporting(String elem, String ns);
//~--- methods --------------------------------------------------------------
/**
* supDiscoFeatures()方法向請求的發起者返回一個XML元素數組格式的服務發現(service discovery)特性信息。
* 返回的服務發現特性取決於該插件支持哪些服務。
*
* @param session 一個XMPPResourceConnection實例
*
@return 一個XML元素數組
*/
Element[] supDiscoFeatures(XMPPResourceConnection session);
/**
* supElements()方法返回該插件「感興趣」的XML元素名數組,數組當中的每個元素名都依次對應着supNamespaces()返回的命名空間
*
*
@return 字符串數組
*/
String[] supElements();
/**
* supNamespaces()方法返回該插件「感興趣」的stanza命名空間,數組當中的每個命名空間都依次對應着supElements()方法返回的XML元素名
*
*
@return 字符串數組
*/
String[] supNamespaces();
/**
* supStreamFeatures()方法對請求的發起者返回一個XML元素數組格式的流特性信息。
* 返回的流特性取決於該插件支持哪些特性。
*
* @param session 一個XMPPResourceConnection實例
*
@return XML元素數組
*/
Element[] supStreamFeatures(XMPPResourceConnection session);
}
|
接下來,咱們實現一個專門處理<message/> packet的簡單插件,插件的工做就是把packet投遞到目的地地址。傳入packet會被轉發給用戶,而傳出packet會被轉發到一個外部目的地地址。這個插件其實已經實現了,它保存在咱們的SVN服務器上(https://svn.tigase.org/reps/tigase-server/trunk/src/main/java/tigase/xmpp/impl/Message.java)。代碼當中有一些備註,可是這篇文檔會更深刻的介紹實現細節。 數組
在開始以前你須要選擇一個插件類型。若是要開發一個處理器插件,那麼就須要實現XMPPProcessorIfc接口;若是是預處理插件,就須要實現XMPPPreprocessorIfc接口;固然你也能夠實現多個接口,這個取決於你的需求和狀況,你也可使用helper抽象類做爲基類來實現全部的插件。插件類的聲明應該像下面那樣(假如你要實現一個處理器插件): 服務器
1
2
|
publicclassMessageextendsXMPPProcessor
implementsXMPPProcessorIfc
|
要作的第一件事情就是肯定插件ID。它是惟一的,須要放到配置文件裏面,告訴服務器在啓動時加載並使用相對應的插件。若是這個插件只對特定命名空間下特定名稱的元素「感興趣」,在多數狀況下,能夠直接以命名空間來做爲ID,固然了誰也沒法保證這個名稱的元素不會出如今其餘的packet裏面。由於咱們想開發一個可以處理全部的的處理器插件,可是又不想花費一成天來考慮如何爲這個插件起一個很酷的ID,因此咱們乾脆就叫它「message」吧。 session
用下面的代碼來聲明插件的ID: dom
1
2
|
private static final String ID ="message";
public String id() {returnID; }
|
就像以前咱們描述的那樣,插件只接收並處理它「感興趣」的packet。咱們的插件只對「jabber:client」命名空間下的元素感興趣。聲明插件所感興趣的東西,須要添加兩個方法: svn
1
2
3
4
5
6
7
|
public String[] supElements() {
returnnewString[] {"message"};
}
public String[] supNamespaces() {
return newString[] {"jabber:client"};
}
|
如今咱們已經準備好了把插件加載到tigase服務器。下一步就是實現packet處理的方,請參考SVN中的代碼(https://svn.tigase.org/reps/tigase-server/trunk/src/main/java/tigase/xmpp/impl/Message.java)。我只會在容易形成困惑的代碼上面添加註釋,而後添加一兩行代碼幫助你理解。 性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
public void process(finalPacket packet,
final XMPPResourceConnection session,
final NonAuthUserRepository repo,
final Queue<Packet> results,
final Map<String, Object> settings)
throwsXMPPException {
// 出於性能的考慮,最好在打印日誌以前現檢查一下日誌級別
if(log.isLoggable(Level.FINEST)) {
log.finest("Processing packet: "+ packet.toString());
}
// 若是用戶不在線,你也許想跳事後面的處理環節
if(session ==null) {
return;
}// end of if (session == null)
// 當插件在第一次處理這個用戶的會話信息的時候,還有另一種方法能夠執行必要的操做
if(session.getSessionData(ID) ==null) {
session.putSessionData(ID, ID);
// 你能夠把你的代碼放到這裏
.....
// 若是你不但願終止操做,那麼就把return語句去掉
return;
}
// 若是用戶的會話沒有受權,那麼每一次調用session.getUserId()方法都會拋出異常
try{
// 在比較JID以前必定記得要去掉resource部分
// JID的組成:jid = [ node "@" ] domain [ "/" resource ]
// 好比:chutianxing@gmail.com/home
String id = JIDUtils.getNodeID(packet.getElemTo());
// 檢查一下這個packet是不是發給會話的擁有者
if(session.getUserId().equals(id)) {
// 若是是,那麼這個消息的確是要發送給這個客戶端的
Element elem = packet.getElement().clone();
Packet result =newPacket(elem);
// 這裏就是咱們爲最終收到消息的用戶設置客戶端組件地址的地方了
// 在大多數狀況,這多是一個可以保持於客戶端鏈接的c2s或Bosh組件
result.setTo(session.getConnectionId(packet.getElemTo()));
// 在大多數狀況,這一步能夠跳過,可是當packet的投遞過程出現了什麼問題,這麼作能夠爲調用者返回一個錯誤
result.setFrom(packet.getTo());
// 最後不要忘記把結果packet放到結果隊列裏面去,不然結果會丟失
results.offer(result);
}// end of else
// 在比較JID以前必定記得要去掉resource部分
id = JIDUtils.getNodeID(packet.getElemFrom());
// 檢查一下這個packet是否由會話的擁有者發出
if(session.getUserId().equals(id)) {
// 這是一個由客戶端發出的packet,最簡單的處理就是把packet轉發到packet的目的地地址:
// 簡單的對XML元素進行克隆,而後……
Element result = packet.getElement().clone();
// 把他放到傳出packet隊列裏面就好了
results.offer(newPacket(result));
return;
}
// 程序真的會運行到這裏嗎?
// 是的,一些packet即沒有from也沒有to地址。最容易理解的一個例子是向服務器發送的獲取某些數據的IQ請求。這類packet沒有任何地址,而且須要對它作不少複雜的處理
// 下面的代碼展現瞭如何肯定這個seesion就是請求發起者的session
id = packet.getFrom();
// 下面的處理和檢查getElementFrom差很少
if(session.getConnectionId().equals(id)) {
// 這裏須要針對IQ packet作一些特別處理,可是咱們須要處理的是message,因此這裏只須要對它進行轉發
Element result = packet.getElement().clone();
// 若是程序運行到這裏說明packet的from地址是沒有的,如今對from屬性就行設置
result.setAttribute("from", session.getJID());
// 最後把傳出packet放到結果隊列裏面就ok樂
results.offer(newPacket(result));
}
}catch(NotAuthorizedException e) {
log.warning("NotAuthorizedException for packet: " +
packet.getStringData());
results.offer(Authorization.NOT_AUTHORIZED.getResponseMessage(packet,
"You must authorize session first.",true));
}// end of try-catch
}
|