XMPP-PubSub w Java (cz. 2)

W poprzednim wpisie, pokazałem jak wystawić pojedynczy węzeł (node), dziś zrobię/zrobimy trochę więcej, a mianowicie opublikujemy “wiadomość”, a przede wszystkim odbierzemy zdarzenie o utworzeniu wiadomości. Przecież o tą zaletę XMPP-PubSub nam (a przynajmniej mi ;)) najbardziej chodzi … o jego zdarzeniowy charakter ;> … a więc do roboby 😉

Żeby to  co piszę było bardziej uniwersalne, celem wprowadzenia warte opisania są właściwości zdarzeniowe obu bibliotek do obsługi XMPP (mam tu na myśli smack (do obsługi samego XMPP) i su-smack (do obsługi XMPP-PubSub)). Pewnie bardziej dociekliwy czytelnik zauważył w poprzedniej części dodanie PacketListener‘a do obiektu połączenia, dla mniej dociekliwych i uważnych poniżej znajduje się ten kawałek kodu:

con.addPacketListener(new PacketListener() {
    public void processPacket(Packet pkt) {
        System.out.println(pkt.toXML());
    }
}, null);

Co ja tutaj takiego zrobiłem ? … W ten sposób rozwiałem problem z debug’owaniem pakietów jakie wracały z serwera, gwoli ścisłości metoda addPacketListener przyjmuje 2 parametry, pierwszy z nich jest to wspominany w nazwie PacketListener (interfejs z jedną metodą void processPacket(Packet pkt)), oraz obiekt PacketFilter. Dzięki takiemu rozwiązaniu programista jest odciążony z obowiązku własnoręcznego oprogramowywania otrzymywanych pakietów. Mały przykład, załóżmy że interesują nas tylko wiadomości od jednego użytkownika … nic prostszego:

PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class),
            new FromContainsFilter("my-love@jabber.swh"));
conn.addPacketFilter(new PacketFilter() {
    public void processPacket(Packet pkt) {
        System.out.println("My love is writing to me ;).")
    }
}, filter );

W ten oto prosty sposób mamy rozwiązany powyższy problem ;).

Jeżeli chodzi natomiast nasłuchiwanie na pakiety PubSub  sprawa jest odrobinę bardziej zakręcona … biblioteka su-smack posiada jedna pomocniczą klasę XMPPUtils która jest bardzo ciekawa ;> posiada ona tylko 5 metod (z czego 2 są dodatkowo przeciążone), na chwilę obecną będą nas interesować tylko 2 obecne tam publiczne statyczne metody: createEventPacketListener(PubSubEventListener listener) oraz createPubSubFilter(). Pierwsza z nich zwraca obiekt PacketListener dla przekaznego w parametrze obiektu PubSubEventListener, a co robi druga jest bardzo łatwo się domyślić ;).

Dodatkowo nasz program kliencki będzie się subskrybował do węzła ... kod subskrypcji jest bardzo prosty i nie wymaga opisu IMHO. Poniżej znajduje się kompletny kod klienta:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
 *     Published on GNU GPLv2
 */
package org.luksza.xmpp.pubsub;
 
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketCollector;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.IQ.Type;
 
import se.su.it.smack.pubsub.PubSub;
import se.su.it.smack.pubsub.PubSubEventListener;
import se.su.it.smack.pubsub.elements.DeleteElement;
import se.su.it.smack.pubsub.elements.ItemElement;
import se.su.it.smack.pubsub.elements.PurgeElement;
import se.su.it.smack.pubsub.elements.RetractElement;
import se.su.it.smack.pubsub.elements.SubscribeElement;
import se.su.it.smack.utils.XMPPUtils;
 
/**
 * @author Dariusz [LocK] Łuksza
 */
public class SubscribeToNode {
 
    private static final String login = "pubsub-test";
    private static final String pass = "pubsub-test";
    private static final String pubSubServerUrl = "pubsub.server";
 
    public static void main(String[] args) {
        final XMPPConnection con = new XMPPConnection(new ConnectionConfiguration("server"));
        try {
            System.out.print("Connecting ...");
            con.connect();
            if (con.isConnected()) {
                System.out.print(" DONE.\nLogging ...");
                con.login(login, pass);
                if (con.isAuthenticated()) {
                    System.out.print(" DONE.\nSubscribing to node ...");
                    final PubSub pubSubSubscribe = new PubSub();
                    pubSubSubscribe.setTo(pubSubServerUrl);
                    pubSubSubscribe.setType(Type.SET);
                    pubSubSubscribe.setFrom(con.getUser());
                    final SubscribeElement subscribe = new SubscribeElement("/home/server/blee");
                    subscribe.setJid(con.getUser());
                    pubSubSubscribe.addChild(subscribe);
                    XMPPUtils.sendAndWait(con, pubSubSubscribe); // subskrybcja jest zawsze akceptowana.
                    System.out.println(" DONE.");
 
                    PacketListener pubsubPacketListner = XMPPUtils.createEventPacketListener(
                                new MyPubSusEventListener(con)
                            );
                    con.addPacketListener(pubsubPacketListner, XMPPUtils.createPubSubFilter());
 
                    XMPPUtils.sendAndWait(con, pubSubSubscribe);
 
                    while (true) {
                        Thread.sleep(10);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            con.disconnect();
        }
    }
 
    public static class MyPubSusEventListener implements PubSubEventListener {
 
        private final XMPPConnection _con;
 
        public MyPubSusEventListener(final XMPPConnection con) {
            _con = con;
        }
 
        public void onDelete(final DeleteElement element) throws Exception {
            System.out.println("Deleted element: " + element.toXML());
        }
 
        // w parametrze przekazywana jest tylko nazwa utworzonego elemnu
        public void onPublish(final ItemElement elemnt) throws Exception {
            System.out.println("\n\tPublished element:\n" + elemnt.toXML());
            // zeby odebrac dane trzeba opakowac otrzymany element
            // w "caly pakiet"
            final PubSub getItem = new PubSub();
            getItem.setType(Type.GET);
            getItem.setTo(pubSubServerUrl);
            getItem.setFrom(_con.getUser());
            getItem.addChild(elemnt);			
 
            // zeby odebrac dane trzeba poczekac na pakiet z id
            // takim samym jak wyslane
            final PacketCollector collector = _con.createPacketCollector(
                    new PacketIDFilter(getItem.getPacketID())
                );
            _con.sendPacket(getItem); // wysylanie
            final Packet result = collector.nextResult(); // oczekiwanie na wynik
 
            System.out.println("\n\tRetrive data:" + result.toXML());
        }
 
        public void onPurge(final PurgeElement elemnt) throws Exception {
            System.out.println("Purged element: " + elemnt.toXML());
        }
 
        public void onRetract(final RetractElement elemnt) throws Exception {
            System.out.println("Retracted element: " + elemnt.toXML());
        }
    }
}

Kod "serwera" (tj. aplikacji publikującej dane) jest bardzo prosty, i sprowadza się do użycia odpowiednich klas i ich wysłania ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
 * 	Published on GNU GPLv2
 */
package org.luksza.xmpp.pubsub;
 
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.IQ.Type;
 
import se.su.it.smack.pubsub.PubSub;
import se.su.it.smack.pubsub.elements.ItemElement;
import se.su.it.smack.pubsub.elements.PublishElement;
import se.su.it.smack.utils.XMPPUtils;
 
/**
 * @author Dariusz [LocK] Łuksza
 */
public class PubSubCreateNode {
 
    public static void main(final String[] argv) throws Exception {
        final String login = "blee";
        final String serverHost = "server";
        final XMPPConnection con = new XMPPConnection(serverHost);
        con.connect();
        try {
            con.login(login, "pass");
            if (con.isAuthenticated()) {
                final PubSub pubSub = new PubSub();
                pubSub.setType(Type.SET);
                pubSub.setFrom(con.getUser());
                pubSub.setTo("pubsub."  + serverHost);
 
                final PublishElement publish = new PublishElement("/home/" + serverHost + "/" + login);
                final ItemElement item = new ItemElement("Test Item");
 
                item.setContent("<entry>Test TEST test</entry>");
                publish.addChild(item);
                pubSub.addChild(publish);
 
                XMPPUtils.sendAndWait(con, pubSub);
            } else {
                System.out.println("Not authenticated!");
                return;
            }
        } finally {
            con.disconnect();
        }
    }
 
}

Jak to działa ? Najpierw odpalamy "klienta" który subskrybuje się do węzła i nasłuchuje (w nie skończoność) na nowe dane. Potem odpalamy kod publikujący dane, aplikacja się spokojnie wykona, a w kliencie dostaniemy informacje o tym, że została opublikowana nowa wiadomość jak i od razu ją odbieramy 😉

Ciekawe linki:

  • oczywiście XEP-0060
  • ciekawy artykuł, nie przeczytałem całego 😉 ale zamieszczone tam kody  pomogły mi zrozumieć parę rzeczy 😉

2 Comments XMPP-PubSub w Java (cz. 2)

  1. tgh

    Super, że poruszyłeś ten temat. Miło by było jakbyś umieszczał źródła (uruchumieniowy też by się przydał 😉 – odpala się i od razu widać z czym mamy do czynienia czytając później wpis).

    Reply
  2. [LocK]

    zrodla sa, wszystko sie kompiluje i uruchamia (kazda klasa ma publiczna statyczna metode main(String)) wiec nie widze tu zadnego problemu … jedyne o co trzeba zadbac to serwer jabbera z xmpp-pubsub i konto tam

    Reply

Leave a Reply to [LocK] Cancel reply

Your email address will not be published. Required fields are marked *