2
votes

I'm using aSmack in my Android app to communicate with my XMPP server, and I turned on the debug for Smack so I could see all the XML's coming/going. My problem here is that I am using a PacketListener to get the server response for the package that I sent, but when I call the toXML() method of the Packet I got an weird output.
Classes and more details below.

My server has the XEP-0136 implementation and since aSmack doesn't have the code for it yet I am making the IQ packet and sending it, and this is working as expected, as you can see below my XML Packet, as designed in the XEP-0136, retrieving list of collections( http://xmpp.org/extensions/xep-0136.html#manage-list )

Xml package sent to the server

06-13 14:11:21.769: D/SMACK(3018): 02:11:21 PM SENT (1079273464): 
<iq id="[email protected]/Smack/Conversations" type="get">
  <list with="[email protected]" xmlns="urn:xmpp:archive">
    <set xmlns="http://jabber.org/protocol/rsm">
      <max>30</max>
    </set>
  </list>
</iq>

To create this XML I got the XML Schema files as designed in the XEP-0136, and with the help of the SimpleXML lib I mapped all the elements and this is the code I am using to create and send the package:

XMPPService.java

private static final int MAX_LIST = 30;

public void getConversations(String email, BaseActivity activity)
{
    if (isAuthenticated())
    {
        String packetId = connection.getUser() + "/Conversations";
        Set set = new Set();
        set.setMax(MAX_LIST);

        List list = new List();
        list.setWith(email);
        list.setSet(set);

        final IQ iq = new IQ();
        iq.setList(list);
        iq.setType(IQType.get);
        iq.setId(packetId);

        PacketIDFilter filter = new PacketIDFilter(packetId);
        connection.addPacketListener(new ChatListListener(activity), filter);
        sendPacket(iq);
    }
}

public void sendPacket(IQ iq)
{
    if (isAuthenticated())
    {
        connection.sendPacket(new IQPacket(iq));
    }
}

IQPacket.java

public class IQPacket extends Packet {

 private IQ iq;

 public IQPacket(IQ iq)
 {
    this.iq = iq;
 }

 public IQPacket(Packet packet, IQ iq)
 {
    super(packet);
    this.iq = iq;
 }

 @Override
 public String toXML()
 {
    StringWriter writer = new StringWriter();
    Serializer serializer = new Persister();
    try
    {
        serializer.write(iq, writer);
        return writer.getBuffer().toString();
    } catch (Exception e)
    {
        Log.e("COMPANY", "Error serializing xml", e);
    }
    return null;
 }

}

As I said, this part works, my problem is for the Listener, when I call the toXML() method for the package received, I can't get the important information about the chats, but the Smack debugging output prints to me all the information that I'm expecting as you can see below:

Smack Debug for received XML

06-13 14:11:21.989: D/SMACK(3018): 02:11:21 PM RCV  (1079273464): 
<iq type="result" id="[email protected]/Smack/Conversations" to="[email protected]/Smack">
  <list xmlns="urn:xmpp:archive">
    <chat with="[email protected]" start="2013-06-10T13:19:25.000Z"/>
    <chat with="[email protected]" start="2013-06-10T13:36:50.876Z"/>
    <set xmlns="http://jabber.org/protocol/rsm">
      <first index="0">2</first>
      <last>3</last>
      <count>9</count>
    </set>
  </list>
</iq>

Also this XML is the expected answer since I have all those elements mapped as JavaBeans, but this is what I get when I receive the Packet on my ChatListener and call the toXML() method:

06-13 14:11:22.009: I/System.out(3018): 
<iq id="[email protected]/Smack/Conversations" to="[email protected]/Smack" type="result">nullnullnullnullnullnull2nullnull3nullnull9nullnull</iq>

ChatListListener.java

public class ChatListListener implements PacketListener {

 private BaseActivity activity;

 public ChatListListener(BaseActivity activity)
 {
    this.activity = activity;
 }

 @Override
 public void processPacket(Packet packet)
 {
    activity.notifyPacketReceived();
    System.out.println(packet.toXML());
 }
}

The Packet is from org.jivesoftware.smack.packet.Packet, so it's the default Packet from the aSmack lib.

So my question is, what am I doing different of the Smack debugger? I looked its code and for what I saw it also calls the toXML() method from the packet and add a ReceiveListener. My idea here is after I call the toXML() I can use SimpleXML to transform it into my IQ.java that I mapped and start using its information.

EDIT

Adding more information. So after looking up the Smack code and how it handles the package received, I figure out that maybe I should use an IQProvider. So I registered my IQProvider

ProviderManager.getInstance().addIQProvider("list", "urn:xmpp:archive", new ListIQProvider());

And after that I put a breakpoint on the method parseIQ(XmlPullParser arg0) of my IQProvider, and the package is actually being sent to my provider, but still it has all those null elements. I'm kind lost right now because I need this to keep working, I will keep investigating the Smack source code.

2

2 Answers

4
votes

After lot of research and looking Smack sources, I found out the solution. The steps are, add IQProvider, add a PacketListener for your packet, send the packet. Wait for the packet on your IQProvider, parse it, and get your response on your Listener.

So the addIQProvider and addPacketListener were both right, the thing is, I need to do the complete parse of the XML on my ListIQProvider, it sounds simple but took me some time to figure that out.

public class ListIQProvider implements IQProvider {

 public ListIQProvider()
 {
 }

 @Override
 public IQ parseIQ(XmlPullParser parser) throws Exception
 {
    Logger.d(String.format("Received iq packet, namespace[%s], name[%s]", parser.getNamespace(), parser.getName()));
    ListIQ iq = new ListIQ();
    ListIQ.Set set = new Set();
    boolean done = false;

    String with = "", start = "";
    while (!done)
    {
        int eventType = parser.next();
        if (eventType == XmlPullParser.START_TAG)
        {
            if (parser.getName().equals("chat"))
            {
                with = parser.getAttributeValue("", "with");
                start = parser.getAttributeValue("", "start");
                iq.addChat(new Chat(with, start));
            }
            else if (parser.getName().equals("first"))
            {
                int index = parseInt(parser.getAttributeValue("", "index"));
                set.setIndexAtt(index);
                int first = parseInt(parser.nextText());
                set.setFirst(first);
            }
            else if (parser.getName().equals("last"))
            {
                int last = parseInt(parser.nextText());
                set.setLast(last);
            }
            else if (parser.getName().equals("count"))
            {
                int count = parseInt(parser.nextText());
                set.setCount(count);
            }
        }
        else if (eventType == XmlPullParser.END_TAG)
        {
            if (parser.getName().equals("list"))
            {
                iq.setSet(set);
                done = true;
            }
        }
    }

    return iq;
 }

 private int parseInt(String integer)
 {
    return Integer.parseInt((integer != null ? integer : "0"));
 }
}

After that, all I had to do on my ChatListListener was casting the Packet to my ListIQ class. That's it. The thing here is, the Packet being received on my ChatListListener is the same Packet that is being returned on the parseIQ method on my ListIQProvider. So in this question/answer we have almost everything you need for XEP-0136, or at least start using it. Since I didn't found any good and simple source on the web to help me, I'm sharing mine here. Below it's the ListIQ class:

public class ListIQ extends IQ {

 private List<Chat> chats;

 private Set set;

 public ListIQ()
 {
    this.chats = new ArrayList<ListIQ.Chat>();
 }

 public Set getSet()
 {
    return set;
 }

 public void setSet(Set set)
 {
    this.set = set;
 }

 public void addChat(Chat chat)
 {
    chats.add(chat);
 }

 public List<Chat> getChats()
 {
    return chats;
 }

 @Override
 public String getChildElementXML()
 {
    StringBuilder builder = new StringBuilder("<list xmlns=\"urn:xmpp:archive\">");
    for (Chat chat : chats)
    {
        builder.append(chat.toXml());
    }
    builder.append(set.toXml());
    builder.append("</list>");
    return builder.toString();
 }

 public static class Chat {
    private String with;
    private String start;

    public Chat()
    {
    }

    public Chat(String with, String start)
    {
        this.with = with;
        this.start = start;
    }

    public String getWith()
    {
        return with;
    }

    public void setWith(String with)
    {
        this.with = with;
    }

    public String getStart()
    {
        return start;
    }

    public void setStart(String start)
    {
        this.start = start;
    }

    public String toXml()
    {
        StringBuilder builder = new StringBuilder("<chat with=\"");
        builder.append(with).append("\"");
        builder.append(" start=\"");
        builder.append(start);
        builder.append("\"/>");
        return builder.toString();
    }

 }

 public static class Set {
    private int last;
    private int count;
    private int indexAtt;
    private int first;

    public Set()
    {
    }

    public int getLast()
    {
        return last;
    }

    public void setLast(int last)
    {
        this.last = last;
    }

    public int getCount()
    {
        return count;
    }

    public void setCount(int count)
    {
        this.count = count;
    }

    public int getIndexAtt()
    {
        return indexAtt;
    }

    public void setIndexAtt(int indexAtt)
    {
        this.indexAtt = indexAtt;
    }

    public int getFirst()
    {
        return first;
    }

    public void setFirst(int first)
    {
        this.first = first;
    }

    public String toXml()
    {
        StringBuilder builder = new StringBuilder("<set xmlns=\"http://jabber.org/protocol/rsm\">");
        builder.append("<first index=\"").append(indexAtt).append("\">").append(first).append("</first>");
        builder.append("<last>").append(last).append("</last>");
        builder.append("<count>").append(count).append("</count>");
        builder.append("</set>");
        return builder.toString();
    }
 }

}
1
votes

Hi i also had the same problem and I can tell that this code works I would just change the way you build the query for the list to the server. (At least on my server)

final IQ iq = new IQ()
    {

        @Override public String getChildElementXML()
        {

            return "<list xmlns='urn:xmpp:archive'/>";

        }
    };

    iq.setType(IQ.Type.GET);

    PacketTypeFilter filter = new PacketTypeFilter(Packet.class);


    connection.addPacketListener(new PacketListener()
    {
        @Override public void processPacket(Packet packet)
        {
            Log.i(TAG, packet.toXML());
        }
    }, filter);

    connection.sendPacket(iq);

as you said before it seems pretty easy but the documentation is not really clear about how to build the IQ object. I hope it helps.