一、插件开发的目录结构设计
我们先看一下openfire源码的插件开发目录结构:
二、插件开发
我们知道openfire插件开发主要有3种方式注册方式:
1)IQHandler(IQ handlers respond to IQ packets with a particular element name and namespace);
2)Interceptor(PacketInterceptor to receive all packets being send through the system and optionally reject them);
3)Component(Components receive all packets addressed to a particular sub-domain)。
今天来讲IQHandler涉及到的一种设计模式——模板方法(Template Method)模式。
模板方法最标志性的特点是它需要一个抽象类。在java设计模式中,其实很少用到抽象类,更多地还是用到接口。
我们如果要做自己的IQ包处理,可以自定义类如TestTemplateMethodHandler:class TestTemplateMethodHandler extends IQHandler,然后在public IQHandlerInfo getInfo()方法中写上自己想要注册的元素名及命名空间,在public IQ handlerIQ(IQ packet)方法中写上自己想要对丢进来的IQ包做什么样的处理(注意IQ包是基于问答形式的,所以应该有IQ包的reply)。
1、创建plugin.xml
<?xml version="1.0" encoding="UTF-8"?><!--
Plugin configuration for the areabroadcast plugin.
-->
<plugin>
<class>org.jivesoft.openfire.plugin.AreaBroadcastPlugin</class> <!--此处为自己添加的插件的类路径 -->
<name>areabroadcast</name> <!-- 插件名称这里最好都是小写否则jsp的java代码里面会出现获取不到的问题 -->
<description>Broadcasts messages to users.</description>
<author>authorname</author>
<version>1.7.0</version>
<date>11/16/2007</date>
<url>http://www.igniterealtime.org</url>
<minServerVersion>3.4.1</minServerVersion>
<!-- UI extension 以下为对后台管理界面的扩展在tab-session下添加了一个siderbar-->
<adminconsole>
<tab id="tab-session"> <!--这里这个名称请参照admin-sidebar.xml-->
<sidebar id="sidebar-areaBroadcast" name="areaBroadcast">
<item id="areaBroadcast" name="areaBroadcast"
url="areaBroadcast.jsp"
description="broadcast messages to users in specific area" />
</sidebar>
</tab>
</adminconsole>
</plugin>
2、实现Plugin 类和IQHandler
Plugin 类主要起到的作用是初始化和释放资源,在初始化的过程中,最重要的的注册一批IQHandler,IQHander 的作用有点类似于Spark 中的IQProvider,其实就是解析XML 文件之后,生成一些有用的实例,以供处理。下面分别给出一个扩展用户注册信息字段(增加了一个province省份字段)的Plugin 类的实例和IQProvider 的实例。
AreaBroadcastPlugin 类:
private XMPPServer server;
private MessageRouter msgRouter;
private UserInfoDao userInfoDao;
private String domain;
@Override
public void destroyPlugin() {
System.out.println("destroy AreaBroadcastPlugin");
if (userInfoIQHander != null)
{
server.getIQRouter().removeHandler(userInfoIQHander);
userInfoIQHander = null;
}
}
/**
* 拦截包含http://jivesoft.org/protocol/userRegister查询的特定请求,如下:
* 例:
* <iq id="g0G4m-1" to="zhanglj" type="set">
* <username>re</username>
* <email>username@test.com</email>
* <name>username</name>
* <password>1</password>
* <province>北京</province>
* </query>
* </iq>
*/
@Override
public void initializePlugin(PluginManager manager, File pluginDirectory) {
System.out.println("initialize AreaBroadcastPlugin");
server = XMPPServer.getInstance();
/*注册用户注册请求拦截器*/
server.getIQRouter().addHandler(userInfoIQHander);
domain = server.getServerInfo().getXMPPDomain();
msgRouter = server.getMessageRouter();
}
/**
* 管理员给特定区域用户发消息,这里这个方法没用到,发消息是从管理员控制台jsp页面中直接调用的
* <message id="j0Dw7-59" from="zhouds@zhanglj" to="zhang@zhanglj"><body>sdfsdf</body></message>
* 2.收消息——离线也能收到!
* <message id="j0Dw7-59" to="zhang@zhanglj" from="zhouds@zhanglj/Spark 2.6.3"><body>sdfsdf</body></message>
* <message id="j0Dw7-61" to="zhang@zhanglj" type="headline"><body>通知</body></message>
*
*2.收通知——离线无法收到!区别在于发消息中有发送人
* <message id="j0Dw7-61" to="zhang@zhanglj" type="headline" from="zhouds@zhanglj/Spark 2.6.3"><body>通知</body></message>
*/
public boolean sendMsg(String province, String message){
try{
userInfoDao = new UserInfoDao();
List<String> ls = new ArrayList<String>();
ls = userInfoDao.getUsernameByProvince(URLDecoder.decode(province, "UTF-8"));
for(String username : ls){
Message Msg = new Message();
Msg.setFrom("admin@" + domain); /*有此为发消息,无为发通知*/
Msg.setTo(username + "@" + domain);
Msg.setSubject("新闻");
Msg.addChildElement("body", "").addText(URLDecoder.decode(message, "UTF-8"));
msgRouter.route(Msg);
}
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
}
}
UserInfoIQHander 类:
private IQHandlerInfo info;
private UserInfoDao userInfoDao;
private XMPPServer server;
private UserManager userManager;
/**
* 初始化查询用户信息请求和服务器域
*/
public UserInfoIQHander(String moduleName) {
super(moduleName);
/*初始化用户注册请求*/
info = new IQHandlerInfo("query", "http://jivesoft.org/protocol/userRegister");
/*获取服务器域*/
server = XMPPServer.getInstance();
domain = server.getServerInfo().getXMPPDomain();
userManager = server.getUserManager();
}
* 用户注册,从xml中解析用户信息并插入数据库,返回如下信息:
* 1.用户名已存在:
* <iq type="error" id="g0G4m-1" from="zhanglj" to="zhang@zhanglj/Spark 2.6.3">
* <query xmlns=" http:// jivesoft .org/protocol/userRegister">
* <register xmlns="" result="Username 're' already exists"/>
* </query>
* </iq>
*
* 2.注册成功:
* <iq type="result" id="g0G4m-1" from="zhanglj" to="zhang@zhanglj/Spark 2.6.3">
* <query xmlns=" http:// jivesoft .org/protocol/userRegister">
* <register xmlns="" result="success"/>
* </query>
* </iq>
*
*/
public IQ handleIQ(IQ iq) throws UnauthorizedException {
IQ reply = IQ.createResultIQ(iq);
try {
userInfoDao = new UserInfoDao();
Element childElement = iq.getChildElement();
String namespace = childElement.getNamespaceURI();
if (IQ.Type.set.equals(iq.getType())){
String username = null;
String password = null;
String email = null;
String name = null;
String province = null;
Element iqElement = iq.getChildElement();
username = iqElement.elementText("username");
password = iqElement.elementText("password");
email = iqElement.elementText("email");
name = iqElement.elementText("name");
province = iqElement.elementText("province");
userManager = server.getUserManager();
boolean isRegisteredUser = userManager.isRegisteredUser(username);
if(isRegisteredUser){
// The user already exists since no exception, so:
//throw new UserAlreadyExistsException("Username " + username + " already exists");
reply.setType(IQ.Type.error);
Element query = new DefaultElement("query");
query.addElement("register").addAttribute("result", "Username '" + username + "' already exists");
query.addNamespace("", "http://qnsoft/protocol/registerAreaUser");
reply.setChildElement(query);
return reply;
}
/* 插入用户信息 */
userInfoDao.createUser(username,password,name,email,province);
}
if ("http://jivesoft.org/protocol/userRegister".equals(namespace)) {
reply.setType(IQ.Type.result);
Element query = new DefaultElement("query");
query.addElement("register").addAttribute("result", "success");
query.addNamespace("", "http://jivesoft.org/protocol/userRegister");
reply.setChildElement(query);
}
} catch (Exception e) {
e.printStackTrace();
}
return reply;
}
}
UserInfoDao类:
private static final Logger Log = LoggerFactory.getLogger(DefaultUserProvider.class);
private static final String GET_USERNAME_BY_PROVINCE = "SELECT username FROM ofUser WHERE province=?";
private static final String INSERT_USER =
"INSERT INTO ofUser (username,plainPassword,encryptedPassword,name,email,creationDate,modificationDate,province) " +
"VALUES (?,?,?,?,?,?,?,?)";
public List<String> getUsernameByProvince(String province){
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<String> list = new ArrayList<String>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(GET_USERNAME_BY_PROVINCE);
pstmt.setString(1, province);
rs = pstmt.executeQuery();
if (rs.wasNull()) {
throw new UserNotFoundException();
}
while(rs.next()){
String username = rs.getString(1);
list.add(username);
}
}catch (Exception e) {
e.printStackTrace();
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return list;
}
/*创建带有省份的用户当然要先在数据库ofuser表中添加一个province字段*/
public void createUser(String username, String password, String name, String email, String province)
{
// Determine if the password should be stored as plain text or encrypted.
boolean usePlainPassword = JiveGlobals.getBooleanProperty("user.usePlainPassword");
String encryptedPassword = null;
if (!usePlainPassword) {
try {
encryptedPassword = AuthFactory.encryptPassword(password);
// Set password to null so that it's inserted that way.
password = null;
}
catch (UnsupportedOperationException uoe) {
// Encrypting the password may have failed if in setup mode. Therefore,
// use the plain password.
}
}
Date now = new Date();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_USER);
pstmt.setString(1, username);
if (password == null) {
pstmt.setNull(2, Types.VARCHAR);
}
else {
pstmt.setString(2, password);
}
if (encryptedPassword == null) {
pstmt.setNull(3, Types.VARCHAR);
}
else {
pstmt.setString(3, encryptedPassword);
}
if (name == null || name.matches("\\s*")) {
pstmt.setNull(4, Types.VARCHAR);
}
else {
pstmt.setString(4, name);
}
if (email == null || email.matches("\\s*")) {
pstmt.setNull(5, Types.VARCHAR);
}
else {
pstmt.setString(5, email);
}
pstmt.setString(6, StringUtils.dateToMillis(now));
pstmt.setString(7, StringUtils.dateToMillis(now));
if (province == null) {
pstmt.setNull(8, Types.VARCHAR);
}
else {
pstmt.setString(8, province);
}
pstmt.execute();
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return;
}
}