001: /**
002: * $Revision$
003: * $Date$
004: *
005: * Copyright (C) 2006-2007 Jive Software. All rights reserved.
006: *
007: * This software is published under the terms of the GNU Public License (GPL),
008: * a copy of which is included in this distribution.
009: */package org.jivesoftware.openfire.gateway.roster;
010:
011: import org.apache.log4j.Logger;
012: import org.dom4j.DocumentHelper;
013: import org.dom4j.Element;
014: import org.dom4j.QName;
015: import org.jivesoftware.openfire.gateway.avatars.Avatar;
016: import org.jivesoftware.openfire.gateway.type.NameSpace;
017: import org.jivesoftware.openfire.gateway.type.PresenceType;
018: import org.jivesoftware.openfire.roster.RosterItem;
019: import org.jivesoftware.util.JiveGlobals;
020: import org.jivesoftware.util.NotFoundException;
021: import org.xmpp.packet.JID;
022: import org.xmpp.packet.Presence;
023:
024: import java.lang.ref.WeakReference;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.List;
028:
029: /**
030: * Transport Buddy.
031: *
032: * This class is intended to be extended and includes all necessary pieces for
033: * syncing with a roster. It also handles keeping current statuses and such for
034: * easy retrieval and only sends presence changes upon status changes. So that
035: * the base transport can manage this list too sometimes, it is very important
036: * that the specific transport implementation take into account that there may
037: * buddy instances that do not know anything about any 'custom' fields the
038: * transport may be implementing.
039: *
040: * @author Daniel Henninger
041: */
042: public class TransportBuddy {
043:
044: static Logger Log = Logger.getLogger(TransportBuddy.class);
045:
046: /**
047: * Default constructor, nothing set up.
048: */
049: public TransportBuddy() {
050: // Nothing
051: }
052:
053: /**
054: * Creates a TransportBuddy instance.
055: *
056: * @param manager Transport buddy manager we are associated with.
057: * @param contactname The legacy contact name.
058: * @param nickname The legacy nickname (can be null).
059: * @param groups The list of groups the legacy contact is in (can be null).
060: */
061: public TransportBuddy(TransportBuddyManager manager,
062: String contactname, String nickname,
063: Collection<String> groups) {
064: this .managerRef = new WeakReference<TransportBuddyManager>(
065: manager);
066: this .jid = manager.getSession().getTransport().convertIDToJID(
067: contactname);
068: this .contactname = manager.getSession().getTransport()
069: .convertJIDToID(this .jid);
070: if (nickname != null) {
071: this .nickname = nickname;
072: } else {
073: this .nickname = this .contactname;
074: }
075: if (groups != null && !groups.isEmpty()) {
076: this .groups = groups;
077: }
078: if (JiveGlobals.getBooleanProperty("plugin.gateway."
079: + getManager().getSession().getTransport().getType()
080: + ".avatars", true)) {
081: try {
082: this .avatar = new Avatar(this .jid);
083: this .avatarSet = true;
084: } catch (NotFoundException e) {
085: // Ok then, no avatar, no worries.
086: }
087: }
088: }
089:
090: /**
091: * The transport buddy manager we are attached to.
092: */
093: private WeakReference<TransportBuddyManager> managerRef = null;
094:
095: public TransportBuddyManager getManager() {
096: return managerRef.get();
097: }
098:
099: /**
100: * ID, Screenname, name, whatever the contact name is on the legacy system
101: */
102: public String contactname = null;
103:
104: /**
105: * Converted JID for the contact, for caching purposes
106: */
107: public JID jid = null;
108:
109: /**
110: * A nickname associated with this contact, if it exists.
111: */
112: public String nickname = null;
113:
114: /**
115: * A group associated with this contact, if it exists.
116: */
117: public Collection<String> groups = new ArrayList<String>();
118:
119: /**
120: * Specific requested subscription status, if desired.
121: */
122: public RosterItem.SubType subtype = null;
123:
124: /**
125: * Specific requested ask status, if desired.
126: */
127: public RosterItem.AskType asktype = null;
128:
129: /**
130: * Current presence status.
131: */
132: public PresenceType presence = PresenceType.unavailable;
133:
134: /**
135: * Current verbose status.
136: */
137: public String verboseStatus = "";
138:
139: /**
140: * Avatar instance associated with this contact.
141: */
142: public Avatar avatar = null;
143:
144: /**
145: * Has the avatar been set?
146: */
147: public Boolean avatarSet = false;
148:
149: /**
150: * Retrieves the name of the contact.
151: *
152: * @return Name of contact.
153: */
154: public String getName() {
155: return contactname;
156: }
157:
158: /**
159: * Retrieves the JID of the contact.
160: *
161: *
162: * @return JID of contact.
163: */
164: public JID getJID() {
165: return jid;
166: }
167:
168: /**
169: * Sets the name of the contact.
170: *
171: * @param contactname Username of the contact.
172: */
173: public void setName(String contactname) {
174: this .jid = getManager().getSession().getTransport()
175: .convertIDToJID(contactname);
176: this .contactname = getManager().getSession().getTransport()
177: .convertJIDToID(this .jid);
178: }
179:
180: /**
181: * Retrieves the nickname of the contact.
182: *
183: * @return Nickname of contact.
184: */
185: public String getNickname() {
186: return nickname;
187: }
188:
189: /**
190: * Sets the nickname of the contact.
191: *
192: * @param nickname Nickname of contact.
193: */
194: public void setNickname(String nickname) {
195: Boolean changed = false;
196: if (nickname != null) {
197: if (this .nickname == null
198: || !this .nickname.equals(nickname)) {
199: changed = true;
200: }
201: this .nickname = nickname;
202: } else {
203: if (this .nickname == null
204: || !this .nickname.equals(getName())) {
205: changed = true;
206: }
207: this .nickname = getName();
208: }
209: if (changed && getManager().isActivated()) {
210: Log.debug("TransportBuddy: Triggering contact update for "
211: + this );
212: getManager().getSession().updateContact(this );
213: }
214: }
215:
216: /**
217: * Retrieves the groups of the contact.
218: *
219: * @return Groups contact is in.
220: */
221: public Collection<String> getGroups() {
222: return groups;
223: }
224:
225: /**
226: * Sets the list of groups of the contact.
227: *
228: * @param groups List of groups the contact is in.
229: */
230: public void setGroups(List<String> groups) {
231: Boolean changed = false;
232: if (groups != null && !groups.isEmpty()) {
233: if (this .groups == null || this .groups.isEmpty()
234: || !groups.containsAll(this .groups)
235: || !this .groups.containsAll(groups)) {
236: changed = true;
237: }
238: this .groups = groups;
239: } else {
240: if (this .groups != null && !this .groups.isEmpty()) {
241: changed = true;
242: }
243: this .groups = null;
244: }
245: if (changed && getManager().isActivated()) {
246: Log.debug("TransportBuddy: Triggering contact update for "
247: + this );
248: getManager().getSession().updateContact(this );
249: }
250: }
251:
252: /**
253: * Sets the nickname and list of groups of the contact.
254: *
255: * @param nickname Nickname of contact.
256: * @param groups List of groups the contact is in.
257: */
258: public void setNicknameAndGroups(String nickname,
259: List<String> groups) {
260: Boolean changed = false;
261: if (nickname != null) {
262: if (this .nickname == null
263: || !this .nickname.equals(nickname)) {
264: changed = true;
265: }
266: this .nickname = nickname;
267: } else {
268: if (this .nickname == null
269: || !this .nickname.equals(getName())) {
270: changed = true;
271: }
272: this .nickname = getName();
273: }
274: if (groups != null && !groups.isEmpty()) {
275: if (this .groups == null || this .groups.isEmpty()
276: || !groups.containsAll(this .groups)
277: || !this .groups.containsAll(groups)) {
278: changed = true;
279: }
280: this .groups = groups;
281: } else {
282: if (this .groups != null && !this .groups.isEmpty()) {
283: changed = true;
284: }
285: this .groups = null;
286: }
287: if (changed && getManager().isActivated()) {
288: Log.debug("TransportBuddy: Triggering contact update for "
289: + this );
290: getManager().getSession().updateContact(this );
291: }
292: }
293:
294: /**
295: * Retrieves the subscription status for the contact.
296: *
297: * @return SubType if set.
298: */
299: public RosterItem.SubType getSubType() {
300: return subtype;
301: }
302:
303: /**
304: * Sets the subscription status for the contact.
305: *
306: * @param substatus Subscription status to be set.
307: */
308: public void setSubType(RosterItem.SubType substatus) {
309: subtype = substatus;
310: }
311:
312: /**
313: * Retrieves the ask status for the contact.
314: *
315: * @return AskType if set.
316: */
317: public RosterItem.AskType getAskType() {
318: return asktype;
319: }
320:
321: /**
322: * Sets the ask status for the contact.
323: *
324: * @param askstatus Ask status to be set.
325: */
326: public void setAskType(RosterItem.AskType askstatus) {
327: asktype = askstatus;
328: }
329:
330: /**
331: * Retrieves the current status.
332: *
333: * @return Current status setting.
334: */
335: public PresenceType getPresence() {
336: return presence;
337: }
338:
339: /**
340: * Sets the current status.
341: *
342: * @param newpresence New presence to set to.
343: */
344: public void setPresence(PresenceType newpresence) {
345: if (newpresence == null) {
346: newpresence = PresenceType.unknown;
347: }
348: if (newpresence.equals(PresenceType.unavailable)) {
349: verboseStatus = "";
350: }
351: if (!presence.equals(newpresence)) {
352: Presence p = new Presence();
353: p.setTo(getManager().getSession().getJID());
354: p.setFrom(jid);
355: getManager().getSession().getTransport()
356: .setUpPresencePacket(p, newpresence);
357: if (!verboseStatus.equals("")) {
358: p.setStatus(verboseStatus);
359: }
360: if (avatarSet && avatar != null) {
361: Element vcard = p.addChildElement("x",
362: NameSpace.VCARD_TEMP_X_UPDATE);
363: vcard.addElement("photo")
364: .addCDATA(avatar.getXmppHash());
365: vcard.addElement("hash").addCDATA(avatar.getXmppHash());
366: }
367: getManager().sendPacket(p);
368: }
369: presence = newpresence;
370: }
371:
372: /**
373: * Retrieves the current verbose status.
374: *
375: * @return Current verbose status.
376: */
377: public String getVerboseStatus() {
378: return verboseStatus;
379: }
380:
381: /**
382: * Sets the current verbose status.
383: *
384: * @param newstatus New verbose status.
385: */
386: public void setVerboseStatus(String newstatus) {
387: if (newstatus == null) {
388: newstatus = "";
389: }
390: if (!verboseStatus.equals(newstatus)) {
391: Presence p = new Presence();
392: p.setTo(getManager().getSession().getJID());
393: p.setFrom(jid);
394: getManager().getSession().getTransport()
395: .setUpPresencePacket(p, presence);
396: if (!newstatus.equals("")) {
397: p.setStatus(newstatus);
398: }
399: if (avatarSet && avatar != null) {
400: Element vcard = p.addChildElement("x",
401: NameSpace.VCARD_TEMP_X_UPDATE);
402: vcard.addElement("photo")
403: .addCDATA(avatar.getXmppHash());
404: vcard.addElement("hash").addCDATA(avatar.getXmppHash());
405: }
406: getManager().sendPacket(p);
407: }
408: verboseStatus = newstatus;
409: }
410:
411: /**
412: * Convenience routine to set both presence and verbose status at the same time.
413: *
414: * @param newpresence New presence to set to.
415: * @param newstatus New verbose status.
416: */
417: public void setPresenceAndStatus(PresenceType newpresence,
418: String newstatus) {
419: Log.debug("Updating status [" + newpresence + "," + newstatus
420: + "] for " + this );
421: if (newpresence == null) {
422: newpresence = PresenceType.unknown;
423: }
424: if (newstatus == null) {
425: newstatus = "";
426: }
427: if (newpresence.equals(PresenceType.unavailable)) {
428: newstatus = "";
429: }
430: if (!presence.equals(newpresence)
431: || !verboseStatus.equals(newstatus)) {
432: Presence p = new Presence();
433: p.setTo(getManager().getSession().getJID());
434: p.setFrom(jid);
435: getManager().getSession().getTransport()
436: .setUpPresencePacket(p, newpresence);
437: if (!newstatus.equals("")) {
438: p.setStatus(newstatus);
439: }
440: if (avatarSet && avatar != null) {
441: Element vcard = p.addChildElement("x",
442: NameSpace.VCARD_TEMP_X_UPDATE);
443: vcard.addElement("photo")
444: .addCDATA(avatar.getXmppHash());
445: vcard.addElement("hash").addCDATA(avatar.getXmppHash());
446: }
447: getManager().sendPacket(p);
448: }
449: presence = newpresence;
450: verboseStatus = newstatus;
451: }
452:
453: /**
454: * Sends the current presence to the session user.
455: *
456: * @param to JID to send presence updates to.
457: */
458: public void sendPresence(JID to) {
459: Presence p = new Presence();
460: p.setTo(to);
461: p.setFrom(jid);
462: getManager().getSession().getTransport().setUpPresencePacket(p,
463: presence);
464: if (verboseStatus != null && verboseStatus.length() > 0) {
465: p.setStatus(verboseStatus);
466: }
467: if (avatarSet && avatar != null) {
468: Element vcard = p.addChildElement("x",
469: NameSpace.VCARD_TEMP_X_UPDATE);
470: vcard.addElement("photo").addCDATA(avatar.getXmppHash());
471: vcard.addElement("hash").addCDATA(avatar.getXmppHash());
472: }
473: getManager().sendPacket(p);
474: }
475:
476: /**
477: * Sends the current presence only if it's not unavailable.
478: *
479: * @param to JID to send presence updates to.
480: */
481: public void sendPresenceIfAvailable(JID to) {
482: if (!presence.equals(PresenceType.unavailable)) {
483: sendPresence(to);
484: }
485: }
486:
487: /**
488: * Sends an offline presence for a contact only if it is currently online.
489: *
490: * In case you are wondering why, this is useful when a resource goes offline and we want to indicate all contacts are offline.
491: *
492: * @param to JID to send presence updates to.
493: */
494: public void sendOfflinePresenceIfAvailable(JID to) {
495: if (!presence.equals(PresenceType.unavailable)) {
496: Presence p = new Presence();
497: p.setType(Presence.Type.unavailable);
498: p.setTo(to);
499: p.setFrom(jid);
500: getManager().sendPacket(p);
501: }
502: }
503:
504: /**
505: * Retrieves the cached avatar associated with this contact.
506: *
507: * @return The avatar associated with this contact, or null if no avatar present.
508: */
509: public Avatar getAvatar() {
510: return avatar;
511: }
512:
513: /**
514: * Sets the current avatar for this contact.
515: *
516: * @param avatar Avatar instance to associate with this contact.
517: */
518: public void setAvatar(Avatar avatar) {
519: boolean triggerUpdate = false;
520: if ((avatar != null && this .avatar == null)
521: || (avatar == null && this .avatar != null)
522: || (avatar != null && !this .avatar.getXmppHash()
523: .equals(avatar.getXmppHash()))) {
524: triggerUpdate = true;
525: }
526: this .avatar = avatar;
527: this .avatarSet = true;
528: if (triggerUpdate) {
529: Presence p = new Presence();
530: p.setTo(getManager().getSession().getJID());
531: p.setFrom(jid);
532: getManager().getSession().getTransport()
533: .setUpPresencePacket(p, presence);
534: if (!verboseStatus.equals("")) {
535: p.setStatus(verboseStatus);
536: }
537: Element vcard = p.addChildElement("x",
538: NameSpace.VCARD_TEMP_X_UPDATE);
539: if (avatar != null) {
540: vcard.addElement("photo")
541: .addCDATA(avatar.getXmppHash());
542: vcard.addElement("hash").addCDATA(avatar.getXmppHash());
543: }
544: getManager().sendPacket(p);
545: }
546: }
547:
548: /**
549: * Returns the PHOTO vcard element for an avatar.
550: *
551: * This will return null if the avatar has not been set. Otherwise will return either an empty
552: * PHOTO element or a properly formatted PHOTO element with base64 encoded data in it. The null
553: * return implies that you shouldn't bother including the PHOTO element in the vcard.
554: *
555: * @return null or PHOTO element
556: */
557: public Element getVCardPhoto() {
558: if (!avatarSet) {
559: Log
560: .debug("TransportBuddy: I've got nothing! (no avatar set)");
561: return null;
562: }
563: Element photo = DocumentHelper.createElement("PHOTO");
564: if (avatar != null) {
565: try {
566: photo.addElement("TYPE").addCDATA(avatar.getMimeType());
567: photo.addElement("BINVAL").addCDATA(
568: avatar.getImageData());
569: } catch (NotFoundException e) {
570: // No problem, leave it empty then.
571: }
572: }
573: return photo;
574: }
575:
576: /**
577: * Returns the entire vcard element for an avatar.
578: *
579: * This will return a vCard element, filled in as much as possible, regardless of whether we have
580: * real data for the user. It'll return minimal regardless.
581: *
582: * @return vCard element
583: */
584: public Element getVCard() {
585: Element vcard = DocumentHelper.createElement(QName.get("vCard",
586: NameSpace.VCARD_TEMP));
587:
588: vcard.addElement("VERSION").addCDATA("2.0");
589: vcard.addElement("JABBERID").addCDATA(getJID().toString());
590: vcard.addElement("NICKNAME").addCDATA(
591: getNickname() == null ? getName() : getNickname());
592:
593: if (JiveGlobals.getBooleanProperty("plugin.gateway."
594: + getManager().getSession().getTransport().getType()
595: + ".avatars", true)) {
596: Element photo = getVCardPhoto();
597: if (photo != null) {
598: Log.debug("Found a photo");
599: vcard.add(photo);
600: }
601: }
602:
603: return vcard;
604: }
605:
606: /**
607: * Outputs information about the transport buddy in a pretty format.
608: */
609: public String toString() {
610: return "{Buddy: " + this .jid + " (Nickname: " + this .nickname
611: + ") (Groups: " + this .groups + ")}";
612: }
613:
614: }
|