001: /**
002: * Copyright (C) 2004-2007 Jive Software. All rights reserved.
003: *
004: * This software is published under the terms of the GNU Public License (GPL),
005: * a copy of which is included in this distribution.
006: */package org.xmpp.packet;
007:
008: import org.dom4j.Element;
009: import org.dom4j.Namespace;
010: import org.dom4j.QName;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.Collections;
015: import java.util.Iterator;
016:
017: /**
018: * Roster packet. The roster is a list of JIDs (typically other users) that
019: * the user wishes to track the presence of. Each roster item is keyed by
020: * JID and contains a nickname (optional), subscription type, and list of
021: * groups (optional).
022: *
023: * @author Matt Tucker
024: */
025: public class Roster extends IQ {
026:
027: /**
028: * Constructs a new Roster with an automatically generated ID and a type
029: * of {@link IQ.Type#get}.
030: */
031: public Roster() {
032: super ();
033: element.addElement("query", "jabber:iq:roster");
034: }
035:
036: /**
037: * Constructs a new Roster using the specified type. A packet ID will
038: * be automatically generated.
039: *
040: * @param type the IQ type.
041: */
042: public Roster(Type type) {
043: super (type);
044: element.addElement("query", "jabber:iq:roster");
045: }
046:
047: /**
048: * Constructs a new Roster using the specified type and ID.
049: *
050: * @param type the IQ type.
051: * @param ID the packet ID of the IQ.
052: */
053: public Roster(Type type, String ID) {
054: super (type, ID);
055: element.addElement("query", "jabber:iq:roster");
056: }
057:
058: /**
059: * Constructs a new Roster that is a copy of an existing Roster.
060: *
061: * @param roster the roster packet.
062: * @see #createCopy()
063: */
064: private Roster(Roster roster) {
065: Element elementCopy = roster.element.createCopy();
066: docFactory.createDocument().add(elementCopy);
067: this .element = elementCopy;
068: }
069:
070: /**
071: * Constructs a new Roster using an existing Element. This is useful
072: * for parsing incoming roster Elements into Roster objects.
073: *
074: * @param element the Roster Element.
075: */
076: public Roster(Element element) {
077: super (element);
078: }
079:
080: /**
081: * Adds a new item to the roster. The name and groups are set to <tt>null</tt>
082: * If the roster packet already contains an item using the same JID, the
083: * information in the existing item will be overwritten with the new information.<p>
084: *
085: * The XMPP specification recommends that if the roster item is associated with another
086: * instant messaging user (human), that the JID be in bare form (e.g. user@domain).
087: * Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
088: *
089: * @param jid the JID.
090: * @param subscription the subscription type.
091: * @return the newly created item.
092: */
093: public Item addItem(String jid, Subscription subscription) {
094: if (getType() == IQ.Type.get || getType() == IQ.Type.error) {
095: throw new IllegalStateException(
096: "IQ type must be 'result' or 'set'");
097: }
098: if (jid == null) {
099: throw new NullPointerException("JID cannot be null");
100: }
101: return addItem(new JID(jid), null, null, subscription, null);
102: }
103:
104: /**
105: * Adds a new item to the roster. The name and groups are set to <tt>null</tt>
106: * If the roster packet already contains an item using the same JID, the
107: * information in the existing item will be overwritten with the new information.<p>
108: *
109: * The XMPP specification recommends that if the roster item is associated with another
110: * instant messaging user (human), that the JID be in bare form (e.g. user@domain).
111: * Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
112: *
113: * @param jid the JID.
114: * @param subscription the subscription type.
115: * @return the newly created item.
116: */
117: public Item addItem(JID jid, Subscription subscription) {
118: if (getType() != IQ.Type.result && getType() != IQ.Type.set) {
119: throw new IllegalStateException(
120: "IQ type must be 'result' or 'set'");
121: }
122: if (jid == null) {
123: throw new NullPointerException("JID cannot be null");
124: }
125: return addItem(jid, null, null, subscription, null);
126: }
127:
128: /**
129: * Adds a new item to the roster. If the roster packet already contains an item
130: * using the same JID, the information in the existing item will be overwritten
131: * with the new information.<p>
132: *
133: * The XMPP specification recommends that if the roster item is associated with another
134: * instant messaging user (human), that the JID be in bare form (e.g. user@domain).
135: * Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
136: *
137: * @param jid the JID.
138: * @param name the nickname.
139: * @param ask the ask type.
140: * @param subscription the subscription type.
141: * @param groups a Collection of groups.
142: * @return the newly created item.
143: */
144: public Item addItem(JID jid, String name, Ask ask,
145: Subscription subscription, Collection<String> groups) {
146: if (jid == null) {
147: throw new NullPointerException("JID cannot be null");
148: }
149: if (subscription == null) {
150: throw new NullPointerException(
151: "Subscription cannot be null");
152: }
153: Element query = element.element(new QName("query", Namespace
154: .get("jabber:iq:roster")));
155: if (query == null) {
156: query = element.addElement("query", "jabber:iq:roster");
157: }
158: Element item = null;
159: for (Iterator i = query.elementIterator("item"); i.hasNext();) {
160: Element el = (Element) i.next();
161: if (el.attributeValue("jid").equals(jid.toString())) {
162: item = el;
163: }
164: }
165: if (item == null) {
166: item = query.addElement("item");
167: }
168: item.addAttribute("jid", jid.toBareJID());
169: item.addAttribute("name", name);
170: if (ask != null) {
171: item.addAttribute("ask", ask.toString());
172: }
173: item.addAttribute("subscription", subscription.toString());
174: // Erase existing groups in case the item previously existed.
175: for (Iterator i = item.elementIterator("group"); i.hasNext();) {
176: item.remove((Element) i.next());
177: }
178: // Add in groups.
179: if (groups != null) {
180: for (String group : groups) {
181: item.addElement("group").setText(group);
182: }
183: }
184: return new Item(jid, name, ask, subscription, groups);
185: }
186:
187: /**
188: * Removes an item from this roster.
189: *
190: * @param jid the JID of the item to remove.
191: */
192: public void removeItem(JID jid) {
193: Element query = element.element(new QName("query", Namespace
194: .get("jabber:iq:roster")));
195: if (query != null) {
196: for (Iterator i = query.elementIterator("item"); i
197: .hasNext();) {
198: Element item = (Element) i.next();
199: if (item.attributeValue("jid").equals(jid.toString())) {
200: query.remove(item);
201: return;
202: }
203: }
204: }
205: }
206:
207: /**
208: * Returns an unmodifiable copy of the {@link Item Items} in the roster packet.
209: *
210: * @return an unmodifable copy of the {@link Item Items} in the roster packet.
211: */
212: public Collection<Item> getItems() {
213: Collection<Item> items = new ArrayList<Item>();
214: Element query = element.element(new QName("query", Namespace
215: .get("jabber:iq:roster")));
216: if (query != null) {
217: for (Iterator i = query.elementIterator("item"); i
218: .hasNext();) {
219: Element item = (Element) i.next();
220: String jid = item.attributeValue("jid");
221: String name = item.attributeValue("name");
222: String ask = item.attributeValue("ask");
223: String subscription = item
224: .attributeValue("subscription");
225: Collection<String> groups = new ArrayList<String>();
226: for (Iterator j = item.elementIterator("group"); j
227: .hasNext();) {
228: Element group = (Element) j.next();
229: groups.add(group.getText().trim());
230: }
231: Ask askStatus = ask == null ? null : Ask.valueOf(ask);
232: Subscription subStatus = subscription == null ? null
233: : Subscription.valueOf(subscription);
234: items.add(new Item(new JID(jid), name, askStatus,
235: subStatus, groups));
236: }
237: }
238: return Collections.unmodifiableCollection(items);
239: }
240:
241: /**
242: * Returns a deep copy of this Roster.
243: *
244: * @return a deep copy of this Roster.
245: */
246: public Roster createCopy() {
247: return new Roster(this );
248: }
249:
250: /**
251: * Item in a roster, which represents an individual contact. Each contact
252: * has a JID, an optional nickname, a subscription type, and can belong to
253: * one ore more groups.
254: */
255: public static class Item {
256:
257: private JID jid;
258: private String name;
259: private Ask ask;
260: private Subscription subscription;
261: private Collection<String> groups;
262:
263: /**
264: * Constructs a new roster item.
265: *
266: * @param jid the JID.
267: * @param name the nickname.
268: * @param ask the ask state.
269: * @param subscription the subscription state.
270: * @param groups the item groups.
271: */
272: private Item(JID jid, String name, Ask ask,
273: Subscription subscription, Collection<String> groups) {
274: this .jid = jid;
275: this .name = name;
276: this .ask = ask;
277: this .subscription = subscription;
278: this .groups = groups;
279: }
280:
281: /**
282: * Returns the JID associated with this item. The JID is the "key" in the
283: * list of items that make up a roster. There can only be a single item per
284: * JID in a roster.
285: *
286: * @return the JID associated with this item.
287: */
288: public JID getJID() {
289: return jid;
290: }
291:
292: /**
293: * Returns the nickname associated with this item. If no nickname exists,
294: * <tt>null</tt> is returned.
295: *
296: * @return the nickname, or <tt>null</tt> if it doesn't exist.
297: */
298: public String getName() {
299: return name;
300: }
301:
302: /**
303: * Returns the ask state of this item.
304: *
305: * @return the ask state of this item.
306: */
307: public Ask getAsk() {
308: return ask;
309: }
310:
311: /**
312: * Returns the subscription state of this item.
313: *
314: * @return the subscription state of this item.
315: */
316: public Subscription getSubscription() {
317: return subscription;
318: }
319:
320: /**
321: * Returns a Collection of the groups defined in this item. If
322: * no groups are defined, an empty Collection is returned.
323: *
324: * @return the groups in this item.
325: */
326: public Collection<String> getGroups() {
327: if (groups == null) {
328: return Collections.emptyList();
329: }
330: return groups;
331: }
332:
333: public String toString() {
334: StringBuffer buf = new StringBuffer();
335: buf.append("<item ");
336: buf.append("jid=\"").append(jid).append("\"");
337: if (name != null) {
338: buf.append(" name=\"").append(name).append("\"");
339: }
340: buf.append(" subscrption=\"").append(subscription).append(
341: "\"");
342: if (groups == null || groups.isEmpty()) {
343: buf.append("/>");
344: } else {
345: buf.append(">\n");
346: for (String group : groups) {
347: buf.append(" <group>").append(group).append(
348: "</group>\n");
349: }
350: buf.append("</item>");
351: }
352: return buf.toString();
353: }
354: }
355:
356: /**
357: * Type-safe enumeration for the roster subscription type. Valid subcription types:
358: *
359: * <ul>
360: * <li>{@link #none Roster.Subscription.none} -- the user does not have a
361: * subscription to the contact's presence information, and the contact
362: * does not have a subscription to the user's presence information.
363: * <li>{@link #to Roster.Subscription.to} -- the user has a subscription to
364: * the contact's presence information, but the contact does not have a
365: * subscription to the user's presence information.
366: * <li>{@link #from Roster.Subscription.from} -- the contact has a subscription
367: * to the user's presence information, but the user does not have a
368: * subscription to the contact's presence information.
369: * <li>{@link #both Roster.Subscription.both} -- both the user and the contact
370: * have subscriptions to each other's presence information.
371: * <li>{@link #remove Roster.Subscription.remove} -- the user is removing a
372: * contact from his or her roster.
373: * </ul>
374: */
375: public enum Subscription {
376:
377: /**
378: * The user does not have a subscription to the contact's presence information,
379: * and the contact does not have a subscription to the user's presence information.
380: */
381: none,
382:
383: /**
384: * The user has a subscription to the contact's presence information, but the
385: * contact does not have a subscription to the user's presence information.
386: */
387: to,
388:
389: /**
390: * The contact has a subscription to the user's presence information, but the
391: * user does not have a subscription to the contact's presence information.
392: */
393: from,
394:
395: /**
396: * Both the user and the contact have subscriptions to each other's presence
397: * information.
398: */
399: both,
400:
401: /**
402: * The user is removing a contact from his or her roster. The user's server will
403: * 1) automatically cancel any existing presence subscription between the user and the
404: * contact, 2) remove the roster item from the user's roster and inform all of the user's
405: * available resources that have requested the roster of the roster item removal, 3) inform
406: * the resource that initiated the removal of success and 4) send unavailable presence from
407: * all of the user's available resources to the contact.
408: */
409: remove;
410: }
411:
412: /**
413: * Type-safe enumeration for the roster ask type. Valid ask types:
414: *
415: * <ul>
416: * <li>{@link #subscribe Roster.Ask.subscribe} -- the roster item has been asked
417: * for permission to subscribe to their presence but no response has been received.
418: * <li>{@link #unsubscribe Roster.Ask.unsubscribe} -- the roster owner has asked
419: * to the roster item to unsubscribe from it's presence but has not received
420: * confirmation.
421: * </ul>
422: */
423: public enum Ask {
424:
425: /**
426: * The roster item has been asked for permission to subscribe to their presence
427: * but no response has been received.
428: */
429: subscribe,
430:
431: /**
432: * The roster owner has asked to the roster item to unsubscribe from it's
433: * presence but has not received confirmation.
434: */
435: unsubscribe;
436: }
437: }
|