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<code> dla przekaznego w parametrze obiektu <code>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 😉
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).
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