Node.java

  1. /**
  2.  *
  3.  * Copyright 2009 Robin Collier.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.pubsub;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.concurrent.ConcurrentHashMap;

  22. import org.jivesoftware.smack.StanzaListener;
  23. import org.jivesoftware.smack.SmackException.NoResponseException;
  24. import org.jivesoftware.smack.SmackException.NotConnectedException;
  25. import org.jivesoftware.smack.XMPPConnection;
  26. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  27. import org.jivesoftware.smack.filter.OrFilter;
  28. import org.jivesoftware.smack.filter.StanzaFilter;
  29. import org.jivesoftware.smack.packet.Message;
  30. import org.jivesoftware.smack.packet.Stanza;
  31. import org.jivesoftware.smack.packet.ExtensionElement;
  32. import org.jivesoftware.smack.packet.IQ.Type;
  33. import org.jivesoftware.smackx.delay.DelayInformationManager;
  34. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  35. import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener;
  36. import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
  37. import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener;
  38. import org.jivesoftware.smackx.pubsub.packet.PubSub;
  39. import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
  40. import org.jivesoftware.smackx.pubsub.util.NodeUtils;
  41. import org.jivesoftware.smackx.shim.packet.Header;
  42. import org.jivesoftware.smackx.shim.packet.HeadersExtension;
  43. import org.jivesoftware.smackx.xdata.Form;
  44. import org.jxmpp.jid.Jid;

  45. abstract public class Node
  46. {
  47.     protected XMPPConnection con;
  48.     protected String id;
  49.     protected Jid to;
  50.    
  51.     protected ConcurrentHashMap<ItemEventListener<Item>, StanzaListener> itemEventToListenerMap = new ConcurrentHashMap<ItemEventListener<Item>, StanzaListener>();
  52.     protected ConcurrentHashMap<ItemDeleteListener, StanzaListener> itemDeleteToListenerMap = new ConcurrentHashMap<ItemDeleteListener, StanzaListener>();
  53.     protected ConcurrentHashMap<NodeConfigListener, StanzaListener> configEventToListenerMap = new ConcurrentHashMap<NodeConfigListener, StanzaListener>();
  54.    
  55.     /**
  56.      * Construct a node associated to the supplied connection with the specified
  57.      * node id.
  58.      *
  59.      * @param connection The connection the node is associated with
  60.      * @param nodeName The node id
  61.      */
  62.     Node(XMPPConnection connection, String nodeName)
  63.     {
  64.         con = connection;
  65.         id = nodeName;
  66.     }

  67.     /**
  68.      * Some XMPP servers may require a specific service to be addressed on the
  69.      * server.
  70.      *
  71.      *   For example, OpenFire requires the server to be prefixed by <b>pubsub</b>
  72.      */
  73.     void setTo(Jid toAddress)
  74.     {
  75.         to = toAddress;
  76.     }

  77.     /**
  78.      * Get the NodeId
  79.      *
  80.      * @return the node id
  81.      */
  82.     public String getId()
  83.     {
  84.         return id;
  85.     }
  86.     /**
  87.      * Returns a configuration form, from which you can create an answer form to be submitted
  88.      * via the {@link #sendConfigurationForm(Form)}.
  89.      *
  90.      * @return the configuration form
  91.      * @throws XMPPErrorException
  92.      * @throws NoResponseException
  93.      * @throws NotConnectedException
  94.      * @throws InterruptedException
  95.      */
  96.     public ConfigureForm getNodeConfiguration() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  97.     {
  98.         PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(
  99.                         PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER);
  100.         Stanza reply = sendPubsubPacket(pubSub);
  101.         return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER);
  102.     }
  103.    
  104.     /**
  105.      * Update the configuration with the contents of the new {@link Form}
  106.      *
  107.      * @param submitForm
  108.      * @throws XMPPErrorException
  109.      * @throws NoResponseException
  110.      * @throws NotConnectedException
  111.      * @throws InterruptedException
  112.      */
  113.     public void sendConfigurationForm(Form submitForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  114.     {
  115.         PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER,
  116.                         getId(), submitForm), PubSubNamespace.OWNER);
  117.         con.createPacketCollectorAndSend(packet).nextResultOrThrow();
  118.     }
  119.    
  120.     /**
  121.      * Discover node information in standard {@link DiscoverInfo} format.
  122.      *
  123.      * @return The discovery information about the node.
  124.      * @throws XMPPErrorException
  125.      * @throws NoResponseException if there was no response from the server.
  126.      * @throws NotConnectedException
  127.      * @throws InterruptedException
  128.      */
  129.     public DiscoverInfo discoverInfo() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  130.     {
  131.         DiscoverInfo info = new DiscoverInfo();
  132.         info.setTo(to);
  133.         info.setNode(getId());
  134.         return (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow();
  135.     }
  136.    
  137.     /**
  138.      * Get the subscriptions currently associated with this node.
  139.      *
  140.      * @return List of {@link Subscription}
  141.      * @throws XMPPErrorException
  142.      * @throws NoResponseException
  143.      * @throws NotConnectedException
  144.      * @throws InterruptedException
  145.      *
  146.      */
  147.     public List<Subscription> getSubscriptions() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  148.     {
  149.         return getSubscriptions(null, null);
  150.     }

  151.     /**
  152.      * Get the subscriptions currently associated with this node.
  153.      * <p>
  154.      * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
  155.      * {@code returnedExtensions} will be filled with the packet extensions found in the answer.
  156.      * </p>
  157.      *
  158.      * @param additionalExtensions
  159.      * @param returnedExtensions a collection that will be filled with the returned packet
  160.      *        extensions
  161.      * @return List of {@link Subscription}
  162.      * @throws NoResponseException
  163.      * @throws XMPPErrorException
  164.      * @throws NotConnectedException
  165.      * @throws InterruptedException
  166.      */
  167.     public List<Subscription> getSubscriptions(List<ExtensionElement> additionalExtensions, Collection<ExtensionElement> returnedExtensions)
  168.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  169.         return getSubscriptions(additionalExtensions, returnedExtensions, null);
  170.     }

  171.     /**
  172.      * Get the subscriptions currently associated with this node as owner.
  173.      *
  174.      * @return List of {@link Subscription}
  175.      * @throws XMPPErrorException
  176.      * @throws NoResponseException
  177.      * @throws NotConnectedException
  178.      * @throws InterruptedException
  179.      * @see #getSubscriptionsAsOwner(List, Collection)
  180.      * @since 4.1
  181.      */
  182.     public List<Subscription> getSubscriptionsAsOwner() throws NoResponseException, XMPPErrorException,
  183.                     NotConnectedException, InterruptedException {
  184.         return getSubscriptionsAsOwner(null, null);
  185.     }

  186.     /**
  187.      * Get the subscriptions currently associated with this node as owner.
  188.      * <p>
  189.      * Unlike {@link #getSubscriptions(List, Collection)}, which only retrieves the subscriptions of the current entity
  190.      * ("user"), this method returns a list of <b>all</b> subscriptions. This requires the entity to have the sufficient
  191.      * privileges to manage subscriptions.
  192.      * </p>
  193.      * <p>
  194.      * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
  195.      * {@code returnedExtensions} will be filled with the packet extensions found in the answer.
  196.      * </p>
  197.      *
  198.      * @param additionalExtensions
  199.      * @param returnedExtensions a collection that will be filled with the returned packet extensions
  200.      * @return List of {@link Subscription}
  201.      * @throws NoResponseException
  202.      * @throws XMPPErrorException
  203.      * @throws NotConnectedException
  204.      * @throws InterruptedException
  205.      * @see <a href="http://www.xmpp.org/extensions/xep-0060.html#owner-subscriptions-retrieve">XEP-60 ยง 8.8.1 -
  206.      *      Retrieve Subscriptions List</a>
  207.      * @since 4.1
  208.      */
  209.     public List<Subscription> getSubscriptionsAsOwner(List<ExtensionElement> additionalExtensions,
  210.                     Collection<ExtensionElement> returnedExtensions) throws NoResponseException, XMPPErrorException,
  211.                     NotConnectedException, InterruptedException {
  212.         return getSubscriptions(additionalExtensions, returnedExtensions, PubSubNamespace.OWNER);
  213.     }

  214.     private List<Subscription> getSubscriptions(List<ExtensionElement> additionalExtensions,
  215.                     Collection<ExtensionElement> returnedExtensions, PubSubNamespace pubSubNamespace)
  216.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  217.         PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId()), pubSubNamespace);
  218.         if (additionalExtensions != null) {
  219.             for (ExtensionElement pe : additionalExtensions) {
  220.                 pubSub.addExtension(pe);
  221.             }
  222.         }
  223.         PubSub reply = sendPubsubPacket(pubSub);
  224.         if (returnedExtensions != null) {
  225.             returnedExtensions.addAll(reply.getExtensions());
  226.         }
  227.         SubscriptionsExtension subElem = (SubscriptionsExtension) reply.getExtension(PubSubElementType.SUBSCRIPTIONS);
  228.         return subElem.getSubscriptions();
  229.     }

  230.     /**
  231.      * Get the affiliations of this node.
  232.      *
  233.      * @return List of {@link Affiliation}
  234.      * @throws NoResponseException
  235.      * @throws XMPPErrorException
  236.      * @throws NotConnectedException
  237.      * @throws InterruptedException
  238.      */
  239.     public List<Affiliation> getAffiliations() throws NoResponseException, XMPPErrorException,
  240.                     NotConnectedException, InterruptedException {
  241.         return getAffiliations(null, null);
  242.     }

  243.     /**
  244.      * Get the affiliations of this node.
  245.      * <p>
  246.      * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
  247.      * {@code returnedExtensions} will be filled with the packet extensions found in the answer.
  248.      * </p>
  249.      *
  250.      * @param additionalExtensions additional {@code PacketExtensions} add to the request
  251.      * @param returnedExtensions a collection that will be filled with the returned packet
  252.      *        extensions
  253.      * @return List of {@link Affiliation}
  254.      * @throws NoResponseException
  255.      * @throws XMPPErrorException
  256.      * @throws NotConnectedException
  257.      * @throws InterruptedException
  258.      */
  259.     public List<Affiliation> getAffiliations(List<ExtensionElement> additionalExtensions, Collection<ExtensionElement> returnedExtensions)
  260.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {

  261.         PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(PubSubElementType.AFFILIATIONS, getId()));
  262.         if (additionalExtensions != null) {
  263.             for (ExtensionElement pe : additionalExtensions) {
  264.                 pubSub.addExtension(pe);
  265.             }
  266.         }
  267.         PubSub reply = sendPubsubPacket(pubSub);
  268.         if (returnedExtensions != null) {
  269.             returnedExtensions.addAll(reply.getExtensions());
  270.         }
  271.         AffiliationsExtension affilElem = (AffiliationsExtension) reply.getExtension(PubSubElementType.AFFILIATIONS);
  272.         return affilElem.getAffiliations();
  273.     }

  274.     /**
  275.      * The user subscribes to the node using the supplied jid.  The
  276.      * bare jid portion of this one must match the jid for the connection.
  277.      *
  278.      * Please note that the {@link Subscription.State} should be checked
  279.      * on return since more actions may be required by the caller.
  280.      * {@link Subscription.State#pending} - The owner must approve the subscription
  281.      * request before messages will be received.
  282.      * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
  283.      * the caller must configure the subscription before messages will be received.  If it is false
  284.      * the caller can configure it but is not required to do so.
  285.      * @param jid The jid to subscribe as.
  286.      * @return The subscription
  287.      * @throws XMPPErrorException
  288.      * @throws NoResponseException
  289.      * @throws NotConnectedException
  290.      * @throws InterruptedException
  291.      */
  292.     public Subscription subscribe(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  293.     {
  294.         PubSub pubSub = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
  295.         PubSub reply = sendPubsubPacket(pubSub);
  296.         return reply.getExtension(PubSubElementType.SUBSCRIPTION);
  297.     }
  298.    
  299.     /**
  300.      * The user subscribes to the node using the supplied jid and subscription
  301.      * options.  The bare jid portion of this one must match the jid for the
  302.      * connection.
  303.      *
  304.      * Please note that the {@link Subscription.State} should be checked
  305.      * on return since more actions may be required by the caller.
  306.      * {@link Subscription.State#pending} - The owner must approve the subscription
  307.      * request before messages will be received.
  308.      * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
  309.      * the caller must configure the subscription before messages will be received.  If it is false
  310.      * the caller can configure it but is not required to do so.
  311.      * @param jid The jid to subscribe as.
  312.      * @return The subscription
  313.      * @throws XMPPErrorException
  314.      * @throws NoResponseException
  315.      * @throws NotConnectedException
  316.      * @throws InterruptedException
  317.      */
  318.     public Subscription subscribe(String jid, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  319.     {
  320.         PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
  321.         request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
  322.         PubSub reply = PubSubManager.sendPubsubPacket(con, request);
  323.         return reply.getExtension(PubSubElementType.SUBSCRIPTION);
  324.     }

  325.     /**
  326.      * Remove the subscription related to the specified JID.  This will only
  327.      * work if there is only 1 subscription.  If there are multiple subscriptions,
  328.      * use {@link #unsubscribe(String, String)}.
  329.      *
  330.      * @param jid The JID used to subscribe to the node
  331.      * @throws XMPPErrorException
  332.      * @throws NoResponseException
  333.      * @throws NotConnectedException
  334.      * @throws InterruptedException
  335.      *
  336.      */
  337.     public void unsubscribe(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  338.     {
  339.         unsubscribe(jid, null);
  340.     }
  341.    
  342.     /**
  343.      * Remove the specific subscription related to the specified JID.
  344.      *
  345.      * @param jid The JID used to subscribe to the node
  346.      * @param subscriptionId The id of the subscription being removed
  347.      * @throws XMPPErrorException
  348.      * @throws NoResponseException
  349.      * @throws NotConnectedException
  350.      * @throws InterruptedException
  351.      */
  352.     public void unsubscribe(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  353.     {
  354.         sendPubsubPacket(createPubsubPacket(Type.set, new UnsubscribeExtension(jid, getId(), subscriptionId)));
  355.     }

  356.     /**
  357.      * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted
  358.      * via the {@link #sendConfigurationForm(Form)}.
  359.      *
  360.      * @return A subscription options form
  361.      * @throws XMPPErrorException
  362.      * @throws NoResponseException
  363.      * @throws NotConnectedException
  364.      * @throws InterruptedException
  365.      */
  366.     public SubscribeForm getSubscriptionOptions(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  367.     {
  368.         return getSubscriptionOptions(jid, null);
  369.     }


  370.     /**
  371.      * Get the options for configuring the specified subscription.
  372.      *
  373.      * @param jid JID the subscription is registered under
  374.      * @param subscriptionId The subscription id
  375.      *
  376.      * @return The subscription option form
  377.      * @throws XMPPErrorException
  378.      * @throws NoResponseException
  379.      * @throws NotConnectedException
  380.      * @throws InterruptedException
  381.      *
  382.      */
  383.     public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  384.     {
  385.         PubSub packet = sendPubsubPacket(createPubsubPacket(Type.get, new OptionsExtension(jid, getId(), subscriptionId)));
  386.         FormNode ext = packet.getExtension(PubSubElementType.OPTIONS);
  387.         return new SubscribeForm(ext.getForm());
  388.     }

  389.     /**
  390.      * Register a listener for item publication events.  This
  391.      * listener will get called whenever an item is published to
  392.      * this node.
  393.      *
  394.      * @param listener The handler for the event
  395.      */
  396.     @SuppressWarnings("unchecked")
  397.     public void addItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener)
  398.     {
  399.         StanzaListener conListener = new ItemEventTranslator(listener);
  400.         itemEventToListenerMap.put(listener, conListener);
  401.         con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
  402.     }

  403.     /**
  404.      * Unregister a listener for publication events.
  405.      *
  406.      * @param listener The handler to unregister
  407.      */
  408.     public void removeItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener)
  409.     {
  410.         StanzaListener conListener = itemEventToListenerMap.remove(listener);
  411.        
  412.         if (conListener != null)
  413.             con.removeSyncStanzaListener(conListener);
  414.     }

  415.     /**
  416.      * Register a listener for configuration events.  This listener
  417.      * will get called whenever the node's configuration changes.
  418.      *
  419.      * @param listener The handler for the event
  420.      */
  421.     public void addConfigurationListener(NodeConfigListener listener)
  422.     {
  423.         StanzaListener conListener = new NodeConfigTranslator(listener);
  424.         configEventToListenerMap.put(listener, conListener);
  425.         con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
  426.     }

  427.     /**
  428.      * Unregister a listener for configuration events.
  429.      *
  430.      * @param listener The handler to unregister
  431.      */
  432.     public void removeConfigurationListener(NodeConfigListener listener)
  433.     {
  434.         StanzaListener conListener = configEventToListenerMap .remove(listener);
  435.        
  436.         if (conListener != null)
  437.             con.removeSyncStanzaListener(conListener);
  438.     }
  439.    
  440.     /**
  441.      * Register an listener for item delete events.  This listener
  442.      * gets called whenever an item is deleted from the node.
  443.      *
  444.      * @param listener The handler for the event
  445.      */
  446.     public void addItemDeleteListener(ItemDeleteListener listener)
  447.     {
  448.         StanzaListener delListener = new ItemDeleteTranslator(listener);
  449.         itemDeleteToListenerMap.put(listener, delListener);
  450.         EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
  451.         EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
  452.        
  453.         con.addSyncStanzaListener(delListener, new OrFilter(deleteItem, purge));
  454.     }

  455.     /**
  456.      * Unregister a listener for item delete events.
  457.      *
  458.      * @param listener The handler to unregister
  459.      */
  460.     public void removeItemDeleteListener(ItemDeleteListener listener)
  461.     {
  462.         StanzaListener conListener = itemDeleteToListenerMap .remove(listener);
  463.        
  464.         if (conListener != null)
  465.             con.removeSyncStanzaListener(conListener);
  466.     }

  467.     @Override
  468.     public String toString()
  469.     {
  470.         return super.toString() + " " + getClass().getName() + " id: " + id;
  471.     }
  472.    
  473.     protected PubSub createPubsubPacket(Type type, ExtensionElement ext)
  474.     {
  475.         return createPubsubPacket(type, ext, null);
  476.     }
  477.    
  478.     protected PubSub createPubsubPacket(Type type, ExtensionElement ext, PubSubNamespace ns)
  479.     {
  480.         return PubSub.createPubsubPacket(to, type, ext, ns);
  481.     }

  482.     protected PubSub sendPubsubPacket(PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException
  483.     {
  484.         return PubSubManager.sendPubsubPacket(con, packet);
  485.     }


  486.     private static List<String> getSubscriptionIds(Stanza packet)
  487.     {
  488.         HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim");
  489.         List<String> values = null;
  490.        
  491.         if (headers != null)
  492.         {
  493.             values = new ArrayList<String>(headers.getHeaders().size());
  494.            
  495.             for (Header header : headers.getHeaders())
  496.             {
  497.                 values.add(header.getValue());
  498.             }
  499.         }
  500.         return values;
  501.     }

  502.     /**
  503.      * This class translates low level item publication events into api level objects for
  504.      * user consumption.
  505.      *
  506.      * @author Robin Collier
  507.      */
  508.     public class ItemEventTranslator implements StanzaListener
  509.     {
  510.         @SuppressWarnings("rawtypes")
  511.         private ItemEventListener listener;

  512.         public ItemEventTranslator(@SuppressWarnings("rawtypes") ItemEventListener eventListener)
  513.         {
  514.             listener = eventListener;
  515.         }
  516.        
  517.         @SuppressWarnings({ "rawtypes", "unchecked" })
  518.         public void processPacket(Stanza packet)
  519.         {
  520.             EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
  521.             ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
  522.             ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List<Item>)itemsElem.getItems(), getSubscriptionIds(packet), DelayInformationManager.getDelayTimestamp(packet));
  523.             listener.handlePublishedItems(eventItems);
  524.         }
  525.     }

  526.     /**
  527.      * This class translates low level item deletion events into api level objects for
  528.      * user consumption.
  529.      *
  530.      * @author Robin Collier
  531.      */
  532.     public class ItemDeleteTranslator implements StanzaListener
  533.     {
  534.         private ItemDeleteListener listener;

  535.         public ItemDeleteTranslator(ItemDeleteListener eventListener)
  536.         {
  537.             listener = eventListener;
  538.         }
  539.        
  540.         public void processPacket(Stanza packet)
  541.         {
  542.             EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
  543.            
  544.             List<ExtensionElement> extList = event.getExtensions();
  545.            
  546.             if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName()))
  547.             {
  548.                 listener.handlePurge();
  549.             }
  550.             else
  551.             {
  552.                 ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
  553.                 @SuppressWarnings("unchecked")
  554.                 Collection<RetractItem> pubItems = (Collection<RetractItem>) itemsElem.getItems();
  555.                 List<String> items = new ArrayList<String>(pubItems.size());

  556.                 for (RetractItem item : pubItems)
  557.                 {
  558.                     items.add(item.getId());
  559.                 }

  560.                 ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet));
  561.                 listener.handleDeletedItems(eventItems);
  562.             }
  563.         }
  564.     }
  565.    
  566.     /**
  567.      * This class translates low level node configuration events into api level objects for
  568.      * user consumption.
  569.      *
  570.      * @author Robin Collier
  571.      */
  572.     public class NodeConfigTranslator implements StanzaListener
  573.     {
  574.         private NodeConfigListener listener;

  575.         public NodeConfigTranslator(NodeConfigListener eventListener)
  576.         {
  577.             listener = eventListener;
  578.         }
  579.        
  580.         public void processPacket(Stanza packet)
  581.         {
  582.             EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
  583.             ConfigurationEvent config = (ConfigurationEvent)event.getEvent();

  584.             listener.handleNodeConfiguration(config);
  585.         }
  586.     }

  587.     /**
  588.      * Filter for {@link StanzaListener} to filter out events not specific to the
  589.      * event type expected for this node.
  590.      *
  591.      * @author Robin Collier
  592.      */
  593.     class EventContentFilter implements StanzaFilter
  594.     {
  595.         private String firstElement;
  596.         private String secondElement;
  597.        
  598.         EventContentFilter(String elementName)
  599.         {
  600.             firstElement = elementName;
  601.         }

  602.         EventContentFilter(String firstLevelEelement, String secondLevelElement)
  603.         {
  604.             firstElement = firstLevelEelement;
  605.             secondElement = secondLevelElement;
  606.         }

  607.         public boolean accept(Stanza packet)
  608.         {
  609.             if (!(packet instanceof Message))
  610.                 return false;

  611.             EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
  612.            
  613.             if (event == null)
  614.                 return false;

  615.             NodeExtension embedEvent = event.getEvent();
  616.            
  617.             if (embedEvent == null)
  618.                 return false;
  619.            
  620.             if (embedEvent.getElementName().equals(firstElement))
  621.             {
  622.                 if (!embedEvent.getNode().equals(getId()))
  623.                     return false;
  624.                
  625.                 if (secondElement == null)
  626.                     return true;
  627.                
  628.                 if (embedEvent instanceof EmbeddedPacketExtension)
  629.                 {
  630.                     List<ExtensionElement> secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions();
  631.                    
  632.                     if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement))
  633.                         return true;
  634.                 }
  635.             }
  636.             return false;
  637.         }
  638.     }
  639. }