/* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2008 "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 General Public License as published by
* the Free Software Foundation, 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev: 1199 $
* Last modified by $Author: kobit $
* $Date: 2008-10-30 11:24:49 +0000 (Thu, 30 Oct 2008) $
*/
package tigase.xmpp.impl.roster;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Queue;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.logging.Logger;
import tigase.server.Packet;
import tigase.xml.Element;
import tigase.xml.XMLUtils;
import tigase.xmpp.NotAuthorizedException;
import tigase.xmpp.StanzaType;
import tigase.xmpp.XMPPResourceConnection;
import tigase.util.JIDUtils;
import tigase.db.UserRepository;
import tigase.db.TigaseDBException;
/**
* Describe class RosterAbstract here.
*
*
* Created: Thu Sep 4 18:09:52 2008
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev: 1199 $
*/
public abstract class RosterAbstract {
/**
* Private logger for class instancess.
*/
private static Logger log = Logger.getLogger("tigase.xmpp.impl.RosterAbstract");
public static final String ROSTER_XMLNS = "jabber:iq:roster";
public static final String ROSTER = "roster";
public static final String GROUPS = "groups";
public static final String GROUP = "group";
public static final String NAME = "name";
public static final String SUBSCRIPTION = "subscription";
public enum PresenceType {
out_initial,
out_subscribe,
out_unsubscribe,
out_subscribed,
out_unsubscribed,
in_initial,
in_subscribe,
in_unsubscribe,
in_subscribed,
in_unsubscribed,
in_probe,
error;
}
public enum SubscriptionType {
none("none", null),
none_pending_out("none", "subscribe"),
none_pending_in("none", null),
none_pending_out_in("none", "subscribe"),
to("to", null),
to_pending_in("to", null),
from("from", null),
from_pending_out("from", "subscribe"),
both("both", null),
remove("remove", null);
private Map<String, String> attrs = new LinkedHashMap<String, String>();
private SubscriptionType(String subscr, String ask) {
attrs.put("subscription", subscr);
if (ask != null) {
attrs.put("ask", ask);
} // end of if (ask != null)
}
public Map<String, String> getSubscriptionAttr() {
return attrs;
}
}
public static final EnumSet<SubscriptionType> SUB_NONE =
EnumSet.of(
SubscriptionType.none,
SubscriptionType.none_pending_out,
SubscriptionType.none_pending_in,
SubscriptionType.none_pending_out_in);
public static final EnumSet<SubscriptionType> SUB_TO =
EnumSet.of(
SubscriptionType.to,
SubscriptionType.to_pending_in);
public static final EnumSet<SubscriptionType> SUB_FROM =
EnumSet.of(
SubscriptionType.from,
SubscriptionType.from_pending_out);
public static final EnumSet<SubscriptionType> SUB_BOTH =
EnumSet.of(SubscriptionType.both);
public static final EnumSet<SubscriptionType> TO_SUBSCRIBED =
EnumSet.of(
SubscriptionType.to,
SubscriptionType.to_pending_in,
SubscriptionType.both);
public static final EnumSet<SubscriptionType> FROM_SUBSCRIBED =
EnumSet.of(
SubscriptionType.from,
SubscriptionType.from_pending_out,
SubscriptionType.both);
public static final EnumSet<StanzaType> INITIAL_PRESENCES =
EnumSet.of(StanzaType.available, StanzaType.unavailable);
public static final EnumSet<SubscriptionType> PENDING_IN =
EnumSet.of(
SubscriptionType.none_pending_in,
SubscriptionType.none_pending_out_in,
SubscriptionType.to_pending_in);
// Below StateTransition enum is implementation of all below tables
// coming from RFC-3921
// Table 1: Recommended handling of outbound "subscribed" stanzas
// +----------------------------------------------------------------+
// | EXISTING STATE | ROUTE? | NEW STATE |
// +----------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | no | no state change |
// | "None + Pending In" | yes | "From" |
// | "None + Pending Out/In" | yes | "From + Pending Out" |
// | "To" | no | no state change |
// | "To + Pending In" | yes | "Both" |
// | "From" | no | no state change |
// | "From + Pending Out" | no | no state change |
// | "Both" | no | no state change |
// +----------------------------------------------------------------+
// Table 2: Recommended handling of outbound "unsubscribed" stanzas
// +----------------------------------------------------------------+
// | EXISTING STATE | ROUTE? | NEW STATE |
// +----------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | no | no state change |
// | "None + Pending In" | yes | "None" |
// | "None + Pending Out/In" | yes | "None + Pending Out" |
// | "To" | no | no state change |
// | "To + Pending In" | yes | "To" |
// | "From" | yes | "None" |
// | "From + Pending Out" | yes | "None + Pending Out" |
// | "Both" | yes | "To" |
// +----------------------------------------------------------------+
// Table 3: Recommended handling of inbound "subscribe" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | DELIVER? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | yes | "None + Pending In" |
// | "None + Pending Out" | yes | "None + Pending Out/In" |
// | "None + Pending In" | no | no state change |
// | "None + Pending Out/In" | no | no state change |
// | "To" | yes | "To + Pending In" |
// | "To + Pending In" | no | no state change |
// | "From" | no * | no state change |
// | "From + Pending Out" | no * | no state change |
// | "Both" | no * | no state change |
// +------------------------------------------------------------------+
// Table 4: Recommended handling of inbound "unsubscribe" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | DELIVER? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | no | no state change |
// | "None + Pending In" | yes * | "None" |
// | "None + Pending Out/In" | yes * | "None + Pending Out" |
// | "To" | no | no state change |
// | "To + Pending In" | yes * | "To" |
// | "From" | yes * | "None" |
// | "From + Pending Out" | yes * | "None + Pending Out" |
// | "Both" | yes * | "To" |
// +------------------------------------------------------------------+
// Table 5: Recommended handling of inbound "subscribed" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | DELIVER? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | yes | "To" |
// | "None + Pending In" | no | no state change |
// | "None + Pending Out/In" | yes | "To + Pending In" |
// | "To" | no | no state change |
// | "To + Pending In" | no | no state change |
// | "From" | no | no state change |
// | "From + Pending Out" | yes | "Both" |
// | "Both" | no | no state change |
// +------------------------------------------------------------------+
// Table 6: Recommended handling of inbound "unsubscribed" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | DELIVER? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | yes | "None" |
// | "None + Pending In" | no | no state change |
// | "None + Pending Out/In" | yes | "None + Pending In" |
// | "To" | yes | "None" |
// | "To + Pending In" | yes | "None + Pending In" |
// | "From" | no | no state change |
// | "From + Pending Out" | yes | "From" |
// | "Both" | yes | "From" |
// +------------------------------------------------------------------+
// There are 2 tables missing I think in RFC-3921:
// Table 7: Recommended handling of outbound "subscribe" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | ROUTE? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | yes | "None + Pending Out" |
// | "None + Pending Out" | no | no state change |
// | "None + Pending In" | yes | "None + Pending Out/In" |
// | "None + Pending Out/In" | no | no state change |
// | "To" | no | no state change |
// | "To + Pending In" | no | no state change |
// | "From" | yes | "From + Pending Out" |
// | "From + Pending Out" | no | no state change |
// | "Both" | no | no state change |
// +------------------------------------------------------------------+
// Table 8: Recommended handling of outbound "unsubscribe" stanzas
// +------------------------------------------------------------------+
// | EXISTING STATE | ROUTE? | NEW STATE |
// +------------------------------------------------------------------+
// | "None" | no | no state change |
// | "None + Pending Out" | yes | "None" |
// | "None + Pending In" | no | no state change |
// | "None + Pending Out/In" | yes | "None + Pending In" |
// | "To" | yes | "None" |
// | "To + Pending In" | yes | "None + Pending In" |
// | "From" | no | no state change |
// | "From + Pending Out" | yes | "From" |
// | "Both" | yes | "From" |
// +------------------------------------------------------------------+
public enum StateTransition {
none(
SubscriptionType.none, // Table 1.
SubscriptionType.none, // Table 2.
SubscriptionType.none_pending_in, // Table 3.
SubscriptionType.none, // Table 4.
SubscriptionType.none, // Table 5.
SubscriptionType.none, // Table 6.
SubscriptionType.none_pending_out, // Table 7.
SubscriptionType.none // Table 8.
),
none_pending_out(
SubscriptionType.none_pending_out, // Table 1.
SubscriptionType.none_pending_out, // Table 2.
SubscriptionType.none_pending_out_in, // Table 3.
SubscriptionType.none_pending_out, // Table 4.
SubscriptionType.to, // Table 5.
SubscriptionType.none, // Table 6.
SubscriptionType.none_pending_out, // Table 7.
SubscriptionType.none // Table 8.
),
none_pending_in(
SubscriptionType.from, // Table 1.
SubscriptionType.none, // Table 2.
SubscriptionType.none_pending_in, // Table 3.
SubscriptionType.none, // Table 4.
SubscriptionType.none_pending_in, // Table 5.
SubscriptionType.none_pending_in, // Table 6.
SubscriptionType.none_pending_out_in, // Table 7.
SubscriptionType.none_pending_in // Table 8.
),
none_pending_out_in(
SubscriptionType.from_pending_out, // Table 1.
SubscriptionType.none_pending_out, // Table 2.
SubscriptionType.none_pending_out_in, // Table 3.
SubscriptionType.none_pending_out, // Table 4.
SubscriptionType.to_pending_in, // Table 5.
SubscriptionType.none_pending_in, // Table 6.
SubscriptionType.none_pending_out_in, // Table 7.
SubscriptionType.none_pending_in // Table 8.
),
to(
SubscriptionType.to, // Table 1.
SubscriptionType.to, // Table 2.
SubscriptionType.to_pending_in, // Table 3.
SubscriptionType.to, // Table 4.
SubscriptionType.to, // Table 5.
SubscriptionType.none, // Table 6.
SubscriptionType.to, // Table 7.
SubscriptionType.none // Table 8.
),
to_pending_in(
SubscriptionType.both, // Table 1.
SubscriptionType.to, // Table 2.
SubscriptionType.to_pending_in, // Table 3.
SubscriptionType.to, // Table 4.
SubscriptionType.to_pending_in, // Table 5.
SubscriptionType.none_pending_in, // Table 6.
SubscriptionType.to_pending_in, // Table 7.
SubscriptionType.none_pending_in // Table 8.
),
from(
SubscriptionType.from, // Table 1.
SubscriptionType.none, // Table 2.
SubscriptionType.from, // Table 3.
SubscriptionType.none, // Table 4.
SubscriptionType.from, // Table 5.
SubscriptionType.from, // Table 6.
SubscriptionType.from_pending_out, // Table 7.
SubscriptionType.from // Table 8.
),
from_pending_out(
SubscriptionType.from_pending_out, // Table 1.
SubscriptionType.none_pending_out, // Table 2.
SubscriptionType.from_pending_out, // Table 3.
SubscriptionType.none_pending_out, // Table 4.
SubscriptionType.both, // Table 5.
SubscriptionType.from, // Table 6.
SubscriptionType.from_pending_out, // Table 7.
SubscriptionType.from // Table 8.
),
both(
SubscriptionType.both, // Table 1.
SubscriptionType.to, // Table 2.
SubscriptionType.both, // Table 3.
SubscriptionType.to, // Table 4.
SubscriptionType.both, // Table 5.
SubscriptionType.from, // Table 6.
SubscriptionType.both, // Table 7.
SubscriptionType.from // Table 8.
);
private EnumMap<PresenceType, SubscriptionType> stateTransition =
new EnumMap<PresenceType, SubscriptionType>(PresenceType.class);
private StateTransition(
SubscriptionType out_subscribed, SubscriptionType out_unsubscribed
, SubscriptionType in_subscribe, SubscriptionType in_unsubscribe
, SubscriptionType in_subscribed, SubscriptionType in_unsubscribed
, SubscriptionType out_subscribe, SubscriptionType out_unsubscribe
) {
stateTransition.put(PresenceType.out_subscribed, out_subscribed);
stateTransition.put(PresenceType.out_unsubscribed, out_unsubscribed);
stateTransition.put(PresenceType.in_subscribe, in_subscribe);
stateTransition.put(PresenceType.in_unsubscribe, in_unsubscribe);
stateTransition.put(PresenceType.in_subscribed, in_subscribed);
stateTransition.put(PresenceType.in_unsubscribed, in_unsubscribed);
stateTransition.put(PresenceType.out_subscribe, out_subscribe);
stateTransition.put(PresenceType.out_unsubscribe, out_unsubscribe);
}
public SubscriptionType getStateTransition(PresenceType pres_type) {
SubscriptionType res = stateTransition.get(pres_type);
log.finest("this="+this.toString()
+", pres_type="+pres_type
+", res="+res);
return res;
}
}
private static EnumMap<SubscriptionType, StateTransition>
subsToStateMap =
new EnumMap<SubscriptionType, StateTransition>(SubscriptionType.class);
static {
subsToStateMap.put(SubscriptionType.none, StateTransition.none);
subsToStateMap.put(SubscriptionType.none_pending_out,
StateTransition.none_pending_out);
subsToStateMap.put(SubscriptionType.none_pending_in,
StateTransition.none_pending_in);
subsToStateMap.put(SubscriptionType.none_pending_out_in,
StateTransition.none_pending_out_in);
subsToStateMap.put(SubscriptionType.to, StateTransition.to);
subsToStateMap.put(SubscriptionType.to_pending_in,
StateTransition.to_pending_in);
subsToStateMap.put(SubscriptionType.from, StateTransition.from);
subsToStateMap.put(SubscriptionType.from_pending_out,
StateTransition.from_pending_out);
subsToStateMap.put(SubscriptionType.both, StateTransition.both);
}
public SubscriptionType getStateTransition(
final SubscriptionType subscription, final PresenceType presence) {
return subsToStateMap.get(subscription).getStateTransition(presence);
}
public PresenceType getPresenceType(
final XMPPResourceConnection session, final Packet packet)
throws NotAuthorizedException {
String to = packet.getElemTo();
if (to != null) {
to = JIDUtils.getNodeID(to);
} // end of if (to != null)
StanzaType type = packet.getType();
if (type == null) {
type = StanzaType.available;
} else {
if (type == StanzaType.error) {
return PresenceType.error;
}
}
if (to == null || !to.equals(session.getUserId())) {
if (INITIAL_PRESENCES.contains(type)) {
return PresenceType.out_initial;
}
if (type == StanzaType.subscribe) {
return PresenceType.out_subscribe;
} // end of if (type == StanzaType.subscribe)
if (type == StanzaType.unsubscribe) {
return PresenceType.out_unsubscribe;
} // end of if (type == StanzaType.unsubscribe)
if (type == StanzaType.subscribed) {
return PresenceType.out_subscribed;
} // end of if (type == StanzaType.subscribed)
if (type == StanzaType.unsubscribed) {
return PresenceType.out_unsubscribed;
} // end of if (type == StanzaType.unsubscribed)
// StanzaType.probe is invalid here....
} // end of if (to == null || to.equals(session.getUserId()))
if (to != null && to.equals(session.getUserId())) {
if (INITIAL_PRESENCES.contains(type)) {
return PresenceType.in_initial;
}
if (type == StanzaType.subscribe) {
return PresenceType.in_subscribe;
} // end of if (type == StanzaType.subscribe)
if (type == StanzaType.unsubscribe) {
return PresenceType.in_unsubscribe;
} // end of if (type == StanzaType.unsubscribe)
if (type == StanzaType.subscribed) {
return PresenceType.in_subscribed;
} // end of if (type == StanzaType.subscribed)
if (type == StanzaType.unsubscribed) {
return PresenceType.in_unsubscribed;
} // end of if (type == StanzaType.unsubscribed)
if (type == StanzaType.probe) {
return PresenceType.in_probe;
} // end of if (type == StanzaType.probe)
} // end of if (to != null && !to.equals(session.getUserId()))
return null;
}
public boolean isPendingIn(final XMPPResourceConnection session,
final String jid) throws NotAuthorizedException, TigaseDBException {
SubscriptionType subscr = getBuddySubscription(session, jid);
return PENDING_IN.contains(subscr);
}
public boolean isSubscribedTo(final XMPPResourceConnection session,
final String jid) throws NotAuthorizedException, TigaseDBException {
SubscriptionType subscr = getBuddySubscription(session, jid);
return TO_SUBSCRIBED.contains(subscr);
}
public boolean isSubscribedFrom(final XMPPResourceConnection session,
final String jid) throws NotAuthorizedException, TigaseDBException {
SubscriptionType subscr = getBuddySubscription(session, jid);
return FROM_SUBSCRIBED.contains(subscr);
}
public boolean isSubscribedFrom(SubscriptionType subscr) {
return FROM_SUBSCRIBED.contains(subscr);
}
public String groupNode(final String buddy) {
return ROSTER + "/" + JIDUtils.getNodeID(buddy);
}
public String[] getBuddies(final XMPPResourceConnection session,
final EnumSet<SubscriptionType> subscrs)
throws NotAuthorizedException, TigaseDBException {
final String[] allBuddies = getBuddies(session);
if (allBuddies == null) {
return null;
} // end of if (allBuddies == null)
ArrayList<String> list = new ArrayList<String>();
for (String buddy : allBuddies) {
final SubscriptionType subs = getBuddySubscription(session, buddy);
if (subscrs.contains(subs)) {
list.add(buddy);
} // end of if (subscrs.contains(subs))
} // end of for ()
return list.toArray(new String[list.size()]);
}
public boolean updateBuddySubscription(
final XMPPResourceConnection session, final PresenceType presence,
final String jid) throws NotAuthorizedException, TigaseDBException {
SubscriptionType current_subscription = getBuddySubscription(session, jid);
log.finest("current_subscription="+current_subscription
+" for jid="+jid);
if (current_subscription == null) {
addBuddy(session, jid, null, null);
current_subscription = SubscriptionType.none;
}
final SubscriptionType new_subscription =
getStateTransition(current_subscription, presence);
log.finest("new_subscription="+new_subscription
+" for presence="+presence);
if (current_subscription == SubscriptionType.none_pending_in
&& presence == PresenceType.out_unsubscribed) {
removeBuddy(session, jid);
return false;
}
if (current_subscription != new_subscription) {
setBuddySubscription(session, new_subscription, jid);
return true;
} else {
return false;
}
// if ((SUB_NONE.contains(current_subscription)
// && SUB_NONE.contains(new_subscription))
// || (SUB_TO.contains(current_subscription)
// && SUB_TO.contains(new_subscription))
// || (SUB_FROM.contains(current_subscription)
// && SUB_FROM.contains(new_subscription))
// || (SUB_BOTH.contains(current_subscription)
// && SUB_BOTH.contains(new_subscription))) {
// return false;
// } else {
// setBuddySubscription(session, new_subscription, jid);
// return true;
// }
}
public Element getBuddyItem(final XMPPResourceConnection session,
final String buddy)
throws NotAuthorizedException, TigaseDBException {
SubscriptionType subscr = getBuddySubscription(session, buddy);
if (subscr == null) {
subscr = SubscriptionType.none;
setBuddySubscription(session, subscr, buddy);
} // end of if
Element item = new Element("item");
item.setAttribute("jid", JIDUtils.getNodeID(buddy));
item.addAttributes(subscr.getSubscriptionAttr());
String name = getBuddyName(session, buddy);
if (name != null) {
item.setAttribute("name", XMLUtils.escape(name));
}
String[] groups = getBuddyGroups(session, buddy);
if (groups != null) {
for (String gr : groups) {
Element group = new Element("group");
group.setCData(XMLUtils.escape(gr));
item.addChild(group);
} // end of for ()
} // end of if-else
return item;
}
public void updateBuddyChange(final XMPPResourceConnection session,
final Queue<Packet> results, final Element item)
throws NotAuthorizedException, TigaseDBException {
Element update = new Element("iq");
update.setAttribute("type", StanzaType.set.toString());
Element query = new Element("query");
query.setXMLNS(ROSTER_XMLNS);
query.addChild(item);
update.addChild(query);
for (XMPPResourceConnection conn: session.getActiveSessions()) {
Element conn_update = update.clone();
conn_update.setAttribute("to", conn.getJID());
conn_update.setAttribute("id", "rst"+session.nextStanzaId());
Packet pack_update = new Packet(conn_update);
pack_update.setTo(conn.getConnectionId());
pack_update.setFrom(session.getJID());
results.offer(pack_update);
} // end of for (XMPPResourceConnection conn: sessions)
}
public abstract String[] getBuddies(final XMPPResourceConnection session)
throws NotAuthorizedException, TigaseDBException;
public abstract String getBuddyName(final XMPPResourceConnection session,
final String buddy)
throws NotAuthorizedException, TigaseDBException;
public abstract void setBuddyName(final XMPPResourceConnection session,
final String buddy, final String name)
throws NotAuthorizedException, TigaseDBException;
public abstract void setBuddySubscription(final XMPPResourceConnection session,
final SubscriptionType subscription, final String buddy)
throws NotAuthorizedException, TigaseDBException;
public abstract SubscriptionType getBuddySubscription(
final XMPPResourceConnection session,
final String buddy) throws NotAuthorizedException, TigaseDBException;
public abstract boolean removeBuddy(final XMPPResourceConnection session,
final String jid) throws NotAuthorizedException, TigaseDBException;
public abstract void addBuddy(XMPPResourceConnection session,
String jid, String name, String[] groups)
throws NotAuthorizedException, TigaseDBException;
public abstract String[] getBuddyGroups(final XMPPResourceConnection session,
final String buddy)
throws NotAuthorizedException, TigaseDBException;
// public abstract void setBuddyGroups(XMPPResourceConnection session,
// String buddy, String[] groups)
// throws NotAuthorizedException, TigaseDBException;
public void init(UserRepository repo)
throws TigaseDBException, TigaseDBException {}
}