Tigase插件 – 编写插件
上一篇文章描述了XMPP stanza如何在session manager当中被处理。处理分为四个步骤,每个步骤都有相对应类型的插件负责处理。
第一步 – 预处理 – XMPPPreprocessorIfc:这是预处理器插件需要实现的接口
第二步 – 处理 – XMPPProcessorIfc:这是处理器插件需要实现的接口
第三步 – 投递 – XMPPPostProcessorIfc:这是投递处理器插件需要实现的接口
第四步 – 过滤 – XMPPPacketFilterIfc:这是结果过滤器插件需要实现的接口
如果你已经看过这四个接口的代码,你会发现每个接口都只有一个方法需要实现。没错,这个方法就是处理packet的地方它们具有非常相似的入口参数,下面对这些参数进行介绍:
Packet packet – 需要被处理的packet,这个参数不可以为null。即使这个对象不是immutable的,在方法里也不能对它进行修改。它的任何一个变亮都不能发生改变。
XMPPResourceConnection session – session里面包含所有的用户会话数据和访问用户数据库的方法。它允许向持久化数据库中存储信息,但如果用户在线只允许向内存中存储数据。在方法调用时,如果没有在线的用户会话,那么这个参数可以为null。
NonAuthUserRespository repo – 当上面的参数-即用户会话为空的时候,这个参数通常用来存储用户数据。它只允许非常有限的数据访问。比如在用户离线时存储用户的离线消息(对已经存在的数据不允许覆写),比如读取用户的公共Vcard信息。
Queue<Packet> results – 这是处理产生的结果packet队列。不管怎样,都必须对输入的packet进行备份,并把备份存储到结果队别里面。
Map<String, Object> setting – map里面保存着tigase服务器专为插件准备的配置信息。在大多数情况下,插件并不需要这些配置信息,但如果某个插件需要访问外部数据库,那么tigase服务器可以通过这个参数向它传递数据库的连接字符串。
如果仔细得看一下上面的这些接口,会发现它们还extend XMPPImplIfc接口。XMPPImplIfc定义了一些可以获得插件基础meta信息的接口。请参考下面的源码:
/**
* 需要添加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) throws TigaseDBException;
//~--- 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抽象类作为基类来实现所有的插件。插件类的声明应该像下面那样(假如你要实现一个处理器插件):
public class Message extends XMPPProcessor
implements XMPPProcessorIfc
要做的第一件事情就是确定插件ID。它是唯一的,需要放到配置文件里面,告诉服务器在启动时加载并使用相对应的插件。如果这个插件只对特定命名空间下特定名称的元素“感兴趣”,在多数情况下,可以直接以命名空间来作为ID,当然了谁也无法保证这个名称的元素不会出现在其他的packet里面。因为我们想开发一个能够处理所有的的处理器插件,但是又不想花费一整天来考虑如何为这个插件起一个很酷的ID,所以我们干脆就叫它“message”吧。
用下面的代码来声明插件的ID:
private static final String ID = "message";
public String id() { return ID; }
就像之前我们描述的那样,插件只接收并处理它“感兴趣”的packet。我们的插件只对“jabber:client”命名空间下的元素感兴趣。声明插件所感兴趣的东西,需要添加两个方法:
public String[] supElements() {
return new String[] {"message"};
}
public String[] supNamespaces() {
return new String[] {"jabber:client"};
}
现在我们已经准备好了把插件加载到tigase服务器。下一步就是实现packet处理的方,请参考源代码(tigase.xmpp.impl.Message.java)。我只会在容易造成困惑的代码上面添加注释,然后添加一两行代码帮助你理解。
public void process(final Packet packet,
final XMPPResourceConnection session,
final NonAuthUserRepository repo,
final Queue<Packet> results,
final Map<String, Object> settings)
throws XMPPException {
// 出于性能的考虑,最好在打印日志之前现检查一下日志级别
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 ]
// 比如:[email protected]/home
String id = JIDUtils.getNodeID(packet.getElemTo());
// 检查一下这个packet是否是发给会话的拥有者
if (session.getUserId().equals(id)) {
// 如果是,那么这个消息的确是要发送给这个客户端的
Element elem = packet.getElement().clone();
Packet result = new Packet(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(new Packet(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(new Packet(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
}
转载于:https://my.oschina.net/wjwei113/blog/375304
上一篇: 2.18 小丑排序
下一篇: 魔术方法总结 --小丑
推荐阅读
-
Mybatis自动生成对象插件Generator最完整的配置教程
-
eclipse-maven项目使用maven的tomcat插件启动
-
写了一个树状图插件
-
jquery的颜色选择插件实例代码_jquery
-
9个JavaScript评级/投票插件_javascript技巧
-
微信支付之扫码支付开发:我遇到的坑及解决办法(附:Ecshop 微信支付插件),ecshop
-
非常有用的40款jQuery 插件推荐(系列二)_jquery
-
在使用@angular/cli创建的angular项目上添加postcss等一系列移动端自适应插件
-
Onethink1.1 钩子和插件的使用!
-
PHP+Jquery与ajax相结合实现下拉淡出瀑布流效果【无需插件】