DBMNG数据库管理与应用

科学是实事求是的学问,来不得半点虚假。
当前位置:首页 > 服务器配置 > OpenFire

修改openfire源代码实现消息记录历史记录保存

实现openfire消息记录通常有两种方式,修改服务端和添加消息记录插件。

今天,简单的说明一下修改服务端方式实现消息记录保存功能。

实现思路

修改前:

默认的,openfire只提供保存离线记录至ofOffline表中。当发送一条消息时,判断用户是否在线,若为true,不保存消息;若为fasle,保存消息至离线消息表中。

修改后:

仿照保存离线消息,用户每发送一条消息,将消息存放在ofHistory表中,ofHistory表结构同ofOffline

实现步骤:

1.修改初始数据库文件,路径src/database/openfire_sqlserver.sql

添加ofHistory表

CREATE TABLE ofHistory (
  username              NVARCHAR(64)    NOT NULL,
  messageID             INTEGER         NOT NULL,
  creationDate          NVARCHAR(64)    NOT NULL,
  messageSize           INTEGER         NOT NULL,
  stanza                NTEXT           NOT NULL,
  CONSTRAINT ofHistory_pk PRIMARY KEY (username, messageID)
);

CREATE TABLE ofOffline (
  username              NVARCHAR(64)    NOT NULL,
  messageID             INTEGER         NOT NULL,
  creationDate          CHAR(15)        NOT NULL,
  messageSize           INTEGER         NOT NULL,
  stanza                NTEXT           NOT NULL,
  CONSTRAINT ofOffline_pk PRIMARY KEY (username, messageID)
);

注:其他数据库修改方式雷同

2.添加保存消息方法

MessageRouter类中110行

   try {
                    // Deliver stanza to requested route
                    routingTable.routePacket(recipientJID, packet, false);
                    //保存消息记录dml@2013.4.15
                    OfflineMessageStore oms = new OfflineMessageStore();
                    oms.addMessage_toHistory(packet);
                    
                }
                catch (Exception e) {
                	log.error("Failed to route packet: " + packet.toXML(), e);
                    routingFailed(recipientJID, packet);
                }
3.修改OfflineMessageStore类,添加保存消息记录方法

/**
 * $RCSfile$
 * $Revision: 2911 $
 * $Date: 2005-10-03 12:35:52 -0300 (Mon, 03 Oct 2005) $
 *
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire;

import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;

/**
 * Represents the user's offline message storage. A message store holds messages
 * that were sent to the user while they were unavailable. The user can retrieve
 * their messages by setting their presence to "available". The messages will
 * then be delivered normally. Offline message storage is optional, in which
 * case a null implementation is returned that always throws
 * UnauthorizedException when adding messages to the store.
 * 
 * @author Iain Shigeoka
 */
public class OfflineMessageStore extends BasicModule implements
		UserEventListener {

	private static final Logger Log = LoggerFactory
			.getLogger(OfflineMessageStore.class);
	// 保存消息记录 dml@2013.4.16
	private static final String INSERT_HISTORY = "INSERT INTO ofHistory (username, messageID, creationDate, messageSize, stanza) "
			+ "VALUES (?, ?, ?, ?, ?)";

	private static final String INSERT_OFFLINE = "INSERT INTO ofOffline (username, messageID, creationDate, messageSize, stanza) "
			+ "VALUES (?, ?, ?, ?, ?)";
	private static final String LOAD_OFFLINE = "SELECT stanza, creationDate FROM ofOffline WHERE username=?";
	private static final String LOAD_OFFLINE_MESSAGE = "SELECT stanza FROM ofOffline WHERE username=? AND creationDate=?";
	private static final String SELECT_SIZE_OFFLINE = "SELECT SUM(messageSize) FROM ofOffline WHERE username=?";
	private static final String SELECT_SIZE_ALL_OFFLINE = "SELECT SUM(messageSize) FROM ofOffline";
	private static final String DELETE_OFFLINE = "DELETE FROM ofOffline WHERE username=?";
	private static final String DELETE_OFFLINE_MESSAGE = "DELETE FROM ofOffline WHERE username=? AND creationDate=?";

	private static final int POOL_SIZE = 10;

	private Cache<String, Integer> sizeCache;

	/**
	 * Pattern to use for detecting invalid XML characters. Invalid XML
	 * characters will be removed from the stored offline messages.
	 */
	private Pattern pattern = Pattern.compile("&\\#[\\d]+;");

	/**
	 * Returns the instance of <tt>OfflineMessageStore</tt> being used by the
	 * XMPPServer.
	 * 
	 * @return the instance of <tt>OfflineMessageStore</tt> being used by the
	 *         XMPPServer.
	 */
	public static OfflineMessageStore getInstance() {
		return XMPPServer.getInstance().getOfflineMessageStore();
	}

	/**
	 * Pool of SAX Readers. SAXReader is not thread safe so we need to have a
	 * pool of readers.
	 */
	private BlockingQueue<SAXReader> xmlReaders = new LinkedBlockingQueue<SAXReader>(
			POOL_SIZE);

	/**
	 * Constructs a new offline message store.
	 */
	public OfflineMessageStore() {
		super("Offline Message Store");
		sizeCache = CacheFactory.createCache("Offline Message Size");
	}

	/**
	 * Adds a message to this message store. Messages will be stored and made
	 * available for later delivery.
	 * 
	 * @param message
	 *            the message to store.
	 */
	public void addMessage(Message message) {
		if (message == null) {
			return;
		}
		// ignore empty bodied message (typically chat-state notifications).
		if (message.getBody() == null || message.getBody().length() == 0) {
			// allow empty pubsub messages (OF-191)
			if (message.getChildElement("event",
					"http://jabber.org/protocol/pubsub#event") == null) {
				return;
			}
		}
		JID recipient = message.getTo();
		String username = recipient.getNode();
		// If the username is null (such as when an anonymous user), don't
		// store.
		if (username == null
				|| !UserManager.getInstance().isRegisteredUser(recipient)) {
			return;
		} else if (!XMPPServer.getInstance().getServerInfo().getXMPPDomain()
				.equals(recipient.getDomain())) {
			// Do not store messages sent to users of remote servers
			return;
		}

		long messageID = SequenceManager.nextID(JiveConstants.OFFLINE);

		// Get the message in XML format.
		String msgXML = message.getElement().asXML();

		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(INSERT_OFFLINE);
			pstmt.setString(1, username);
			pstmt.setLong(2, messageID);
			pstmt.setString(3, StringUtils.dateToMillis(new java.util.Date()));
			// SimpleDateFormat df = new
			// SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			// pstmt.setString(3, df.format(new Date()).toString());

			pstmt.setInt(4, msgXML.length());
			pstmt.setString(5, msgXML);
			pstmt.executeUpdate();
		}

		catch (Exception e) {
			Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}

		// Update the cached size if it exists.
		if (sizeCache.containsKey(username)) {
			int size = sizeCache.get(username);
			size += msgXML.length();
			sizeCache.put(username, size);
		}
	}

	/**
	 * 保存消息记录
	 * 
	 * @author dml
	 * @param message
	 */
	public void addMessage_toHistory(Message message) {
		if (message == null) {
			return;
		}
		// ignore empty bodied message (typically chat-state notifications).
		if (message.getBody() == null || message.getBody().length() == 0) {
			// allow empty pubsub messages (OF-191)
			if (message.getChildElement("event",
					"http://jabber.org/protocol/pubsub#event") == null) {
				return;
			}
		}
		JID recipient = message.getTo();
		String username = recipient.getNode();
		// If the username is null (such as when an anonymous user), don't
		// store.
		if (username == null
				|| !UserManager.getInstance().isRegisteredUser(recipient)) {
			return;
		} else if (!XMPPServer.getInstance().getServerInfo().getXMPPDomain()
				.equals(recipient.getDomain())) {
			// Do not store messages sent to users of remote servers
			return;
		}

		long messageID = SequenceManager.nextID(JiveConstants.OFFLINE);

		// Get the message in XML format.
		String msgXML = message.getElement().asXML();

		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(INSERT_HISTORY);
			pstmt.setString(1, username);
			pstmt.setLong(2, messageID);
			// pstmt.setString(3, StringUtils.dateToMillis(new
			// java.util.Date()));
			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			pstmt.setString(3, df.format(new Date()).toString());

			pstmt.setInt(4, msgXML.length());
			pstmt.setString(5, msgXML);
			pstmt.executeUpdate();
		}

		catch (Exception e) {
			Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}

		// Update the cached size if it exists.
		if (sizeCache.containsKey(username)) {
			int size = sizeCache.get(username);
			size += msgXML.length();
			sizeCache.put(username, size);
		}
	}

	/**
	 * Returns a Collection of all messages in the store for a user. Messages
	 * may be deleted after being selected from the database depending on the
	 * delete param.
	 * 
	 * @param username
	 *            the username of the user who's messages you'd like to receive.
	 * @param delete
	 *            true if the offline messages should be deleted.
	 * @return An iterator of packets containing all offline messages.
	 */
	public Collection<OfflineMessage> getMessages(String username,
			boolean delete) {
		List<OfflineMessage> messages = new ArrayList<OfflineMessage>();
		SAXReader xmlReader = null;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			// Get a sax reader from the pool
			xmlReader = xmlReaders.take();
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(LOAD_OFFLINE);
			pstmt.setString(1, username);
			rs = pstmt.executeQuery();
			while (rs.next()) {
				String msgXML = rs.getString(1);
				// 解析时间eg.Tue Apr 16 15:32:39 CST 2013
				Date creationDate = new Date(Long.parseLong(rs.getString(2)
						.trim()));
				OfflineMessage message;
				try {
					message = new OfflineMessage(creationDate, xmlReader.read(
							new StringReader(msgXML)).getRootElement());
				} catch (DocumentException e) {
					// Try again after removing invalid XML chars (e.g. )
					Matcher matcher = pattern.matcher(msgXML);
					if (matcher.find()) {
						msgXML = matcher.replaceAll("");
					}
					message = new OfflineMessage(creationDate, xmlReader.read(
							new StringReader(msgXML)).getRootElement());
				}

				// Add a delayed delivery (XEP-0203) element to the message.
				Element delay = message.addChildElement("delay",
						"urn:xmpp:delay");
				delay.addAttribute("from", XMPPServer.getInstance()
						.getServerInfo().getXMPPDomain());
				delay.addAttribute("stamp",
						XMPPDateTimeFormat.format(creationDate));
				// Add a legacy delayed delivery (XEP-0091) element to the
				// message. XEP is obsolete and support should be dropped in
				// future.
				delay = message.addChildElement("x", "jabber:x:delay");
				delay.addAttribute("from", XMPPServer.getInstance()
						.getServerInfo().getXMPPDomain());
				delay.addAttribute("stamp",
						XMPPDateTimeFormat.formatOld(creationDate));
				messages.add(message);
			}
			// Check if the offline messages loaded should be deleted, and that
			// there are
			// messages to delete.
			if (delete && !messages.isEmpty()) {
				PreparedStatement pstmt2 = null;
				try {
					pstmt2 = con.prepareStatement(DELETE_OFFLINE);
					pstmt2.setString(1, username);
					pstmt2.executeUpdate();
					removeUsernameFromSizeCache(username);
				} catch (Exception e) {
					Log.error("Error deleting offline messages of username: "
							+ username, e);
				} finally {
					DbConnectionManager.closeStatement(pstmt2);
				}
			}
		} catch (Exception e) {
			Log.error("Error retrieving offline messages of username: "
					+ username, e);
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
			// Return the sax reader to the pool
			if (xmlReader != null) {
				xmlReaders.add(xmlReader);
			}
		}
		return messages;
	}

	/**
	 * Returns the offline message of the specified user with the given creation
	 * date. The returned message will NOT be deleted from the database.
	 * 
	 * @param username
	 *            the username of the user who's message you'd like to receive.
	 * @param creationDate
	 *            the date when the offline message was stored in the database.
	 * @return the offline message of the specified user with the given creation
	 *         stamp.
	 */
	public OfflineMessage getMessage(String username, Date creationDate) {
		OfflineMessage message = null;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		SAXReader xmlReader = null;
		try {
			// Get a sax reader from the pool
			xmlReader = xmlReaders.take();
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(LOAD_OFFLINE_MESSAGE);
			pstmt.setString(1, username);
			pstmt.setString(2, StringUtils.dateToMillis(creationDate));
			rs = pstmt.executeQuery();
			while (rs.next()) {
				String msgXML = rs.getString(1);
				message = new OfflineMessage(creationDate, xmlReader.read(
						new StringReader(msgXML)).getRootElement());
				// Add a delayed delivery (XEP-0203) element to the message.
				Element delay = message.addChildElement("delay",
						"urn:xmpp:delay");
				delay.addAttribute("from", XMPPServer.getInstance()
						.getServerInfo().getXMPPDomain());
				delay.addAttribute("stamp",
						XMPPDateTimeFormat.format(creationDate));
				// Add a legacy delayed delivery (XEP-0091) element to the
				// message. XEP is obsolete and support should be dropped in
				// future.
				delay = message.addChildElement("x", "jabber:x:delay");
				delay.addAttribute("from", XMPPServer.getInstance()
						.getServerInfo().getXMPPDomain());
				delay.addAttribute("stamp",
						XMPPDateTimeFormat.formatOld(creationDate));
			}
		} catch (Exception e) {
			Log.error("Error retrieving offline messages of username: "
					+ username + " creationDate: " + creationDate, e);
		} finally {
			// Return the sax reader to the pool
			if (xmlReader != null) {
				xmlReaders.add(xmlReader);
			}
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
		return message;
	}

	/**
	 * Deletes all offline messages in the store for a user.
	 * 
	 * @param username
	 *            the username of the user who's messages are going to be
	 *            deleted.
	 */
	public void deleteMessages(String username) {
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(DELETE_OFFLINE);
			pstmt.setString(1, username);
			pstmt.executeUpdate();

			removeUsernameFromSizeCache(username);
		} catch (Exception e) {
			Log.error("Error deleting offline messages of username: "
					+ username, e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}
	}

	private void removeUsernameFromSizeCache(String username) {
		// Update the cached size if it exists.
		if (sizeCache.containsKey(username)) {
			sizeCache.remove(username);
		}
	}

	/**
	 * Deletes the specified offline message in the store for a user. The way to
	 * identify the message to delete is based on the creationDate and username.
	 * 
	 * @param username
	 *            the username of the user who's message is going to be deleted.
	 * @param creationDate
	 *            the date when the offline message was stored in the database.
	 */
	public void deleteMessage(String username, Date creationDate) {
		Connection con = null;
		PreparedStatement pstmt = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(DELETE_OFFLINE_MESSAGE);
			pstmt.setString(1, username);
			pstmt.setString(2, StringUtils.dateToMillis(creationDate));
			pstmt.executeUpdate();

			// Force a refresh for next call to getSize(username),
			// it's easier than loading the message to be deleted just
			// to update the cache.
			removeUsernameFromSizeCache(username);
		} catch (Exception e) {
			Log.error("Error deleting offline messages of username: "
					+ username + " creationDate: " + creationDate, e);
		} finally {
			DbConnectionManager.closeConnection(pstmt, con);
		}
	}

	/**
	 * Returns the approximate size (in bytes) of the XML messages stored for a
	 * particular user.
	 * 
	 * @param username
	 *            the username of the user.
	 * @return the approximate size of stored messages (in bytes).
	 */
	public int getSize(String username) {
		// See if the size is cached.
		if (sizeCache.containsKey(username)) {
			return sizeCache.get(username);
		}
		int size = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(SELECT_SIZE_OFFLINE);
			pstmt.setString(1, username);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				size = rs.getInt(1);
			}
			// Add the value to cache.
			sizeCache.put(username, size);
		} catch (Exception e) {
			Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
		return size;
	}

	/**
	 * Returns the approximate size (in bytes) of the XML messages stored for
	 * all users.
	 * 
	 * @return the approximate size of all stored messages (in bytes).
	 */
	public int getSize() {
		int size = 0;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			con = DbConnectionManager.getConnection();
			pstmt = con.prepareStatement(SELECT_SIZE_ALL_OFFLINE);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				size = rs.getInt(1);
			}
		} catch (Exception e) {
			Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
		} finally {
			DbConnectionManager.closeConnection(rs, pstmt, con);
		}
		return size;
	}

	public void userCreated(User user, Map params) {
		// Do nothing
	}

	public void userDeleting(User user, Map params) {
		// Delete all offline messages of the user
		deleteMessages(user.getUsername());
	}

	public void userModified(User user, Map params) {
		// Do nothing
	}

	@Override
	public void start() throws IllegalStateException {
		super.start();
		// Initialize the pool of sax readers
		for (int i = 0; i < POOL_SIZE; i++) {
			SAXReader xmlReader = new SAXReader();
			xmlReader.setEncoding("UTF-8");
			xmlReaders.add(xmlReader);
		}
		// Add this module as a user event listener so we can delete
		// all offline messages when a user is deleted
		UserEventDispatcher.addListener(this);
	}

	@Override
	public void stop() {
		super.stop();
		// Clean up the pool of sax readers
		xmlReaders.clear();
		// Remove this module as a user event listener
		UserEventDispatcher.removeListener(this);
	}
}
完

p.s.部署服务端过程http://blog.csdn.net/duanml61/article/details/8803600
本站文章内容,部分来自于互联网,若侵犯了您的权益,请致邮件chuanghui423#sohu.com(请将#换为@)联系,我们会尽快核实后删除。
Copyright © 2006-2023 DBMNG.COM All Rights Reserved. Powered by DEVSOARTECH            豫ICP备11002312号-2

豫公网安备 41010502002439号