此篇文章為《 開 發(fā)你自己的XMPP IM 續(xù) - Openfire 插件開發(fā) 》的轉(zhuǎn)發(fā),原文章地址: http://phoenixtoday.blogbus.com/logs/20285574.html

繼續(xù)上一篇的內(nèi)容,本篇文章介紹開發(fā)Openfire 的插件

這篇文章拖了很久了,呵呵,真是千呼萬喚始出來呀。Openfire 服務(wù)器端是支持插件開發(fā)的,開發(fā)過程可能會涉及到數(shù)據(jù)庫的操作,本篇文章專注于Openfire 插件的部分,對服務(wù)器端涉及到數(shù)據(jù)庫的開發(fā)只做簡單介紹。

Openfire 是一個(gè)用Java 實(shí)現(xiàn)的XMPP 服務(wù)器,客戶端可以通過IQ 的方式與其進(jìn)行通信(其實(shí)就是XML),客戶端和服務(wù)器之間的通信是依靠底層Smack 庫提供的各種功能來完成的。其實(shí)利用插件方式來擴(kuò)展Openfire 服務(wù)器端主要有兩種擴(kuò)展方式,一種是對服務(wù)器控制臺頁面進(jìn)行擴(kuò)展(不是本文的主要內(nèi)容),其實(shí)就是遵循Openfire 頁面的布局方式,進(jìn)行相應(yīng)的頁面擴(kuò)展和功能擴(kuò)展;另一種是對通信功能進(jìn)行擴(kuò)展。本文主要針對后者進(jìn)行具體的描述

本篇文章的結(jié)構(gòu)如下:

1、創(chuàng)建plugin.xml(這是整個(gè)插件最關(guān)鍵的文檔)
2、創(chuàng)建服務(wù)器插件實(shí)例(實(shí)現(xiàn)Plugin 接口的一個(gè)類還有一批IQHandler)
3、打包插件(Openfire 插件也有自己的打包方式)和部署插件

好滴,實(shí)刀實(shí)槍的來動(dòng)手做吧

1、創(chuàng)建plugin.xml

初次開發(fā)Openfire 和Spark 插件的時(shí)候,很容易把二者搞混,千萬記得,這里是Openfire 的plugin.xml 不是第二篇文章說的那個(gè)啦!

<?xml version="1.0" encoding="UTF-8"?>
<plugin>
??? <!-- Main plugin class? 這里是最重要滴-->
??? <class>com.im.server.plugin.GroupTreePlugin</class>

??? <!-- Plugin meta-data -->
??? <name>GroupTreePlugin</name>
??? <description>This is the group plugin.</description>
??? <author>Phoenix</author>

??? <version>1.0</version>
??? <date>14/03/2008</date>
??? <url>http://localhost:9001/openfire/plugins.jsp</url>
??? <minServerVersion>3.4.1</minServerVersion>
??? <licenseType>gpl</licenseType>

??? <!-- Admin console entries -->
??? <adminconsole>
??????? <!-- More on this below -->
??? </adminconsole>
</plugin>

最重要的那一行我已經(jīng)標(biāo)記出來啦,就是你這個(gè)插件的初始化和垃圾清理類,例子中是在com.im.server.plugin 包中的GroupTreePlugin 類,下文會對這個(gè)類進(jìn)行詳細(xì)描述。其余的都是描述信息,只要你提供了正確的描述信息,一般都不會出錯(cuò)。建議初次開發(fā)者,在寫完plugin.xml 文件后,寫一個(gè)簡單的Plugin 實(shí)例,并打印出一些信息,如果重新啟動(dòng)Openfire 信息成功顯示,恭喜你,你已經(jīng)邁出一大步了!

2、實(shí)現(xiàn)Plugin 類和IQHandler

Plugin 類主要起到的作用是初始化和釋放資源,在初始化的過程中,最重要的的注冊一批IQHandler,IQHander 的作用有點(diǎn)類似于Spark 中的IQProvider,其實(shí)就是解析XML 文件之后,生成一些有用的實(shí)例,以供處理。下面分別給出一個(gè)Plugin 類的實(shí)例和IQProvider 的實(shí)例

GroupTreePlugin 類

/**
?* 服務(wù)器端插件類
?*?
?* @author Phoenix
?*?
?* Mar 14, 2008 11:03:11 AM
?*?
?* version 0.1
?*/
public class GroupTreePlugin implements Plugin
{
??? private XMPPServer server;

??? /*
??? ?* (non-Javadoc)
??? ?*?
??? ?* @see org.jivesoftware.openfire.container.Plugin#destroyPlugin()
??? ?*/
??? public void destroyPlugin()
??? {

??? }

??? /*
??? ?* (non-Javadoc)
??? ?*?
??? ?* @see org.jivesoftware.openfire.container.Plugin#initializePlugin(org.jivesoftware.openfire.container.PluginManager,
??? ?*????? java.io.File)
??? ?*/
??? public void initializePlugin(PluginManager manager, File pluginDirectory)
??? {
??? ??? PluginLog.trace("注冊群組樹IQ處理器");
??? ??? server = XMPPServer.getInstance();
??? ????
??? ??? server.getIQRouter().addHandler(new GroupTreeIQHander()); //1
??? ??? server.getIQRouter().addHandler(new UserInfoIQHandler());
??? ??? server.getIQRouter().addHandler(new DelUserIQHandler());
??? ??? server.getIQRouter().addHandler(new CreateUserIQHandler());
??? ??? server.getIQRouter().addHandler(new AddGroupUserIQHandler());
??? ??? server.getIQRouter().addHandler(new SetRoleIQHandler());

??? }

}

上例所示,在初始化中先找到IQRouter,然后通過IQRouter 注冊一批IQHandler,這些IQHander 會自動(dòng)監(jiān)聽相應(yīng)命名空間的IQ,然后進(jìn)行處理;由于這個(gè)Plugin 不需要做資源釋放的工作,所以在destroyPlugin() 方法中沒有任何內(nèi)容。具體的IQHander 類如下

GroupTreeIQHander

/**
?* 處理客戶端發(fā)來的IQ,并回送結(jié)果IQ
?*?
?* @author Phoenix
?*?
?* Mar 14, 2008 4:55:33 PM
?*?
?* version 0.1
?*/
public class GroupTreeIQHander extends IQHandler
{

??? private static final String MODULE_NAME = "group tree handler";

??? private static final String NAME_SPACE = "com:im:group";

??? private IQHandlerInfo info;

??? public GroupTreeIQHander()
??? {
??? ??? super(MODULE_NAME);
??? ??? info = new IQHandlerInfo("gruops", NAME_SPACE);
??? }

??? /*
??? ?* (non-Javadoc)
??? ?*?
??? ?* @see org.jivesoftware.openfire.handler.IQHandler#getInfo()
??? ?*/
??? @Override
??? public IQHandlerInfo getInfo()
??? {
??? ??? return info;
??? }

??? /*
??? ?* (non-Javadoc)
??? ?*?
??? ?* @see org.jivesoftware.openfire.handler.IQHandler#handleIQ(org.xmpp.packet.IQ)
??? ?*/
??? @Override
??? public IQ handleIQ(IQ packet) throws UnauthorizedException
??? {
??? ??? IQ reply = IQ.createResultIQ(packet);
??? ??? Element groups = packet.getChildElement();//1
??? ????
??? ??? if (!IQ.Type.get.equals(packet.getType()))
??? ??? {
??? ??? ??? System.out.println("非法的請求類型");
??? ??? ??? reply.setChildElement(groups.createCopy());
??? ??? ??? reply.setError(PacketError.Condition.bad_request);
??? ??? ??? return reply;
??? ??? }
??? ????
??? ??? String userName = StringUtils.substringBefore(packet.getFrom().toString(),"@");

??? ??? GroupManager.getInstance().initElement(groups,userName);
??? ????
??? ??? reply.setChildElement(groups.createCopy());//2

??? ??? System.out.println("返回的最終XML" + reply.toXML());

??? ??? return reply;
??? }

}

可以看到主要有兩個(gè)方法,一個(gè)是getInfo() 這個(gè)方法的目的是提供要解析的命名空間,在本例中,這個(gè)IQHandler 對每個(gè)命名空間為"com:im:group" 的實(shí)例進(jìn)行處理;還有一個(gè)最重要的方法:handleIQ() 該方法對包含指定命名空間的XML 進(jìn)行解析,然后返回一個(gè)解析好的IQ。其實(shí)我認(rèn)為,這個(gè)IQHandler 和IQ 的關(guān)系就是Controller 和Model 的關(guān)系(如果你了解MVC 的話,那么你一定知道我再說什么),只不過這里并沒有指定什么View,你完全可以把IQ 當(dāng)成Model 類進(jìn)行理解。在這里,我用了GroupManager 進(jìn)行了XML 的處理,因?yàn)槲曳祷氐腎Q 內(nèi)容中要從數(shù)據(jù)庫讀取所有群組信息,所以轉(zhuǎn)交給GroupManager 進(jìn)行處理,你完全可以在這個(gè)方法中進(jìn)行具體的XML 處理,在這里,解析和創(chuàng)建新的XML 主要用到的是JDOM(如果你對Java 解析XML 有所了解,那真的太好了!)。程序//1 處主要是獲取創(chuàng)建返回的IQ,并獲取原來IQ 的子元素(用于創(chuàng)建我們返回的IQ);程序//2 處很關(guān)鍵,如果你不調(diào)用createCopy 方法,程序會出錯(cuò)(程序會死鎖還是什么,忘記咧,不好以西)。

這就是程序的主體部分,我在這里有一個(gè)建議,能不用Openfire 原始的程序函數(shù),就不要用它們。我的提取數(shù)據(jù)庫方式都是自己寫的Bean,這樣有利于你自己對程序的掌控,其實(shí)更有利于快速開發(fā)(這世道不是啥都講究敏捷么,哇哈哈)

3、打包插件

打包依然遵循二次打包的原則(如果你不了解啥叫要二次打包,請看上一篇)
這是我的ant 文件,由于Eclipse 幫我做了build 等很多工作,實(shí)際我的ant 工作就是在打包,并放入插件目錄下的plugin 文件夾下

<?xml version="1.0" encoding="UTF-8"?>
<project name="IM" default="release" basedir=".">

??? <property name="openfire.path"
??? ??? value="E:/workspace/europa/openfire_src/target/openfire" />
??? <property name="classes.dir" value="classes" />
??? <property name="lib.dir" value="lib" />

??? <target name="jar">
??? ??? <jar jarfile="${lib.dir}/grouptreeplugin.jar" basedir="${classes.dir}" >
??? ??? ??? <fileset dir=".">
??? ??? ??? ??? <include name="*.jar"/>
??? ??? ??? </fileset>
??? ??? </jar>
??? ??? <jar jarfile="${openfire.path}/plugins/groupTreePlugin.jar">
??? ??? ??? <fileset dir=".">
??? ??? ??? ??? <include name="lib/*.jar" />
??? ??? ??? ??? <include name="plugin.xml" />
??? ??? ??? ??? <include name="logo_small.gif" />
??? ??? ??? ??? <include name="logo_large.gif" />
??? ??? ??? ??? <include name="readme.html" />
??? ??? ??? ??? <include name="changelog.html" />
??? ??? ??? ??? <include name="build.xml" />
??? ??? ??? </fileset>
??? ??? </jar>

??? </target>

??? <target name="release" depends="jar">
??? </target>

</project>

好了,至此XMPP+Spark+Openfire 的插件開發(fā)三部曲徹底結(jié)束了,希望你們對這個(gè)開發(fā)流程有了系統(tǒng)的了解。