Commit 39c56232 authored by Mike McCarthy's avatar Mike McCarthy Committed by michaelcmccarthy

OF-418 - Introduce a pluggable RosterItemProvider. Committing both code and...

OF-418 - Introduce a pluggable RosterItemProvider. Committing both code and documentation patch around pluggable roster item providers

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13336 b35dd754-fafc-0310-a699-88a17e54d16e
parent 25edb24c
...@@ -50,6 +50,10 @@ messaging (IM) services using the XMPP protocol. ...@@ -50,6 +50,10 @@ messaging (IM) services using the XMPP protocol.
<a href="db-integration-guide.html">Custom Database Integration Guide</a> - <a href="db-integration-guide.html">Custom Database Integration Guide</a> -
A guide to integrating Openfire authentication, user, and group data with a custom database. A guide to integrating Openfire authentication, user, and group data with a custom database.
</li> </li>
<li>
<a href="pluggable-roster-support-guide.html">Pluggable Roster Support Guide</a> -
A guide to integrating Openfire rosters with an alternate store.
</li>
</ul> </ul>
<p><b>Developer Documentation:</b></p> <p><b>Developer Documentation:</b></p>
......
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Openfire: Pluggable Roster Support Guide</title>
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="pageContainer">
<a name="top"></a>
<div id="pageHeader">
<div id="logo"></div>
<h1>Pluggable Roster Support Guide</h1>
</div>
<div class="navigation">
<a href="index.html">&laquo; Back to documentation index</a>
</div>
<div id="pageBody">
<h2>Introduction</h2>
<p>
This document provides instructions on how to configure Openfire's roster support to work
with alternate sources of roster data rather than the provided database tables.
</p>
<p>
Consider the scenario where Openfire is integrated as the chat server solution for an existing
'social' application. In this application a user is linked to other users via a relationship
of sorts, and the system of record for the relationship data is NOT the Openfire system.
Using an XMPP server such as Openfire the applications benefit from the real-time nature of
XMPP rosters and other subscription based features such as PEP, but with the caveat that
the existing relationship information has to be duplicated in Openfire and could potentially
become out of sync.
</p>
<p>
With the introduction of pluggable roster providers, Openfire can be instructed to retrieve and
modify roster data that lives in alternative locations to the standard database tables. The options
are limitless as to what this could be, e.g. an alternative database table(s), a web service, a NoSQL
database etc.
</p>
<h2>Background</h2>
<p>
This integration requires some Java knowledge in order to implement a custom roster provider
for Openfire. The skill needed will vary depending on what you are trying to achieve.
The extension approach is similar to the custom AuthProvider approach on which it is based.
</p>
<h2>The RosterItemProvider extension point</h2>
<p>
As of Openfire 3.7.2, the class RosterItemProvider has changed from being a concrete class to an interface.
The default implementation of this provider is the DefaultRosterItemProvider, which as the name suggests is the
version of this provider Openfire will use if not overriden. The DefaultRosterItemProvider uses the Openfire
database to retrieve roster information.
</p>
<p>
The steps to get Openfire using a custom RosterItemProvider are described below.
<ol>
<li>Write a class that implements RosterItemProvider, providing your own business logic.</li>
<li>Make the class available in a jar and make this available to Openfire by placing it in the lib directory.
There are numerous ways to package a jar with this class inside it, popular build systems such as Gradle and Maven
can make your life easier.</li>
<li>Set the property 'provider.roster.className' to be the full name of your class, e.g. 'com.foo.bar.MyRosterItemProvider'.
</li>
<li>Restart Openfire. Your custom class should now be handling all the roster based operations.</li>
</ol>
</p>
<h2>Frequently Asked Questions</h2>
<p>
<ol>
<li><b>Do I have to compile my custom class into the Openfire jar?</b> No, the class only needs to be visible on the Openfire classpath.</li>
<li><b>How do I ensure my custom class is visible on the Openfire classpath?</b> Just place your new custom library in the Openfire lib directory,
this will ensure it is automatically available at startup.</li>
<li><b>Will I have a degradation in performance using a custom RosterItemProvider?</b> It completely depends on your implementation. As with any
Openfire customisation or plugin, badly written code has the potential to cause Openfire to perform slower. Use performance testing tools such
as Tsung to ensure issues haven't been introduced.</li>
<li><b>How can I have my custom class connect to another DB/Web service/NoSQL store etc?</b>
This is outside of the scope of this documentation and is your choice as a developer. If you are looking to externalize properties like
connection details, the Openfire properties mechanism and the JiveGlobals class are good places to start investigating.</li>
<li><b>Can I have multiple RosterItemProviders, in a similar vein to the HybridAuthProvider?</b> No, this feature is currently not implemented.
Get involved if you feel a need to have it!</li>
</ol>
</p>
</div>
</div>
</body>
</html>
/**
* $RCSfile$
* $Revision: 1751 $
* $Date: 2005-08-07 20:08:47 -0300 (Sun, 07 Aug 2005) $
*
* Copyright (C) 2005-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.roster;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Defines the provider methods required for creating, reading, updating and deleting roster
* items.<p>
*
* Rosters are another user resource accessed via the user or chatbot's long ID. A user/chatbot
* may have zero or more roster items and each roster item may have zero or more groups. Each
* roster item is additionaly keyed on a XMPP jid. In most cases, the entire roster will be read
* in from memory and manipulated or sent to the user. However some operations will need to retrive
* specific roster items rather than the entire roster.
*
* @author Iain Shigeoka
*/
public class DefaultRosterItemProvider implements RosterItemProvider {
private static final Logger Log = LoggerFactory.getLogger(DefaultRosterItemProvider.class);
private static final String CREATE_ROSTER_ITEM =
"INSERT INTO ofRoster (username, rosterID, jid, sub, ask, recv, nick) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
private static final String UPDATE_ROSTER_ITEM =
"UPDATE ofRoster SET sub=?, ask=?, recv=?, nick=? WHERE rosterID=?";
private static final String DELETE_ROSTER_ITEM_GROUPS =
"DELETE FROM ofRosterGroups WHERE rosterID=?";
private static final String CREATE_ROSTER_ITEM_GROUPS =
"INSERT INTO ofRosterGroups (rosterID, rank, groupName) VALUES (?, ?, ?)";
private static final String DELETE_ROSTER_ITEM =
"DELETE FROM ofRoster WHERE rosterID=?";
private static final String LOAD_USERNAMES =
"SELECT DISTINCT username from ofRoster WHERE jid=?";
private static final String COUNT_ROSTER_ITEMS =
"SELECT COUNT(rosterID) FROM ofRoster WHERE username=?";
private static final String LOAD_ROSTER =
"SELECT jid, rosterID, sub, ask, recv, nick FROM ofRoster WHERE username=?";
private static final String LOAD_ROSTER_ITEM_GROUPS =
"SELECT rosterID,groupName FROM ofRosterGroups";
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#createItem(java.lang.String, org.jivesoftware.openfire.roster.RosterItem)
*/
@Override
public RosterItem createItem(String username, RosterItem item)
throws UserAlreadyExistsException
{
Connection con = null;
PreparedStatement pstmt = null;
try {
long rosterID = SequenceManager.nextID(JiveConstants.ROSTER);
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM);
pstmt.setString(1, username);
pstmt.setLong(2, rosterID);
pstmt.setString(3, item.getJid().toBareJID());
pstmt.setInt(4, item.getSubStatus().getValue());
pstmt.setInt(5, item.getAskStatus().getValue());
pstmt.setInt(6, item.getRecvStatus().getValue());
pstmt.setString(7, item.getNickname());
pstmt.executeUpdate();
item.setID(rosterID);
insertGroups(rosterID, item.getGroups().iterator(), con);
}
catch (SQLException e) {
Log.warn("Error trying to insert a new row in ofRoster", e);
throw new UserAlreadyExistsException(item.getJid().toBareJID());
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return item;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#updateItem(java.lang.String, org.jivesoftware.openfire.roster.RosterItem)
*/
@Override
public void updateItem(String username, RosterItem item) throws UserNotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
long rosterID = item.getID();
try {
con = DbConnectionManager.getConnection();
// Update existing roster item
pstmt = con.prepareStatement(UPDATE_ROSTER_ITEM);
pstmt.setInt(1, item.getSubStatus().getValue());
pstmt.setInt(2, item.getAskStatus().getValue());
pstmt.setInt(3, item.getRecvStatus().getValue());
pstmt.setString(4, item.getNickname());
pstmt.setLong(5, rosterID);
pstmt.executeUpdate();
// Close now the statement (do not wait to be GC'ed)
DbConnectionManager.fastcloseStmt(pstmt);
// Delete old group list
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
pstmt.executeUpdate();
insertGroups(rosterID, item.getGroups().iterator(), con);
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#deleteItem(java.lang.String, long)
*/
@Override
public void deleteItem(String username, long rosterItemID) {
// Only try to remove the user if they exist in the roster already:
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
// Remove roster groups
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterItemID);
pstmt.executeUpdate();
// Close now the statement (do not wait to be GC'ed)
DbConnectionManager.fastcloseStmt(pstmt);
// Remove roster
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM);
pstmt.setLong(1, rosterItemID);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#getUsernames(java.lang.String)
*/
@Override
public Iterator<String> getUsernames(String jid) {
List<String> answer = new ArrayList<String>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_USERNAMES);
pstmt.setString(1, jid);
rs = pstmt.executeQuery();
while (rs.next()) {
answer.add(rs.getString(1));
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return answer.iterator();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#getItemCount(java.lang.String)
*/
@Override
public int getItemCount(String username) {
int count = 0;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(COUNT_ROSTER_ITEMS);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
if (rs.next()) {
count = rs.getInt(1);
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return count;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.RosterItemProvider#getItems(java.lang.String)
*/
@Override
public Iterator<RosterItem> getItems(String username) {
LinkedList<RosterItem> itemList = new LinkedList<RosterItem>();
Map<Long, RosterItem> itemsByID = new HashMap<Long, RosterItem>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// Load all the contacts in the roster
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_ROSTER);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
while (rs.next()) {
// Create a new RosterItem (ie. user contact) from the stored information
RosterItem item = new RosterItem(rs.getLong(2),
new JID(rs.getString(1)),
RosterItem.SubType.getTypeFromInt(rs.getInt(3)),
RosterItem.AskType.getTypeFromInt(rs.getInt(4)),
RosterItem.RecvType.getTypeFromInt(rs.getInt(5)),
rs.getString(6),
null);
// Add the loaded RosterItem (ie. user contact) to the result
itemList.add(item);
itemsByID.put(item.getID(), item);
}
// Close the statement and result set
DbConnectionManager.fastcloseStmt(rs, pstmt);
// Set null to pstmt to be sure that it's not closed twice. It seems that
// Sybase driver is raising an error when trying to close an already closed statement.
// it2000 comment: TODO interesting, that's the only place with the sybase fix
// it2000 comment: one should move this in closeStatement()
pstmt = null;
// Load the groups for the loaded contact
if (!itemList.isEmpty()) {
StringBuilder sb = new StringBuilder(100);
sb.append(LOAD_ROSTER_ITEM_GROUPS).append(" WHERE rosterID IN (");
for (RosterItem item : itemList) {
sb.append(item.getID()).append(",");
}
sb.setLength(sb.length()-1);
sb.append(") ORDER BY rosterID, rank");
pstmt = con.prepareStatement(sb.toString());
rs = pstmt.executeQuery();
while (rs.next()) {
itemsByID.get(rs.getLong(1)).getGroups().add(rs.getString(2));
}
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return itemList.iterator();
}
/**
* Insert the groups into the given roster item.
*
* @param rosterID the roster ID of the item the groups belong to
* @param iter an iterator over the group names to insert
* @param con the database connection to use for the operation.
* @throws SQLException if an SQL exception occurs.
*/
private void insertGroups(long rosterID, Iterator<String> iter, Connection con) throws SQLException
{
PreparedStatement pstmt = null;
try {
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
for (int i = 0; iter.hasNext(); i++) {
pstmt.setInt(2, i);
String groupName = iter.next();
pstmt.setString(3, groupName);
try {
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(e.getMessage(), e);
}
}
}
finally {
DbConnectionManager.closeStatement(pstmt);
}
}
}
...@@ -129,7 +129,7 @@ public class Roster implements Cacheable, Externalizable { ...@@ -129,7 +129,7 @@ public class Roster implements Cacheable, Externalizable {
//Collection<Group> userGroups = GroupManager.getInstance().getGroups(getUserJID()); //Collection<Group> userGroups = GroupManager.getInstance().getGroups(getUserJID());
// Add RosterItems that belong to the personal roster // Add RosterItems that belong to the personal roster
rosterItemProvider = RosterItemProvider.getInstance(); rosterItemProvider = RosterManager.getRosterItemProvider();
Iterator<RosterItem> items = rosterItemProvider.getItems(username); Iterator<RosterItem> items = rosterItemProvider.getItems(username);
while (items.hasNext()) { while (items.hasNext()) {
RosterItem item = items.next(); RosterItem item = items.next();
...@@ -1152,7 +1152,7 @@ public class Roster implements Cacheable, Externalizable { ...@@ -1152,7 +1152,7 @@ public class Roster implements Cacheable, Externalizable {
presenceManager = XMPPServer.getInstance().getPresenceManager(); presenceManager = XMPPServer.getInstance().getPresenceManager();
rosterManager = XMPPServer.getInstance().getRosterManager(); rosterManager = XMPPServer.getInstance().getRosterManager();
sessionManager = SessionManager.getInstance(); sessionManager = SessionManager.getInstance();
rosterItemProvider = RosterItemProvider.getInstance(); rosterItemProvider = RosterManager.getRosterItemProvider();
routingTable = XMPPServer.getInstance().getRoutingTable(); routingTable = XMPPServer.getInstance().getRoutingTable();
username = ExternalizableUtil.getInstance().readSafeUTF(in); username = ExternalizableUtil.getInstance().readSafeUTF(in);
......
/**
* $RCSfile$
* $Revision: 1751 $
* $Date: 2005-08-07 20:08:47 -0300 (Sun, 07 Aug 2005) $
*
* Copyright (C) 2005-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.roster; package org.jivesoftware.openfire.roster;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.database.SequenceManager;
import org.jivesoftware.openfire.user.UserAlreadyExistsException; import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveConstants;
import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* Defines the provider methods required for creating, reading, updating and deleting roster
* items.<p>
*
* Rosters are another user resource accessed via the user or chatbot's long ID. A user/chatbot
* may have zero or more roster items and each roster item may have zero or more groups. Each
* roster item is additionaly keyed on a XMPP jid. In most cases, the entire roster will be read
* in from memory and manipulated or sent to the user. However some operations will need to retrive
* specific roster items rather than the entire roster.
*
* @author Iain Shigeoka
*/
public class RosterItemProvider {
private static final Logger Log = LoggerFactory.getLogger(RosterItemProvider.class);
private static final String CREATE_ROSTER_ITEM =
"INSERT INTO ofRoster (username, rosterID, jid, sub, ask, recv, nick) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)";
private static final String UPDATE_ROSTER_ITEM =
"UPDATE ofRoster SET sub=?, ask=?, recv=?, nick=? WHERE rosterID=?";
private static final String DELETE_ROSTER_ITEM_GROUPS =
"DELETE FROM ofRosterGroups WHERE rosterID=?";
private static final String CREATE_ROSTER_ITEM_GROUPS =
"INSERT INTO ofRosterGroups (rosterID, rank, groupName) VALUES (?, ?, ?)";
private static final String DELETE_ROSTER_ITEM =
"DELETE FROM ofRoster WHERE rosterID=?";
private static final String LOAD_USERNAMES =
"SELECT DISTINCT username from ofRoster WHERE jid=?";
private static final String COUNT_ROSTER_ITEMS =
"SELECT COUNT(rosterID) FROM ofRoster WHERE username=?";
private static final String LOAD_ROSTER =
"SELECT jid, rosterID, sub, ask, recv, nick FROM ofRoster WHERE username=?";
private static final String LOAD_ROSTER_ITEM_GROUPS =
"SELECT rosterID,groupName FROM ofRosterGroups";
public interface RosterItemProvider {
private static RosterItemProvider instance = new RosterItemProvider();
public static RosterItemProvider getInstance() {
return instance;
}
/** /**
* Creates a new roster item for the given user (optional operation).<p> * Creates a new roster item for the given user (optional operation).<p>
...@@ -99,36 +22,8 @@ public class RosterItemProvider { ...@@ -99,36 +22,8 @@ public class RosterItemProvider {
* @return the new roster item. * @return the new roster item.
* @throws UserAlreadyExistsException if a roster item with the username already exists. * @throws UserAlreadyExistsException if a roster item with the username already exists.
*/ */
public RosterItem createItem(String username, RosterItem item) public abstract RosterItem createItem(String username, RosterItem item)
throws UserAlreadyExistsException throws UserAlreadyExistsException;
{
Connection con = null;
PreparedStatement pstmt = null;
try {
long rosterID = SequenceManager.nextID(JiveConstants.ROSTER);
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM);
pstmt.setString(1, username);
pstmt.setLong(2, rosterID);
pstmt.setString(3, item.getJid().toBareJID());
pstmt.setInt(4, item.getSubStatus().getValue());
pstmt.setInt(5, item.getAskStatus().getValue());
pstmt.setInt(6, item.getRecvStatus().getValue());
pstmt.setString(7, item.getNickname());
pstmt.executeUpdate();
item.setID(rosterID);
insertGroups(rosterID, item.getGroups().iterator(), con);
}
catch (SQLException e) {
Log.warn("Error trying to insert a new row in ofRoster", e);
throw new UserAlreadyExistsException(item.getJid().toBareJID());
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
return item;
}
/** /**
* Update the roster item in storage with the information contained in the given item * Update the roster item in storage with the information contained in the given item
...@@ -140,37 +35,8 @@ public class RosterItemProvider { ...@@ -140,37 +35,8 @@ public class RosterItemProvider {
* @param item The roster item to update * @param item The roster item to update
* @throws UserNotFoundException If no entry could be found to update * @throws UserNotFoundException If no entry could be found to update
*/ */
public void updateItem(String username, RosterItem item) throws UserNotFoundException { public abstract void updateItem(String username, RosterItem item)
Connection con = null; throws UserNotFoundException;
PreparedStatement pstmt = null;
long rosterID = item.getID();
try {
con = DbConnectionManager.getConnection();
// Update existing roster item
pstmt = con.prepareStatement(UPDATE_ROSTER_ITEM);
pstmt.setInt(1, item.getSubStatus().getValue());
pstmt.setInt(2, item.getAskStatus().getValue());
pstmt.setInt(3, item.getRecvStatus().getValue());
pstmt.setString(4, item.getNickname());
pstmt.setLong(5, rosterID);
pstmt.executeUpdate();
// Close now the statement (do not wait to be GC'ed)
DbConnectionManager.fastcloseStmt(pstmt);
// Delete old group list
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
pstmt.executeUpdate();
insertGroups(rosterID, item.getGroups().iterator(), con);
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/** /**
* Delete the roster item with the given itemJID for the user (optional operation).<p> * Delete the roster item with the given itemJID for the user (optional operation).<p>
...@@ -181,33 +47,7 @@ public class RosterItemProvider { ...@@ -181,33 +47,7 @@ public class RosterItemProvider {
* @param username the long ID of the user/chatbot that owns the roster item * @param username the long ID of the user/chatbot that owns the roster item
* @param rosterItemID The roster item to delete * @param rosterItemID The roster item to delete
*/ */
public void deleteItem(String username, long rosterItemID) { public abstract void deleteItem(String username, long rosterItemID);
// Only try to remove the user if they exist in the roster already:
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
// Remove roster groups
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterItemID);
pstmt.executeUpdate();
// Close now the statement (do not wait to be GC'ed)
DbConnectionManager.fastcloseStmt(pstmt);
// Remove roster
pstmt = con.prepareStatement(DELETE_ROSTER_ITEM);
pstmt.setLong(1, rosterItemID);
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/** /**
* Returns an iterator on the usernames whose roster includes the specified JID. * Returns an iterator on the usernames whose roster includes the specified JID.
...@@ -215,28 +55,7 @@ public class RosterItemProvider { ...@@ -215,28 +55,7 @@ public class RosterItemProvider {
* @param jid the jid that the rosters should include. * @param jid the jid that the rosters should include.
* @return an iterator on the usernames whose roster includes the specified JID. * @return an iterator on the usernames whose roster includes the specified JID.
*/ */
public Iterator<String> getUsernames(String jid) { public abstract Iterator<String> getUsernames(String jid);
List<String> answer = new ArrayList<String>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_USERNAMES);
pstmt.setString(1, jid);
rs = pstmt.executeQuery();
while (rs.next()) {
answer.add(rs.getString(1));
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return answer.iterator();
}
/** /**
* Obtain a count of the number of roster items available for the given user. * Obtain a count of the number of roster items available for the given user.
...@@ -244,28 +63,7 @@ public class RosterItemProvider { ...@@ -244,28 +63,7 @@ public class RosterItemProvider {
* @param username the username of the user/chatbot that owns the roster items * @param username the username of the user/chatbot that owns the roster items
* @return The number of roster items available for the user * @return The number of roster items available for the user
*/ */
public int getItemCount(String username) { public abstract int getItemCount(String username);
int count = 0;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(COUNT_ROSTER_ITEMS);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
if (rs.next()) {
count = rs.getInt(1);
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return count;
}
/** /**
* Retrieve an iterator of RosterItems for the given user.<p> * Retrieve an iterator of RosterItems for the given user.<p>
...@@ -277,94 +75,6 @@ public class RosterItemProvider { ...@@ -277,94 +75,6 @@ public class RosterItemProvider {
* @param username the username of the user/chatbot that owns the roster items * @param username the username of the user/chatbot that owns the roster items
* @return An iterator of all RosterItems owned by the user * @return An iterator of all RosterItems owned by the user
*/ */
public Iterator<RosterItem> getItems(String username) { public abstract Iterator<RosterItem> getItems(String username);
LinkedList<RosterItem> itemList = new LinkedList<RosterItem>();
Map<Long, RosterItem> itemsByID = new HashMap<Long, RosterItem>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// Load all the contacts in the roster
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_ROSTER);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
while (rs.next()) {
// Create a new RosterItem (ie. user contact) from the stored information
RosterItem item = new RosterItem(rs.getLong(2),
new JID(rs.getString(1)),
RosterItem.SubType.getTypeFromInt(rs.getInt(3)),
RosterItem.AskType.getTypeFromInt(rs.getInt(4)),
RosterItem.RecvType.getTypeFromInt(rs.getInt(5)),
rs.getString(6),
null);
// Add the loaded RosterItem (ie. user contact) to the result
itemList.add(item);
itemsByID.put(item.getID(), item);
}
// Close the statement and result set
DbConnectionManager.fastcloseStmt(rs, pstmt);
// Set null to pstmt to be sure that it's not closed twice. It seems that
// Sybase driver is raising an error when trying to close an already closed statement.
// it2000 comment: TODO interesting, that's the only place with the sybase fix
// it2000 comment: one should move this in closeStatement()
pstmt = null;
// Load the groups for the loaded contact
if (!itemList.isEmpty()) {
StringBuilder sb = new StringBuilder(100);
sb.append(LOAD_ROSTER_ITEM_GROUPS).append(" WHERE rosterID IN (");
for (RosterItem item : itemList) {
sb.append(item.getID()).append(",");
}
sb.setLength(sb.length()-1);
sb.append(") ORDER BY rosterID, rank");
pstmt = con.prepareStatement(sb.toString());
rs = pstmt.executeQuery();
while (rs.next()) {
itemsByID.get(rs.getLong(1)).getGroups().add(rs.getString(2));
}
}
}
catch (SQLException e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return itemList.iterator();
}
/**
* Insert the groups into the given roster item.
*
* @param rosterID the roster ID of the item the groups belong to
* @param iter an iterator over the group names to insert
* @param con the database connection to use for the operation.
* @throws SQLException if an SQL exception occurs.
*/
private void insertGroups(long rosterID, Iterator<String> iter, Connection con) throws SQLException
{
PreparedStatement pstmt = null;
try {
pstmt = con.prepareStatement(CREATE_ROSTER_ITEM_GROUPS);
pstmt.setLong(1, rosterID);
for (int i = 0; iter.hasNext(); i++) {
pstmt.setInt(2, i);
String groupName = iter.next();
pstmt.setString(3, groupName);
try {
pstmt.executeUpdate();
}
catch (SQLException e) {
Log.error("Unable to insert group with name '" + groupName
+ "' (rank: '" + i + "')into roster with id '"
+ rosterID + "'.", e);
}
}
}
finally {
DbConnectionManager.closeStatement(pstmt);
}
}
} }
\ No newline at end of file
...@@ -34,9 +34,14 @@ import org.jivesoftware.openfire.group.GroupNotFoundException; ...@@ -34,9 +34,14 @@ import org.jivesoftware.openfire.group.GroupNotFoundException;
import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.cache.Cache; import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory; import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
...@@ -55,9 +60,12 @@ import java.util.*; ...@@ -55,9 +60,12 @@ import java.util.*;
*/ */
public class RosterManager extends BasicModule implements GroupEventListener, UserEventListener { public class RosterManager extends BasicModule implements GroupEventListener, UserEventListener {
private static final Logger Log = LoggerFactory.getLogger(RosterManager.class);
private Cache<String, Roster> rosterCache = null; private Cache<String, Roster> rosterCache = null;
private XMPPServer server; private XMPPServer server;
private RoutingTable routingTable; private RoutingTable routingTable;
private RosterItemProvider provider;
/** /**
* Returns true if the roster service is enabled. When disabled it is not possible to * Returns true if the roster service is enabled. When disabled it is not possible to
...@@ -72,6 +80,20 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us ...@@ -72,6 +80,20 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
public RosterManager() { public RosterManager() {
super("Roster Manager"); super("Roster Manager");
rosterCache = CacheFactory.createCache("Roster"); rosterCache = CacheFactory.createCache("Roster");
initProvider();
PropertyEventDispatcher.addListener(new PropertyEventListener() {
public void propertySet(String property, Map params) {
if (property.equals("provider.roster.className")) {
initProvider();
}
}
public void propertyDeleted(String property, Map params) {}
public void xmlPropertySet(String property, Map params) {}
public void xmlPropertyDeleted(String property, Map params) {}
});
} }
/** /**
...@@ -127,8 +149,7 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us ...@@ -127,8 +149,7 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
rosterCache.remove(username); rosterCache.remove(username);
// Get the rosters that have a reference to the deleted user // Get the rosters that have a reference to the deleted user
RosterItemProvider rosteItemProvider = RosterItemProvider.getInstance(); Iterator<String> usernames = provider.getUsernames(user.toBareJID());
Iterator<String> usernames = rosteItemProvider.getUsernames(user.toBareJID());
while (usernames.hasNext()) { while (usernames.hasNext()) {
username = usernames.next(); username = usernames.next();
try { try {
...@@ -963,4 +984,27 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us ...@@ -963,4 +984,27 @@ public class RosterManager extends BasicModule implements GroupEventListener, Us
// Remove this module as a listener of group events // Remove this module as a listener of group events
GroupEventDispatcher.removeListener(this); GroupEventDispatcher.removeListener(this);
} }
public static RosterItemProvider getRosterItemProvider() {
return XMPPServer.getInstance().getRosterManager().provider;
}
private void initProvider() {
JiveGlobals.migrateProperty("provider.roster.className");
String className = JiveGlobals.getProperty("provider.roster.className",
"org.jivesoftware.openfire.roster.DefaultRosterItemProvider");
if (provider == null || !className.equals(provider.getClass().getName())) {
try {
Class c = ClassUtils.forName(className);
provider = (RosterItemProvider) c.newInstance();
}
catch (Exception e) {
Log.error("Error loading roster provider: " + className, e);
provider = new DefaultRosterItemProvider();
}
}
}
} }
...@@ -26,6 +26,7 @@ import org.jivesoftware.openfire.container.Plugin; ...@@ -26,6 +26,7 @@ import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.roster.RosterItem; import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItemProvider; import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User; import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException; import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager; import org.jivesoftware.openfire.user.UserManager;
...@@ -210,7 +211,7 @@ public class ImportExportPlugin implements Plugin { ...@@ -210,7 +211,7 @@ public class ImportExportPlugin implements Plugin {
List<String> invalidUsers = new ArrayList<String>(); List<String> invalidUsers = new ArrayList<String>();
UserManager userManager = UserManager.getInstance(); UserManager userManager = UserManager.getInstance();
RosterItemProvider rosterItemProvider = RosterItemProvider.getInstance(); RosterItemProvider rosterItemProvider = RosterManager.getRosterItemProvider();
Element users = document.getRootElement(); Element users = document.getRootElement();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment