/* Copyright (c) 2008 Bluendo S.r.L.
* See about.html for details about license.
*
* $Id: Roster.java 1588 2009-06-18 09:22:57Z luca $
*/
package it.yup.xmpp;
// #mdebug
//@
//@import it.yup.util.Logger;
//@
// #enddebug
import it.yup.xml.BProcessor;
import it.yup.xml.Element;
import it.yup.xmlstream.BasicXmlStream;
import it.yup.xmlstream.EventQuery;
import it.yup.xmlstream.PacketListener;
import it.yup.xmpp.XMPPClient.XmppListener;
import it.yup.xmpp.packets.Iq;
import it.yup.xmpp.packets.Presence;
import it.yup.xmpp.packets.Stanza;
import it.yup.util.RMSIndex;
import it.yup.util.Utils;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.lcdui.AlertType;
public class Roster implements PacketListener {
/*
* Implements the XEP for roster push
*/
class RosterX implements PacketListener {
public RosterX() {
EventQuery q = new EventQuery("message", null, null);
EventQuery x = new EventQuery("x", new String[] { "xmlns" },
new String[] { XMPPClient.NS_ROSTERX });
q.child = x;
BasicXmlStream.addEventListener(q, this);
q = new EventQuery("iq", new String[] { Iq.ATT_TYPE },
new String[] { Iq.T_SET });
q.child = x;
BasicXmlStream.addEventListener(q, this);
}
public void packetReceived(Element e) {
//System.out.println(new String(e.toXml()));
// check the packet sender
// check what to do with contacts
// answer in case it is an Iq
if (client.getXmppListener() != null) client.getXmppListener()
.rosterXsubscription(e);
}
}
/*
* The roster version
*/
String rosterVersion = "0";
/** All contacts */
public Hashtable contacts = new Hashtable();
private XMPPClient client;
private RosterX rosterX;
public static String unGroupedCode = new String(
new char[] { ((char) 0x08) });
public static String NS_IQ_ROSTER = "jabber:iq:roster";
public Hashtable registeredGateways = new Hashtable(5);
Roster(XMPPClient _client) {
loadGateways();
client = _client;
}
public void streamInitialized() {
EventQuery eq = new EventQuery(Iq.IQ, new String[] { "type" },
new String[] { "set" });
eq.child = new EventQuery(Iq.QUERY, new String[] { "xmlns" },
new String[] { NS_IQ_ROSTER });
BasicXmlStream.addEventListener(eq, this);
this.rosterX = new RosterX();
}
RMSIndex rosterStore;
/*
* The configuration instance
*/
private Config cfg = Config.getInstance();
/**
* Read the contacts from the RMS
*
*/
protected synchronized void readFromStorage() {
try {
// #mdebug
//@ Logger.log("Start read from storage:" + System.currentTimeMillis());
// #enddebug
rosterStore.open();
// #mdebug
//@ Logger.log("Start load from rms:" + System.currentTimeMillis());
// #enddebug
byte[] rosterData = rosterStore.load(Utils.getBytesUtf8("roster"));
// #mdebug
//@ Logger.log("End load from rms:" + System.currentTimeMillis());
// #enddebug
if (rosterData != null) {
// #mdebug
//@ Logger.log("Start parse :" + System.currentTimeMillis());
// #enddebug
Element rosterEl = BProcessor.parse(rosterData);
// #mdebug
//@ Logger.log("End parse :" + System.currentTimeMillis());
// #enddebug
rosterVersion = rosterEl.getAttribute("ver");
if (rosterVersion == null) rosterVersion = "0";
Element[] children = rosterEl.getChildrenByName(null, "group");
for (int i = 0; i < children.length; i++) {
Element ithChild = children[i];
String gName = ithChild.getText();
byte[] gData = rosterStore.load(Utils.getBytesUtf8(gName));
if (gData != null) {
// #mdebug
//@ System.out.println("Read group: " + gName);
// #enddebug
Element gEl = BProcessor.parse(gData);
Element[] gChildren = gEl.getChildrenByName(null,
"item");
for (int j = 0; j < gChildren.length; j++) {
Element item = gChildren[j];
// #mdebug
//@ String jid = item.getAttribute("jid");
//@ System.out.println("Read: " + jid);
// #enddebug
this.updateRosterItem(item);
}
}
}
}
} catch (Exception e) {
// #mdebug
//@ Logger.log("Error in reading from storage: " + e.getMessage(),
//@ Logger.DEBUG);
//@ e.printStackTrace();
// #enddebug
client.showAlert(AlertType.ERROR, "Exception",
"Error reading roster fromstorage:\n" + e, null);
} finally {
rosterStore.close();
}
// #mdebug
//@ Logger.log("Finish read from storage:" + System.currentTimeMillis());
// #enddebug
}
/**
* Save the roster to the RMS
*
*/
protected synchronized void saveToStorage() {
try {
rosterStore.open();
Element rosterEl = new Element(Roster.NS_IQ_ROSTER, "roster");
if (rosterVersion == null) rosterVersion = "0";
rosterEl.setAttribute("ver", this.rosterVersion);
Hashtable groups = Group.getGroups();
Enumeration en = groups.elements();
while (en.hasMoreElements()) {
Group g = (Group) en.nextElement();
byte[] groupData = BProcessor.toBinary(g.store());
rosterStore.store(Utils.getBytesUtf8(g.name), groupData);
Element groupEl = new Element(Roster.NS_IQ_ROSTER, "group");
groupEl.addText(g.name);
rosterEl.addElement(groupEl);
}
rosterStore.store(Utils.getBytesUtf8("roster"), BProcessor
.toBinary(rosterEl));
} catch (Exception e) {
// #mdebug
//@ Logger.log("Error in saving to storage: " + e.getMessage(),
//@ Logger.DEBUG);
// #enddebug
client.showAlert(AlertType.ERROR, "Exception",
"Error saving roster to storage:\n" + e, null);
} finally {
rosterStore.close();
}
}
public void packetReceived(Element e) {
// #mdebug
//@ Logger.log("RosterHandler: received packet: " + new String(e.toXml()),
//@ Logger.DEBUG);
// #enddebug
Element query = e.getChildByName(null, Iq.QUERY);
Element items[] = query.getChildrenByName(null, "item");
for (int i = 0; i < items.length; i++) {
updateRosterItem(items[i]);
}
String tempVer = query.getAttribute("ver");
if (tempVer != null) this.rosterVersion = tempVer;
saveToStorage();
}
/**
* Send a roster query
*
* @param go_online
* if true we go online when received the roster
*/
public void retrieveRoster(final boolean go_online, boolean purge) {
Iq iq_roster = new Iq(null, Iq.T_GET);
iq_roster.addElement(NS_IQ_ROSTER, Iq.QUERY);
iq_roster.setAttribute("ver", this.rosterVersion);
client.sendIQ(iq_roster, new IQResultListener() {
public void handleError(Element e) {
System.out.println(e.toXml());
}
// XXX I don't link this method, we should study some events for
// doing this
public void handleResult(Element e) {
recreateRoster(e, true);
saveToStorage();
if (go_online) {
client.setPresence(-1, null);
}
// Handle subscription to the agent
Contact c = getContactByJid(Config.LAMPIRO_AGENT);
if (c == null || !"both".equals(c.subscription)) {
c = new Contact(Config.LAMPIRO_AGENT, "Lampiro Agent",
null, null);
subscribeContact(c, false);
}
XmppListener listener = XMPPClient.getInstance()
.getXmppListener();
if (listener != null) listener.rosterRetrieved();
}
});
}
/**
* Subscribe to a contact. Adding a contact fires the transmission of two
* messages: an iq of type set for updating the roster, and a presence of
* type subscribe
* @param c: the contact to be subscribed
* @param accept: true if this is a response to a subscribe request
*/
public void subscribeContact(Contact c, final boolean accept) {
contacts.put(c.jid, c);
Iq iq_roster = new Iq(null, Iq.T_SET);
Element query = iq_roster.addElement(NS_IQ_ROSTER, Iq.QUERY);
Element item = query.addElement(NS_IQ_ROSTER, "item");
item.setAttribute("jid", c.jid);
if (c.name.length() > 0) {
item.setAttribute("name", c.name);
}
for (int i = 0; i < c.getGroups().length; i++) {
item.addElement(NS_IQ_ROSTER, "group").addText(c.getGroups()[i]);
}
if (c.getGroups().length == 0) this.addGatewayGroup(c, item);
final String contactJid = c.jid;
client.sendIQ(iq_roster, new IQResultListener() {
public void handleError(Element e) {
// TODO Auto-generated method stub
}
public void handleResult(Element e) {
Presence psub;
if (accept) {
psub = new Presence(Presence.T_SUBSCRIBED, null, null, -1);
psub.setAttribute(Stanza.ATT_TO, contactJid);
client.sendPacket(psub);
}
psub = new Presence(Presence.T_SUBSCRIBE, null, null, -1);
psub.setAttribute(Stanza.ATT_TO, contactJid);
client.sendPacket(psub);
}
});
// recreateGroups();
}
private void addGatewayGroup(Contact c, Element item) {
Enumeration en = registeredGateways.keys();
while (en.hasMoreElements()) {
String from = (String) en.nextElement();
String domain = Contact.domain(c.jid);
if (domain != null && domain.equals(from)) {
item.addElement(NS_IQ_ROSTER, "group").addText(
((String[]) (registeredGateways.get(from)))[0]);
break;
}
}
}
/** remove a contact */
public void unsubscribeContact(Contact c) {
contacts.remove(c.jid);
if (client.getXmppListener() != null) client.getXmppListener()
.removeContact(c);
Iq iq_roster = new Iq(null, Iq.T_SET);
Element query = iq_roster.addElement(NS_IQ_ROSTER, Iq.QUERY);
Element item = query.addElement(NS_IQ_ROSTER, "item");
item.setAttribute("jid", c.jid);
item.setAttribute("subscription", "remove");
client.sendPacket(iq_roster);
// recreateGroups();
}
private void recreateRoster(Element iq, boolean purge) {
// XXX -> this should be run within a synchronized
// // Build a lookup table with roster
// Hashtable oldrst = new Hashtable();
// Enumeration en = contacts.elements();
// while (en.hasMoreElements()) {
// Contact c = (Contact) en.nextElement();
// oldrst.put(c.jid, c);
// }
Element query = iq.getChildByName(null, Iq.QUERY);
if (query == null) { return; }
String tempVer = query.getAttribute("ver");
if (tempVer != null) this.rosterVersion = tempVer;
Element items[] = query.getChildrenByName(null, "item");
if (purge && tempVer == null) {
Hashtable newContacts = new Hashtable();
// the old contacts that have a presence but are not
// in the roster
Vector oldUnRosterContacts = new Vector();
for (int i = 0; i < items.length; i++) {
Contact c = getContactByJid(items[i].getAttribute("jid"));
if (c != null) newContacts.put(c.jid, c);
}
Enumeration en = this.contacts.keys();
while (en.hasMoreElements()) {
Object ithElem = en.nextElement();
Contact contactToRemove = (Contact) this.contacts.get(ithElem);
Presence[] ps = contactToRemove.getAllPresences();
if (newContacts.containsKey(ithElem) == false) {
if (ps == null || ps.length == 0) {
if (client.getXmppListener() != null) client
.getXmppListener().removeContact(
contactToRemove);
} else {
newContacts.put(contactToRemove.jid, contactToRemove);
oldUnRosterContacts.addElement(contactToRemove);
}
}
}
this.contacts = newContacts;
// these old contacts must be updated
en = oldUnRosterContacts.elements();
while (en.hasMoreElements()) {
if (client.getXmppListener() != null) client.getXmppListener()
.updateContact((Contact) en.nextElement(),
Contact.CH_STATUS);
}
}
for (int i = 0; i < items.length; i++) {
updateRosterItem(items[i]);
}
// add the server contact
// XXX: is it correct to do it here ?
// and/or is it the nicest way to do it
XMPPClient me = XMPPClient.getInstance();
String myDomain = Contact.domain(me.my_jid);
Contact c = getContactByJid(myDomain);
if (c == null) {
Element serverEl = new Element("", "serverEl");
serverEl.setAttributes(new String[] { Iq.ATT_TO, "jid", "name",
"subscription" }, new String[] { me.my_jid, myDomain,
"Jabber Server", "both" });
updateRosterItem(serverEl);
/// create a a fictitious presence
Presence p = new Presence(me.my_jid, Presence.T_SUBSCRIBED,
"online", "Jabber Server", 1);
p.setAttribute(Presence.ATT_FROM, myDomain);
c = getContactByJid(myDomain);
c.updatePresence(p);
updateRosterItem(serverEl);
}
}
/**
* Update roster item
*
* @param item
*/
private void updateRosterItem(Element item) {
// XXX handle the case in which the subscription is "remove"
// XXX: A lot of the group logic should be redone
// for example I don't like all the translations between group <--> String and so on.. ugly
String jid = item.getAttribute("jid");
boolean changedGroups = false;
Element group_elements[] = item.getChildrenByName(null, "group");
String groups[] = new String[group_elements.length];
for (int j = 0; j < groups.length; j++) {
groups[j] = group_elements[j].getText();
}
// "ungrouped" contact if no group assign the ungrouped
Contact c = getContactByJid(jid);
if (c == null) {
c = new Contact(jid, item.getAttribute("name"), item
.getAttribute("subscription"), groups);
} else {
// contact found, just update
c.subscription = item.getAttribute("subscription");
String name = item.getAttribute("name");
if (name != null) {
c.name = name;
}
changedGroups = c.setGroups(groups);
}
if (changedGroups) {
if (client.getXmppListener() != null) client.getXmppListener()
.updateContact(c, Contact.CH_GROUP);
}
// XXX not sure that is completely correct...
String subscription = item.getAttribute("subscription");
if (subscription != null && subscription.compareTo("remove") == 0) {
// if the user has removed me from roster
// there is nothing to do remove contacts and nothing all
contacts.remove(c.jid);
if (client.getXmppListener() != null) client.getXmppListener()
.removeContact(c);
return;
}
contacts.put(c.jid, c);
// check if this contact is one of my registered gateways
updateGateways(c);
if (client.getXmppListener() != null) client.getXmppListener()
.updateContact(c, Contact.CH_STATUS);
}
/*
* Load the registered gateways from recordStore
*/
private void loadGateways() {
byte[] gwBytes = cfg.getData(Config.REGISTERED_GATEWAYS.getBytes());
// to check it is a valid xml
if (gwBytes == null || gwBytes.length == 0) return;
Element decodedPacket = null;
try {
decodedPacket = BProcessor.parse(gwBytes);
} catch (Exception e) {
// #mdebug
//@ e.printStackTrace();
//@ Logger.log("In loading gateways" + e.getClass().getName() + "\n"
//@ + e.getMessage());
//#enddebug
return;
}
Element[] children = decodedPacket.getChildren();
try {
for (int i = 0; i < children.length; i++) {
Element ithElem = children[i];
String ithFrom = ithElem.getChildByName(null, "from").getText();
String ithType = ithElem.getChildByName(null, "type").getText();
String ithName = ithElem.getChildByName(null, "name").getText();
this.registeredGateways.put(ithFrom, new String[] { ithType,
ithName });
}
} catch (Exception e) {
// corrupted configuration reset it
cfg.setData(Config.REGISTERED_GATEWAYS.getBytes(), new byte[] {});
}
}
/*
* save the registered gateways to recordStore
*/
private void saveGateways() {
Element el = new Element("", "gws");
Enumeration en = this.registeredGateways.keys();
while (en.hasMoreElements()) {
String ithFrom = (String) en.nextElement();
String[] data = (String[]) this.registeredGateways.get(ithFrom);
String ithType = data[0];
String ithName = data[1];
Element gw = el.addElement(null, "gw");
gw.addElement(null, "from").addText(ithFrom);
gw.addElement(null, "type").addText(ithType);
gw.addElement(null, "name").addText(ithName);
}
cfg.setData(Config.REGISTERED_GATEWAYS.getBytes(), BProcessor
.toBinary(el));
}
/*
* Check if contact is a gateway and in case
* start the procedure to add it to the registered gateways
*
* @param c
* The contact to check for
*/
private void updateGateways(Contact c) {
if (c.jid.indexOf('@') >= 0
|| registeredGateways.containsKey(Contact.userhost(c.jid))
/*|| c.isVisible() == false*/) return;
IQResultListener gw = new IQResultListener() {
public void handleError(Element e) {
}
public void handleResult(Element e) {
Element q = e.getChildByName(XMPPClient.NS_IQ_DISCO_INFO,
Iq.QUERY);
if (q != null) {
String type = null;
String name = "";
String from = e.getAttribute("from");
Element identity = q.getChildByName(
XMPPClient.NS_IQ_DISCO_INFO, "identity");
if (identity != null) {
type = identity.getAttribute("type");
String category = identity.getAttribute("category");
name = identity.getAttribute("name");
if (category.compareTo("gateway") == 0) {
Roster.this.registeredGateways.put(from,
new String[] { type, name });
saveGateways();
}
}
}
}
};
Iq iq = new Iq(c.jid, Iq.T_GET);
iq.addElement(XMPPClient.NS_IQ_DISCO_INFO, Iq.QUERY);
XMPPClient.getInstance().sendIQ(iq, gw);
}
public Contact getContactByJid(String jid) {
return (Contact) contacts.get(Contact.userhost(jid));
}
public void purge() {
this.contacts.clear();
}
// XXX temporary removed
// private void recreateGroups() {
//
// // unclassified users are group 0, remove all other groups
// groups.removeAllElements();
// Group ng = new Group("No Group");
// groups.addElement(ng);
//
// Group gi;
// Enumeration en = contacts.elements();
// while(en.hasMoreElements()) {
// Contact c = (Contact) en.nextElement();
//
// // the contact is not in any group
// if(c.groups.length == 0) {
// ng.addContact(c);
// } else {
//
// // add a contact in all the pertaining groups
// for(int p = 0; p < c.groups.length; p++) {
// gi = findGroup(c.groups[p]);
// gi.addContact(c);
// }
// }
// }
// }
// private Group findGroup(String gname) {
// Group g = null;
// for(int i = 1; i < groups.size(); i++) {
// g = (Group)groups.elementAt(i);
// if(g.name.equals(gname)) {
// return g;
// }
// }
//
// /* arrivando qui, non ho trovato il gruppo */
// g = new Group(gname);
// groups.addElement(g);
// return g;
// }
}