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
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