Node.java
- /**
- *
- * Copyright 2009 Robin Collier.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package org.jivesoftware.smackx.pubsub;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.List;
- import java.util.concurrent.ConcurrentHashMap;
- import org.jivesoftware.smack.SmackException.NoResponseException;
- import org.jivesoftware.smack.SmackException.NotConnectedException;
- import org.jivesoftware.smack.StanzaListener;
- import org.jivesoftware.smack.XMPPConnection;
- import org.jivesoftware.smack.XMPPException.XMPPErrorException;
- import org.jivesoftware.smack.filter.FlexibleStanzaTypeFilter;
- import org.jivesoftware.smack.filter.OrFilter;
- import org.jivesoftware.smack.packet.IQ;
- import org.jivesoftware.smack.packet.Message;
- import org.jivesoftware.smack.packet.Stanza;
- import org.jivesoftware.smack.packet.XmlElement;
- import org.jivesoftware.smackx.delay.DelayInformationManager;
- import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
- import org.jivesoftware.smackx.pubsub.Affiliation.AffiliationNamespace;
- import org.jivesoftware.smackx.pubsub.SubscriptionsExtension.SubscriptionsNamespace;
- import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
- import org.jivesoftware.smackx.pubsub.form.FillableConfigureForm;
- import org.jivesoftware.smackx.pubsub.form.FillableSubscribeForm;
- import org.jivesoftware.smackx.pubsub.form.SubscribeForm;
- import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener;
- import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
- import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener;
- import org.jivesoftware.smackx.pubsub.packet.PubSub;
- import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
- import org.jivesoftware.smackx.pubsub.util.NodeUtils;
- import org.jivesoftware.smackx.shim.packet.Header;
- import org.jivesoftware.smackx.shim.packet.HeadersExtension;
- import org.jivesoftware.smackx.xdata.packet.DataForm;
- import org.jxmpp.jid.Jid;
- public abstract class Node {
- protected final PubSubManager pubSubManager;
- protected final String id;
- protected ConcurrentHashMap<ItemEventListener<Item>, StanzaListener> itemEventToListenerMap = new ConcurrentHashMap<>();
- protected ConcurrentHashMap<ItemDeleteListener, StanzaListener> itemDeleteToListenerMap = new ConcurrentHashMap<>();
- protected ConcurrentHashMap<NodeConfigListener, StanzaListener> configEventToListenerMap = new ConcurrentHashMap<>();
- /**
- * Construct a node associated to the supplied connection with the specified
- * node id.
- *
- * @param pubSubManager The PubSubManager for the connection the node is associated with
- * @param nodeId The node id
- */
- Node(PubSubManager pubSubManager, String nodeId) {
- this.pubSubManager = pubSubManager;
- id = nodeId;
- }
- /**
- * Get the NodeId.
- *
- * @return the node id
- */
- public String getId() {
- return id;
- }
- /**
- * Returns a configuration form, from which you can create an answer form to be submitted
- * via the {@link #sendConfigurationForm(FillableConfigureForm)}.
- *
- * @return the configuration form
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public ConfigureForm getNodeConfiguration() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSub pubSub = createPubsubPacket(IQ.Type.get, new NodeExtension(
- PubSubElementType.CONFIGURE_OWNER, getId()));
- Stanza reply = sendPubsubPacket(pubSub);
- return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER);
- }
- /**
- * Update the configuration with the contents of the new {@link FillableConfigureForm}.
- *
- * @param configureForm the filled node configuration form with the nodes new configuration.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public void sendConfigurationForm(FillableConfigureForm configureForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSub packet = createPubsubPacket(IQ.Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER,
- getId(), configureForm.getDataFormToSubmit()));
- pubSubManager.getConnection().sendIqRequestAndWaitForResponse(packet);
- }
- /**
- * Discover node information in standard {@link DiscoverInfo} format.
- *
- * @return The discovery information about the node.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the server.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public DiscoverInfo discoverInfo() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- XMPPConnection connection = pubSubManager.getConnection();
- DiscoverInfo discoverInfoRequest = DiscoverInfo.builder(connection)
- .to(pubSubManager.getServiceJid())
- .setNode(getId())
- .build();
- return connection.sendIqRequestAndWaitForResponse(discoverInfoRequest);
- }
- /**
- * Get the subscriptions currently associated with this node.
- *
- * @return List of {@link Subscription}
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- *
- */
- public List<Subscription> getSubscriptions() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getSubscriptions(null, null);
- }
- /**
- * Get the subscriptions currently associated with this node.
- * <p>
- * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
- * {@code returnedExtensions} will be filled with the stanza extensions found in the answer.
- * </p>
- *
- * @param additionalExtensions TODO javadoc me please
- * @param returnedExtensions a collection that will be filled with the returned packet
- * extensions
- * @return List of {@link Subscription}
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public List<Subscription> getSubscriptions(List<XmlElement> additionalExtensions, Collection<XmlElement> returnedExtensions)
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getSubscriptions(SubscriptionsNamespace.basic, additionalExtensions, returnedExtensions);
- }
- /**
- * Get the subscriptions currently associated with this node as owner.
- *
- * @return List of {@link Subscription}
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see #getSubscriptionsAsOwner(List, Collection)
- * @since 4.1
- */
- public List<Subscription> getSubscriptionsAsOwner() throws NoResponseException, XMPPErrorException,
- NotConnectedException, InterruptedException {
- return getSubscriptionsAsOwner(null, null);
- }
- /**
- * Get the subscriptions currently associated with this node as owner.
- * <p>
- * Unlike {@link #getSubscriptions(List, Collection)}, which only retrieves the subscriptions of the current entity
- * ("user"), this method returns a list of <b>all</b> subscriptions. This requires the entity to have the sufficient
- * privileges to manage subscriptions.
- * </p>
- * <p>
- * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
- * {@code returnedExtensions} will be filled with the stanza extensions found in the answer.
- * </p>
- *
- * @param additionalExtensions TODO javadoc me please
- * @param returnedExtensions a collection that will be filled with the returned stanza extensions
- * @return List of {@link Subscription}
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see <a href="http://www.xmpp.org/extensions/xep-0060.html#owner-subscriptions-retrieve">XEP-60 § 8.8.1 -
- * Retrieve Subscriptions List</a>
- * @since 4.1
- */
- public List<Subscription> getSubscriptionsAsOwner(List<XmlElement> additionalExtensions,
- Collection<XmlElement> returnedExtensions) throws NoResponseException, XMPPErrorException,
- NotConnectedException, InterruptedException {
- return getSubscriptions(SubscriptionsNamespace.owner, additionalExtensions, returnedExtensions);
- }
- private List<Subscription> getSubscriptions(SubscriptionsNamespace subscriptionsNamespace, List<XmlElement> additionalExtensions,
- Collection<XmlElement> returnedExtensions)
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSubElementType pubSubElementType = subscriptionsNamespace.type;
- PubSub pubSub = createPubsubPacket(IQ.Type.get, new NodeExtension(pubSubElementType, getId()));
- if (additionalExtensions != null) {
- for (XmlElement pe : additionalExtensions) {
- pubSub.addExtension(pe);
- }
- }
- PubSub reply = sendPubsubPacket(pubSub);
- if (returnedExtensions != null) {
- returnedExtensions.addAll(reply.getExtensions());
- }
- SubscriptionsExtension subElem = reply.getExtension(pubSubElementType);
- return subElem.getSubscriptions();
- }
- /**
- * Modify the subscriptions for this PubSub node as owner.
- * <p>
- * Note that the subscriptions are _not_ checked against the existing subscriptions
- * since these are not cached (and indeed could change asynchronously)
- * </p>
- *
- * @param changedSubs subscriptions that have changed
- * @return <code>null</code> or a PubSub stanza with additional information on success.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see <a href="https://xmpp.org/extensions/xep-0060.html#owner-subscriptions-modify">XEP-60 § 8.8.2 Modify Subscriptions</a>
- * @since 4.3
- */
- public PubSub modifySubscriptionsAsOwner(List<Subscription> changedSubs)
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSub pubSub = createPubsubPacket(IQ.Type.set,
- new SubscriptionsExtension(SubscriptionsNamespace.owner, getId(), changedSubs));
- return sendPubsubPacket(pubSub);
- }
- /**
- * Get the affiliations of this node.
- *
- * @return List of {@link Affiliation}
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public List<Affiliation> getAffiliations() throws NoResponseException, XMPPErrorException,
- NotConnectedException, InterruptedException {
- return getAffiliations(null, null);
- }
- /**
- * Get the affiliations of this node.
- * <p>
- * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
- * {@code returnedExtensions} will be filled with the stanza extensions found in the answer.
- * </p>
- *
- * @param additionalExtensions additional {@code PacketExtensions} add to the request
- * @param returnedExtensions a collection that will be filled with the returned packet
- * extensions
- * @return List of {@link Affiliation}
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public List<Affiliation> getAffiliations(List<XmlElement> additionalExtensions, Collection<XmlElement> returnedExtensions)
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getAffiliations(AffiliationNamespace.basic, additionalExtensions, returnedExtensions);
- }
- /**
- * Retrieve the affiliation list for this node as owner.
- *
- * @return list of entities whose affiliation is not 'none'.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see #getAffiliations(List, Collection)
- * @since 4.2
- */
- public List<Affiliation> getAffiliationsAsOwner()
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getAffiliationsAsOwner(null, null);
- }
- /**
- * Retrieve the affiliation list for this node as owner.
- * <p>
- * Note that this is an <b>optional</b> PubSub feature ('pubsub#modify-affiliations').
- * </p>
- *
- * @param additionalExtensions optional additional extension elements add to the request.
- * @param returnedExtensions an optional collection that will be filled with the returned
- * extension elements.
- * @return list of entities whose affiliation is not 'none'.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see <a href="http://www.xmpp.org/extensions/xep-0060.html#owner-affiliations-retrieve">XEP-60 § 8.9.1 Retrieve Affiliations List</a>
- * @since 4.2
- */
- public List<Affiliation> getAffiliationsAsOwner(List<XmlElement> additionalExtensions, Collection<XmlElement> returnedExtensions)
- throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getAffiliations(AffiliationNamespace.owner, additionalExtensions, returnedExtensions);
- }
- private List<Affiliation> getAffiliations(AffiliationNamespace affiliationsNamespace, List<XmlElement> additionalExtensions,
- Collection<XmlElement> returnedExtensions) throws NoResponseException, XMPPErrorException,
- NotConnectedException, InterruptedException {
- PubSubElementType pubSubElementType = affiliationsNamespace.type;
- PubSub pubSub = createPubsubPacket(IQ.Type.get, new NodeExtension(pubSubElementType, getId()));
- if (additionalExtensions != null) {
- for (XmlElement pe : additionalExtensions) {
- pubSub.addExtension(pe);
- }
- }
- PubSub reply = sendPubsubPacket(pubSub);
- if (returnedExtensions != null) {
- returnedExtensions.addAll(reply.getExtensions());
- }
- AffiliationsExtension affilElem = reply.getExtension(pubSubElementType);
- return affilElem.getAffiliations();
- }
- /**
- * Modify the affiliations for this PubSub node as owner. The {@link Affiliation}s given must be created with the
- * {@link Affiliation#Affiliation(org.jxmpp.jid.BareJid, Affiliation.Type)} constructor.
- * <p>
- * Note that this is an <b>optional</b> PubSub feature ('pubsub#modify-affiliations').
- * </p>
- *
- * @param affiliations TODO javadoc me please
- * @return <code>null</code> or a PubSub stanza with additional information on success.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- * @see <a href="http://www.xmpp.org/extensions/xep-0060.html#owner-affiliations-modify">XEP-60 § 8.9.2 Modify Affiliation</a>
- * @since 4.2
- */
- public PubSub modifyAffiliationAsOwner(List<Affiliation> affiliations) throws NoResponseException,
- XMPPErrorException, NotConnectedException, InterruptedException {
- for (Affiliation affiliation : affiliations) {
- if (affiliation.getPubSubNamespace() != PubSubNamespace.owner) {
- throw new IllegalArgumentException("Must use Affiliation(BareJid, Type) affiliations");
- }
- }
- PubSub pubSub = createPubsubPacket(IQ.Type.set, new AffiliationsExtension(AffiliationNamespace.owner, affiliations, getId()));
- return sendPubsubPacket(pubSub);
- }
- /**
- * The user subscribes to the node using the supplied jid. The
- * bare jid portion of this one must match the jid for the connection.
- *
- * Please note that the {@link Subscription.State} should be checked
- * on return since more actions may be required by the caller.
- * {@link Subscription.State#pending} - The owner must approve the subscription
- * request before messages will be received.
- * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
- * the caller must configure the subscription before messages will be received. If it is false
- * the caller can configure it but is not required to do so.
- * @param jid The jid to subscribe as.
- * @return The subscription
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public Subscription subscribe(Jid jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSub pubSub = createPubsubPacket(IQ.Type.set, new SubscribeExtension(jid, getId()));
- PubSub reply = sendPubsubPacket(pubSub);
- return reply.getExtension(PubSubElementType.SUBSCRIPTION);
- }
- /**
- * The user subscribes to the node using the supplied jid and subscription
- * options. The bare jid portion of this one must match the jid for the
- * connection.
- *
- * Please note that the {@link Subscription.State} should be checked
- * on return since more actions may be required by the caller.
- * {@link Subscription.State#pending} - The owner must approve the subscription
- * request before messages will be received.
- * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true,
- * the caller must configure the subscription before messages will be received. If it is false
- * the caller can configure it but is not required to do so.
- *
- * @param jid The jid to subscribe as.
- * @param subForm TODO javadoc me please
- *
- * @return The subscription
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public Subscription subscribe(Jid jid, FillableSubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- DataForm submitForm = subForm.getDataFormToSubmit();
- PubSub request = createPubsubPacket(IQ.Type.set, new SubscribeExtension(jid, getId()));
- request.addExtension(new FormNode(FormNodeType.OPTIONS, submitForm));
- PubSub reply = sendPubsubPacket(request);
- return reply.getExtension(PubSubElementType.SUBSCRIPTION);
- }
- /**
- * Remove the subscription related to the specified JID. This will only
- * work if there is only 1 subscription. If there are multiple subscriptions,
- * use {@link #unsubscribe(String, String)}.
- *
- * @param jid The JID used to subscribe to the node
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- *
- */
- public void unsubscribe(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- unsubscribe(jid, null);
- }
- /**
- * Remove the specific subscription related to the specified JID.
- *
- * @param jid The JID used to subscribe to the node
- * @param subscriptionId The id of the subscription being removed
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public void unsubscribe(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- sendPubsubPacket(createPubsubPacket(IQ.Type.set, new UnsubscribeExtension(jid, getId(), subscriptionId)));
- }
- /**
- * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted
- * via the {@link #sendConfigurationForm(FillableConfigureForm)}.
- *
- * @param jid TODO javadoc me please
- *
- * @return A subscription options form
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- */
- public SubscribeForm getSubscriptionOptions(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return getSubscriptionOptions(jid, null);
- }
- /**
- * Get the options for configuring the specified subscription.
- *
- * @param jid JID the subscription is registered under
- * @param subscriptionId The subscription id
- *
- * @return The subscription option form
- * @throws XMPPErrorException if there was an XMPP error returned.
- * @throws NoResponseException if there was no response from the remote entity.
- * @throws NotConnectedException if the XMPP connection is not connected.
- * @throws InterruptedException if the calling thread was interrupted.
- *
- */
- public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- PubSub packet = sendPubsubPacket(createPubsubPacket(IQ.Type.get, new OptionsExtension(jid, getId(), subscriptionId)));
- FormNode ext = packet.getExtension(PubSubElementType.OPTIONS);
- return new SubscribeForm(ext.getForm());
- }
- /**
- * Register a listener for item publication events. This
- * listener will get called whenever an item is published to
- * this node.
- *
- * @param listener The handler for the event
- */
- @SuppressWarnings("unchecked")
- public void addItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener) {
- StanzaListener conListener = new ItemEventTranslator(listener);
- itemEventToListenerMap.put(listener, conListener);
- pubSubManager.getConnection().addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
- }
- /**
- * Unregister a listener for publication events.
- *
- * @param listener The handler to unregister
- */
- public void removeItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener) {
- StanzaListener conListener = itemEventToListenerMap.remove(listener);
- if (conListener != null)
- pubSubManager.getConnection().removeSyncStanzaListener(conListener);
- }
- /**
- * Register a listener for configuration events. This listener
- * will get called whenever the node's configuration changes.
- *
- * @param listener The handler for the event
- */
- public void addConfigurationListener(NodeConfigListener listener) {
- StanzaListener conListener = new NodeConfigTranslator(listener);
- configEventToListenerMap.put(listener, conListener);
- pubSubManager.getConnection().addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
- }
- /**
- * Unregister a listener for configuration events.
- *
- * @param listener The handler to unregister
- */
- public void removeConfigurationListener(NodeConfigListener listener) {
- StanzaListener conListener = configEventToListenerMap .remove(listener);
- if (conListener != null)
- pubSubManager.getConnection().removeSyncStanzaListener(conListener);
- }
- /**
- * Register an listener for item delete events. This listener
- * gets called whenever an item is deleted from the node.
- *
- * @param listener The handler for the event
- */
- public void addItemDeleteListener(ItemDeleteListener listener) {
- StanzaListener delListener = new ItemDeleteTranslator(listener);
- itemDeleteToListenerMap.put(listener, delListener);
- EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
- EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
- // TODO: Use AsyncButOrdered (with Node as Key?)
- pubSubManager.getConnection().addSyncStanzaListener(delListener, new OrFilter(deleteItem, purge));
- }
- /**
- * Unregister a listener for item delete events.
- *
- * @param listener The handler to unregister
- */
- public void removeItemDeleteListener(ItemDeleteListener listener) {
- StanzaListener conListener = itemDeleteToListenerMap .remove(listener);
- if (conListener != null)
- pubSubManager.getConnection().removeSyncStanzaListener(conListener);
- }
- @Override
- public String toString() {
- return super.toString() + " " + getClass().getName() + " id: " + id;
- }
- protected PubSub createPubsubPacket(IQ.Type type, NodeExtension ext) {
- return PubSub.createPubsubPacket(pubSubManager.getServiceJid(), type, ext);
- }
- protected PubSub sendPubsubPacket(PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
- return pubSubManager.sendPubsubPacket(packet);
- }
- private static List<String> getSubscriptionIds(Stanza packet) {
- HeadersExtension headers = packet.getExtension(HeadersExtension.class);
- List<String> values = null;
- if (headers != null) {
- values = new ArrayList<>(headers.getHeaders().size());
- for (Header header : headers.getHeaders()) {
- values.add(header.getValue());
- }
- }
- return values;
- }
- /**
- * This class translates low level item publication events into api level objects for
- * user consumption.
- *
- * @author Robin Collier
- */
- public static class ItemEventTranslator implements StanzaListener {
- @SuppressWarnings("rawtypes")
- private final ItemEventListener listener;
- public ItemEventTranslator(@SuppressWarnings("rawtypes") ItemEventListener eventListener) {
- listener = eventListener;
- }
- @Override
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public void processStanza(Stanza packet) {
- EventElement event = (EventElement) packet.getExtensionElement("event", PubSubNamespace.event.getXmlns());
- ItemsExtension itemsElem = (ItemsExtension) event.getEvent();
- ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), itemsElem.getItems(), getSubscriptionIds(packet), DelayInformationManager.getDelayTimestamp(packet));
- // TODO: Use AsyncButOrdered (with Node as Key?)
- listener.handlePublishedItems(eventItems);
- }
- }
- /**
- * This class translates low level item deletion events into api level objects for
- * user consumption.
- *
- * @author Robin Collier
- */
- public static class ItemDeleteTranslator implements StanzaListener {
- private final ItemDeleteListener listener;
- public ItemDeleteTranslator(ItemDeleteListener eventListener) {
- listener = eventListener;
- }
- @Override
- public void processStanza(Stanza packet) {
- // CHECKSTYLE:OFF
- EventElement event = (EventElement) packet.getExtensionElement("event", PubSubNamespace.event.getXmlns());
- List<XmlElement> extList = event.getExtensions();
- if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName())) {
- listener.handlePurge();
- }
- else {
- ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
- @SuppressWarnings("unchecked")
- Collection<RetractItem> pubItems = (Collection<RetractItem>) itemsElem.getItems();
- List<String> items = new ArrayList<>(pubItems.size());
- for (RetractItem item : pubItems) {
- items.add(item.getId());
- }
- ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet));
- listener.handleDeletedItems(eventItems);
- }
- // CHECKSTYLE:ON
- }
- }
- /**
- * This class translates low level node configuration events into api level objects for
- * user consumption.
- *
- * @author Robin Collier
- */
- public static class NodeConfigTranslator implements StanzaListener {
- private final NodeConfigListener listener;
- public NodeConfigTranslator(NodeConfigListener eventListener) {
- listener = eventListener;
- }
- @Override
- public void processStanza(Stanza packet) {
- EventElement event = (EventElement) packet.getExtensionElement("event", PubSubNamespace.event.getXmlns());
- ConfigurationEvent config = (ConfigurationEvent) event.getEvent();
- // TODO: Use AsyncButOrdered (with Node as Key?)
- listener.handleNodeConfiguration(config);
- }
- }
- /**
- * Filter for {@link StanzaListener} to filter out events not specific to the
- * event type expected for this node.
- *
- * @author Robin Collier
- */
- class EventContentFilter extends FlexibleStanzaTypeFilter<Message> {
- private final String firstElement;
- private final String secondElement;
- private final boolean allowEmpty;
- EventContentFilter(String elementName) {
- this(elementName, null);
- }
- EventContentFilter(String firstLevelElement, String secondLevelElement) {
- firstElement = firstLevelElement;
- secondElement = secondLevelElement;
- allowEmpty = firstElement.equals(EventElementType.items.toString())
- && "item".equals(secondLevelElement);
- }
- @Override
- public boolean acceptSpecific(Message message) {
- EventElement event = EventElement.from(message);
- if (event == null)
- return false;
- NodeExtension embedEvent = event.getEvent();
- if (embedEvent == null)
- return false;
- if (embedEvent.getElementName().equals(firstElement)) {
- if (!embedEvent.getNode().equals(getId()))
- return false;
- if (secondElement == null)
- return true;
- if (embedEvent instanceof EmbeddedPacketExtension) {
- List<XmlElement> secondLevelList = ((EmbeddedPacketExtension) embedEvent).getExtensions();
- // XEP-0060 allows no elements on second level for notifications. See schema or
- // for example § 4.3:
- // "although event notifications MUST include an empty <items/> element;"
- if (allowEmpty && secondLevelList.isEmpty()) {
- return true;
- }
- if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement))
- return true;
- }
- }
- return false;
- }
- }
- }