aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smackx/pubsub
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smackx/pubsub')
-rw-r--r--src/org/jivesoftware/smackx/pubsub/AccessModel.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/Affiliation.java111
-rw-r--r--src/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java91
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java32
-rw-r--r--src/org/jivesoftware/smackx/pubsub/CollectionNode.java31
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java56
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ConfigureForm.java709
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java218
-rw-r--r--src/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java45
-rw-r--r--src/org/jivesoftware/smackx/pubsub/EventElement.java74
-rw-r--r--src/org/jivesoftware/smackx/pubsub/EventElementType.java41
-rw-r--r--src/org/jivesoftware/smackx/pubsub/FormNode.java99
-rw-r--r--src/org/jivesoftware/smackx/pubsub/FormNodeType.java50
-rw-r--r--src/org/jivesoftware/smackx/pubsub/FormType.java26
-rw-r--r--src/org/jivesoftware/smackx/pubsub/GetItemsRequest.java85
-rw-r--r--src/org/jivesoftware/smackx/pubsub/Item.java132
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java62
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java123
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ItemReply.java29
-rw-r--r--src/org/jivesoftware/smackx/pubsub/ItemsExtension.java196
-rw-r--r--src/org/jivesoftware/smackx/pubsub/LeafNode.java352
-rw-r--r--src/org/jivesoftware/smackx/pubsub/Node.java541
-rw-r--r--src/org/jivesoftware/smackx/pubsub/NodeEvent.java35
-rw-r--r--src/org/jivesoftware/smackx/pubsub/NodeExtension.java85
-rw-r--r--src/org/jivesoftware/smackx/pubsub/NodeType.java25
-rw-r--r--src/org/jivesoftware/smackx/pubsub/OptionsExtension.java72
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PayloadItem.java138
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PresenceState.java25
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PubSubElementType.java80
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PubSubManager.java329
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PublishItem.java70
-rw-r--r--src/org/jivesoftware/smackx/pubsub/PublishModel.java32
-rw-r--r--src/org/jivesoftware/smackx/pubsub/RetractItem.java59
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SimplePayload.java65
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SubscribeExtension.java60
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SubscribeForm.java241
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java99
-rw-r--r--src/org/jivesoftware/smackx/pubsub/Subscription.java160
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java75
-rw-r--r--src/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java96
-rw-r--r--src/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java73
-rw-r--r--src/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java41
-rw-r--r--src/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java36
-rw-r--r--src/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java35
-rw-r--r--src/org/jivesoftware/smackx/pubsub/packet/PubSub.java106
-rw-r--r--src/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java63
-rw-r--r--src/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java63
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java37
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java42
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/EventProvider.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java39
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java92
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java62
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java37
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java52
-rw-r--r--src/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java38
-rw-r--r--src/org/jivesoftware/smackx/pubsub/util/NodeUtils.java43
-rw-r--r--src/org/jivesoftware/smackx/pubsub/util/XmlUtils.java35
61 files changed, 5933 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smackx/pubsub/AccessModel.java b/src/org/jivesoftware/smackx/pubsub/AccessModel.java
new file mode 100644
index 0000000..c1fa546
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/AccessModel.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * This enumeration represents the access models for the pubsub node
+ * as defined in the pubsub specification section <a href="http://xmpp.org/extensions/xep-0060.html#registrar-formtypes-config">16.4.3</a>
+ *
+ * @author Robin Collier
+ */
+public enum AccessModel
+{
+ /** Anyone may subscribe and retrieve items */
+ open,
+
+ /** Subscription request must be approved and only subscribers may retrieve items */
+ authorize,
+
+ /** Anyone with a presence subscription of both or from may subscribe and retrieve items */
+ presence,
+
+ /** Anyone in the specified roster group(s) may subscribe and retrieve items */
+ roster,
+
+ /** Only those on a whitelist may subscribe and retrieve items */
+ whitelist;
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/Affiliation.java b/src/org/jivesoftware/smackx/pubsub/Affiliation.java
new file mode 100644
index 0000000..d55534d
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/Affiliation.java
@@ -0,0 +1,111 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents a affiliation between a user and a node, where the {@link #type} defines
+ * the type of affiliation.
+ *
+ * Affiliations are retrieved from the {@link PubSubManager#getAffiliations()} method, which
+ * gets affiliations for the calling user, based on the identity that is associated with
+ * the {@link Connection}.
+ *
+ * @author Robin Collier
+ */
+public class Affiliation implements PacketExtension
+{
+ protected String jid;
+ protected String node;
+ protected Type type;
+
+ public enum Type
+ {
+ member, none, outcast, owner, publisher
+ }
+
+ /**
+ * Constructs an affiliation.
+ *
+ * @param jid The JID with affiliation.
+ * @param affiliation The type of affiliation.
+ */
+ public Affiliation(String jid, Type affiliation)
+ {
+ this(jid, null, affiliation);
+ }
+
+ /**
+ * Constructs an affiliation.
+ *
+ * @param jid The JID with affiliation.
+ * @param node The node with affiliation.
+ * @param affiliation The type of affiliation.
+ */
+ public Affiliation(String jid, String node, Type affiliation)
+ {
+ this.jid = jid;
+ this.node = node;
+ type = affiliation;
+ }
+
+ public String getJid()
+ {
+ return jid;
+ }
+
+ public String getNode()
+ {
+ return node;
+ }
+
+ public Type getType()
+ {
+ return type;
+ }
+
+ public String getElementName()
+ {
+ return "affiliation";
+ }
+
+ public String getNamespace()
+ {
+ return null;
+ }
+
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ if (node != null)
+ appendAttribute(builder, "node", node);
+ appendAttribute(builder, "jid", jid);
+ appendAttribute(builder, "affiliation", type.toString());
+
+ builder.append("/>");
+ return builder.toString();
+ }
+
+ private void appendAttribute(StringBuilder builder, String att, String value)
+ {
+ builder.append(" ");
+ builder.append(att);
+ builder.append("='");
+ builder.append(value);
+ builder.append("'");
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java b/src/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java
new file mode 100644
index 0000000..563147e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java
@@ -0,0 +1,91 @@
+/**
+ * All rights reserved. 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.Collections;
+import java.util.List;
+
+/**
+ * Represents the <b>affiliations</b> element of the reply to a request for affiliations.
+ * It is defined in the specification in section <a href="http://xmpp.org/extensions/xep-0060.html#entity-affiliations">5.7 Retrieve Affiliations</a>.
+ *
+ * @author Robin Collier
+ */
+public class AffiliationsExtension extends NodeExtension
+{
+ protected List<Affiliation> items = Collections.EMPTY_LIST;
+
+ public AffiliationsExtension()
+ {
+ super(PubSubElementType.AFFILIATIONS);
+ }
+
+ public AffiliationsExtension(List<Affiliation> affiliationList)
+ {
+ super(PubSubElementType.AFFILIATIONS);
+
+ if (affiliationList != null)
+ items = affiliationList;
+ }
+
+ /**
+ * Affiliations for the specified node.
+ *
+ * @param nodeId
+ * @param subList
+ */
+ public AffiliationsExtension(String nodeId, List<Affiliation> affiliationList)
+ {
+ super(PubSubElementType.AFFILIATIONS, nodeId);
+
+ if (affiliationList != null)
+ items = affiliationList;
+ }
+
+ public List<Affiliation> getAffiliations()
+ {
+ return items;
+ }
+
+ @Override
+ public String toXML()
+ {
+ if ((items == null) || (items.size() == 0))
+ {
+ return super.toXML();
+ }
+ else
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ if (getNode() != null)
+ {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+ }
+ builder.append(">");
+
+ for (Affiliation item : items)
+ {
+ builder.append(item.toXML());
+ }
+
+ builder.append("</");
+ builder.append(getElementName());
+ builder.append(">");
+ return builder.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java b/src/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java
new file mode 100644
index 0000000..933a39e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java
@@ -0,0 +1,32 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * This enumeration represents the children association policy for associating leaf nodes
+ * with collection nodes as defined in the pubsub specification section <a href="http://xmpp.org/extensions/xep-0060.html#registrar-formtypes-config">16.4.3</a>
+ *
+ * @author Robin Collier
+ */
+public enum ChildrenAssociationPolicy
+{
+ /** Anyone may associate leaf nodes with the collection */
+ all,
+
+ /** Only collection node owners may associate leaf nodes with the collection. */
+ owners,
+
+ /** Only those on a whitelist may associate leaf nodes with the collection. */
+ whitelist;
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/CollectionNode.java b/src/org/jivesoftware/smackx/pubsub/CollectionNode.java
new file mode 100644
index 0000000..dcd1cc4
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/CollectionNode.java
@@ -0,0 +1,31 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2009 Robin Collier.
+ *
+ * All rights reserved. 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 org.jivesoftware.smack.Connection;
+
+public class CollectionNode extends Node
+{
+ CollectionNode(Connection connection, String nodeId)
+ {
+ super(connection, nodeId);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java b/src/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java
new file mode 100644
index 0000000..67b8304
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java
@@ -0,0 +1,56 @@
+/**
+ * All rights reserved. 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.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * Represents the <b>configuration</b> element of a pubsub message event which
+ * associates a configuration form to the node which was configured. The form
+ * contains the current node configuration.
+ *
+ * @author Robin Collier
+ */
+public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension
+{
+ private ConfigureForm form;
+
+ public ConfigurationEvent(String nodeId)
+ {
+ super(PubSubElementType.CONFIGURATION, nodeId);
+ }
+
+ public ConfigurationEvent(String nodeId, ConfigureForm configForm)
+ {
+ super(PubSubElementType.CONFIGURATION, nodeId);
+ form = configForm;
+ }
+
+ public ConfigureForm getConfiguration()
+ {
+ return form;
+ }
+
+ public List<PacketExtension> getExtensions()
+ {
+ if (getConfiguration() == null)
+ return Collections.EMPTY_LIST;
+ else
+ return Arrays.asList(((PacketExtension)getConfiguration().getDataFormToSend()));
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/src/org/jivesoftware/smackx/pubsub/ConfigureForm.java
new file mode 100644
index 0000000..f6fe140
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ConfigureForm.java
@@ -0,0 +1,709 @@
+/**
+ * All rights reserved. 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.Iterator;
+import java.util.List;
+
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.FormField;
+import org.jivesoftware.smackx.packet.DataForm;
+
+/**
+ * A decorator for a {@link Form} to easily enable reading and updating
+ * of node configuration. All operations read or update the underlying {@link DataForm}.
+ *
+ * <p>Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not
+ * exist, all <b>ConfigureForm.setXXX</b> methods will create the field in the wrapped form
+ * if it does not already exist.
+ *
+ * @author Robin Collier
+ */
+public class ConfigureForm extends Form
+{
+ /**
+ * Create a decorator from an existing {@link DataForm} that has been
+ * retrieved from parsing a node configuration request.
+ *
+ * @param configDataForm
+ */
+ public ConfigureForm(DataForm configDataForm)
+ {
+ super(configDataForm);
+ }
+
+ /**
+ * Create a decorator from an existing {@link Form} for node configuration.
+ * Typically, this can be used to create a decorator for an answer form
+ * by using the result of {@link #createAnswerForm()} as the input parameter.
+ *
+ * @param nodeConfigForm
+ */
+ public ConfigureForm(Form nodeConfigForm)
+ {
+ super(nodeConfigForm.getDataFormToSend());
+ }
+
+ /**
+ * Create a new form for configuring a node. This would typically only be used
+ * when creating and configuring a node at the same time via {@link PubSubManager#createNode(String, Form)}, since
+ * configuration of an existing node is typically accomplished by calling {@link LeafNode#getNodeConfiguration()} and
+ * using the resulting form to create a answer form. See {@link #ConfigureForm(Form)}.
+ * @param formType
+ */
+ public ConfigureForm(FormType formType)
+ {
+ super(formType.toString());
+ }
+
+ /**
+ * Get the currently configured {@link AccessModel}, null if it is not set.
+ *
+ * @return The current {@link AccessModel}
+ */
+ public AccessModel getAccessModel()
+ {
+ String value = getFieldValue(ConfigureNodeFields.access_model);
+
+ if (value == null)
+ return null;
+ else
+ return AccessModel.valueOf(value);
+ }
+
+ /**
+ * Sets the value of access model.
+ *
+ * @param accessModel
+ */
+ public void setAccessModel(AccessModel accessModel)
+ {
+ addField(ConfigureNodeFields.access_model, FormField.TYPE_LIST_SINGLE);
+ setAnswer(ConfigureNodeFields.access_model.getFieldName(), getListSingle(accessModel.toString()));
+ }
+
+ /**
+ * Returns the URL of an XSL transformation which can be applied to payloads in order to
+ * generate an appropriate message body element.
+ *
+ * @return URL to an XSL
+ */
+ public String getBodyXSLT()
+ {
+ return getFieldValue(ConfigureNodeFields.body_xslt);
+ }
+
+ /**
+ * Set the URL of an XSL transformation which can be applied to payloads in order to
+ * generate an appropriate message body element.
+ *
+ * @param bodyXslt The URL of an XSL
+ */
+ public void setBodyXSLT(String bodyXslt)
+ {
+ addField(ConfigureNodeFields.body_xslt, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.body_xslt.getFieldName(), bodyXslt);
+ }
+
+ /**
+ * The id's of the child nodes associated with a collection node (both leaf and collection).
+ *
+ * @return Iterator over the list of child nodes.
+ */
+ public Iterator<String> getChildren()
+ {
+ return getFieldValues(ConfigureNodeFields.children);
+ }
+
+ /**
+ * Set the list of child node ids that are associated with a collection node.
+ *
+ * @param children
+ */
+ public void setChildren(List<String> children)
+ {
+ addField(ConfigureNodeFields.children, FormField.TYPE_TEXT_MULTI);
+ setAnswer(ConfigureNodeFields.children.getFieldName(), children);
+ }
+
+ /**
+ * Returns the policy that determines who may associate children with the node.
+ *
+ * @return The current policy
+ */
+ public ChildrenAssociationPolicy getChildrenAssociationPolicy()
+ {
+ String value = getFieldValue(ConfigureNodeFields.children_association_policy);
+
+ if (value == null)
+ return null;
+ else
+ return ChildrenAssociationPolicy.valueOf(value);
+ }
+
+ /**
+ * Sets the policy that determines who may associate children with the node.
+ *
+ * @param policy The policy being set
+ */
+ public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy)
+ {
+ addField(ConfigureNodeFields.children_association_policy, FormField.TYPE_LIST_SINGLE);
+ List<String> values = new ArrayList<String>(1);
+ values.add(policy.toString());
+ setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), values);
+ }
+
+ /**
+ * Iterator of JID's that are on the whitelist that determines who can associate child nodes
+ * with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
+ * {@link ChildrenAssociationPolicy#whitelist}.
+ *
+ * @return Iterator over whitelist
+ */
+ public Iterator<String> getChildrenAssociationWhitelist()
+ {
+ return getFieldValues(ConfigureNodeFields.children_association_whitelist);
+ }
+
+ /**
+ * Set the JID's in the whitelist of users that can associate child nodes with the collection
+ * node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to
+ * {@link ChildrenAssociationPolicy#whitelist}.
+ *
+ * @param whitelist The list of JID's
+ */
+ public void setChildrenAssociationWhitelist(List<String> whitelist)
+ {
+ addField(ConfigureNodeFields.children_association_whitelist, FormField.TYPE_JID_MULTI);
+ setAnswer(ConfigureNodeFields.children_association_whitelist.getFieldName(), whitelist);
+ }
+
+ /**
+ * Gets the maximum number of child nodes that can be associated with the collection node.
+ *
+ * @return The maximum number of child nodes
+ */
+ public int getChildrenMax()
+ {
+ return Integer.parseInt(getFieldValue(ConfigureNodeFields.children_max));
+ }
+
+ /**
+ * Set the maximum number of child nodes that can be associated with a collection node.
+ *
+ * @param max The maximum number of child nodes.
+ */
+ public void setChildrenMax(int max)
+ {
+ addField(ConfigureNodeFields.children_max, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.children_max.getFieldName(), max);
+ }
+
+ /**
+ * Gets the collection node which the node is affiliated with.
+ *
+ * @return The collection node id
+ */
+ public String getCollection()
+ {
+ return getFieldValue(ConfigureNodeFields.collection);
+ }
+
+ /**
+ * Sets the collection node which the node is affiliated with.
+ *
+ * @param collection The node id of the collection node
+ */
+ public void setCollection(String collection)
+ {
+ addField(ConfigureNodeFields.collection, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.collection.getFieldName(), collection);
+ }
+
+ /**
+ * Gets the URL of an XSL transformation which can be applied to the payload
+ * format in order to generate a valid Data Forms result that the client could
+ * display using a generic Data Forms rendering engine.
+ *
+ * @return The URL of an XSL transformation
+ */
+ public String getDataformXSLT()
+ {
+ return getFieldValue(ConfigureNodeFields.dataform_xslt);
+ }
+
+ /**
+ * Sets the URL of an XSL transformation which can be applied to the payload
+ * format in order to generate a valid Data Forms result that the client could
+ * display using a generic Data Forms rendering engine.
+ *
+ * @param url The URL of an XSL transformation
+ */
+ public void setDataformXSLT(String url)
+ {
+ addField(ConfigureNodeFields.dataform_xslt, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.dataform_xslt.getFieldName(), url);
+ }
+
+ /**
+ * Does the node deliver payloads with event notifications.
+ *
+ * @return true if it does, false otherwise
+ */
+ public boolean isDeliverPayloads()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.deliver_payloads));
+ }
+
+ /**
+ * Sets whether the node will deliver payloads with event notifications.
+ *
+ * @param deliver true if the payload will be delivered, false otherwise
+ */
+ public void setDeliverPayloads(boolean deliver)
+ {
+ addField(ConfigureNodeFields.deliver_payloads, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver);
+ }
+
+ /**
+ * Determines who should get replies to items
+ *
+ * @return Who should get the reply
+ */
+ public ItemReply getItemReply()
+ {
+ String value = getFieldValue(ConfigureNodeFields.itemreply);
+
+ if (value == null)
+ return null;
+ else
+ return ItemReply.valueOf(value);
+ }
+
+ /**
+ * Sets who should get the replies to items
+ *
+ * @param reply Defines who should get the reply
+ */
+ public void setItemReply(ItemReply reply)
+ {
+ addField(ConfigureNodeFields.itemreply, FormField.TYPE_LIST_SINGLE);
+ setAnswer(ConfigureNodeFields.itemreply.getFieldName(), getListSingle(reply.toString()));
+ }
+
+ /**
+ * Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is
+ * true.
+ *
+ * @return The maximum number of items to persist
+ */
+ public int getMaxItems()
+ {
+ return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_items));
+ }
+
+ /**
+ * Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is
+ * true.
+ *
+ * @param max The maximum number of items to persist
+ */
+ public void setMaxItems(int max)
+ {
+ addField(ConfigureNodeFields.max_items, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.max_items.getFieldName(), max);
+ }
+
+ /**
+ * Gets the maximum payload size in bytes.
+ *
+ * @return The maximum payload size
+ */
+ public int getMaxPayloadSize()
+ {
+ return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_payload_size));
+ }
+
+ /**
+ * Sets the maximum payload size in bytes
+ *
+ * @param max The maximum payload size
+ */
+ public void setMaxPayloadSize(int max)
+ {
+ addField(ConfigureNodeFields.max_payload_size, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.max_payload_size.getFieldName(), max);
+ }
+
+ /**
+ * Gets the node type
+ *
+ * @return The node type
+ */
+ public NodeType getNodeType()
+ {
+ String value = getFieldValue(ConfigureNodeFields.node_type);
+
+ if (value == null)
+ return null;
+ else
+ return NodeType.valueOf(value);
+ }
+
+ /**
+ * Sets the node type
+ *
+ * @param type The node type
+ */
+ public void setNodeType(NodeType type)
+ {
+ addField(ConfigureNodeFields.node_type, FormField.TYPE_LIST_SINGLE);
+ setAnswer(ConfigureNodeFields.node_type.getFieldName(), getListSingle(type.toString()));
+ }
+
+ /**
+ * Determines if subscribers should be notified when the configuration changes.
+ *
+ * @return true if they should be notified, false otherwise
+ */
+ public boolean isNotifyConfig()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_config));
+ }
+
+ /**
+ * Sets whether subscribers should be notified when the configuration changes.
+ *
+ * @param notify true if subscribers should be notified, false otherwise
+ */
+ public void setNotifyConfig(boolean notify)
+ {
+ addField(ConfigureNodeFields.notify_config, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.notify_config.getFieldName(), notify);
+ }
+
+ /**
+ * Determines whether subscribers should be notified when the node is deleted.
+ *
+ * @return true if subscribers should be notified, false otherwise
+ */
+ public boolean isNotifyDelete()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_delete));
+ }
+
+ /**
+ * Sets whether subscribers should be notified when the node is deleted.
+ *
+ * @param notify true if subscribers should be notified, false otherwise
+ */
+ public void setNotifyDelete(boolean notify)
+ {
+ addField(ConfigureNodeFields.notify_delete, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.notify_delete.getFieldName(), notify);
+ }
+
+ /**
+ * Determines whether subscribers should be notified when items are deleted
+ * from the node.
+ *
+ * @return true if subscribers should be notified, false otherwise
+ */
+ public boolean isNotifyRetract()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.notify_retract));
+ }
+
+ /**
+ * Sets whether subscribers should be notified when items are deleted
+ * from the node.
+ *
+ * @param notify true if subscribers should be notified, false otherwise
+ */
+ public void setNotifyRetract(boolean notify)
+ {
+ addField(ConfigureNodeFields.notify_retract, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.notify_retract.getFieldName(), notify);
+ }
+
+ /**
+ * Determines whether items should be persisted in the node.
+ *
+ * @return true if items are persisted
+ */
+ public boolean isPersistItems()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.persist_items));
+ }
+
+ /**
+ * Sets whether items should be persisted in the node.
+ *
+ * @param persist true if items should be persisted, false otherwise
+ */
+ public void setPersistentItems(boolean persist)
+ {
+ addField(ConfigureNodeFields.persist_items, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.persist_items.getFieldName(), persist);
+ }
+
+ /**
+ * Determines whether to deliver notifications to available users only.
+ *
+ * @return true if users must be available
+ */
+ public boolean isPresenceBasedDelivery()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.presence_based_delivery));
+ }
+
+ /**
+ * Sets whether to deliver notifications to available users only.
+ *
+ * @param presenceBased true if user must be available, false otherwise
+ */
+ public void setPresenceBasedDelivery(boolean presenceBased)
+ {
+ addField(ConfigureNodeFields.presence_based_delivery, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased);
+ }
+
+ /**
+ * Gets the publishing model for the node, which determines who may publish to it.
+ *
+ * @return The publishing model
+ */
+ public PublishModel getPublishModel()
+ {
+ String value = getFieldValue(ConfigureNodeFields.publish_model);
+
+ if (value == null)
+ return null;
+ else
+ return PublishModel.valueOf(value);
+ }
+
+ /**
+ * Sets the publishing model for the node, which determines who may publish to it.
+ *
+ * @param publish The enum representing the possible options for the publishing model
+ */
+ public void setPublishModel(PublishModel publish)
+ {
+ addField(ConfigureNodeFields.publish_model, FormField.TYPE_LIST_SINGLE);
+ setAnswer(ConfigureNodeFields.publish_model.getFieldName(), getListSingle(publish.toString()));
+ }
+
+ /**
+ * Iterator over the multi user chat rooms that are specified as reply rooms.
+ *
+ * @return The reply room JID's
+ */
+ public Iterator<String> getReplyRoom()
+ {
+ return getFieldValues(ConfigureNodeFields.replyroom);
+ }
+
+ /**
+ * Sets the multi user chat rooms that are specified as reply rooms.
+ *
+ * @param replyRooms The multi user chat room to use as reply rooms
+ */
+ public void setReplyRoom(List<String> replyRooms)
+ {
+ addField(ConfigureNodeFields.replyroom, FormField.TYPE_LIST_MULTI);
+ setAnswer(ConfigureNodeFields.replyroom.getFieldName(), replyRooms);
+ }
+
+ /**
+ * Gets the specific JID's for reply to.
+ *
+ * @return The JID's
+ */
+ public Iterator<String> getReplyTo()
+ {
+ return getFieldValues(ConfigureNodeFields.replyto);
+ }
+
+ /**
+ * Sets the specific JID's for reply to.
+ *
+ * @param replyTos The JID's to reply to
+ */
+ public void setReplyTo(List<String> replyTos)
+ {
+ addField(ConfigureNodeFields.replyto, FormField.TYPE_LIST_MULTI);
+ setAnswer(ConfigureNodeFields.replyto.getFieldName(), replyTos);
+ }
+
+ /**
+ * Gets the roster groups that are allowed to subscribe and retrieve items.
+ *
+ * @return The roster groups
+ */
+ public Iterator<String> getRosterGroupsAllowed()
+ {
+ return getFieldValues(ConfigureNodeFields.roster_groups_allowed);
+ }
+
+ /**
+ * Sets the roster groups that are allowed to subscribe and retrieve items.
+ *
+ * @param groups The roster groups
+ */
+ public void setRosterGroupsAllowed(List<String> groups)
+ {
+ addField(ConfigureNodeFields.roster_groups_allowed, FormField.TYPE_LIST_MULTI);
+ setAnswer(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups);
+ }
+
+ /**
+ * Determines if subscriptions are allowed.
+ *
+ * @return true if subscriptions are allowed, false otherwise
+ */
+ public boolean isSubscibe()
+ {
+ return parseBoolean(getFieldValue(ConfigureNodeFields.subscribe));
+ }
+
+ /**
+ * Sets whether subscriptions are allowed.
+ *
+ * @param subscribe true if they are, false otherwise
+ */
+ public void setSubscribe(boolean subscribe)
+ {
+ addField(ConfigureNodeFields.subscribe, FormField.TYPE_BOOLEAN);
+ setAnswer(ConfigureNodeFields.subscribe.getFieldName(), subscribe);
+ }
+
+ /**
+ * Gets the human readable node title.
+ *
+ * @return The node title
+ */
+ public String getTitle()
+ {
+ return getFieldValue(ConfigureNodeFields.title);
+ }
+
+ /**
+ * Sets a human readable title for the node.
+ *
+ * @param title The node title
+ */
+ public void setTitle(String title)
+ {
+ addField(ConfigureNodeFields.title, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.title.getFieldName(), title);
+ }
+
+ /**
+ * The type of node data, usually specified by the namespace of the payload (if any).
+ *
+ * @return The type of node data
+ */
+ public String getDataType()
+ {
+ return getFieldValue(ConfigureNodeFields.type);
+ }
+
+ /**
+ * Sets the type of node data, usually specified by the namespace of the payload (if any).
+ *
+ * @param type The type of node data
+ */
+ public void setDataType(String type)
+ {
+ addField(ConfigureNodeFields.type, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(ConfigureNodeFields.type.getFieldName(), type);
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder result = new StringBuilder(getClass().getName() + " Content [");
+
+ Iterator<FormField> fields = getFields();
+
+ while (fields.hasNext())
+ {
+ FormField formField = fields.next();
+ result.append('(');
+ result.append(formField.getVariable());
+ result.append(':');
+
+ Iterator<String> values = formField.getValues();
+ StringBuilder valuesBuilder = new StringBuilder();
+
+ while (values.hasNext())
+ {
+ if (valuesBuilder.length() > 0)
+ result.append(',');
+ String value = (String)values.next();
+ valuesBuilder.append(value);
+ }
+
+ if (valuesBuilder.length() == 0)
+ valuesBuilder.append("NOT SET");
+ result.append(valuesBuilder);
+ result.append(')');
+ }
+ result.append(']');
+ return result.toString();
+ }
+
+ static private boolean parseBoolean(String fieldValue)
+ {
+ return ("1".equals(fieldValue) || "true".equals(fieldValue));
+ }
+
+ private String getFieldValue(ConfigureNodeFields field)
+ {
+ FormField formField = getField(field.getFieldName());
+
+ return (formField.getValues().hasNext()) ? formField.getValues().next() : null;
+ }
+
+ private Iterator<String> getFieldValues(ConfigureNodeFields field)
+ {
+ FormField formField = getField(field.getFieldName());
+
+ return formField.getValues();
+ }
+
+ private void addField(ConfigureNodeFields nodeField, String type)
+ {
+ String fieldName = nodeField.getFieldName();
+
+ if (getField(fieldName) == null)
+ {
+ FormField field = new FormField(fieldName);
+ field.setType(type);
+ addField(field);
+ }
+ }
+
+ private List<String> getListSingle(String value)
+ {
+ List<String> list = new ArrayList<String>(1);
+ list.add(value);
+ return list;
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java b/src/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java
new file mode 100644
index 0000000..3912483
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java
@@ -0,0 +1,218 @@
+/**
+ * All rights reserved. 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.net.URL;
+
+import org.jivesoftware.smackx.Form;
+
+/**
+ * This enumeration represents all the fields of a node configuration form. This enumeration
+ * is not required when using the {@link ConfigureForm} to configure nodes, but may be helpful
+ * for generic UI's using only a {@link Form} for configuration.
+ *
+ * @author Robin Collier
+ */
+public enum ConfigureNodeFields
+{
+ /**
+ * Determines who may subscribe and retrieve items
+ *
+ * <p><b>Value: {@link AccessModel}</b></p>
+ */
+ access_model,
+
+ /**
+ * The URL of an XSL transformation which can be applied to
+ * payloads in order to generate an appropriate message
+ * body element
+ *
+ * <p><b>Value: {@link URL}</b></p>
+ */
+ body_xslt,
+
+ /**
+ * The collection with which a node is affiliated
+ *
+ * <p><b>Value: String</b></p>
+ */
+ collection,
+
+ /**
+ * The URL of an XSL transformation which can be applied to
+ * payload format in order to generate a valid Data Forms result
+ * that the client could display using a generic Data Forms
+ * rendering engine body element.
+ *
+ * <p><b>Value: {@link URL}</b></p>
+ */
+ dataform_xslt,
+
+ /**
+ * Whether to deliver payloads with event notifications
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ deliver_payloads,
+
+ /**
+ * Whether owners or publisher should receive replies to items
+ *
+ * <p><b>Value: {@link ItemReply}</b></p>
+ */
+ itemreply,
+
+ /**
+ * Who may associate leaf nodes with a collection
+ *
+ * <p><b>Value: {@link ChildrenAssociationPolicy}</b></p>
+ */
+ children_association_policy,
+
+ /**
+ * The list of JIDs that may associate leaf nodes with a
+ * collection
+ *
+ * <p><b>Value: List of JIDs as Strings</b></p>
+ */
+ children_association_whitelist,
+
+ /**
+ * The child nodes (leaf or collection) associated with a collection
+ *
+ * <p><b>Value: List of Strings</b></p>
+ */
+ children,
+
+ /**
+ * The maximum number of child nodes that can be associated with a
+ * collection
+ *
+ * <p><b>Value: int</b></p>
+ */
+ children_max,
+
+ /**
+ * The maximum number of items to persist
+ *
+ * <p><b>Value: int</b></p>
+ */
+ max_items,
+
+ /**
+ * The maximum payload size in bytes
+ *
+ * <p><b>Value: int</b></p>
+ */
+ max_payload_size,
+
+ /**
+ * Whether the node is a leaf (default) or collection
+ *
+ * <p><b>Value: {@link NodeType}</b></p>
+ */
+ node_type,
+
+ /**
+ * Whether to notify subscribers when the node configuration changes
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ notify_config,
+
+ /**
+ * Whether to notify subscribers when the node is deleted
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ notify_delete,
+
+ /**
+ * Whether to notify subscribers when items are removed from the node
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ notify_retract,
+
+ /**
+ * Whether to persist items to storage. This is required to have multiple
+ * items in the node.
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ persist_items,
+
+ /**
+ * Whether to deliver notifications to available users only
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ presence_based_delivery,
+
+ /**
+ * Defines who can publish to the node
+ *
+ * <p><b>Value: {@link PublishModel}</b></p>
+ */
+ publish_model,
+
+ /**
+ * The specific multi-user chat rooms to specify for replyroom
+ *
+ * <p><b>Value: List of JIDs as Strings</b></p>
+ */
+ replyroom,
+
+ /**
+ * The specific JID(s) to specify for replyto
+ *
+ * <p><b>Value: List of JIDs as Strings</b></p>
+ */
+ replyto,
+
+ /**
+ * The roster group(s) allowed to subscribe and retrieve items
+ *
+ * <p><b>Value: List of strings</b></p>
+ */
+ roster_groups_allowed,
+
+ /**
+ * Whether to allow subscriptions
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ subscribe,
+
+ /**
+ * A friendly name for the node
+ *
+ * <p><b>Value: String</b></p>
+ */
+ title,
+
+ /**
+ * The type of node data, ussually specified by the namespace
+ * of the payload(if any);MAY be a list-single rather than a
+ * text single
+ *
+ * <p><b>Value: String</b></p>
+ */
+ type;
+
+ public String getFieldName()
+ {
+ return "pubsub#" + toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java b/src/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java
new file mode 100644
index 0000000..b17a66a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java
@@ -0,0 +1,45 @@
+/**
+ * All rights reserved. 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.List;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.util.PacketParserUtils;
+
+/**
+ * This interface defines {@link PacketExtension} implementations that contain other
+ * extensions. This effectively extends the idea of an extension within one of the
+ * top level {@link Packet} types to consider any embedded element to be an extension
+ * of its parent. This more easily enables the usage of some of Smacks parsing
+ * utilities such as {@link PacketParserUtils#parsePacketExtension(String, String, org.xmlpull.v1.XmlPullParser)} to be used
+ * to parse any element of the XML being parsed.
+ *
+ * <p>Top level extensions have only one element, but they can have multiple children, or
+ * their children can have multiple children. This interface is a way of allowing extensions
+ * to be embedded within one another as a partial or complete one to one mapping of extension
+ * to element.
+ *
+ * @author Robin Collier
+ */
+public interface EmbeddedPacketExtension extends PacketExtension
+{
+ /**
+ * Get the list of embedded {@link PacketExtension} objects.
+ *
+ * @return List of embedded {@link PacketExtension}
+ */
+ List<PacketExtension> getExtensions();
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/EventElement.java b/src/org/jivesoftware/smackx/pubsub/EventElement.java
new file mode 100644
index 0000000..165970f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/EventElement.java
@@ -0,0 +1,74 @@
+/**
+ * All rights reserved. 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.Arrays;
+import java.util.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+
+/**
+ * Represents the top level element of a pubsub event extension. All types of pubsub events are
+ * represented by this class. The specific type can be found by {@link #getEventType()}. The
+ * embedded event information, which is specific to the event type, can be retrieved by the {@link #getEvent()}
+ * method.
+ *
+ * @author Robin Collier
+ */
+public class EventElement implements EmbeddedPacketExtension
+{
+ private EventElementType type;
+ private NodeExtension ext;
+
+ public EventElement(EventElementType eventType, NodeExtension eventExt)
+ {
+ type = eventType;
+ ext = eventExt;
+ }
+
+ public EventElementType getEventType()
+ {
+ return type;
+ }
+
+ public List<PacketExtension> getExtensions()
+ {
+ return Arrays.asList(new PacketExtension[]{getEvent()});
+ }
+
+ public NodeExtension getEvent()
+ {
+ return ext;
+ }
+
+ public String getElementName()
+ {
+ return "event";
+ }
+
+ public String getNamespace()
+ {
+ return PubSubNamespace.EVENT.getXmlns();
+ }
+
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<event xmlns='" + PubSubNamespace.EVENT.getXmlns() + "'>");
+
+ builder.append(ext.toXML());
+ builder.append("</event>");
+ return builder.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/EventElementType.java b/src/org/jivesoftware/smackx/pubsub/EventElementType.java
new file mode 100644
index 0000000..343edbe
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/EventElementType.java
@@ -0,0 +1,41 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * This enumeration defines the possible event types that are supported within pubsub
+ * event messages.
+ *
+ * @author Robin Collier
+ */
+public enum EventElementType
+{
+ /** A node has been associated or dissassociated with a collection node */
+ collection,
+
+ /** A node has had its configuration changed */
+ configuration,
+
+ /** A node has been deleted */
+ delete,
+
+ /** Items have been published to a node */
+ items,
+
+ /** All items have been purged from a node */
+ purge,
+
+ /** A node has been subscribed to */
+ subscription
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/FormNode.java b/src/org/jivesoftware/smackx/pubsub/FormNode.java
new file mode 100644
index 0000000..e08bed2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/FormNode.java
@@ -0,0 +1,99 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.Form;
+
+/**
+ * Generic packet extension which represents any pubsub form that is
+ * parsed from the incoming stream or being sent out to the server.
+ *
+ * Form types are defined in {@link FormNodeType}.
+ *
+ * @author Robin Collier
+ */
+public class FormNode extends NodeExtension
+{
+ private Form configForm;
+
+ /**
+ * Create a {@link FormNode} which contains the specified form.
+ *
+ * @param formType The type of form being sent
+ * @param submitForm The form
+ */
+ public FormNode(FormNodeType formType, Form submitForm)
+ {
+ super(formType.getNodeElement());
+
+ if (submitForm == null)
+ throw new IllegalArgumentException("Submit form cannot be null");
+ configForm = submitForm;
+ }
+
+ /**
+ * Create a {@link FormNode} which contains the specified form, which is
+ * associated with the specified node.
+ *
+ * @param formType The type of form being sent
+ * @param nodeId The node the form is associated with
+ * @param submitForm The form
+ */
+ public FormNode(FormNodeType formType, String nodeId, Form submitForm)
+ {
+ super(formType.getNodeElement(), nodeId);
+
+ if (submitForm == null)
+ throw new IllegalArgumentException("Submit form cannot be null");
+ configForm = submitForm;
+ }
+
+ /**
+ * Get the Form that is to be sent, or was retrieved from the server.
+ *
+ * @return The form
+ */
+ public Form getForm()
+ {
+ return configForm;
+ }
+
+ @Override
+ public String toXML()
+ {
+ if (configForm == null)
+ {
+ return super.toXML();
+ }
+ else
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+
+ if (getNode() != null)
+ {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'>");
+ }
+ else
+ builder.append('>');
+ builder.append(configForm.getDataFormToSend().toXML());
+ builder.append("</");
+ builder.append(getElementName() + '>');
+ return builder.toString();
+ }
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/FormNodeType.java b/src/org/jivesoftware/smackx/pubsub/FormNodeType.java
new file mode 100644
index 0000000..6a163ee
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/FormNodeType.java
@@ -0,0 +1,50 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+
+/**
+ * The types of forms supported by the pubsub specification.
+ *
+ * @author Robin Collier
+ */
+public enum FormNodeType
+{
+ /** Form for configuring an existing node */
+ CONFIGURE_OWNER,
+
+ /** Form for configuring a node during creation */
+ CONFIGURE,
+
+ /** Form for configuring subscription options */
+ OPTIONS,
+
+ /** Form which represents the default node configuration options */
+ DEFAULT;
+
+ public PubSubElementType getNodeElement()
+ {
+ return PubSubElementType.valueOf(toString());
+ }
+
+ public static FormNodeType valueOfFromElementName(String elem, String configNamespace)
+ {
+ if ("configure".equals(elem) && PubSubNamespace.OWNER.getXmlns().equals(configNamespace))
+ {
+ return CONFIGURE_OWNER;
+ }
+ return valueOf(elem.toUpperCase());
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/FormType.java b/src/org/jivesoftware/smackx/pubsub/FormType.java
new file mode 100644
index 0000000..e0fff51
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/FormType.java
@@ -0,0 +1,26 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.Form;
+
+/**
+ * Defines the allowable types for a {@link Form}
+ *
+ * @author Robin Collier
+ */
+public enum FormType
+{
+ form, submit, cancel, result;
+} \ No newline at end of file
diff --git a/src/org/jivesoftware/smackx/pubsub/GetItemsRequest.java b/src/org/jivesoftware/smackx/pubsub/GetItemsRequest.java
new file mode 100644
index 0000000..341b7b5
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/GetItemsRequest.java
@@ -0,0 +1,85 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Represents a request to subscribe to a node.
+ *
+ * @author Robin Collier
+ */
+public class GetItemsRequest extends NodeExtension
+{
+ protected String subId;
+ protected int maxItems;
+
+ public GetItemsRequest(String nodeId)
+ {
+ super(PubSubElementType.ITEMS, nodeId);
+ }
+
+ public GetItemsRequest(String nodeId, String subscriptionId)
+ {
+ super(PubSubElementType.ITEMS, nodeId);
+ subId = subscriptionId;
+ }
+
+ public GetItemsRequest(String nodeId, int maxItemsToReturn)
+ {
+ super(PubSubElementType.ITEMS, nodeId);
+ maxItems = maxItemsToReturn;
+ }
+
+ public GetItemsRequest(String nodeId, String subscriptionId, int maxItemsToReturn)
+ {
+ this(nodeId, maxItemsToReturn);
+ subId = subscriptionId;
+ }
+
+ public String getSubscriptionId()
+ {
+ return subId;
+ }
+
+ public int getMaxItems()
+ {
+ return maxItems;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+
+ if (getSubscriptionId() != null)
+ {
+ builder.append(" subid='");
+ builder.append(getSubscriptionId());
+ builder.append("'");
+ }
+
+ if (getMaxItems() > 0)
+ {
+ builder.append(" max_items='");
+ builder.append(getMaxItems());
+ builder.append("'");
+ }
+ builder.append("/>");
+ return builder.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/Item.java b/src/org/jivesoftware/smackx/pubsub/Item.java
new file mode 100644
index 0000000..2ce0baa
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/Item.java
@@ -0,0 +1,132 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
+
+/**
+ * This class represents an item that has been, or will be published to a
+ * pubsub node. An <tt>Item</tt> has several properties that are dependent
+ * on the configuration of the node to which it has been or will be published.
+ *
+ * <h1>An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)}</b>
+ * <li>Will always have an id (either user or server generated) unless node configuration has both
+ * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false.
+ * <li>Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set
+ * to true, otherwise it will be null.
+ *
+ * <h1>An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()}</b>
+ * <li>The id is optional, since the server will generate one if necessary, but should be used if it is
+ * meaningful in the context of the node. This value must be unique within the node that it is sent to, since
+ * resending an item with the same id will overwrite the one that already exists if the items are persisted.
+ * <li>Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set
+ * to true.
+ *
+ * <p>To customise the payload object being returned from the {@link #getPayload()} method, you can
+ * add a custom parser as explained in {@link ItemProvider}.
+ *
+ * @author Robin Collier
+ */
+public class Item extends NodeExtension
+{
+ private String id;
+
+ /**
+ * Create an empty <tt>Item</tt> with no id. This is a valid item for nodes which are configured
+ * so that {@link ConfigureForm#isDeliverPayloads()} is false. In most cases an id will be generated by the server.
+ * For nodes configured with {@link ConfigureForm#isDeliverPayloads()} and {@link ConfigureForm#isPersistItems()}
+ * set to false, no <tt>Item</tt> is sent to the node, you have to use {@link LeafNode#send()} or {@link LeafNode#publish()}
+ * methods in this case.
+ */
+ public Item()
+ {
+ super(PubSubElementType.ITEM);
+ }
+
+ /**
+ * Create an <tt>Item</tt> with an id but no payload. This is a valid item for nodes which are configured
+ * so that {@link ConfigureForm#isDeliverPayloads()} is false.
+ *
+ * @param itemId The id if the item. It must be unique within the node unless overwriting and existing item.
+ * Passing null is the equivalent of calling {@link #Item()}.
+ */
+ public Item(String itemId)
+ {
+ // The element type is actually irrelevant since we override getNamespace() to return null
+ super(PubSubElementType.ITEM);
+ id = itemId;
+ }
+
+ /**
+ * Create an <tt>Item</tt> with an id and a node id.
+ * <p>
+ * <b>Note:</b> This is not valid for publishing an item to a node, only receiving from
+ * one as part of {@link Message}. If used to create an Item to publish
+ * (via {@link LeafNode#publish(Item)}, the server <i>may</i> return an
+ * error for an invalid packet.
+ *
+ * @param itemId The id of the item.
+ * @param nodeId The id of the node which the item was published to.
+ */
+ public Item(String itemId, String nodeId)
+ {
+ super(PubSubElementType.ITEM_EVENT, nodeId);
+ id = itemId;
+ }
+
+ /**
+ * Get the item id. Unique to the node it is associated with.
+ *
+ * @return The id
+ */
+ public String getId()
+ {
+ return id;
+ }
+
+ @Override
+ public String getNamespace()
+ {
+ return null;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<item");
+
+ if (id != null)
+ {
+ builder.append(" id='");
+ builder.append(id);
+ builder.append("'");
+ }
+
+ if (getNode() != null) {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+ }
+ builder.append("/>");
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + " | Content [" + toXML() + "]";
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java b/src/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java
new file mode 100644
index 0000000..82ab7df
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java
@@ -0,0 +1,62 @@
+/**
+ * All rights reserved. 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.Collections;
+import java.util.List;
+
+/**
+ * Represents an event in which items have been deleted from the node.
+ *
+ * @author Robin Collier
+ */
+public class ItemDeleteEvent extends SubscriptionEvent
+{
+ private List<String> itemIds = Collections.EMPTY_LIST;
+
+ /**
+ * Constructs an <tt>ItemDeleteEvent</tt> that indicates the the supplied
+ * items (by id) have been deleted, and that the event matches the listed
+ * subscriptions. The subscriptions would have been created by calling
+ * {@link LeafNode#subscribe(String)}.
+ *
+ * @param nodeId The id of the node the event came from
+ * @param deletedItemIds The item ids of the items that were deleted.
+ * @param subscriptionIds The subscriptions that match the event.
+ */
+ public ItemDeleteEvent(String nodeId, List<String> deletedItemIds, List<String> subscriptionIds)
+ {
+ super(nodeId, subscriptionIds);
+
+ if (deletedItemIds == null)
+ throw new IllegalArgumentException("deletedItemIds cannot be null");
+ itemIds = deletedItemIds;
+ }
+
+ /**
+ * Get the item id's of the items that have been deleted.
+ *
+ * @return List of item id's
+ */
+ public List<String> getItemIds()
+ {
+ return Collections.unmodifiableList(itemIds);
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Deleted Items: " + itemIds + ']';
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java b/src/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java
new file mode 100644
index 0000000..1ef1f67
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java
@@ -0,0 +1,123 @@
+/**
+ * All rights reserved. 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.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Represents an event generated by an item(s) being published to a node.
+ *
+ * @author Robin Collier
+ */
+public class ItemPublishEvent <T extends Item> extends SubscriptionEvent
+{
+ private List<T> items;
+ private Date originalDate;
+
+ /**
+ * Constructs an <tt>ItemPublishEvent</tt> with the provided list
+ * of {@link Item} that were published.
+ *
+ * @param nodeId The id of the node the event came from
+ * @param eventItems The list of {@link Item} that were published
+ */
+ public ItemPublishEvent(String nodeId, List<T> eventItems)
+ {
+ super(nodeId);
+ items = eventItems;
+ }
+
+ /**
+ * Constructs an <tt>ItemPublishEvent</tt> with the provided list
+ * of {@link Item} that were published. The list of subscription ids
+ * represents the subscriptions that matched the event, in the case
+ * of the user having multiple subscriptions.
+ *
+ * @param nodeId The id of the node the event came from
+ * @param eventItems The list of {@link Item} that were published
+ * @param subscriptionIds The list of subscriptionIds
+ */
+ public ItemPublishEvent(String nodeId, List<T> eventItems, List<String> subscriptionIds)
+ {
+ super(nodeId, subscriptionIds);
+ items = eventItems;
+ }
+
+ /**
+ * Constructs an <tt>ItemPublishEvent</tt> with the provided list
+ * of {@link Item} that were published in the past. The published
+ * date signifies that this is delayed event. The list of subscription ids
+ * represents the subscriptions that matched the event, in the case
+ * of the user having multiple subscriptions.
+ *
+ * @param nodeId The id of the node the event came from
+ * @param eventItems The list of {@link Item} that were published
+ * @param subscriptionIds The list of subscriptionIds
+ * @param publishedDate The original publishing date of the events
+ */
+ public ItemPublishEvent(String nodeId, List<T> eventItems, List<String> subscriptionIds, Date publishedDate)
+ {
+ super(nodeId, subscriptionIds);
+ items = eventItems;
+
+ if (publishedDate != null)
+ originalDate = publishedDate;
+ }
+
+ /**
+ * Get the list of {@link Item} that were published.
+ *
+ * @return The list of published {@link Item}
+ */
+ public List<T> getItems()
+ {
+ return Collections.unmodifiableList(items);
+ }
+
+ /**
+ * Indicates whether this event was delayed. That is, the items
+ * were published to the node at some time in the past. This will
+ * typically happen if there is an item already published to the node
+ * before a user subscribes to it. In this case, when the user
+ * subscribes, the server may send the last item published to the node
+ * with a delay date showing its time of original publication.
+ *
+ * @return true if the items are delayed, false otherwise.
+ */
+ public boolean isDelayed()
+ {
+ return (originalDate != null);
+ }
+
+ /**
+ * Gets the original date the items were published. This is only
+ * valid if {@link #isDelayed()} is true.
+ *
+ * @return Date items were published if {@link #isDelayed()} is true, null otherwise.
+ */
+ public Date getPublishedDate()
+ {
+ return originalDate;
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Delayed: " +
+ (isDelayed() ? originalDate.toString() : "false") + ']';
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ItemReply.java b/src/org/jivesoftware/smackx/pubsub/ItemReply.java
new file mode 100644
index 0000000..3e090d9
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ItemReply.java
@@ -0,0 +1,29 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)},
+ * which defines who should receive replies to items.
+ *
+ * @author Robin Collier
+ */
+public enum ItemReply
+{
+ /** The node owner */
+ owner,
+
+ /** The item publisher */
+ publisher;
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/src/org/jivesoftware/smackx/pubsub/ItemsExtension.java
new file mode 100644
index 0000000..c98d93a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/ItemsExtension.java
@@ -0,0 +1,196 @@
+/**
+ * All rights reserved. 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.List;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * This class is used to for multiple purposes.
+ * <li>It can represent an event containing a list of items that have been published
+ * <li>It can represent an event containing a list of retracted (deleted) items.
+ * <li>It can represent a request to delete a list of items.
+ * <li>It can represent a request to get existing items.
+ *
+ * <p><b>Please note, this class is used for internal purposes, and is not required for usage of
+ * pubsub functionality.</b>
+ *
+ * @author Robin Collier
+ */
+public class ItemsExtension extends NodeExtension implements EmbeddedPacketExtension
+{
+ protected ItemsElementType type;
+ protected Boolean notify;
+ protected List<? extends PacketExtension> items;
+
+ public enum ItemsElementType
+ {
+ /** An items element, which has an optional <b>max_items</b> attribute when requesting items */
+ items(PubSubElementType.ITEMS, "max_items"),
+
+ /** A retract element, which has an optional <b>notify</b> attribute when publishing deletions */
+ retract(PubSubElementType.RETRACT, "notify");
+
+ private PubSubElementType elem;
+ private String att;
+
+ private ItemsElementType(PubSubElementType nodeElement, String attribute)
+ {
+ elem = nodeElement;
+ att = attribute;
+ }
+
+ public PubSubElementType getNodeElement()
+ {
+ return elem;
+ }
+
+ public String getElementAttribute()
+ {
+ return att;
+ }
+ }
+
+ /**
+ * Construct an instance with a list representing items that have been published or deleted.
+ *
+ * <p>Valid scenarios are:
+ * <li>Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an
+ * optional value for the <b>max_items</b> attribute.
+ * <li>Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing
+ * only id's and an optional value for the <b>notify</b> attribute.
+ * <li>Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and
+ * attributeValue = <code>null</code>
+ * <li>Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and
+ * attributeValue = <code>null</code>
+ *
+ * @param itemsType Type of representation
+ * @param nodeId The node to which the items are being sent or deleted
+ * @param items The list of {@link Item} or {@link RetractItem}
+ * @param attributeValue The value of the <b>max_items</b>
+ */
+ public ItemsExtension(ItemsElementType itemsType, String nodeId, List<? extends PacketExtension> items)
+ {
+ super(itemsType.getNodeElement(), nodeId);
+ type = itemsType;
+ this.items = items;
+ }
+
+ /**
+ * Construct an instance with a list representing items that have been published or deleted.
+ *
+ * <p>Valid scenarios are:
+ * <li>Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an
+ * optional value for the <b>max_items</b> attribute.
+ * <li>Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing
+ * only id's and an optional value for the <b>notify</b> attribute.
+ * <li>Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and
+ * attributeValue = <code>null</code>
+ * <li>Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and
+ * attributeValue = <code>null</code>
+ *
+ * @param itemsType Type of representation
+ * @param nodeId The node to which the items are being sent or deleted
+ * @param items The list of {@link Item} or {@link RetractItem}
+ * @param attributeValue The value of the <b>max_items</b>
+ */
+ public ItemsExtension(String nodeId, List<? extends PacketExtension> items, boolean notify)
+ {
+ super(ItemsElementType.retract.getNodeElement(), nodeId);
+ type = ItemsElementType.retract;
+ this.items = items;
+ this.notify = notify;
+ }
+
+ /**
+ * Get the type of element
+ *
+ * @return The element type
+ */
+ public ItemsElementType getItemsElementType()
+ {
+ return type;
+ }
+
+ public List<PacketExtension> getExtensions()
+ {
+ return (List<PacketExtension>)getItems();
+ }
+
+ /**
+ * Gets the items related to the type of request or event.
+ *
+ * return List of {@link Item}, {@link RetractItem}, or null
+ */
+ public List<? extends PacketExtension> getItems()
+ {
+ return items;
+ }
+
+ /**
+ * Gets the value of the optional attribute related to the {@link ItemsElementType}.
+ *
+ * @return The attribute value
+ */
+ public boolean getNotify()
+ {
+ return notify;
+ }
+
+ @Override
+ public String toXML()
+ {
+ if ((items == null) || (items.size() == 0))
+ {
+ return super.toXML();
+ }
+ else
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ builder.append(" node='");
+ builder.append(getNode());
+
+ if (notify != null)
+ {
+ builder.append("' ");
+ builder.append(type.getElementAttribute());
+ builder.append("='");
+ builder.append(notify.equals(Boolean.TRUE) ? 1 : 0);
+ builder.append("'>");
+ }
+ else
+ {
+ builder.append("'>");
+ for (PacketExtension item : items)
+ {
+ builder.append(item.toXML());
+ }
+ }
+
+ builder.append("</");
+ builder.append(getElementName());
+ builder.append(">");
+ return builder.toString();
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + "Content [" + toXML() + "]";
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/LeafNode.java b/src/org/jivesoftware/smackx/pubsub/LeafNode.java
new file mode 100644
index 0000000..eee6293
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/LeafNode.java
@@ -0,0 +1,352 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+import org.jivesoftware.smackx.pubsub.packet.PubSub;
+import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend;
+
+/**
+ * The main class for the majority of pubsub functionality. In general
+ * almost all pubsub capabilities are related to the concept of a node.
+ * All items are published to a node, and typically subscribed to by other
+ * users. These users then retrieve events based on this subscription.
+ *
+ * @author Robin Collier
+ */
+public class LeafNode extends Node
+{
+ LeafNode(Connection connection, String nodeName)
+ {
+ super(connection, nodeName);
+ }
+
+ /**
+ * Get information on the items in the node in standard
+ * {@link DiscoverItems} format.
+ *
+ * @return The item details in {@link DiscoverItems} format
+ *
+ * @throws XMPPException
+ */
+ public DiscoverItems discoverItems()
+ throws XMPPException
+ {
+ DiscoverItems items = new DiscoverItems();
+ items.setTo(to);
+ items.setNode(getId());
+ return (DiscoverItems)SyncPacketSend.getReply(con, items);
+ }
+
+ /**
+ * Get the current items stored in the node.
+ *
+ * @return List of {@link Item} in the node
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> List<T> getItems()
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId()));
+
+ PubSub result = (PubSub)SyncPacketSend.getReply(con, request);
+ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS);
+ return (List<T>)itemsElem.getItems();
+ }
+
+ /**
+ * Get the current items stored in the node based
+ * on the subscription associated with the provided
+ * subscription id.
+ *
+ * @param subscriptionId - The subscription id for the
+ * associated subscription.
+ * @return List of {@link Item} in the node
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> List<T> getItems(String subscriptionId)
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId));
+
+ PubSub result = (PubSub)SyncPacketSend.getReply(con, request);
+ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS);
+ return (List<T>)itemsElem.getItems();
+ }
+
+ /**
+ * Get the items specified from the node. This would typically be
+ * used when the server does not return the payload due to size
+ * constraints. The user would be required to retrieve the payload
+ * after the items have been retrieved via {@link #getItems()} or an
+ * event, that did not include the payload.
+ *
+ * @param ids Item ids of the items to retrieve
+ *
+ * @return The list of {@link Item} with payload
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> List<T> getItems(Collection<String> ids)
+ throws XMPPException
+ {
+ List<Item> itemList = new ArrayList<Item>(ids.size());
+
+ for (String id : ids)
+ {
+ itemList.add(new Item(id));
+ }
+ PubSub request = createPubsubPacket(Type.GET, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList));
+
+ PubSub result = (PubSub)SyncPacketSend.getReply(con, request);
+ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS);
+ return (List<T>)itemsElem.getItems();
+ }
+
+ /**
+ * Get items persisted on the node, limited to the specified number.
+ *
+ * @param maxItems Maximum number of items to return
+ *
+ * @return List of {@link Item}
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> List<T> getItems(int maxItems)
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), maxItems));
+
+ PubSub result = (PubSub)SyncPacketSend.getReply(con, request);
+ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS);
+ return (List<T>)itemsElem.getItems();
+ }
+
+ /**
+ * Get items persisted on the node, limited to the specified number
+ * based on the subscription associated with the provided subscriptionId.
+ *
+ * @param maxItems Maximum number of items to return
+ * @param subscriptionId The subscription which the retrieval is based
+ * on.
+ *
+ * @return List of {@link Item}
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> List<T> getItems(int maxItems, String subscriptionId)
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId, maxItems));
+
+ PubSub result = (PubSub)SyncPacketSend.getReply(con, request);
+ ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS);
+ return (List<T>)itemsElem.getItems();
+ }
+
+ /**
+ * Publishes an event to the node. This is an empty event
+ * with no item.
+ *
+ * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false
+ * and {@link ConfigureForm#isDeliverPayloads()}=false.
+ *
+ * This is an asynchronous call which returns as soon as the
+ * packet has been sent.
+ *
+ * For synchronous calls use {@link #send() send()}.
+ */
+ public void publish()
+ {
+ PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId()));
+
+ con.sendPacket(packet);
+ }
+
+ /**
+ * Publishes an event to the node. This is a simple item
+ * with no payload.
+ *
+ * If the id is null, an empty item (one without an id) will be sent.
+ * Please note that this is not the same as {@link #send()}, which
+ * publishes an event with NO item.
+ *
+ * This is an asynchronous call which returns as soon as the
+ * packet has been sent.
+ *
+ * For synchronous calls use {@link #send(Item) send(Item))}.
+ *
+ * @param item - The item being sent
+ */
+ public <T extends Item> void publish(T item)
+ {
+ Collection<T> items = new ArrayList<T>(1);
+ items.add((T)(item == null ? new Item() : item));
+ publish(items);
+ }
+
+ /**
+ * Publishes multiple events to the node. Same rules apply as in {@link #publish(Item)}.
+ *
+ * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input
+ * list will get stored on the node, assuming it stores the last sent item.
+ *
+ * This is an asynchronous call which returns as soon as the
+ * packet has been sent.
+ *
+ * For synchronous calls use {@link #send(Collection) send(Collection))}.
+ *
+ * @param items - The collection of items being sent
+ */
+ public <T extends Item> void publish(Collection<T> items)
+ {
+ PubSub packet = createPubsubPacket(Type.SET, new PublishItem<T>(getId(), items));
+
+ con.sendPacket(packet);
+ }
+
+ /**
+ * Publishes an event to the node. This is an empty event
+ * with no item.
+ *
+ * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false
+ * and {@link ConfigureForm#isDeliverPayloads()}=false.
+ *
+ * This is a synchronous call which will throw an exception
+ * on failure.
+ *
+ * For asynchronous calls, use {@link #publish() publish()}.
+ *
+ * @throws XMPPException
+ */
+ public void send()
+ throws XMPPException
+ {
+ PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId()));
+
+ SyncPacketSend.getReply(con, packet);
+ }
+
+ /**
+ * Publishes an event to the node. This can be either a simple item
+ * with no payload, or one with it. This is determined by the Node
+ * configuration.
+ *
+ * If the node has <b>deliver_payload=false</b>, the Item must not
+ * have a payload.
+ *
+ * If the id is null, an empty item (one without an id) will be sent.
+ * Please note that this is not the same as {@link #send()}, which
+ * publishes an event with NO item.
+ *
+ * This is a synchronous call which will throw an exception
+ * on failure.
+ *
+ * For asynchronous calls, use {@link #publish(Item) publish(Item)}.
+ *
+ * @param item - The item being sent
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> void send(T item)
+ throws XMPPException
+ {
+ Collection<T> items = new ArrayList<T>(1);
+ items.add((item == null ? (T)new Item() : item));
+ send(items);
+ }
+
+ /**
+ * Publishes multiple events to the node. Same rules apply as in {@link #send(Item)}.
+ *
+ * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input
+ * list will get stored on the node, assuming it stores the last sent item.
+ *
+ * This is a synchronous call which will throw an exception
+ * on failure.
+ *
+ * For asynchronous calls, use {@link #publish(Collection) publish(Collection))}.
+ *
+ * @param items - The collection of {@link Item} objects being sent
+ *
+ * @throws XMPPException
+ */
+ public <T extends Item> void send(Collection<T> items)
+ throws XMPPException
+ {
+ PubSub packet = createPubsubPacket(Type.SET, new PublishItem<T>(getId(), items));
+
+ SyncPacketSend.getReply(con, packet);
+ }
+
+ /**
+ * Purges the node of all items.
+ *
+ * <p>Note: Some implementations may keep the last item
+ * sent.
+ *
+ * @throws XMPPException
+ */
+ public void deleteAllItems()
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PURGE_OWNER, getId()), PubSubElementType.PURGE_OWNER.getNamespace());
+
+ SyncPacketSend.getReply(con, request);
+ }
+
+ /**
+ * Delete the item with the specified id from the node.
+ *
+ * @param itemId The id of the item
+ *
+ * @throws XMPPException
+ */
+ public void deleteItem(String itemId)
+ throws XMPPException
+ {
+ Collection<String> items = new ArrayList<String>(1);
+ items.add(itemId);
+ deleteItem(items);
+ }
+
+ /**
+ * Delete the items with the specified id's from the node.
+ *
+ * @param itemIds The list of id's of items to delete
+ *
+ * @throws XMPPException
+ */
+ public void deleteItem(Collection<String> itemIds)
+ throws XMPPException
+ {
+ List<Item> items = new ArrayList<Item>(itemIds.size());
+
+ for (String id : itemIds)
+ {
+ items.add(new Item(id));
+ }
+ PubSub request = createPubsubPacket(Type.SET, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items));
+ SyncPacketSend.getReply(con, request);
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/Node.java b/src/org/jivesoftware/smackx/pubsub/Node.java
new file mode 100644
index 0000000..1b0ff5a
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/Node.java
@@ -0,0 +1,541 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2009 Robin Collier.
+ *
+ * All rights reserved. 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.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jivesoftware.smack.PacketListener;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.OrFilter;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.packet.DelayInformation;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.Header;
+import org.jivesoftware.smackx.packet.HeadersExtension;
+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.packet.SyncPacketSend;
+import org.jivesoftware.smackx.pubsub.util.NodeUtils;
+
+abstract public class Node
+{
+ protected Connection con;
+ protected String id;
+ protected String to;
+
+ protected ConcurrentHashMap<ItemEventListener<Item>, PacketListener> itemEventToListenerMap = new ConcurrentHashMap<ItemEventListener<Item>, PacketListener>();
+ protected ConcurrentHashMap<ItemDeleteListener, PacketListener> itemDeleteToListenerMap = new ConcurrentHashMap<ItemDeleteListener, PacketListener>();
+ protected ConcurrentHashMap<NodeConfigListener, PacketListener> configEventToListenerMap = new ConcurrentHashMap<NodeConfigListener, PacketListener>();
+
+ /**
+ * Construct a node associated to the supplied connection with the specified
+ * node id.
+ *
+ * @param connection The connection the node is associated with
+ * @param nodeName The node id
+ */
+ Node(Connection connection, String nodeName)
+ {
+ con = connection;
+ id = nodeName;
+ }
+
+ /**
+ * Some XMPP servers may require a specific service to be addressed on the
+ * server.
+ *
+ * For example, OpenFire requires the server to be prefixed by <b>pubsub</b>
+ */
+ void setTo(String toAddress)
+ {
+ to = toAddress;
+ }
+
+ /**
+ * 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(Form)}.
+ *
+ * @return the configuration form
+ */
+ public ConfigureForm getNodeConfiguration()
+ throws XMPPException
+ {
+ Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER);
+ return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER);
+ }
+
+ /**
+ * Update the configuration with the contents of the new {@link Form}
+ *
+ * @param submitForm
+ */
+ public void sendConfigurationForm(Form submitForm)
+ throws XMPPException
+ {
+ PubSub packet = createPubsubPacket(Type.SET, new FormNode(FormNodeType.CONFIGURE_OWNER, getId(), submitForm), PubSubNamespace.OWNER);
+ SyncPacketSend.getReply(con, packet);
+ }
+
+ /**
+ * Discover node information in standard {@link DiscoverInfo} format.
+ *
+ * @return The discovery information about the node.
+ *
+ * @throws XMPPException
+ */
+ public DiscoverInfo discoverInfo()
+ throws XMPPException
+ {
+ DiscoverInfo info = new DiscoverInfo();
+ info.setTo(to);
+ info.setNode(getId());
+ return (DiscoverInfo)SyncPacketSend.getReply(con, info);
+ }
+
+ /**
+ * Get the subscriptions currently associated with this node.
+ *
+ * @return List of {@link Subscription}
+ *
+ * @throws XMPPException
+ */
+ public List<Subscription> getSubscriptions()
+ throws XMPPException
+ {
+ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId()));
+ SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS);
+ return subElem.getSubscriptions();
+ }
+
+ /**
+ * 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
+ * @exception XMPPException
+ */
+ public Subscription subscribe(String jid)
+ throws XMPPException
+ {
+ PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new SubscribeExtension(jid, getId()));
+ return (Subscription)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.
+ * @return The subscription
+ * @exception XMPPException
+ */
+ public Subscription subscribe(String jid, SubscribeForm subForm)
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(Type.SET, new SubscribeExtension(jid, getId()));
+ request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
+ PubSub reply = (PubSub)PubSubManager.sendPubsubPacket(con, jid, Type.SET, request);
+ return (Subscription)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 XMPPException
+ */
+ public void unsubscribe(String jid)
+ throws XMPPException
+ {
+ 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 XMPPException
+ */
+ public void unsubscribe(String jid, String subscriptionId)
+ throws XMPPException
+ {
+ sendPubsubPacket(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(Form)}.
+ *
+ * @return A subscription options form
+ *
+ * @throws XMPPException
+ */
+ public SubscribeForm getSubscriptionOptions(String jid)
+ throws XMPPException
+ {
+ 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 XMPPException
+ */
+ public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId)
+ throws XMPPException
+ {
+ PubSub packet = (PubSub)sendPubsubPacket(Type.GET, new OptionsExtension(jid, getId(), subscriptionId));
+ FormNode ext = (FormNode)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
+ */
+ public void addItemEventListener(ItemEventListener listener)
+ {
+ PacketListener conListener = new ItemEventTranslator(listener);
+ itemEventToListenerMap.put(listener, conListener);
+ con.addPacketListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
+ }
+
+ /**
+ * Unregister a listener for publication events.
+ *
+ * @param listener The handler to unregister
+ */
+ public void removeItemEventListener(ItemEventListener listener)
+ {
+ PacketListener conListener = itemEventToListenerMap.remove(listener);
+
+ if (conListener != null)
+ con.removePacketListener(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)
+ {
+ PacketListener conListener = new NodeConfigTranslator(listener);
+ configEventToListenerMap.put(listener, conListener);
+ con.addPacketListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
+ }
+
+ /**
+ * Unregister a listener for configuration events.
+ *
+ * @param listener The handler to unregister
+ */
+ public void removeConfigurationListener(NodeConfigListener listener)
+ {
+ PacketListener conListener = configEventToListenerMap .remove(listener);
+
+ if (conListener != null)
+ con.removePacketListener(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)
+ {
+ PacketListener delListener = new ItemDeleteTranslator(listener);
+ itemDeleteToListenerMap.put(listener, delListener);
+ EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
+ EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
+
+ con.addPacketListener(delListener, new OrFilter(deleteItem, purge));
+ }
+
+ /**
+ * Unregister a listener for item delete events.
+ *
+ * @param listener The handler to unregister
+ */
+ public void removeItemDeleteListener(ItemDeleteListener listener)
+ {
+ PacketListener conListener = itemDeleteToListenerMap .remove(listener);
+
+ if (conListener != null)
+ con.removePacketListener(conListener);
+ }
+
+ @Override
+ public String toString()
+ {
+ return super.toString() + " " + getClass().getName() + " id: " + id;
+ }
+
+ protected PubSub createPubsubPacket(Type type, PacketExtension ext)
+ {
+ return createPubsubPacket(type, ext, null);
+ }
+
+ protected PubSub createPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns)
+ {
+ return PubSubManager.createPubsubPacket(to, type, ext, ns);
+ }
+
+ protected Packet sendPubsubPacket(Type type, NodeExtension ext)
+ throws XMPPException
+ {
+ return PubSubManager.sendPubsubPacket(con, to, type, ext);
+ }
+
+ protected Packet sendPubsubPacket(Type type, NodeExtension ext, PubSubNamespace ns)
+ throws XMPPException
+ {
+ return PubSubManager.sendPubsubPacket(con, to, type, ext, ns);
+ }
+
+
+ private static List<String> getSubscriptionIds(Packet packet)
+ {
+ HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim");
+ List<String> values = null;
+
+ if (headers != null)
+ {
+ values = new ArrayList<String>(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 class ItemEventTranslator implements PacketListener
+ {
+ private ItemEventListener listener;
+
+ public ItemEventTranslator(ItemEventListener eventListener)
+ {
+ listener = eventListener;
+ }
+
+ public void processPacket(Packet packet)
+ {
+ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
+ ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
+ DelayInformation delay = (DelayInformation)packet.getExtension("delay", "urn:xmpp:delay");
+
+ // If there was no delay based on XEP-0203, then try XEP-0091 for backward compatibility
+ if (delay == null)
+ {
+ delay = (DelayInformation)packet.getExtension("x", "jabber:x:delay");
+ }
+ ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List<Item>)itemsElem.getItems(), getSubscriptionIds(packet), (delay == null ? null : delay.getStamp()));
+ listener.handlePublishedItems(eventItems);
+ }
+ }
+
+ /**
+ * This class translates low level item deletion events into api level objects for
+ * user consumption.
+ *
+ * @author Robin Collier
+ */
+ public class ItemDeleteTranslator implements PacketListener
+ {
+ private ItemDeleteListener listener;
+
+ public ItemDeleteTranslator(ItemDeleteListener eventListener)
+ {
+ listener = eventListener;
+ }
+
+ public void processPacket(Packet packet)
+ {
+ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
+
+ List<PacketExtension> extList = event.getExtensions();
+
+ if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName()))
+ {
+ listener.handlePurge();
+ }
+ else
+ {
+ ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
+ Collection<? extends PacketExtension> pubItems = itemsElem.getItems();
+ Iterator<RetractItem> it = (Iterator<RetractItem>)pubItems.iterator();
+ List<String> items = new ArrayList<String>(pubItems.size());
+
+ while (it.hasNext())
+ {
+ RetractItem item = it.next();
+ items.add(item.getId());
+ }
+
+ ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet));
+ listener.handleDeletedItems(eventItems);
+ }
+ }
+ }
+
+ /**
+ * This class translates low level node configuration events into api level objects for
+ * user consumption.
+ *
+ * @author Robin Collier
+ */
+ public class NodeConfigTranslator implements PacketListener
+ {
+ private NodeConfigListener listener;
+
+ public NodeConfigTranslator(NodeConfigListener eventListener)
+ {
+ listener = eventListener;
+ }
+
+ public void processPacket(Packet packet)
+ {
+ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
+ ConfigurationEvent config = (ConfigurationEvent)event.getEvent();
+
+ listener.handleNodeConfiguration(config);
+ }
+ }
+
+ /**
+ * Filter for {@link PacketListener} to filter out events not specific to the
+ * event type expected for this node.
+ *
+ * @author Robin Collier
+ */
+ class EventContentFilter implements PacketFilter
+ {
+ private String firstElement;
+ private String secondElement;
+
+ EventContentFilter(String elementName)
+ {
+ firstElement = elementName;
+ }
+
+ EventContentFilter(String firstLevelEelement, String secondLevelElement)
+ {
+ firstElement = firstLevelEelement;
+ secondElement = secondLevelElement;
+ }
+
+ public boolean accept(Packet packet)
+ {
+ if (!(packet instanceof Message))
+ return false;
+
+ EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
+
+ 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<PacketExtension> secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions();
+
+ if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement))
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/NodeEvent.java b/src/org/jivesoftware/smackx/pubsub/NodeEvent.java
new file mode 100644
index 0000000..1392e85
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/NodeEvent.java
@@ -0,0 +1,35 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2009 Robin Collier.
+ *
+ * All rights reserved. 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;
+
+abstract public class NodeEvent
+{
+ private String nodeId;
+
+ protected NodeEvent(String id)
+ {
+ nodeId = id;
+ }
+
+ public String getNodeId()
+ {
+ return nodeId;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/NodeExtension.java b/src/org/jivesoftware/smackx/pubsub/NodeExtension.java
new file mode 100644
index 0000000..7e4cdec
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/NodeExtension.java
@@ -0,0 +1,85 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * A class which represents a common element within the pubsub defined
+ * schemas. One which has a <b>node</b> as an attribute. This class is
+ * used on its own as well as a base class for many others, since the
+ * node is a central concept to most pubsub functionality.
+ *
+ * @author Robin Collier
+ */
+public class NodeExtension implements PacketExtension
+{
+ private PubSubElementType element;
+ private String node;
+
+ /**
+ * Constructs a <tt>NodeExtension</tt> with an element name specified
+ * by {@link PubSubElementType} and the specified node id.
+ *
+ * @param elem Defines the element name and namespace
+ * @param nodeId Specifies the id of the node
+ */
+ public NodeExtension(PubSubElementType elem, String nodeId)
+ {
+ element = elem;
+ this.node = nodeId;
+ }
+
+ /**
+ * Constructs a <tt>NodeExtension</tt> with an element name specified
+ * by {@link PubSubElementType}.
+ *
+ * @param elem Defines the element name and namespace
+ */
+ public NodeExtension(PubSubElementType elem)
+ {
+ this(elem, null);
+ }
+
+ /**
+ * Gets the node id
+ *
+ * @return The node id
+ */
+ public String getNode()
+ {
+ return node;
+ }
+
+ public String getElementName()
+ {
+ return element.getElementName();
+ }
+
+ public String getNamespace()
+ {
+ return element.getNamespace().getXmlns();
+ }
+
+ public String toXML()
+ {
+ return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>";
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + " - content [" + toXML() + "]";
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/NodeType.java b/src/org/jivesoftware/smackx/pubsub/NodeType.java
new file mode 100644
index 0000000..5ee5a05
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/NodeType.java
@@ -0,0 +1,25 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Defines the available types of nodes
+ *
+ * @author Robin Collier
+ */
+public enum NodeType
+{
+ leaf,
+ collection;
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/src/org/jivesoftware/smackx/pubsub/OptionsExtension.java
new file mode 100644
index 0000000..32c0331
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/OptionsExtension.java
@@ -0,0 +1,72 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.pubsub.util.XmlUtils;
+
+/**
+ * A packet extension representing the <b>options</b> element.
+ *
+ * @author Robin Collier
+ */
+public class OptionsExtension extends NodeExtension
+{
+ protected String jid;
+ protected String id;
+
+ public OptionsExtension(String subscriptionJid)
+ {
+ this(subscriptionJid, null, null);
+ }
+
+ public OptionsExtension(String subscriptionJid, String nodeId)
+ {
+ this(subscriptionJid, nodeId, null);
+ }
+
+ public OptionsExtension(String jid, String nodeId, String subscriptionId)
+ {
+ super(PubSubElementType.OPTIONS, nodeId);
+ this.jid = jid;
+ id = subscriptionId;
+ }
+
+ public String getJid()
+ {
+ return jid;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ XmlUtils.appendAttribute(builder, "jid", jid);
+
+ if (getNode() != null)
+ XmlUtils.appendAttribute(builder, "node", getNode());
+
+ if (id != null)
+ XmlUtils.appendAttribute(builder, "subid", id);
+
+ builder.append("/>");
+ return builder.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PayloadItem.java b/src/org/jivesoftware/smackx/pubsub/PayloadItem.java
new file mode 100644
index 0000000..488fd97
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PayloadItem.java
@@ -0,0 +1,138 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.packet.Message;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
+
+/**
+ * This class represents an item that has been, or will be published to a
+ * pubsub node. An <tt>Item</tt> has several properties that are dependent
+ * on the configuration of the node to which it has been or will be published.
+ *
+ * <h1>An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)}</b>
+ * <li>Will always have an id (either user or server generated) unless node configuration has both
+ * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false.
+ * <li>Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set
+ * to true, otherwise it will be null.
+ *
+ * <h1>An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()}</b>
+ * <li>The id is optional, since the server will generate one if necessary, but should be used if it is
+ * meaningful in the context of the node. This value must be unique within the node that it is sent to, since
+ * resending an item with the same id will overwrite the one that already exists if the items are persisted.
+ * <li>Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set
+ * to true.
+ *
+ * <p>To customise the payload object being returned from the {@link #getPayload()} method, you can
+ * add a custom parser as explained in {@link ItemProvider}.
+ *
+ * @author Robin Collier
+ */
+public class PayloadItem<E extends PacketExtension> extends Item
+{
+ private E payload;
+
+ /**
+ * Create an <tt>Item</tt> with no id and a payload The id will be set by the server.
+ *
+ * @param payloadExt A {@link PacketExtension} which represents the payload data.
+ */
+ public PayloadItem(E payloadExt)
+ {
+ super();
+
+ if (payloadExt == null)
+ throw new IllegalArgumentException("payload cannot be 'null'");
+ payload = payloadExt;
+ }
+
+ /**
+ * Create an <tt>Item</tt> with an id and payload.
+ *
+ * @param itemId The id of this item. It can be null if we want the server to set the id.
+ * @param payloadExt A {@link PacketExtension} which represents the payload data.
+ */
+ public PayloadItem(String itemId, E payloadExt)
+ {
+ super(itemId);
+
+ if (payloadExt == null)
+ throw new IllegalArgumentException("payload cannot be 'null'");
+ payload = payloadExt;
+ }
+
+ /**
+ * Create an <tt>Item</tt> with an id, node id and payload.
+ *
+ * <p>
+ * <b>Note:</b> This is not valid for publishing an item to a node, only receiving from
+ * one as part of {@link Message}. If used to create an Item to publish
+ * (via {@link LeafNode#publish(Item)}, the server <i>may</i> return an
+ * error for an invalid packet.
+ *
+ * @param itemId The id of this item.
+ * @param nodeId The id of the node the item was published to.
+ * @param payloadExt A {@link PacketExtension} which represents the payload data.
+ */
+ public PayloadItem(String itemId, String nodeId, E payloadExt)
+ {
+ super(itemId, nodeId);
+
+ if (payloadExt == null)
+ throw new IllegalArgumentException("payload cannot be 'null'");
+ payload = payloadExt;
+ }
+
+ /**
+ * Get the payload associated with this <tt>Item</tt>. Customising the payload
+ * parsing from the server can be accomplished as described in {@link ItemProvider}.
+ *
+ * @return The payload as a {@link PacketExtension}.
+ */
+ public E getPayload()
+ {
+ return payload;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<item");
+
+ if (getId() != null)
+ {
+ builder.append(" id='");
+ builder.append(getId());
+ builder.append("'");
+ }
+
+ if (getNode() != null) {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+ }
+ builder.append(">");
+ builder.append(payload.toXML());
+ builder.append("</item>");
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + " | Content [" + toXML() + "]";
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PresenceState.java b/src/org/jivesoftware/smackx/pubsub/PresenceState.java
new file mode 100644
index 0000000..0612fc2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PresenceState.java
@@ -0,0 +1,25 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Defines the possible valid presence states for node subscription via
+ * {@link SubscribeForm#getShowValues()}.
+ *
+ * @author Robin Collier
+ */
+public enum PresenceState
+{
+ chat, online, away, xa, dnd
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PubSubElementType.java b/src/org/jivesoftware/smackx/pubsub/PubSubElementType.java
new file mode 100644
index 0000000..a887ca2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PubSubElementType.java
@@ -0,0 +1,80 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+
+/**
+ * Defines all the possible element types as defined for all the pubsub
+ * schemas in all 3 namespaces.
+ *
+ * @author Robin Collier
+ */
+public enum PubSubElementType
+{
+ CREATE("create", PubSubNamespace.BASIC),
+ DELETE("delete", PubSubNamespace.OWNER),
+ DELETE_EVENT("delete", PubSubNamespace.EVENT),
+ CONFIGURE("configure", PubSubNamespace.BASIC),
+ CONFIGURE_OWNER("configure", PubSubNamespace.OWNER),
+ CONFIGURATION("configuration", PubSubNamespace.EVENT),
+ OPTIONS("options", PubSubNamespace.BASIC),
+ DEFAULT("default", PubSubNamespace.OWNER),
+ ITEMS("items", PubSubNamespace.BASIC),
+ ITEMS_EVENT("items", PubSubNamespace.EVENT),
+ ITEM("item", PubSubNamespace.BASIC),
+ ITEM_EVENT("item", PubSubNamespace.EVENT),
+ PUBLISH("publish", PubSubNamespace.BASIC),
+ PUBLISH_OPTIONS("publish-options", PubSubNamespace.BASIC),
+ PURGE_OWNER("purge", PubSubNamespace.OWNER),
+ PURGE_EVENT("purge", PubSubNamespace.EVENT),
+ RETRACT("retract", PubSubNamespace.BASIC),
+ AFFILIATIONS("affiliations", PubSubNamespace.BASIC),
+ SUBSCRIBE("subscribe", PubSubNamespace.BASIC),
+ SUBSCRIPTION("subscription", PubSubNamespace.BASIC),
+ SUBSCRIPTIONS("subscriptions", PubSubNamespace.BASIC),
+ UNSUBSCRIBE("unsubscribe", PubSubNamespace.BASIC);
+
+ private String eName;
+ private PubSubNamespace nSpace;
+
+ private PubSubElementType(String elemName, PubSubNamespace ns)
+ {
+ eName = elemName;
+ nSpace = ns;
+ }
+
+ public PubSubNamespace getNamespace()
+ {
+ return nSpace;
+ }
+
+ public String getElementName()
+ {
+ return eName;
+ }
+
+ public static PubSubElementType valueOfFromElemName(String elemName, String namespace)
+ {
+ int index = namespace.lastIndexOf('#');
+ String fragment = (index == -1 ? null : namespace.substring(index+1));
+
+ if (fragment != null)
+ {
+ return valueOf((elemName + '_' + fragment).toUpperCase());
+ }
+ return valueOf(elemName.toUpperCase().replace('-', '_'));
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PubSubManager.java b/src/org/jivesoftware/smackx/pubsub/PubSubManager.java
new file mode 100644
index 0000000..4fb0158
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PubSubManager.java
@@ -0,0 +1,329 @@
+/**
+ * All rights reserved. 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.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.packet.IQ.Type;
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.FormField;
+import org.jivesoftware.smackx.ServiceDiscoveryManager;
+import org.jivesoftware.smackx.packet.DiscoverInfo;
+import org.jivesoftware.smackx.packet.DiscoverItems;
+import org.jivesoftware.smackx.pubsub.packet.PubSub;
+import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend;
+import org.jivesoftware.smackx.pubsub.util.NodeUtils;
+
+/**
+ * This is the starting point for access to the pubsub service. It
+ * will provide access to general information about the service, as
+ * well as create or retrieve pubsub {@link LeafNode} instances. These
+ * instances provide the bulk of the functionality as defined in the
+ * pubsub specification <a href="http://xmpp.org/extensions/xep-0060.html">XEP-0060</a>.
+ *
+ * @author Robin Collier
+ */
+final public class PubSubManager
+{
+ private Connection con;
+ private String to;
+ private Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>();
+
+ /**
+ * Create a pubsub manager associated to the specified connection. Defaults the service
+ * name to <i>pubsub</i>
+ *
+ * @param connection The XMPP connection
+ */
+ public PubSubManager(Connection connection)
+ {
+ con = connection;
+ to = "pubsub." + connection.getServiceName();
+ }
+
+ /**
+ * Create a pubsub manager associated to the specified connection where
+ * the pubsub requests require a specific to address for packets.
+ *
+ * @param connection The XMPP connection
+ * @param toAddress The pubsub specific to address (required for some servers)
+ */
+ public PubSubManager(Connection connection, String toAddress)
+ {
+ con = connection;
+ to = toAddress;
+ }
+
+ /**
+ * Creates an instant node, if supported.
+ *
+ * @return The node that was created
+ * @exception XMPPException
+ */
+ public LeafNode createNode()
+ throws XMPPException
+ {
+ PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.CREATE));
+ NodeExtension elem = (NodeExtension)reply.getExtension("create", PubSubNamespace.BASIC.getXmlns());
+
+ LeafNode newNode = new LeafNode(con, elem.getNode());
+ newNode.setTo(to);
+ nodeMap.put(newNode.getId(), newNode);
+
+ return newNode;
+ }
+
+ /**
+ * Creates a node with default configuration.
+ *
+ * @param id The id of the node, which must be unique within the
+ * pubsub service
+ * @return The node that was created
+ * @exception XMPPException
+ */
+ public LeafNode createNode(String id)
+ throws XMPPException
+ {
+ return (LeafNode)createNode(id, null);
+ }
+
+ /**
+ * Creates a node with specified configuration.
+ *
+ * Note: This is the only way to create a collection node.
+ *
+ * @param name The name of the node, which must be unique within the
+ * pubsub service
+ * @param config The configuration for the node
+ * @return The node that was created
+ * @exception XMPPException
+ */
+ public Node createNode(String name, Form config)
+ throws XMPPException
+ {
+ PubSub request = createPubsubPacket(to, Type.SET, new NodeExtension(PubSubElementType.CREATE, name));
+ boolean isLeafNode = true;
+
+ if (config != null)
+ {
+ request.addExtension(new FormNode(FormNodeType.CONFIGURE, config));
+ FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName());
+
+ if (nodeTypeField != null)
+ isLeafNode = nodeTypeField.getValues().next().equals(NodeType.leaf.toString());
+ }
+
+ // Errors will cause exceptions in getReply, so it only returns
+ // on success.
+ sendPubsubPacket(con, to, Type.SET, request);
+ Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name);
+ newNode.setTo(to);
+ nodeMap.put(newNode.getId(), newNode);
+
+ return newNode;
+ }
+
+ /**
+ * Retrieves the requested node, if it exists. It will throw an
+ * exception if it does not.
+ *
+ * @param id - The unique id of the node
+ * @return the node
+ * @throws XMPPException The node does not exist
+ */
+ public <T extends Node> T getNode(String id)
+ throws XMPPException
+ {
+ Node node = nodeMap.get(id);
+
+ if (node == null)
+ {
+ DiscoverInfo info = new DiscoverInfo();
+ info.setTo(to);
+ info.setNode(id);
+
+ DiscoverInfo infoReply = (DiscoverInfo)SyncPacketSend.getReply(con, info);
+
+ if (infoReply.getIdentities().next().getType().equals(NodeType.leaf.toString()))
+ node = new LeafNode(con, id);
+ else
+ node = new CollectionNode(con, id);
+ node.setTo(to);
+ nodeMap.put(id, node);
+ }
+ return (T) node;
+ }
+
+ /**
+ * Get all the nodes that currently exist as a child of the specified
+ * collection node. If the service does not support collection nodes
+ * then all nodes will be returned.
+ *
+ * To retrieve contents of the root collection node (if it exists),
+ * or there is no root collection node, pass null as the nodeId.
+ *
+ * @param nodeId - The id of the collection node for which the child
+ * nodes will be returned.
+ * @return {@link DiscoverItems} representing the existing nodes
+ *
+ * @throws XMPPException
+ */
+ public DiscoverItems discoverNodes(String nodeId)
+ throws XMPPException
+ {
+ DiscoverItems items = new DiscoverItems();
+
+ if (nodeId != null)
+ items.setNode(nodeId);
+ items.setTo(to);
+ DiscoverItems nodeItems = (DiscoverItems)SyncPacketSend.getReply(con, items);
+ return nodeItems;
+ }
+
+ /**
+ * Gets the subscriptions on the root node.
+ *
+ * @return List of exceptions
+ *
+ * @throws XMPPException
+ */
+ public List<Subscription> getSubscriptions()
+ throws XMPPException
+ {
+ Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS));
+ SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns());
+ return subElem.getSubscriptions();
+ }
+
+ /**
+ * Gets the affiliations on the root node.
+ *
+ * @return List of affiliations
+ *
+ * @throws XMPPException
+ */
+ public List<Affiliation> getAffiliations()
+ throws XMPPException
+ {
+ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.AFFILIATIONS));
+ AffiliationsExtension listElem = (AffiliationsExtension)reply.getExtension(PubSubElementType.AFFILIATIONS);
+ return listElem.getAffiliations();
+ }
+
+ /**
+ * Delete the specified node
+ *
+ * @param nodeId
+ * @throws XMPPException
+ */
+ public void deleteNode(String nodeId)
+ throws XMPPException
+ {
+ sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace());
+ nodeMap.remove(nodeId);
+ }
+
+ /**
+ * Returns the default settings for Node configuration.
+ *
+ * @return configuration form containing the default settings.
+ */
+ public ConfigureForm getDefaultConfiguration()
+ throws XMPPException
+ {
+ // Errors will cause exceptions in getReply, so it only returns
+ // on success.
+ PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace());
+ return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT);
+ }
+
+ /**
+ * Gets the supported features of the servers pubsub implementation
+ * as a standard {@link DiscoverInfo} instance.
+ *
+ * @return The supported features
+ *
+ * @throws XMPPException
+ */
+ public DiscoverInfo getSupportedFeatures()
+ throws XMPPException
+ {
+ ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con);
+ return mgr.discoverInfo(to);
+ }
+
+ private Packet sendPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns)
+ throws XMPPException
+ {
+ return sendPubsubPacket(con, to, type, ext, ns);
+ }
+
+ private Packet sendPubsubPacket(Type type, PacketExtension ext)
+ throws XMPPException
+ {
+ return sendPubsubPacket(type, ext, null);
+ }
+
+ static PubSub createPubsubPacket(String to, Type type, PacketExtension ext)
+ {
+ return createPubsubPacket(to, type, ext, null);
+ }
+
+ static PubSub createPubsubPacket(String to, Type type, PacketExtension ext, PubSubNamespace ns)
+ {
+ PubSub request = new PubSub();
+ request.setTo(to);
+ request.setType(type);
+
+ if (ns != null)
+ {
+ request.setPubSubNamespace(ns);
+ }
+ request.addExtension(ext);
+
+ return request;
+ }
+
+ static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext)
+ throws XMPPException
+ {
+ return sendPubsubPacket(con, to, type, ext, null);
+ }
+
+ static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext, PubSubNamespace ns)
+ throws XMPPException
+ {
+ return SyncPacketSend.getReply(con, createPubsubPacket(to, type, ext, ns));
+ }
+
+ static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet)
+ throws XMPPException
+ {
+ return sendPubsubPacket(con, to, type, packet, null);
+ }
+
+ static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet, PubSubNamespace ns)
+ throws XMPPException
+ {
+ return SyncPacketSend.getReply(con, packet);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PublishItem.java b/src/org/jivesoftware/smackx/pubsub/PublishItem.java
new file mode 100644
index 0000000..ffbd705
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PublishItem.java
@@ -0,0 +1,70 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Represents a request to publish an item(s) to a specific node.
+ *
+ * @author Robin Collier
+ */
+public class PublishItem <T extends Item> extends NodeExtension
+{
+ protected Collection<T> items;
+
+ /**
+ * Construct a request to publish an item to a node.
+ *
+ * @param nodeId The node to publish to
+ * @param toPublish The {@link Item} to publish
+ */
+ public PublishItem(String nodeId, T toPublish)
+ {
+ super(PubSubElementType.PUBLISH, nodeId);
+ items = new ArrayList<T>(1);
+ items.add(toPublish);
+ }
+
+ /**
+ * Construct a request to publish multiple items to a node.
+ *
+ * @param nodeId The node to publish to
+ * @param toPublish The list of {@link Item} to publish
+ */
+ public PublishItem(String nodeId, Collection<T> toPublish)
+ {
+ super(PubSubElementType.PUBLISH, nodeId);
+ items = toPublish;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'>");
+
+ for (Item item : items)
+ {
+ builder.append(item.toXML());
+ }
+ builder.append("</publish>");
+
+ return builder.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/PublishModel.java b/src/org/jivesoftware/smackx/pubsub/PublishModel.java
new file mode 100644
index 0000000..4b5a851
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/PublishModel.java
@@ -0,0 +1,32 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Determines who may publish to a node. Denotes possible values
+ * for {@link ConfigureForm#setPublishModel(PublishModel)}.
+ *
+ * @author Robin Collier
+ */
+public enum PublishModel
+{
+ /** Only publishers may publish */
+ publishers,
+
+ /** Only subscribers may publish */
+ subscribers,
+
+ /** Anyone may publish */
+ open;
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/RetractItem.java b/src/org/jivesoftware/smackx/pubsub/RetractItem.java
new file mode 100644
index 0000000..97db5cc
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/RetractItem.java
@@ -0,0 +1,59 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+
+/**
+ * Represents and item that has been deleted from a node.
+ *
+ * @author Robin Collier
+ */
+public class RetractItem implements PacketExtension
+{
+ private String id;
+
+ /**
+ * Construct a <tt>RetractItem</tt> with the specified id.
+ *
+ * @param itemId The id if the item deleted
+ */
+ public RetractItem(String itemId)
+ {
+ if (itemId == null)
+ throw new IllegalArgumentException("itemId must not be 'null'");
+ id = itemId;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public String getElementName()
+ {
+ return "retract";
+ }
+
+ public String getNamespace()
+ {
+ return PubSubNamespace.EVENT.getXmlns();
+ }
+
+ public String toXML()
+ {
+ return "<retract id='" + id + "'/>";
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SimplePayload.java b/src/org/jivesoftware/smackx/pubsub/SimplePayload.java
new file mode 100644
index 0000000..9d114b0
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SimplePayload.java
@@ -0,0 +1,65 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smack.packet.PacketExtension;
+
+/**
+ * The default payload representation for {@link Item#getPayload()}. It simply
+ * stores the XML payload as a string.
+ *
+ * @author Robin Collier
+ */
+public class SimplePayload implements PacketExtension
+{
+ private String elemName;
+ private String ns;
+ private String payload;
+
+ /**
+ * Construct a <tt>SimplePayload</tt> object with the specified element name,
+ * namespace and content. The content must be well formed XML.
+ *
+ * @param elementName The root element name (of the payload)
+ * @param namespace The namespace of the payload, null if there is none
+ * @param xmlPayload The payload data
+ */
+ public SimplePayload(String elementName, String namespace, String xmlPayload)
+ {
+ elemName = elementName;
+ payload = xmlPayload;
+ ns = namespace;
+ }
+
+ public String getElementName()
+ {
+ return elemName;
+ }
+
+ public String getNamespace()
+ {
+ return ns;
+ }
+
+ public String toXML()
+ {
+ return payload;
+ }
+
+ @Override
+ public String toString()
+ {
+ return getClass().getName() + "payload [" + toXML() + "]";
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SubscribeExtension.java b/src/org/jivesoftware/smackx/pubsub/SubscribeExtension.java
new file mode 100644
index 0000000..daf8db7
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SubscribeExtension.java
@@ -0,0 +1,60 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Represents a request to subscribe to a node.
+ *
+ * @author Robin Collier
+ */
+public class SubscribeExtension extends NodeExtension
+{
+ protected String jid;
+
+ public SubscribeExtension(String subscribeJid)
+ {
+ super(PubSubElementType.SUBSCRIBE);
+ jid = subscribeJid;
+ }
+
+ public SubscribeExtension(String subscribeJid, String nodeId)
+ {
+ super(PubSubElementType.SUBSCRIBE, nodeId);
+ jid = subscribeJid;
+ }
+
+ public String getJid()
+ {
+ return jid;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+
+ if (getNode() != null)
+ {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+ }
+ builder.append(" jid='");
+ builder.append(getJid());
+ builder.append("'/>");
+
+ return builder.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/src/org/jivesoftware/smackx/pubsub/SubscribeForm.java
new file mode 100644
index 0000000..53f2606
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SubscribeForm.java
@@ -0,0 +1,241 @@
+/**
+ * All rights reserved. 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.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.UnknownFormatConversionException;
+
+import org.jivesoftware.smack.util.StringUtils;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.FormField;
+import org.jivesoftware.smackx.packet.DataForm;
+
+/**
+ * A decorator for a {@link Form} to easily enable reading and updating
+ * of subscription options. All operations read or update the underlying {@link DataForm}.
+ *
+ * <p>Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not
+ * exist, all <b>SubscribeForm.setXXX</b> methods will create the field in the wrapped form
+ * if it does not already exist.
+ *
+ * @author Robin Collier
+ */
+public class SubscribeForm extends Form
+{
+ public SubscribeForm(DataForm configDataForm)
+ {
+ super(configDataForm);
+ }
+
+ public SubscribeForm(Form subscribeOptionsForm)
+ {
+ super(subscribeOptionsForm.getDataFormToSend());
+ }
+
+ public SubscribeForm(FormType formType)
+ {
+ super(formType.toString());
+ }
+
+ /**
+ * Determines if an entity wants to receive notifications.
+ *
+ * @return true if want to receive, false otherwise
+ */
+ public boolean isDeliverOn()
+ {
+ return parseBoolean(getFieldValue(SubscribeOptionFields.deliver));
+ }
+
+ /**
+ * Sets whether an entity wants to receive notifications.
+ *
+ * @param deliverNotifications
+ */
+ public void setDeliverOn(boolean deliverNotifications)
+ {
+ addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN);
+ setAnswer(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications);
+ }
+
+ /**
+ * Determines if notifications should be delivered as aggregations or not.
+ *
+ * @return true to aggregate, false otherwise
+ */
+ public boolean isDigestOn()
+ {
+ return parseBoolean(getFieldValue(SubscribeOptionFields.digest));
+ }
+
+ /**
+ * Sets whether notifications should be delivered as aggregations or not.
+ *
+ * @param digestOn true to aggregate, false otherwise
+ */
+ public void setDigestOn(boolean digestOn)
+ {
+ addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN);
+ setAnswer(SubscribeOptionFields.deliver.getFieldName(), digestOn);
+ }
+
+ /**
+ * Gets the minimum number of milliseconds between sending notification digests
+ *
+ * @return The frequency in milliseconds
+ */
+ public int getDigestFrequency()
+ {
+ return Integer.parseInt(getFieldValue(SubscribeOptionFields.digest_frequency));
+ }
+
+ /**
+ * Sets the minimum number of milliseconds between sending notification digests
+ *
+ * @param frequency The frequency in milliseconds
+ */
+ public void setDigestFrequency(int frequency)
+ {
+ addField(SubscribeOptionFields.digest_frequency, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(SubscribeOptionFields.digest_frequency.getFieldName(), frequency);
+ }
+
+ /**
+ * Get the time at which the leased subscription will expire, or has expired.
+ *
+ * @return The expiry date
+ */
+ public Date getExpiry()
+ {
+ String dateTime = getFieldValue(SubscribeOptionFields.expire);
+ try
+ {
+ return StringUtils.parseDate(dateTime);
+ }
+ catch (ParseException e)
+ {
+ UnknownFormatConversionException exc = new UnknownFormatConversionException(dateTime);
+ exc.initCause(e);
+ throw exc;
+ }
+ }
+
+ /**
+ * Sets the time at which the leased subscription will expire, or has expired.
+ *
+ * @param expire The expiry date
+ */
+ public void setExpiry(Date expire)
+ {
+ addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE);
+ setAnswer(SubscribeOptionFields.expire.getFieldName(), StringUtils.formatXEP0082Date(expire));
+ }
+
+ /**
+ * Determines whether the entity wants to receive an XMPP message body in
+ * addition to the payload format.
+ *
+ * @return true to receive the message body, false otherwise
+ */
+ public boolean isIncludeBody()
+ {
+ return parseBoolean(getFieldValue(SubscribeOptionFields.include_body));
+ }
+
+ /**
+ * Sets whether the entity wants to receive an XMPP message body in
+ * addition to the payload format.
+ *
+ * @param include true to receive the message body, false otherwise
+ */
+ public void setIncludeBody(boolean include)
+ {
+ addField(SubscribeOptionFields.include_body, FormField.TYPE_BOOLEAN);
+ setAnswer(SubscribeOptionFields.include_body.getFieldName(), include);
+ }
+
+ /**
+ * Gets the {@link PresenceState} for which an entity wants to receive
+ * notifications.
+ *
+ * @return iterator over the list of states
+ */
+ public Iterator<PresenceState> getShowValues()
+ {
+ ArrayList<PresenceState> result = new ArrayList<PresenceState>(5);
+ Iterator<String > it = getFieldValues(SubscribeOptionFields.show_values);
+
+ while (it.hasNext())
+ {
+ String state = it.next();
+ result.add(PresenceState.valueOf(state));
+ }
+ return result.iterator();
+ }
+
+ /**
+ * Sets the list of {@link PresenceState} for which an entity wants
+ * to receive notifications.
+ *
+ * @param stateValues The list of states
+ */
+ public void setShowValues(Collection<PresenceState> stateValues)
+ {
+ ArrayList<String> values = new ArrayList<String>(stateValues.size());
+
+ for (PresenceState state : stateValues)
+ {
+ values.add(state.toString());
+ }
+ addField(SubscribeOptionFields.show_values, FormField.TYPE_LIST_MULTI);
+ setAnswer(SubscribeOptionFields.show_values.getFieldName(), values);
+ }
+
+
+ static private boolean parseBoolean(String fieldValue)
+ {
+ return ("1".equals(fieldValue) || "true".equals(fieldValue));
+ }
+
+ private String getFieldValue(SubscribeOptionFields field)
+ {
+ FormField formField = getField(field.getFieldName());
+
+ return formField.getValues().next();
+ }
+
+ private Iterator<String> getFieldValues(SubscribeOptionFields field)
+ {
+ FormField formField = getField(field.getFieldName());
+
+ return formField.getValues();
+ }
+
+ private void addField(SubscribeOptionFields nodeField, String type)
+ {
+ String fieldName = nodeField.getFieldName();
+
+ if (getField(fieldName) == null)
+ {
+ FormField field = new FormField(fieldName);
+ field.setType(type);
+ addField(field);
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java b/src/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java
new file mode 100644
index 0000000..dfca601
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java
@@ -0,0 +1,99 @@
+/**
+ * All rights reserved. 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.Calendar;
+
+/**
+ * Defines the possible field options for a subscribe options form as defined
+ * by <a href="http://xmpp.org/extensions/xep-0060.html#registrar-formtypes-subscribe">Section 16.4.2</a>.
+ *
+ * @author Robin Collier
+ */
+public enum SubscribeOptionFields
+{
+ /**
+ * Whether an entity wants to receive or disable notifications
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ deliver,
+
+ /**
+ * Whether an entity wants to receive digests (aggregations) of
+ * notifications or all notifications individually.
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ digest,
+
+ /**
+ * The minimum number of seconds between sending any two notifications digests
+ *
+ * <p><b>Value: int</b></p>
+ */
+ digest_frequency,
+
+ /**
+ * The DateTime at which a leased subsscription will end ro has ended.
+ *
+ * <p><b>Value: {@link Calendar}</b></p>
+ */
+ expire,
+
+ /**
+ * Whether an entity wants to receive an XMPP message body in addition to
+ * the payload format.
+ *
+ * <p><b>Value: boolean</b></p>
+ */
+ include_body,
+
+ /**
+ * The presence states for which an entity wants to receive notifications.
+ *
+ * <p><b>Value: {@link PresenceState}</b></p>
+ */
+ show_values,
+
+ /**
+ *
+ *
+ * <p><b>Value: </b></p>
+ */
+ subscription_type,
+
+ /**
+ *
+ * <p><b>Value: </b></p>
+ */
+ subscription_depth;
+
+ public String getFieldName()
+ {
+ if (this == show_values)
+ return "pubsub#" + toString().replace('_', '-');
+ return "pubsub#" + toString();
+ }
+
+ static public SubscribeOptionFields valueOfFromElement(String elementName)
+ {
+ String portion = elementName.substring(elementName.lastIndexOf('#' + 1));
+
+ if ("show-values".equals(portion))
+ return show_values;
+ else
+ return valueOf(portion);
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/Subscription.java b/src/org/jivesoftware/smackx/pubsub/Subscription.java
new file mode 100644
index 0000000..19ad8a8
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/Subscription.java
@@ -0,0 +1,160 @@
+/**
+ * All rights reserved. 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;
+
+/**
+ * Represents a subscription to node for both requests and replies.
+ *
+ * @author Robin Collier
+ */
+public class Subscription extends NodeExtension
+{
+ protected String jid;
+ protected String id;
+ protected State state;
+ protected boolean configRequired = false;
+
+ public enum State
+ {
+ subscribed, unconfigured, pending, none
+ }
+
+ /**
+ * Used to constructs a subscription request to the root node with the specified
+ * JID.
+ *
+ * @param subscriptionJid The subscriber JID
+ */
+ public Subscription(String subscriptionJid)
+ {
+ this(subscriptionJid, null, null, null);
+ }
+
+ /**
+ * Used to constructs a subscription request to the specified node with the specified
+ * JID.
+ *
+ * @param subscriptionJid The subscriber JID
+ * @param nodeId The node id
+ */
+ public Subscription(String subscriptionJid, String nodeId)
+ {
+ this(subscriptionJid, nodeId, null, null);
+ }
+
+ /**
+ * Constructs a representation of a subscription reply to the specified node
+ * and JID. The server will have supplied the subscription id and current state.
+ *
+ * @param jid The JID the request was made under
+ * @param nodeId The node subscribed to
+ * @param subscriptionId The id of this subscription
+ * @param state The current state of the subscription
+ */
+ public Subscription(String jid, String nodeId, String subscriptionId, State state)
+ {
+ super(PubSubElementType.SUBSCRIPTION, nodeId);
+ this.jid = jid;
+ id = subscriptionId;
+ this.state = state;
+ }
+
+ /**
+ * Constructs a representation of a subscription reply to the specified node
+ * and JID. The server will have supplied the subscription id and current state
+ * and whether the subscription need to be configured.
+ *
+ * @param jid The JID the request was made under
+ * @param nodeId The node subscribed to
+ * @param subscriptionId The id of this subscription
+ * @param state The current state of the subscription
+ * @param configRequired Is configuration required to complete the subscription
+ */
+ public Subscription(String jid, String nodeId, String subscriptionId, State state, boolean configRequired)
+ {
+ super(PubSubElementType.SUBSCRIPTION, nodeId);
+ this.jid = jid;
+ id = subscriptionId;
+ this.state = state;
+ this.configRequired = configRequired;
+ }
+
+ /**
+ * Gets the JID the subscription is created for
+ *
+ * @return The JID
+ */
+ public String getJid()
+ {
+ return jid;
+ }
+
+ /**
+ * Gets the subscription id
+ *
+ * @return The subscription id
+ */
+ public String getId()
+ {
+ return id;
+ }
+
+ /**
+ * Gets the current subscription state.
+ *
+ * @return Current subscription state
+ */
+ public State getState()
+ {
+ return state;
+ }
+
+ /**
+ * This value is only relevant when the {@link #getState()} is {@link State#unconfigured}
+ *
+ * @return true if configuration is required, false otherwise
+ */
+ public boolean isConfigRequired()
+ {
+ return configRequired;
+ }
+
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<subscription");
+ appendAttribute(builder, "jid", jid);
+
+ if (getNode() != null)
+ appendAttribute(builder, "node", getNode());
+
+ if (id != null)
+ appendAttribute(builder, "subid", id);
+
+ if (state != null)
+ appendAttribute(builder, "subscription", state.toString());
+
+ builder.append("/>");
+ return builder.toString();
+ }
+
+ private void appendAttribute(StringBuilder builder, String att, String value)
+ {
+ builder.append(" ");
+ builder.append(att);
+ builder.append("='");
+ builder.append(value);
+ builder.append("'");
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java b/src/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java
new file mode 100644
index 0000000..99f18d5
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java
@@ -0,0 +1,75 @@
+/**
+ * All rights reserved. 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.Collections;
+import java.util.List;
+
+/**
+ * Base class to represents events that are associated to subscriptions.
+ *
+ * @author Robin Collier
+ */
+abstract public class SubscriptionEvent extends NodeEvent
+{
+ private List<String> subIds = Collections.EMPTY_LIST;
+
+ /**
+ * Construct an event with no subscription id's. This can
+ * occur when there is only one subscription to a node. The
+ * event may or may not report the subscription id along
+ * with the event.
+ *
+ * @param nodeId The id of the node the event came from
+ */
+ protected SubscriptionEvent(String nodeId)
+ {
+ super(nodeId);
+ }
+
+ /**
+ * Construct an event with multiple subscriptions.
+ *
+ * @param nodeId The id of the node the event came from
+ * @param subscriptionIds The list of subscription id's
+ */
+ protected SubscriptionEvent(String nodeId, List<String> subscriptionIds)
+ {
+ super(nodeId);
+
+ if (subscriptionIds != null)
+ subIds = subscriptionIds;
+ }
+
+ /**
+ * Get the subscriptions this event is associated with.
+ *
+ * @return List of subscription id's
+ */
+ public List<String> getSubscriptions()
+ {
+ return Collections.unmodifiableList(subIds);
+ }
+
+ /**
+ * Set the list of subscription id's for this event.
+ *
+ * @param subscriptionIds The list of subscription id's
+ */
+ protected void setSubscriptions(List<String> subscriptionIds)
+ {
+ if (subscriptionIds != null)
+ subIds = subscriptionIds;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java b/src/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java
new file mode 100644
index 0000000..a28cbe2
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java
@@ -0,0 +1,96 @@
+/**
+ * All rights reserved. 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.Collections;
+import java.util.List;
+
+/**
+ * Represents the element holding the list of subscription elements.
+ *
+ * @author Robin Collier
+ */
+public class SubscriptionsExtension extends NodeExtension
+{
+ protected List<Subscription> items = Collections.EMPTY_LIST;
+
+ /**
+ * Subscriptions to the root node
+ *
+ * @param subList The list of subscriptions
+ */
+ public SubscriptionsExtension(List<Subscription> subList)
+ {
+ super(PubSubElementType.SUBSCRIPTIONS);
+
+ if (subList != null)
+ items = subList;
+ }
+
+ /**
+ * Subscriptions to the specified node.
+ *
+ * @param nodeId The node subscribed to
+ * @param subList The list of subscriptions
+ */
+ public SubscriptionsExtension(String nodeId, List<Subscription> subList)
+ {
+ super(PubSubElementType.SUBSCRIPTIONS, nodeId);
+
+ if (subList != null)
+ items = subList;
+ }
+
+ /**
+ * Gets the list of subscriptions.
+ *
+ * @return List of subscriptions
+ */
+ public List<Subscription> getSubscriptions()
+ {
+ return items;
+ }
+
+ @Override
+ public String toXML()
+ {
+ if ((items == null) || (items.size() == 0))
+ {
+ return super.toXML();
+ }
+ else
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+
+ if (getNode() != null)
+ {
+ builder.append(" node='");
+ builder.append(getNode());
+ builder.append("'");
+ }
+ builder.append(">");
+
+ for (Subscription item : items)
+ {
+ builder.append(item.toXML());
+ }
+
+ builder.append("</");
+ builder.append(getElementName());
+ builder.append(">");
+ return builder.toString();
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java b/src/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java
new file mode 100644
index 0000000..ac14c60
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java
@@ -0,0 +1,73 @@
+/**
+ * All rights reserved. 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 org.jivesoftware.smackx.pubsub.util.XmlUtils;
+
+
+/**
+ * Represents an unsubscribe element.
+ *
+ * @author Robin Collier
+ */
+public class UnsubscribeExtension extends NodeExtension
+{
+ protected String jid;
+ protected String id;
+
+ public UnsubscribeExtension(String subscriptionJid)
+ {
+ this(subscriptionJid, null, null);
+ }
+
+ public UnsubscribeExtension(String subscriptionJid, String nodeId)
+ {
+ this(subscriptionJid, nodeId, null);
+ }
+
+ public UnsubscribeExtension(String jid, String nodeId, String subscriptionId)
+ {
+ super(PubSubElementType.UNSUBSCRIBE, nodeId);
+ this.jid = jid;
+ id = subscriptionId;
+ }
+
+ public String getJid()
+ {
+ return jid;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ @Override
+ public String toXML()
+ {
+ StringBuilder builder = new StringBuilder("<");
+ builder.append(getElementName());
+ XmlUtils.appendAttribute(builder, "jid", jid);
+
+ if (getNode() != null)
+ XmlUtils.appendAttribute(builder, "node", getNode());
+
+ if (id != null)
+ XmlUtils.appendAttribute(builder, "subid", id);
+
+ builder.append("/>");
+ return builder.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java b/src/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java
new file mode 100644
index 0000000..d228e8f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java
@@ -0,0 +1,41 @@
+/**
+ * All rights reserved. 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.listener;
+
+import org.jivesoftware.smackx.pubsub.ItemDeleteEvent;
+import org.jivesoftware.smackx.pubsub.LeafNode;
+
+/**
+ * Defines the listener for item deletion events from a node.
+ *
+ * @see LeafNode#addItemDeleteListener(ItemDeleteListener)
+ *
+ * @author Robin Collier
+ */
+public interface ItemDeleteListener
+{
+ /**
+ * Called when items are deleted from a node the listener is
+ * registered with.
+ *
+ * @param items The event with item deletion details
+ */
+ void handleDeletedItems(ItemDeleteEvent items);
+
+ /**
+ * Called when <b>all</b> items are deleted from a node the listener is
+ * registered with.
+ */
+ void handlePurge();
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java b/src/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java
new file mode 100644
index 0000000..714b2c0
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java
@@ -0,0 +1,36 @@
+/**
+ * All rights reserved. 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.listener;
+
+import org.jivesoftware.smackx.pubsub.Item;
+import org.jivesoftware.smackx.pubsub.ItemPublishEvent;
+import org.jivesoftware.smackx.pubsub.LeafNode;
+
+/**
+ * Defines the listener for items being published to a node.
+ *
+ * @see LeafNode#addItemEventListener(ItemEventListener)
+ *
+ * @author Robin Collier
+ */
+public interface ItemEventListener <T extends Item>
+{
+ /**
+ * Called whenever an item is published to the node the listener
+ * is registered with.
+ *
+ * @param items The publishing details.
+ */
+ void handlePublishedItems(ItemPublishEvent<T> items);
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java b/src/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java
new file mode 100644
index 0000000..39db5a5
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java
@@ -0,0 +1,35 @@
+/**
+ * All rights reserved. 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.listener;
+
+import org.jivesoftware.smackx.pubsub.ConfigurationEvent;
+import org.jivesoftware.smackx.pubsub.LeafNode;
+
+/**
+ * Defines the listener for a node being configured.
+ *
+ * @see LeafNode#addConfigurationListener(NodeConfigListener)
+ *
+ * @author Robin Collier
+ */
+public interface NodeConfigListener
+{
+ /**
+ * Called whenever the node the listener
+ * is registered with is configured.
+ *
+ * @param config The configuration details.
+ */
+ void handleNodeConfiguration(ConfigurationEvent config);
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/packet/PubSub.java b/src/org/jivesoftware/smackx/pubsub/packet/PubSub.java
new file mode 100644
index 0000000..5aa4865
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/packet/PubSub.java
@@ -0,0 +1,106 @@
+/**
+ * All rights reserved. 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.packet;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.pubsub.PubSubElementType;
+
+/**
+ * The standard PubSub extension of an {@link IQ} packet. This is the topmost
+ * element of all pubsub requests and replies as defined in the <a href="http://xmpp.org/extensions/xep-0060">Publish-Subscribe</a>
+ * specification.
+ *
+ * @author Robin Collier
+ */
+public class PubSub extends IQ
+{
+ private PubSubNamespace ns = PubSubNamespace.BASIC;
+
+ /**
+ * Returns the XML element name of the extension sub-packet root element.
+ *
+ * @return the XML element name of the packet extension.
+ */
+ public String getElementName() {
+ return "pubsub";
+ }
+
+ /**
+ * Returns the XML namespace of the extension sub-packet root element.
+ * According the specification the namespace is
+ * http://jabber.org/protocol/pubsub with a specific fragment depending
+ * on the request. The namespace is defined at <a href="http://xmpp.org/registrar/namespaces.html">XMPP Registrar</a> at
+ *
+ * The default value has no fragment.
+ *
+ * @return the XML namespace of the packet extension.
+ */
+ public String getNamespace()
+ {
+ return ns.getXmlns();
+ }
+
+ /**
+ * Set the namespace for the packet if it something other than the default
+ * case of {@link PubSubNamespace#BASIC}. The {@link #getNamespace()} method will return
+ * the result of calling {@link PubSubNamespace#getXmlns()} on the specified enum.
+ *
+ * @param ns - The new value for the namespace.
+ */
+ public void setPubSubNamespace(PubSubNamespace ns)
+ {
+ this.ns = ns;
+ }
+
+ public PacketExtension getExtension(PubSubElementType elem)
+ {
+ return getExtension(elem.getElementName(), elem.getNamespace().getXmlns());
+ }
+
+ /**
+ * Returns the current value of the namespace. The {@link #getNamespace()} method will return
+ * the result of calling {@link PubSubNamespace#getXmlns()} this value.
+ *
+ * @return The current value of the namespace.
+ */
+ public PubSubNamespace getPubSubNamespace()
+ {
+ return ns;
+ }
+ /**
+ * Returns the XML representation of a pubsub element according the specification.
+ *
+ * The XML representation will be inside of an iq packet like
+ * in the following example:
+ * <pre>
+ * &lt;iq type='set' id="MlIpV-4" to="pubsub.gato.home" from="gato3@gato.home/Smack"&gt;
+ * &lt;pubsub xmlns="http://jabber.org/protocol/pubsub"&gt;
+ * :
+ * Specific request extension
+ * :
+ * &lt;/pubsub&gt;
+ * &lt;/iq&gt;
+ * </pre>
+ *
+ */
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">");
+ buf.append(getExtensionsXML());
+ buf.append("</").append(getElementName()).append(">");
+ return buf.toString();
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java b/src/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java
new file mode 100644
index 0000000..eecf959
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java
@@ -0,0 +1,63 @@
+/**
+ * All rights reserved. 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.packet;
+
+/**
+ * Defines all the valid namespaces that are used with the {@link PubSub} packet
+ * as defined by the specification.
+ *
+ * @author Robin Collier
+ */
+public enum PubSubNamespace
+{
+ BASIC(null),
+ ERROR("errors"),
+ EVENT("event"),
+ OWNER("owner");
+
+ private String fragment;
+
+ private PubSubNamespace(String fragment)
+ {
+ this.fragment = fragment;
+ }
+
+ public String getXmlns()
+ {
+ String ns = "http://jabber.org/protocol/pubsub";
+
+ if (fragment != null)
+ ns += '#' + fragment;
+
+ return ns;
+ }
+
+ public String getFragment()
+ {
+ return fragment;
+ }
+
+ public static PubSubNamespace valueOfFromXmlns(String ns)
+ {
+ int index = ns.lastIndexOf('#');
+
+ if (index != -1)
+ {
+ String suffix = ns.substring(ns.lastIndexOf('#')+1);
+ return valueOf(suffix.toUpperCase());
+ }
+ else
+ return BASIC;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java b/src/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java
new file mode 100644
index 0000000..080129b
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java
@@ -0,0 +1,63 @@
+/**
+ * All rights reserved. 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.packet;
+
+import org.jivesoftware.smack.PacketCollector;
+import org.jivesoftware.smack.SmackConfiguration;
+import org.jivesoftware.smack.Connection;
+import org.jivesoftware.smack.XMPPException;
+import org.jivesoftware.smack.filter.PacketFilter;
+import org.jivesoftware.smack.filter.PacketIDFilter;
+import org.jivesoftware.smack.packet.Packet;
+
+/**
+ * Utility class for doing synchronous calls to the server. Provides several
+ * methods for sending a packet to the server and waiting for the reply.
+ *
+ * @author Robin Collier
+ */
+final public class SyncPacketSend
+{
+ private SyncPacketSend()
+ { }
+
+ static public Packet getReply(Connection connection, Packet packet, long timeout)
+ throws XMPPException
+ {
+ PacketFilter responseFilter = new PacketIDFilter(packet.getPacketID());
+ PacketCollector response = connection.createPacketCollector(responseFilter);
+
+ connection.sendPacket(packet);
+
+ // Wait up to a certain number of seconds for a reply.
+ Packet result = response.nextResult(timeout);
+
+ // Stop queuing results
+ response.cancel();
+
+ if (result == null) {
+ throw new XMPPException("No response from server.");
+ }
+ else if (result.getError() != null) {
+ throw new XMPPException(result.getError());
+ }
+ return result;
+ }
+
+ static public Packet getReply(Connection connection, Packet packet)
+ throws XMPPException
+ {
+ return getReply(connection, packet, SmackConfiguration.getPacketReplyTimeout());
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java
new file mode 100644
index 0000000..892eec6
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java
@@ -0,0 +1,37 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.Affiliation;
+
+/**
+ * Parses the affiliation element out of the reply stanza from the server
+ * as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-pubsub">affiliation schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class AffiliationProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new Affiliation(attributeMap.get("jid"), attributeMap.get("node"), Affiliation.Type.valueOf(attributeMap.get("affiliation")));
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java
new file mode 100644
index 0000000..ee7af05
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.Affiliation;
+import org.jivesoftware.smackx.pubsub.AffiliationsExtension;
+
+/**
+ * Parses the affiliations element out of the reply stanza from the server
+ * as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-pubsub">affiliation schema</a>.
+ *
+ * @author Robin Collier
+ */public class AffiliationsProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new AffiliationsExtension(attributeMap.get("node"), (List<Affiliation>)content);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java
new file mode 100644
index 0000000..30e3017
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java
@@ -0,0 +1,42 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.packet.DataForm;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.ConfigurationEvent;
+import org.jivesoftware.smackx.pubsub.ConfigureForm;
+
+/**
+ * Parses the node configuration element out of the message event stanza from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-event">configuration schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class ConfigEventProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attMap, List<? extends PacketExtension> content)
+ {
+ if (content.size() == 0)
+ return new ConfigurationEvent(attMap.get("node"));
+ else
+ return new ConfigurationEvent(attMap.get("node"), new ConfigureForm((DataForm)content.iterator().next()));
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/EventProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/EventProvider.java
new file mode 100644
index 0000000..ef5671e
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/EventProvider.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.EventElement;
+import org.jivesoftware.smackx.pubsub.EventElementType;
+import org.jivesoftware.smackx.pubsub.NodeExtension;
+
+/**
+ * Parses the event element out of the message stanza from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-event">event schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class EventProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attMap, List<? extends PacketExtension> content)
+ {
+ return new EventElement(EventElementType.valueOf(content.get(0).getElementName()), (NodeExtension)content.get(0));
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java
new file mode 100644
index 0000000..da75b24
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java
@@ -0,0 +1,39 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.packet.DataForm;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.FormNode;
+import org.jivesoftware.smackx.pubsub.FormNodeType;
+
+/**
+ * Parses one of several elements used in pubsub that contain a form of some kind as a child element. The
+ * elements and namespaces supported is defined in {@link FormNodeType}.
+ *
+ * @author Robin Collier
+ */
+public class FormNodeProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), new Form((DataForm)content.iterator().next()));
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java
new file mode 100644
index 0000000..a6b8694
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java
@@ -0,0 +1,92 @@
+/**
+ * All rights reserved. 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.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smack.provider.ProviderManager;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.pubsub.Item;
+import org.jivesoftware.smackx.pubsub.PayloadItem;
+import org.jivesoftware.smackx.pubsub.SimplePayload;
+import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses an <b>item</b> element as is defined in both the {@link PubSubNamespace#BASIC} and {@link PubSubNamespace#EVENT}
+ * namespaces. To parse the item contents, it will use whatever {@link PacketExtensionProvider} is registered in
+ * <b>smack.providers</b> for its element name and namespace. If no provider is registered, it will return a {@link SimplePayload}.
+ *
+ * @author Robin Collier
+ */
+public class ItemProvider implements PacketExtensionProvider {
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
+ String id = parser.getAttributeValue(null, "id");
+ String node = parser.getAttributeValue(null, "node");
+ String elem = parser.getName();
+
+ int tag = parser.next();
+
+ if (tag == XmlPullParser.END_TAG) {
+ return new Item(id, node);
+ } else {
+ String payloadElemName = parser.getName();
+ String payloadNS = parser.getNamespace();
+
+ if (ProviderManager.getInstance().getExtensionProvider(payloadElemName, payloadNS) == null) {
+ StringBuilder payloadText = new StringBuilder();
+ boolean done = false;
+ boolean isEmptyElement = false;
+
+ // Parse custom payload
+ while (!done) {
+ if (tag == XmlPullParser.END_TAG && parser.getName().equals(elem)) {
+ done = true;
+ } else if (parser.getEventType() == XmlPullParser.START_TAG) {
+ payloadText.append("<").append(parser.getName());
+ if (parser.getName().equals(payloadElemName) && (!"".equals(payloadNS))) {
+ payloadText.append(" xmlns=\"").append(payloadNS).append("\"");
+ }
+ int n = parser.getAttributeCount();
+ for (int i = 0; i < n; i++) {
+ payloadText.append(" ").append(parser.getAttributeName(i)).append("=\"")
+ .append(parser.getAttributeValue(i)).append("\"");
+ }
+ if (parser.isEmptyElementTag()) {
+ payloadText.append("/>");
+ isEmptyElement = true;
+ } else {
+ payloadText.append(">");
+ }
+ } else if (parser.getEventType() == XmlPullParser.END_TAG) {
+ if (isEmptyElement) {
+ isEmptyElement = false;
+ } else {
+ payloadText.append("</").append(parser.getName()).append(">");
+ }
+ } else if (parser.getEventType() == XmlPullParser.TEXT) {
+ payloadText.append(parser.getText());
+ }
+
+ tag = parser.next();
+ }
+ return new PayloadItem<SimplePayload>(id, node, new SimplePayload(payloadElemName, payloadNS,
+ payloadText.toString()));
+ } else {
+ return new PayloadItem<PacketExtension>(id, node, PacketParserUtils.parsePacketExtension(
+ payloadElemName, payloadNS, parser));
+ }
+ }
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java
new file mode 100644
index 0000000..01cb9d4
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.ItemsExtension;
+
+/**
+ * Parses the <b>items</b> element out of the message event stanza from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-event">items schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class ItemsProvider extends EmbeddedExtensionProvider
+{
+
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new ItemsExtension(ItemsExtension.ItemsElementType.items, attributeMap.get("node"), content);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java
new file mode 100644
index 0000000..742f219
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java
@@ -0,0 +1,62 @@
+/**
+ * All rights reserved. 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.provider;
+
+import org.jivesoftware.smack.packet.IQ;
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.IQProvider;
+import org.jivesoftware.smack.util.PacketParserUtils;
+import org.jivesoftware.smackx.pubsub.packet.PubSub;
+import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses the root pubsub packet extensions of the {@link IQ} packet and returns
+ * a {@link PubSub} instance.
+ *
+ * @author Robin Collier
+ */
+public class PubSubProvider implements IQProvider
+{
+ public IQ parseIQ(XmlPullParser parser) throws Exception
+ {
+ PubSub pubsub = new PubSub();
+ String namespace = parser.getNamespace();
+ pubsub.setPubSubNamespace(PubSubNamespace.valueOfFromXmlns(namespace));
+ boolean done = false;
+
+ while (!done)
+ {
+ int eventType = parser.next();
+
+ if (eventType == XmlPullParser.START_TAG)
+ {
+ PacketExtension ext = PacketParserUtils.parsePacketExtension(parser.getName(), namespace, parser);
+
+ if (ext != null)
+ {
+ pubsub.addExtension(ext);
+ }
+ }
+ else if (eventType == XmlPullParser.END_TAG)
+ {
+ if (parser.getName().equals("pubsub"))
+ {
+ done = true;
+ }
+ }
+ }
+ return pubsub;
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java
new file mode 100644
index 0000000..8fa3337
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.RetractItem;
+
+/**
+ * Parses the <b>retract</b> element out of the message event stanza from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-event">retract schema</a>.
+ * This element is a child of the <b>items</b> element.
+ *
+ * @author Robin Collier
+ */
+public class RetractEventProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new RetractItem(attributeMap.get("id"));
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java
new file mode 100644
index 0000000..d2b7d30
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java
@@ -0,0 +1,37 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.NodeExtension;
+import org.jivesoftware.smackx.pubsub.PubSubElementType;
+
+/**
+ * Parses simple elements that only contain a <b>node</b> attribute. This is common amongst many of the
+ * elements defined in the pubsub specification. For this common case a {@link NodeExtension} is returned.
+ *
+ * @author Robin Collier
+ */
+public class SimpleNodeProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new NodeExtension(PubSubElementType.valueOfFromElemName(currentElement, currentNamespace), attributeMap.get("node"));
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java
new file mode 100644
index 0000000..eccbe08
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java
@@ -0,0 +1,52 @@
+/**
+ * All rights reserved. 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.provider;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smack.provider.PacketExtensionProvider;
+import org.jivesoftware.smackx.pubsub.Subscription;
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Parses the <b>subscription</b> element out of the pubsub IQ message from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-pubsub">subscription schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class SubscriptionProvider implements PacketExtensionProvider
+{
+ public PacketExtension parseExtension(XmlPullParser parser) throws Exception
+ {
+ String jid = parser.getAttributeValue(null, "jid");
+ String nodeId = parser.getAttributeValue(null, "node");
+ String subId = parser.getAttributeValue(null, "subid");
+ String state = parser.getAttributeValue(null, "subscription");
+ boolean isRequired = false;
+
+ int tag = parser.next();
+
+ if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("subscribe-options"))
+ {
+ tag = parser.next();
+
+ if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("required"))
+ isRequired = true;
+
+ while (parser.next() != XmlPullParser.END_TAG && parser.getName() != "subscribe-options");
+ }
+ while (parser.getEventType() != XmlPullParser.END_TAG) parser.next();
+ return new Subscription(jid, nodeId, subId, (state == null ? null : Subscription.State.valueOf(state)), isRequired);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java b/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java
new file mode 100644
index 0000000..94dc61d
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java
@@ -0,0 +1,38 @@
+/**
+ * All rights reserved. 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.provider;
+
+import java.util.List;
+import java.util.Map;
+
+import org.jivesoftware.smack.packet.PacketExtension;
+import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider;
+import org.jivesoftware.smackx.pubsub.Subscription;
+import org.jivesoftware.smackx.pubsub.SubscriptionsExtension;
+
+/**
+ * Parses the <b>subscriptions</b> element out of the pubsub IQ message from
+ * the server as specified in the <a href="http://xmpp.org/extensions/xep-0060.html#schemas-pubsub">subscriptions schema</a>.
+ *
+ * @author Robin Collier
+ */
+public class SubscriptionsProvider extends EmbeddedExtensionProvider
+{
+ @Override
+ protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map<String, String> attributeMap, List<? extends PacketExtension> content)
+ {
+ return new SubscriptionsExtension(attributeMap.get("node"), (List<Subscription>)content);
+ }
+
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/util/NodeUtils.java b/src/org/jivesoftware/smackx/pubsub/util/NodeUtils.java
new file mode 100644
index 0000000..414601f
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/util/NodeUtils.java
@@ -0,0 +1,43 @@
+/**
+ * All rights reserved. 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.util;
+
+import org.jivesoftware.smack.packet.Packet;
+import org.jivesoftware.smackx.Form;
+import org.jivesoftware.smackx.pubsub.ConfigureForm;
+import org.jivesoftware.smackx.pubsub.FormNode;
+import org.jivesoftware.smackx.pubsub.PubSubElementType;
+
+/**
+ * Utility for extracting information from packets.
+ *
+ * @author Robin Collier
+ */
+public class NodeUtils
+{
+ /**
+ * Get a {@link ConfigureForm} from a packet.
+ *
+ * @param packet
+ * @param elem
+ * @return The configuration form
+ */
+ public static ConfigureForm getFormFromPacket(Packet packet, PubSubElementType elem)
+ {
+ FormNode config = (FormNode)packet.getExtension(elem.getElementName(), elem.getNamespace().getXmlns());
+ Form formReply = config.getForm();
+ return new ConfigureForm(formReply);
+
+ }
+}
diff --git a/src/org/jivesoftware/smackx/pubsub/util/XmlUtils.java b/src/org/jivesoftware/smackx/pubsub/util/XmlUtils.java
new file mode 100644
index 0000000..8e4a77c
--- /dev/null
+++ b/src/org/jivesoftware/smackx/pubsub/util/XmlUtils.java
@@ -0,0 +1,35 @@
+/**
+ * All rights reserved. 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.util;
+
+import java.io.StringReader;
+
+/**
+ * Simple utility for pretty printing xml.
+ *
+ * @author Robin Collier
+ */
+public class XmlUtils
+{
+
+ static public void appendAttribute(StringBuilder builder, String att, String value)
+ {
+ builder.append(" ");
+ builder.append(att);
+ builder.append("='");
+ builder.append(value);
+ builder.append("'");
+ }
+
+}