/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.xmpp.impl.roster;
//~--- non-JDK imports --------------------------------------------------------
import tigase.db.TigaseDBException;
import tigase.xml.DomBuilderHandler;
import tigase.xml.Element;
import tigase.xml.SimpleParser;
import tigase.xml.SingletonFactory;
import tigase.xmpp.BareJID;
import tigase.xmpp.JID;
import tigase.xmpp.NotAuthorizedException;
import tigase.xmpp.XMPPResourceConnection;
//~--- JDK imports ------------------------------------------------------------
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
//~--- classes ----------------------------------------------------------------
/**
* Describe class RosterFlat here.
*
*
* Created: Tue Feb 21 18:05:53 2006
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class RosterFlat extends RosterAbstract {
/**
* Private logger for class instances.
*/
private static final Logger log = Logger.getLogger(RosterFlat.class.getName());
private static final SimpleParser parser = SingletonFactory.getParserInstance();
private static int maxRosterSize = new Long(Runtime.getRuntime().maxMemory() / 250000L)
.intValue();
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param relem
* @param roster
*
* @return
*/
public static boolean addBuddy(RosterElement relem, Map<BareJID, RosterElement> roster) {
if (roster.size() < maxRosterSize) {
roster.put(relem.getJid().getBareJID(), relem);
return true;
}
return false;
}
public RosterElement addTempBuddy(JID buddy, XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElementInstance(buddy, null, null, session);
relem.setPersistent(false);
addBuddy(relem, getUserRoster(session));
return relem;
}
/**
* Method description
*
*
* @param roster_str
* @param roster
* @param session
*
* @return
*/
public static boolean parseRosterUtil(String roster_str,
Map<BareJID, RosterElement> roster, XMPPResourceConnection session) {
boolean result = false;
DomBuilderHandler domHandler = new DomBuilderHandler();
parser.parse(domHandler, roster_str.toCharArray(), 0, roster_str.length());
Queue<Element> elems = domHandler.getParsedElements();
if ((elems != null) && (elems.size() > 0)) {
for (Element elem : elems) {
try {
RosterElement relem = new RosterElement(elem, session);
result |= relem.isModified();
if (!addBuddy(relem, roster)) {
break;
}
} catch (Exception e) {
log.log(Level.WARNING, "Can't load roster element: {0}", elem);
}
}
}
return result;
}
/**
* Method description
*
*
* @param session
* @param buddy
* @param name
* @param groups
* @param otherData
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public void addBuddy(XMPPResourceConnection session, JID buddy, String name,
String[] groups, String otherData) throws NotAuthorizedException, TigaseDBException {
// String buddy = JIDUtils.getNodeID(jid);
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
Map<BareJID, RosterElement> roster = getUserRoster(session);
relem = getRosterElementInstance(buddy, name, groups, session);
relem.setOtherData(otherData);
if (addBuddy(relem, roster)) {
saveUserRoster(session);
} else {
throw new TigaseDBException("Too many elements in the user roster.");
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Added buddy to roster: {0}", buddy);
}
} else {
if ((name != null) && !name.isEmpty()) {
relem.setName(name);
}
// Hm, as one user reported this make it impossible to remove the user
// from
// all groups. Let's comments it out for now to see how it works.
// Probably added this some time ago , before RosterFlat to prevent NPE.
// if ((groups != null) && (groups.length > 0)) {
relem.setGroups(groups);
// }
saveUserRoster(session);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Updated buddy in roster: {0}", buddy);
}
}
}
/**
* Method description
*
*
* @param session
* @param buddy
* @param groups
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public boolean
addBuddyGroup(XMPPResourceConnection session, JID buddy, String[] groups)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem != null) {
relem.addGroups(groups);
// Intentionally not saving the roster here.
// At the moment it is only used to combine dynamic roster with the
// static roster in case a contact exist in both but in a different
// group.
return true;
} else {
return false;
}
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public boolean containsBuddy(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
return relem != null && relem.isPersistent();
}
// ~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param session
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
// public String[] getBuddies(XMPPResourceConnection session,
// boolean onlineOnly)
public
JID[] getBuddies(XMPPResourceConnection session) throws NotAuthorizedException,
TigaseDBException {
Map<BareJID, RosterElement> roster = getUserRoster(session);
if (roster.size() == 0) {
return null;
}
JID[] result = new JID[roster.size()];
int idx = 0;
for (RosterElement rosterElement : roster.values()) {
result[idx++] = rosterElement.getJid();
}
// TODO: this sorting should be optional as it may impact performance
Arrays.sort(result, new RosterElemComparator(roster));
return result;
// if (onlineOnly) {
// ArrayList<String> online = new ArrayList<String>();
// for (Map.Entry<String, RosterElement> rosterEl : roster.entrySet()) {
// if (rosterEl.getValue().isOnline()) {
// online.add(rosterEl.getKey());
// }
// }
// return online.toArray(new String[online.size()]);
// } else {
// }
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public String[] getBuddyGroups(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
return null;
} else {
return relem.getGroups();
}
}
/**
* Method description
*
*
* @param relem
*
* @return
*/
public Element getBuddyItem(RosterElement relem) {
return relem.getRosterItem();
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public Element getBuddyItem(final XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
return null;
} else {
return getBuddyItem(relem);
}
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public String getBuddyName(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
return null;
} else {
return relem.getName();
}
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public SubscriptionType getBuddySubscription(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
return null;
} else {
return relem.getSubscription();
}
// return SubscriptionType.both;
}
/**
* Method description
*
*
* @param buddy
* @param name
* @param groups
* @param session
*
* @return
*/
public RosterElement getRosterElementInstance(JID buddy, String name, String[] groups,
XMPPResourceConnection session) {
return new RosterElement(buddy, name, groups, session);
}
/**
* Method description
*
*
* @param session
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public List<Element> getRosterItems(XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
LinkedList<Element> items = new LinkedList<Element>();
Map<BareJID, RosterElement> roster = getUserRoster(session);
for (RosterElement relem : roster.values()) {
// Skip temporary roster elements added only for online presence tracking
// from dynamic roster
if (relem.isPersistent()) {
items.add(getBuddyItem(relem));
}
}
return items;
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public boolean isOnline(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
return (relem != null) && relem.isOnline();
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param roster_str
* @param roster
* @param session
*
* @return
*/
public boolean parseRoster(String roster_str, Map<BareJID, RosterElement> roster,
XMPPResourceConnection session) {
return parseRosterUtil(roster_str, roster, session);
}
/**
* Method description
*
*
* @param session
* @param buddy
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public boolean presenceSent(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
return (relem != null) && relem.isPresence_sent();
}
/**
* Method description
*
*
* @param session
* @param jid
*
* @return
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public boolean removeBuddy(XMPPResourceConnection session, JID jid)
throws NotAuthorizedException, TigaseDBException {
Map<BareJID, RosterElement> roster = getUserRoster(session);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Removing roster buddy: {0}, before removal: {1}",
new Object[] { jid, roster });
}
roster.remove(jid.getBareJID());
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Removing roster buddy: {0}, after removal: {1}",
new Object[] { jid, roster });
}
saveUserRoster(session);
return true;
}
// ~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param session
* @param buddy
* @param name
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public void setBuddyName(XMPPResourceConnection session, JID buddy, String name)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem != null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Setting name: ''{0}'' for buddy: {1}", new Object[] {
name, buddy });
}
if ((name != null) && !name.isEmpty()) {
relem.setName(name);
}
saveUserRoster(session);
} else {
log.log(Level.WARNING, "Setting buddy name for non-existen contact: {0}", buddy);
}
}
/**
* Method description
*
*
* @param session
* @param subscription
* @param buddy
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public void setBuddySubscription(XMPPResourceConnection session,
SubscriptionType subscription, JID buddy) throws NotAuthorizedException,
TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem != null) {
relem.setSubscription(subscription);
saveUserRoster(session);
} else {
log.log(Level.WARNING, "Missing roster contact for subscription set: {0}", buddy);
}
}
/**
* Method description
*
*
* @param session
* @param buddy
* @param online
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public void setOnline(XMPPResourceConnection session, JID buddy, boolean online)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
relem = addTempBuddy(buddy, session);
}
relem.setOnline(buddy.getResource(), online);
}
/**
* Method description
*
*
* @param session
* @param buddy
* @param sent
*
* @throws NotAuthorizedException
* @throws TigaseDBException
*/
@Override
public void setPresenceSent(XMPPResourceConnection session, JID buddy, boolean sent)
throws NotAuthorizedException, TigaseDBException {
RosterElement relem = getRosterElement(session, buddy);
if (relem == null) {
relem = addTempBuddy(buddy, session);
}
relem.setPresence_sent(sent);
}
// ~--- get methods ----------------------------------------------------------
public RosterElement getRosterElement(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
Map<BareJID, RosterElement> roster = getUserRoster(session);
return roster.get(buddy.getBareJID());
}
@SuppressWarnings({ "unchecked" })
protected Map<BareJID, RosterElement> getUserRoster(XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
Map<BareJID, RosterElement> roster = null;
// The method can be called from different plugins concurrently.
// If the roster is not yet loaded from DB this causes concurent
// access problems
synchronized (session) {
roster = (Map<BareJID, RosterElement>) session.getCommonSessionData(ROSTER);
if (roster == null) {
roster = loadUserRoster(session);
}
}
return roster;
}
// ~--- methods --------------------------------------------------------------
protected void saveUserRoster(XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
Map<BareJID, RosterElement> roster = getUserRoster(session);
StringBuilder sb = new StringBuilder(5000);
for (RosterElement relem : roster.values()) {
if (relem.isPersistent()) {
sb.append(relem.getRosterElement().toString());
}
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Saving user roster: {0}", sb);
}
session.setData(null, ROSTER, sb.toString());
}
private Map<BareJID, RosterElement> loadUserRoster(XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
// In most times we just read from this data structure
// From time to time there might be some modification, posibly concurrent
// very unlikely by more than one thread
Map<BareJID, RosterElement> roster =
new ConcurrentHashMap<BareJID, RosterElement>(100, 0.25f, 1);
session.putCommonSessionData(ROSTER, roster);
String roster_str = session.getData(null, ROSTER, null);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Loaded user roster: {0}", roster_str);
}
if ((roster_str != null) && !roster_str.isEmpty()) {
updateRosterHash(roster_str, session);
boolean modified = parseRoster(roster_str, roster, session);
if (modified) {
saveUserRoster(session);
}
} else {
// Try to load a roster from the 'old' style roster storage and
// convert it the the flat roster storage
Roster oldRoster = new Roster();
JID[] buddies = oldRoster.getBuddies(session);
if ((buddies != null) && (buddies.length > 0)) {
for (JID buddy : buddies) {
String name = oldRoster.getBuddyName(session, buddy);
SubscriptionType subscr = oldRoster.getBuddySubscription(session, buddy);
String[] groups = oldRoster.getBuddyGroups(session, buddy);
RosterElement relem = getRosterElementInstance(buddy, name, groups, session);
relem.setSubscription(subscr);
if (!addBuddy(relem, roster)) {
break;
}
}
saveUserRoster(session);
}
}
return roster;
}
// @Override
// public void setBuddyOnline(XMPPResourceConnection session, String buddy,
// boolean online)
// throws NotAuthorizedException, TigaseDBException {
// RosterElement relem = getRosterElement(session, buddy);
// if (relem != null) {
// relem.setOnline(online);
// }
// }
//
// @Override
// public boolean isBuddyOnline(XMPPResourceConnection session, String buddy)
// throws NotAuthorizedException, TigaseDBException {
// RosterElement relem = getRosterElement(session, buddy);
// if (relem != null) {
// return relem.isOnline();
// }
// return false;
// }
private class RosterElemComparator implements Comparator<JID> {
private Map<BareJID, RosterElement> roster = null;
private RosterElemComparator(Map<BareJID, RosterElement> roster) {
this.roster = roster;
}
/*
* (non-Javadoc)
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
@Override
public int compare(JID arg0, JID arg1) {
double w0 = roster.get(arg0.getBareJID()).getWeight();
double w1 = roster.get(arg1.getBareJID()).getWeight();
return Double.compare(w0, w1);
}
}
public String getCustomStatus(XMPPResourceConnection session, JID buddy)
throws NotAuthorizedException, TigaseDBException {
RosterElement rel = getRosterElement(session, buddy);
String result = null;
if (rel != null) {
if (rel.getLastSeen() > RosterElement.INITIAL_LAST_SEEN_VAL) {
result =
"Buddy last seen on: " + new Date(rel.getLastSeen()) + ", weight: "
+ rel.getWeight();
} else {
result = "Never seen";
}
}
return result;
}
/*
* (non-Javadoc)
*
* @see tigase.xmpp.impl.roster.RosterAbstract#logout()
*/
@Override
public void logout(XMPPResourceConnection session) {
try {
if (session.isAuthorized() && isModified(session)) {
saveUserRoster(session);
}
} catch (NotAuthorizedException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
} catch (TigaseDBException ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
}
/**
* @param session
* @return
* @throws TigaseDBException
* @throws NotAuthorizedException
*/
public boolean isModified(XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException {
Map<BareJID, RosterElement> roster = getUserRoster(session);
boolean result = false;
if (roster != null) {
for (RosterElement rel : roster.values()) {
result |= rel.isModified();
}
}
return result;
}
} // RosterFlat
// ~ Formatted in Sun Code Convention
// ~ Formatted by Jindent --- http://www.jindent.com