Openfire 是一個用Java 實現的XMPP 服務器,客戶端可以通過IQ 的方式與其進行通信(其實就是XML),客戶端和服務器之間的通信是依靠底層Smack 庫提供的各種功能來完成的。其實利用插件方式來擴展Openfire 服務器端主要有兩種擴展方式,一種是對服務器控制檯頁面進行擴展(不是本文的主要內容),其實就是遵循Openfire 頁面的佈局方式,進行相應的頁面擴展和功能擴展;另一種是對通信功能進行擴展。本文主要針對後者進行具體的描述
本篇文章的結構如下:
1、創建plugin.xml(這是整個插件最關鍵的文檔)
2、創建服務器插件實例(實現Plugin 接口的一個類還有一批IQHandler)
3、打包插件(Openfire 插件也有自己的打包方式)和部署插件
好滴,實刀實槍的來動手做吧
1、創建plugin.xml
初次開發Openfire 和Spark 插件的時候,很容易把二者搞混,千萬記得,這裏是Openfire 的plugin.xml 不是第二篇文章說的那個啦!
<?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>
最重要的那一行我已經標記出來啦,就是你這個插件的初始化和垃圾清理類,例子中是在com.im.server.plugin 包中的GroupTreePlugin 類,下文會對這個類進行詳細描述。其餘的都是描述信息,只要你提供了正確的描述信息,一般都不會出錯。建議初次開發者,在寫完plugin.xml 文件後,寫一個簡單的Plugin 實例,並打印出一些信息,如果重新啓動Openfire 信息成功顯示,恭喜你,你已經邁出一大步了!
2、實現Plugin 類和IQHandler
Plugin 類主要起到的作用是初始化和釋放資源,在初始化的過程中,最重要的的註冊一批IQHandler,IQHander 的作用有點類似於Spark 中的IQProvider,其實就是解析XML 文件之後,生成一些有用的實例,以供處理。下面分別給出一個Plugin 類的實例和IQProvider 的實例
GroupTreePlugin 類
/**
* 服務器端插件類
*
* @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 會自動監聽相應命名空間的IQ,然後進行處理;由於這個Plugin 不需要做資源釋放的工作,所以在destroyPlugin() 方法中沒有任何內容。具體的IQHander 類如下
GroupTreeIQHander
/**
* 處理客戶端發來的IQ,並回送結果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;
}
}
可以看到主要有兩個方法,一個是getInfo() 這個方法的目的是提供要解析的命名空間,在本例中,這個IQHandler 對每個命名空間爲"com:im:group" 的實例進行處理;還有一個最重要的方法:handleIQ() 該方法對包含指定命名空間的XML 進行解析,然後返回一個解析好的IQ。其實我認爲,這個IQHandler 和IQ 的關係就是Controller 和Model 的關係(如果你瞭解MVC 的話,那麼你一定知道我再說什麼),只不過這裏並沒有指定什麼View,你完全可以把IQ 當成Model 類進行理解。在這裏,我用了GroupManager 進行了XML 的處理,因爲我返回的IQ 內容中要從數據庫讀取所有羣組信息,所以轉交給GroupManager 進行處理,你完全可以在這個方法中進行具體的XML 處理,在這裏,解析和創建新的XML 主要用到的是JDOM(如果你對Java 解析XML 有所瞭解,那真的太好了!)。程序//1 處主要是獲取創建返回的IQ,並獲取原來IQ 的子元素(用於創建我們返回的IQ);程序//2 處很關鍵,如果你不調用createCopy 方法,程序會出錯(程序會死鎖還是什麼,忘記咧,不好以西)。
這就是程序的主體部分,我在這裏有一個建議,能不用Openfire 原始的程序函數,就不要用它們。我的提取數據庫方式都是自己寫的Bean,這樣有利於你自己對程序的掌控,其實更有利於快速開發(這世道不是啥都講究敏捷麼,哇哈哈)
3、打包插件
打包依然遵循二次打包的原則(如果你不瞭解啥叫要二次打包,請看上一篇)
這是我的ant 文件,由於Eclipse 幫我做了build 等很多工作,實際我的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 的插件開發三部曲徹底結束了,希望你們對這個開發流程有了系統的瞭解。
在此聲明,你們如有問題請留言,我會擠出時間儘量回覆你們的問題。如果我不小心忘記了,請原諒我,因爲現實生活中,我也是一個大忙人。我會盡量給你們回信,還有請使用email 的方式聯繫我,我會把我知道的內容發到你們的信箱中,還有,告訴你們一個事實,其實我很菜的,年齡也不大,別把我叫老啦!