diff --git a/build/resources/META-INF/smack.providers b/build/resources/META-INF/smack.providers index 56171003a..9733d0da3 100644 --- a/build/resources/META-INF/smack.providers +++ b/build/resources/META-INF/smack.providers @@ -124,6 +124,12 @@ org.jivesoftware.smackx.provider.DelayInformationProvider + + delay + urn:xmpp:delay + org.jivesoftware.smackx.provider.DelayInfoProvider + + query @@ -472,4 +478,140 @@ org.jivesoftware.smackx.workgroup.packet.RoomTransfer$Provider + + + headers + http://jabber.org/protocol/shim + org.jivesoftware.smackx.provider.HeadersProvider + + + + header + http://jabber.org/protocol/shim + org.jivesoftware.smackx.provider.HeaderProvider + + + + + pubsub + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.PubSubProvider + + + + create + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.SimpleNodeProvider + + + + items + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.ItemsProvider + + + + item + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.ItemProvider + + + + subscriptions + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.SubscriptionsProvider + + + + subscription + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider + + + + affiliations + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.AffiliationsProvider + + + + affiliation + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.pubsub.provider.AffiliationProvider + + + + options + http://jabber.org/protocol/pubsub + org.jivesoftware.smackx.provider.FormNodeProvider + + + + + pubsub + http://jabber.org/protocol/pubsub#owner + org.jivesoftware.smackx.pubsub.provider.PubSubProvider + + + + configure + http://jabber.org/protocol/pubsub#owner + org.jivesoftware.smackx.provider.FormNodeProvider + + + + default + http://jabber.org/protocol/pubsub#owner + org.jivesoftware.smackx.provider.FormNodeProvider + + + + + event + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.EventProvider + + + + configuration + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.ConfigEventProvider + + + + delete + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.SimpleNodeProvider + + + + options + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.provider.FormNodeProvider + + + + items + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.ItemsProvider + + + + item + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.ItemProvider + + + + retract + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.RetractEventProvider + + + + purge + http://jabber.org/protocol/pubsub#event + org.jivesoftware.smackx.pubsub.provider.SimpleNodeProvider + + \ No newline at end of file diff --git a/documentation/extensions/intro.html b/documentation/extensions/intro.html index cf923e67f..03dae469c 100644 --- a/documentation/extensions/intro.html +++ b/documentation/extensions/intro.html @@ -70,6 +70,11 @@ XEP-0096 Transfer files between two users over XMPP. + + PubSub + XEP-0060 + Generic publish and subscribe functionality. + \ No newline at end of file diff --git a/documentation/extensions/toc.html b/documentation/extensions/toc.html index 6246a3644..f5d8ac778 100644 --- a/documentation/extensions/toc.html +++ b/documentation/extensions/toc.html @@ -21,6 +21,7 @@ Group Chat Invitations
Service Discovery
File Transfer
+PubSub

diff --git a/source/org/jivesoftware/smackx/pubsub/AccessModel.java b/source/org/jivesoftware/smackx/pubsub/AccessModel.java new file mode 100644 index 000000000..c1fa5461a --- /dev/null +++ b/source/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 16.4.3 + * + * @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/source/org/jivesoftware/smackx/pubsub/Affiliation.java b/source/org/jivesoftware/smackx/pubsub/Affiliation.java new file mode 100644 index 000000000..2be8d103f --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/Affiliation.java @@ -0,0 +1,90 @@ +/** + * 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.XMPPConnection; +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 XMPPConnection}. + * + * @author Robin Collier + */ +public class Affiliation implements PacketExtension +{ + protected String node; + protected Type type; + + public enum Type + { + member, none, outcast, owner, publisher + } + + /** + * Constructs an affiliation. + * + * @param nodeId The node the user is affiliated with. + * @param affiliation The type of affiliation. + */ + public Affiliation(String nodeId, Type affiliation) + { + node = nodeId; + type = affiliation; + } + + public String getNodeId() + { + return node; + } + + public Type getType() + { + return type; + } + + public String getElementName() + { + return "subscription"; + } + + public String getNamespace() + { + return null; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + appendAttribute(builder, "node", node); + 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/source/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java b/source/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java new file mode 100644 index 000000000..aa82dcbab --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java @@ -0,0 +1,69 @@ +/** + * 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 affiliations element of the reply to a request for affiliations. + * It is defined in the specification in section 5.7 Retrieve Affiliations. + * + * @author Robin Collier + */ +public class AffiliationsExtension extends NodeExtension +{ + protected List items = Collections.EMPTY_LIST; + + public AffiliationsExtension() + { + super(PubSubElementType.AFFILIATIONS); + } + + public AffiliationsExtension(List subList) + { + super(PubSubElementType.AFFILIATIONS); + items = subList; + } + + public List getAffiliations() + { + return items; + } + + @Override + public String toXML() + { + if ((items == null) || (items.size() == 0)) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + builder.append(">"); + + for (Affiliation item : items) + { + builder.append(item.toXML()); + } + + builder.append(""); + return builder.toString(); + } + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java b/source/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java new file mode 100644 index 000000000..933a39ece --- /dev/null +++ b/source/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 16.4.3 + * + * @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/source/org/jivesoftware/smackx/pubsub/CollectionNode.java b/source/org/jivesoftware/smackx/pubsub/CollectionNode.java new file mode 100644 index 000000000..84e362263 --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/CollectionNode.java @@ -0,0 +1,15 @@ +/* + * Created on 2009-07-13 + */ +package org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.XMPPConnection; + +public class CollectionNode extends Node +{ + CollectionNode(XMPPConnection connection, String nodeId) + { + super(connection, nodeId); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java b/source/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java new file mode 100644 index 000000000..67b8304a4 --- /dev/null +++ b/source/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 configuration 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 getExtensions() + { + if (getConfiguration() == null) + return Collections.EMPTY_LIST; + else + return Arrays.asList(((PacketExtension)getConfiguration().getDataFormToSend())); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/source/org/jivesoftware/smackx/pubsub/ConfigureForm.java new file mode 100644 index 000000000..2962c9bee --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/ConfigureForm.java @@ -0,0 +1,707 @@ +/** + * 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}. + * + *

Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all ConfigureForm.setXXX 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 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 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); + setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), policy.toString()); + } + + /** + * 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 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 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 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 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 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 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 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 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 fields = getFields(); + + while (fields.hasNext()) + { + FormField formField = fields.next(); + result.append('('); + result.append(formField.getVariable()); + result.append(':'); + + Iterator 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().next(); + } + + private Iterator 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 getListSingle(String value) + { + List list = new ArrayList(1); + list.add(value); + return list; + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java b/source/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java new file mode 100644 index 000000000..39124831d --- /dev/null +++ b/source/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 + * + *

Value: {@link AccessModel}

+ */ + access_model, + + /** + * The URL of an XSL transformation which can be applied to + * payloads in order to generate an appropriate message + * body element + * + *

Value: {@link URL}

+ */ + body_xslt, + + /** + * The collection with which a node is affiliated + * + *

Value: String

+ */ + 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. + * + *

Value: {@link URL}

+ */ + dataform_xslt, + + /** + * Whether to deliver payloads with event notifications + * + *

Value: boolean

+ */ + deliver_payloads, + + /** + * Whether owners or publisher should receive replies to items + * + *

Value: {@link ItemReply}

+ */ + itemreply, + + /** + * Who may associate leaf nodes with a collection + * + *

Value: {@link ChildrenAssociationPolicy}

+ */ + children_association_policy, + + /** + * The list of JIDs that may associate leaf nodes with a + * collection + * + *

Value: List of JIDs as Strings

+ */ + children_association_whitelist, + + /** + * The child nodes (leaf or collection) associated with a collection + * + *

Value: List of Strings

+ */ + children, + + /** + * The maximum number of child nodes that can be associated with a + * collection + * + *

Value: int

+ */ + children_max, + + /** + * The maximum number of items to persist + * + *

Value: int

+ */ + max_items, + + /** + * The maximum payload size in bytes + * + *

Value: int

+ */ + max_payload_size, + + /** + * Whether the node is a leaf (default) or collection + * + *

Value: {@link NodeType}

+ */ + node_type, + + /** + * Whether to notify subscribers when the node configuration changes + * + *

Value: boolean

+ */ + notify_config, + + /** + * Whether to notify subscribers when the node is deleted + * + *

Value: boolean

+ */ + notify_delete, + + /** + * Whether to notify subscribers when items are removed from the node + * + *

Value: boolean

+ */ + notify_retract, + + /** + * Whether to persist items to storage. This is required to have multiple + * items in the node. + * + *

Value: boolean

+ */ + persist_items, + + /** + * Whether to deliver notifications to available users only + * + *

Value: boolean

+ */ + presence_based_delivery, + + /** + * Defines who can publish to the node + * + *

Value: {@link PublishModel}

+ */ + publish_model, + + /** + * The specific multi-user chat rooms to specify for replyroom + * + *

Value: List of JIDs as Strings

+ */ + replyroom, + + /** + * The specific JID(s) to specify for replyto + * + *

Value: List of JIDs as Strings

+ */ + replyto, + + /** + * The roster group(s) allowed to subscribe and retrieve items + * + *

Value: List of strings

+ */ + roster_groups_allowed, + + /** + * Whether to allow subscriptions + * + *

Value: boolean

+ */ + subscribe, + + /** + * A friendly name for the node + * + *

Value: String

+ */ + 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 + * + *

Value: String

+ */ + type; + + public String getFieldName() + { + return "pubsub#" + toString(); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java b/source/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java new file mode 100644 index 000000000..b17a66a9d --- /dev/null +++ b/source/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. + * + *

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 getExtensions(); +} diff --git a/source/org/jivesoftware/smackx/pubsub/EventElement.java b/source/org/jivesoftware/smackx/pubsub/EventElement.java new file mode 100644 index 000000000..165970f5d --- /dev/null +++ b/source/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 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(""); + + builder.append(ext.toXML()); + builder.append(""); + return builder.toString(); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/EventElementType.java b/source/org/jivesoftware/smackx/pubsub/EventElementType.java new file mode 100644 index 000000000..343edbe6f --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/FormNode.java b/source/org/jivesoftware/smackx/pubsub/FormNode.java new file mode 100644 index 000000000..e08bed259 --- /dev/null +++ b/source/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("'); + return builder.toString(); + } + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/FormNodeType.java b/source/org/jivesoftware/smackx/pubsub/FormNodeType.java new file mode 100644 index 000000000..6a163ee9f --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/FormType.java b/source/org/jivesoftware/smackx/pubsub/FormType.java new file mode 100644 index 000000000..e0fff519f --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/Item.java b/source/org/jivesoftware/smackx/pubsub/Item.java new file mode 100644 index 000000000..9d627abfe --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/Item.java @@ -0,0 +1,109 @@ +/** + * 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.provider.ItemProvider; + +/** + * This class represents an item that has been, or will be published to a + * pubsub node. An Item has several properties that are dependent + * on the configuration of the node to which it has been or will be published. + * + *

An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} + *
  • 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. + *
  • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true, otherwise it will be null. + * + *

    An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} + *
  • 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. + *
  • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true. + * + *

    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 implements PacketExtension +{ + private String id; + + /** + * Create an empty Item 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 Item is sent to the node, you have to use {@link LeafNode#send()} or {@link LeafNode#publish()} + * methods in this case. + */ + public Item() + { + } + + /** + * Create an Item 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) + { + id = itemId; + } + + /** + * Get the item id. Unique to the node it is associated with. + * + * @return The id + */ + public String getId() + { + return id; + } + + public String getElementName() + { + return "item"; + } + + public String getNamespace() + { + return null; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + + return builder.toString(); + } + + @Override + public String toString() + { + return getClass().getName() + " | Content [" + toXML() + "]"; + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java b/source/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java new file mode 100644 index 000000000..82ab7dfe9 --- /dev/null +++ b/source/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 itemIds = Collections.EMPTY_LIST; + + /** + * Constructs an ItemDeleteEvent 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 deletedItemIds, List 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 getItemIds() + { + return Collections.unmodifiableList(itemIds); + } + + @Override + public String toString() + { + return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Deleted Items: " + itemIds + ']'; + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java b/source/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java new file mode 100644 index 000000000..1ef1f6776 --- /dev/null +++ b/source/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 extends SubscriptionEvent +{ + private List items; + private Date originalDate; + + /** + * Constructs an ItemPublishEvent 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 eventItems) + { + super(nodeId); + items = eventItems; + } + + /** + * Constructs an ItemPublishEvent 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 eventItems, List subscriptionIds) + { + super(nodeId, subscriptionIds); + items = eventItems; + } + + /** + * Constructs an ItemPublishEvent 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 eventItems, List 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 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/source/org/jivesoftware/smackx/pubsub/ItemReply.java b/source/org/jivesoftware/smackx/pubsub/ItemReply.java new file mode 100644 index 000000000..3e090d98f --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/source/org/jivesoftware/smackx/pubsub/ItemsExtension.java new file mode 100644 index 000000000..320901c0c --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/ItemsExtension.java @@ -0,0 +1,183 @@ +/** + * 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. + *

  • It can represent an event containing a list of items that have been published + *
  • It can represent an event containing a list of retracted (deleted) items. + *
  • It can represent a request to delete a list of items. + *
  • It can represent a request to get existing items. + * + *

    Please note, this class is used for internal purposes, and is not required for usage of + * pubsub functionality. + * + * @author Robin Collier + */ +public class ItemsExtension extends NodeExtension implements EmbeddedPacketExtension +{ + protected ItemsElementType type; + protected String attValue; + protected List items; + + public enum ItemsElementType + { + /** An items element, which has an optional max_items attribute when requesting items */ + items(PubSubElementType.ITEMS, "max_items"), + + /** A retract element, which has an optional notify 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. + * + *

    Valid scenarios are: + *

  • Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an + * optional value for the max_items attribute. + *
  • Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing + * only id's and an optional value for the notify attribute. + *
  • Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and + * attributeValue = null + *
  • Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and + * attributeValue = null + * + * @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 max_items + */ + public ItemsExtension(ItemsElementType itemsType, String nodeId, List items, String attributeValue) + { + super(itemsType.getNodeElement(), nodeId); + type = itemsType; + this.items = items; + attValue = attributeValue; + } + + /** + * Constructs a request to get items from the node as defined in the first scenario + * in {@link #ItemsExtension(ItemsElementType, String, List, String)} + * + * @param nodeId The node the items will be requested from + * @param maxItems The limit on the number of items to retrieve (null for all) + */ + public ItemsExtension(String nodeId, Integer maxItems) + { + this(ItemsElementType.items, nodeId, null, maxItems == null ? null : maxItems.toString()); + } + + /** + * Get the type of element + * + * @return The element type + */ + public ItemsElementType getItemsElementType() + { + return type; + } + + public List getExtensions() + { + return (List)getItems(); + } + + /** + * Gets the items related to the type of request or event. + * + * return List of {@link Item}, {@link RetractItem}, or null + */ + public List getItems() + { + return items; + } + + /** + * Gets the value of the optional attribute related to the {@link ItemsElementType}. + * + * @return The attribute value + */ + public String getAttributeValue() + { + return attValue; + } + + @Override + public String toXML() + { + if (((items == null) || (items.size() == 0)) && (attValue == null)) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + builder.append(" node='"); + builder.append(getNode()); + + if (attValue != null) + { + builder.append("' "); + builder.append(type.getElementAttribute()); + builder.append("='"); + builder.append(attValue); + builder.append("'>"); + } + else + { + builder.append("'>"); + for (PacketExtension item : items) + { + builder.append(item.toXML()); + } + } + + builder.append(""); + return builder.toString(); + } + } + + @Override + public String toString() + { + return getClass().getName() + "Content [" + toXML() + "]"; + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/LeafNode.java b/source/org/jivesoftware/smackx/pubsub/LeafNode.java new file mode 100644 index 000000000..413d6c2bb --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/LeafNode.java @@ -0,0 +1,309 @@ +/** + * 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.XMPPConnection; +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(XMPPConnection 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 List getItems() + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.ITEMS, getId())); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)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 List getItems(Collection ids) + throws XMPPException + { + List itemList = new ArrayList(ids.size()); + + for (String id : ids) + { + itemList.add(new Item(id)); + } + PubSub request = createPubsubPacket(Type.GET, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList, null)); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)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 List getItems(int maxItems) + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new ItemsExtension(getId(), Integer.valueOf(maxItems))); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)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 void publish(T item) + { + Collection items = new ArrayList(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 void publish(Collection items) + { + PubSub packet = createPubsubPacket(Type.SET, new PublishItem(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 deliver_payload=false, 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 void send(T item) + throws XMPPException + { + Collection items = new ArrayList(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 void send(Collection items) + throws XMPPException + { + PubSub packet = createPubsubPacket(Type.SET, new PublishItem(getId(), items)); + + SyncPacketSend.getReply(con, packet); + } + + /** + * Purges the node of all items. + * + *

    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 items = new ArrayList(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 itemIds) + throws XMPPException + { + List items = new ArrayList(itemIds.size()); + + for (String id : itemIds) + { + items.add(new Item(id)); + } + PubSub request = createPubsubPacket(Type.SET, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items, null)); + SyncPacketSend.getReply(con, request); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/Node.java b/source/org/jivesoftware/smackx/pubsub/Node.java new file mode 100644 index 000000000..9b03caaaf --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/Node.java @@ -0,0 +1,525 @@ +/* + * Created on 2009-07-09 + */ +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.XMPPConnection; +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 XMPPConnection con; + protected String id; + protected String to; + + protected ConcurrentHashMap itemEventToListenerMap = new ConcurrentHashMap(); + protected ConcurrentHashMap itemDeleteToListenerMap = new ConcurrentHashMap(); + protected ConcurrentHashMap configEventToListenerMap = new ConcurrentHashMap(); + + /** + * 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(XMPPConnection 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 pubsub + */ + 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 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 getSubscriptionIds(Packet packet) + { + HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim"); + List values = null; + + if (headers != null) + { + values = new ArrayList(headers.getHeaders().size()); + + for (Header header : headers.getHeaders()) + { + values.add(header.getValue()); + } + } + return values; + } + + /** + * This class translates low level item publication events into api level objects for + * user consumption. + * + * @author Robin Collier + */ + public 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)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 extList = event.getExtensions(); + + if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName())) + { + listener.handlePurge(); + } + else + { + ItemsExtension itemsElem = (ItemsExtension)event.getEvent(); + Collection pubItems = itemsElem.getItems(); + Iterator it = (Iterator)pubItems.iterator(); + List items = new ArrayList(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 secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions(); + + if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement)) + return true; + } + } + return false; + } + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/NodeEvent.java b/source/org/jivesoftware/smackx/pubsub/NodeEvent.java new file mode 100644 index 000000000..c83204fbf --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/NodeEvent.java @@ -0,0 +1,19 @@ +/* + * Created on 2009-05-12 + */ +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/source/org/jivesoftware/smackx/pubsub/NodeExtension.java b/source/org/jivesoftware/smackx/pubsub/NodeExtension.java new file mode 100644 index 000000000..7e4cdecbb --- /dev/null +++ b/source/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 node 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 NodeExtension 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 NodeExtension 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/source/org/jivesoftware/smackx/pubsub/NodeType.java b/source/org/jivesoftware/smackx/pubsub/NodeType.java new file mode 100644 index 000000000..5ee5a0520 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/source/org/jivesoftware/smackx/pubsub/OptionsExtension.java new file mode 100644 index 000000000..32c033180 --- /dev/null +++ b/source/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 options 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/source/org/jivesoftware/smackx/pubsub/PayloadItem.java b/source/org/jivesoftware/smackx/pubsub/PayloadItem.java new file mode 100644 index 000000000..e147e6102 --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/PayloadItem.java @@ -0,0 +1,95 @@ +/** + * 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.provider.ItemProvider; + +/** + * This class represents an item that has been, or will be published to a + * pubsub node. An Item has several properties that are dependent + * on the configuration of the node to which it has been or will be published. + * + *

    An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} + *
  • 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. + *
  • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true, otherwise it will be null. + * + *

    An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} + *
  • 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. + *
  • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true. + * + *

    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 extends Item +{ + private E payload; + + /** + * Create an Item 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; + } + + /** + * Get the payload associated with this Item. 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; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + builder.append(payload.toXML()); + builder.append(""); + + return builder.toString(); + } + + @Override + public String toString() + { + return getClass().getName() + " | Content [" + toXML() + "]"; + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/PresenceState.java b/source/org/jivesoftware/smackx/pubsub/PresenceState.java new file mode 100644 index 000000000..0612fc25e --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/PubSubElementType.java b/source/org/jivesoftware/smackx/pubsub/PubSubElementType.java new file mode 100644 index 000000000..78fa9c193 --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/PubSubElementType.java @@ -0,0 +1,77 @@ +/** + * 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), + 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/source/org/jivesoftware/smackx/pubsub/PubSubManager.java b/source/org/jivesoftware/smackx/pubsub/PubSubManager.java new file mode 100644 index 000000000..b1edbaffb --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/PubSubManager.java @@ -0,0 +1,327 @@ +/** + * 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.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +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.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 XEP-0060. + * + * @author Robin Collier + */ +final public class PubSubManager +{ + private XMPPConnection con; + private String to; + private Map nodeMap = new ConcurrentHashMap(); + + /** + * Create a pubsub manager associated to the specified connection. + * + * @param connection The XMPP connection + */ + public PubSubManager(XMPPConnection connection) + { + con = connection; + } + + /** + * 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(XMPPConnection 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 Node 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 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 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 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(XMPPConnection con, String to, Type type, PacketExtension ext) + throws XMPPException + { + return sendPubsubPacket(con, to, type, ext, null); + } + + static Packet sendPubsubPacket(XMPPConnection con, String to, Type type, PacketExtension ext, PubSubNamespace ns) + throws XMPPException + { + return SyncPacketSend.getReply(con, createPubsubPacket(to, type, ext, ns)); + } + + static Packet sendPubsubPacket(XMPPConnection con, String to, Type type, PubSub packet) + throws XMPPException + { + return sendPubsubPacket(con, to, type, packet, null); + } + + static Packet sendPubsubPacket(XMPPConnection con, String to, Type type, PubSub packet, PubSubNamespace ns) + throws XMPPException + { + return SyncPacketSend.getReply(con, packet); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/PublishItem.java b/source/org/jivesoftware/smackx/pubsub/PublishItem.java new file mode 100644 index 000000000..ffbd70503 --- /dev/null +++ b/source/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 extends NodeExtension +{ + protected Collection 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(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 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(""); + + return builder.toString(); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/PublishModel.java b/source/org/jivesoftware/smackx/pubsub/PublishModel.java new file mode 100644 index 000000000..4b5a851a4 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/RetractItem.java b/source/org/jivesoftware/smackx/pubsub/RetractItem.java new file mode 100644 index 000000000..97db5ccd2 --- /dev/null +++ b/source/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 RetractItem 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 ""; + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/SimplePayload.java b/source/org/jivesoftware/smackx/pubsub/SimplePayload.java new file mode 100644 index 000000000..9d114b090 --- /dev/null +++ b/source/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 SimplePayload 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/source/org/jivesoftware/smackx/pubsub/SubscribeExtension.java b/source/org/jivesoftware/smackx/pubsub/SubscribeExtension.java new file mode 100644 index 000000000..daf8db7fb --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/source/org/jivesoftware/smackx/pubsub/SubscribeForm.java new file mode 100644 index 000000000..5ed531148 --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/SubscribeForm.java @@ -0,0 +1,242 @@ +/** + * 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.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}. + * + *

    Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all SubscribeForm.setXXX methods will create the field in the wrapped form + * if it does not already exist. + * + * @author Robin Collier + */ +public class SubscribeForm extends Form +{ + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + 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 format.parse(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(), format.format(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 getShowValues() + { + ArrayList result = new ArrayList(5); + Iterator 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 stateValues) + { + ArrayList values = new ArrayList(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 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/source/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java b/source/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java new file mode 100644 index 000000000..dfca6016f --- /dev/null +++ b/source/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 Section 16.4.2. + * + * @author Robin Collier + */ +public enum SubscribeOptionFields +{ + /** + * Whether an entity wants to receive or disable notifications + * + *

    Value: boolean

    + */ + deliver, + + /** + * Whether an entity wants to receive digests (aggregations) of + * notifications or all notifications individually. + * + *

    Value: boolean

    + */ + digest, + + /** + * The minimum number of seconds between sending any two notifications digests + * + *

    Value: int

    + */ + digest_frequency, + + /** + * The DateTime at which a leased subsscription will end ro has ended. + * + *

    Value: {@link Calendar}

    + */ + expire, + + /** + * Whether an entity wants to receive an XMPP message body in addition to + * the payload format. + * + *

    Value: boolean

    + */ + include_body, + + /** + * The presence states for which an entity wants to receive notifications. + * + *

    Value: {@link PresenceState}

    + */ + show_values, + + /** + * + * + *

    Value:

    + */ + subscription_type, + + /** + * + *

    Value:

    + */ + 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/source/org/jivesoftware/smackx/pubsub/Subscription.java b/source/org/jivesoftware/smackx/pubsub/Subscription.java new file mode 100644 index 000000000..19ad8a8f1 --- /dev/null +++ b/source/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(""); + 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/source/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java b/source/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java new file mode 100644 index 000000000..99f18d5f1 --- /dev/null +++ b/source/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 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 subscriptionIds) + { + super(nodeId); + + if (subscriptionIds != null) + subIds = subscriptionIds; + } + + /** + * Get the subscriptions this event is associated with. + * + * @return List of subscription id's + */ + public List 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 subscriptionIds) + { + if (subscriptionIds != null) + subIds = subscriptionIds; + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java b/source/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java new file mode 100644 index 000000000..a28cbe2c9 --- /dev/null +++ b/source/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 items = Collections.EMPTY_LIST; + + /** + * Subscriptions to the root node + * + * @param subList The list of subscriptions + */ + public SubscriptionsExtension(List 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 subList) + { + super(PubSubElementType.SUBSCRIPTIONS, nodeId); + + if (subList != null) + items = subList; + } + + /** + * Gets the list of subscriptions. + * + * @return List of subscriptions + */ + public List 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(""); + return builder.toString(); + } + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java b/source/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java new file mode 100644 index 000000000..ac14c603a --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java b/source/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java new file mode 100644 index 000000000..d228e8f09 --- /dev/null +++ b/source/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 all items are deleted from a node the listener is + * registered with. + */ + void handlePurge(); +} diff --git a/source/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java b/source/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java new file mode 100644 index 000000000..714b2c088 --- /dev/null +++ b/source/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 +{ + /** + * Called whenever an item is published to the node the listener + * is registered with. + * + * @param items The publishing details. + */ + void handlePublishedItems(ItemPublishEvent items); +} diff --git a/source/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java b/source/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java new file mode 100644 index 000000000..39db5a5db --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/packet/PubSub.java b/source/org/jivesoftware/smackx/pubsub/packet/PubSub.java new file mode 100644 index 000000000..5aa4865a4 --- /dev/null +++ b/source/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 Publish-Subscribe + * 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 XMPP Registrar 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: + *
    +     * <iq type='set' id="MlIpV-4" to="pubsub.gato.home" from="gato3@gato.home/Smack">
    +     *     <pubsub xmlns="http://jabber.org/protocol/pubsub">
    +     *                      :
    +     *         Specific request extension
    +     *                      :
    +     *     </pubsub>
    +     * </iq>
    +     * 
    + * + */ + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java b/source/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java new file mode 100644 index 000000000..eecf95950 --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java b/source/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java new file mode 100644 index 000000000..c5be230eb --- /dev/null +++ b/source/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.XMPPConnection; +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(XMPPConnection 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(XMPPConnection connection, Packet packet) + throws XMPPException + { + return getReply(connection, packet, SmackConfiguration.getPacketReplyTimeout()); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java new file mode 100644 index 000000000..4e27c5075 --- /dev/null +++ b/source/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 affiliation schema. + * + * @author Robin Collier + */ +public class AffiliationProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new Affiliation(attributeMap.get("node"), Affiliation.Type.valueOf(attributeMap.get("affiliation"))); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java new file mode 100644 index 000000000..9bfeb8110 --- /dev/null +++ b/source/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 affiliation schema. + * + * @author Robin Collier + */public class AffiliationsProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new AffiliationsExtension((List)content); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java new file mode 100644 index 000000000..30e3017ce --- /dev/null +++ b/source/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 configuration schema. + * + * @author Robin Collier + */ +public class ConfigEventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List 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/source/org/jivesoftware/smackx/pubsub/provider/EventProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/EventProvider.java new file mode 100644 index 000000000..ef5671ec8 --- /dev/null +++ b/source/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 event schema. + * + * @author Robin Collier + */ +public class EventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List content) + { + return new EventElement(EventElementType.valueOf(content.get(0).getElementName()), (NodeExtension)content.get(0)); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java new file mode 100644 index 000000000..ccf85efc7 --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/provider/ItemProvider.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.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 item 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 + * smack.providers 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 elem = parser.getName(); + + int tag = parser.next(); + + if (tag == XmlPullParser.END_TAG) + { + return new Item(id); + } + else + { + String payloadElemName = parser.getName(); + String payloadNS = parser.getNamespace(); + + if (ProviderManager.getInstance().getExtensionProvider(payloadElemName, payloadNS) == null) + { + boolean done = false; + String payloadText = null; + + while (!done) + { + if (tag == XmlPullParser.END_TAG && parser.getName().equals(elem)) + { + done = true; + } + else if (!((tag == XmlPullParser.START_TAG) && parser.isEmptyElementTag())) + { + if (payloadText == null) + payloadText = parser.getText(); + else + payloadText += parser.getText(); + } + tag = parser.next(); + } + return new PayloadItem(id, new SimplePayload(payloadElemName, payloadNS, payloadText)); + } + else + { + return new PayloadItem(id, PacketParserUtils.parsePacketExtension(payloadElemName, payloadNS, parser)); + } + } + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java new file mode 100644 index 000000000..d058786d5 --- /dev/null +++ b/source/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 items element out of the message event stanza from + * the server as specified in the items schema. + * + * @author Robin Collier + */ +public class ItemsProvider extends EmbeddedExtensionProvider +{ + + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new ItemsExtension(ItemsExtension.ItemsElementType.items, attributeMap.get("node"), content, null); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java new file mode 100644 index 000000000..742f2198d --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java new file mode 100644 index 000000000..8fa333770 --- /dev/null +++ b/source/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 retract element out of the message event stanza from + * the server as specified in the retract schema. + * This element is a child of the items element. + * + * @author Robin Collier + */ +public class RetractEventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new RetractItem(attributeMap.get("id")); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java new file mode 100644 index 000000000..d2b7d30a4 --- /dev/null +++ b/source/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 node 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 attributeMap, List content) + { + return new NodeExtension(PubSubElementType.valueOfFromElemName(currentElement, currentNamespace), attributeMap.get("node")); + } +} diff --git a/source/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java new file mode 100644 index 000000000..eccbe08f5 --- /dev/null +++ b/source/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 subscription element out of the pubsub IQ message from + * the server as specified in the subscription schema. + * + * @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/source/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java b/source/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java new file mode 100644 index 000000000..94dc61d1f --- /dev/null +++ b/source/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 subscriptions element out of the pubsub IQ message from + * the server as specified in the subscriptions schema. + * + * @author Robin Collier + */ +public class SubscriptionsProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new SubscriptionsExtension(attributeMap.get("node"), (List)content); + } + +} diff --git a/source/org/jivesoftware/smackx/pubsub/util/NodeUtils.java b/source/org/jivesoftware/smackx/pubsub/util/NodeUtils.java new file mode 100644 index 000000000..414601f3e --- /dev/null +++ b/source/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/source/org/jivesoftware/smackx/pubsub/util/XmlUtils.java b/source/org/jivesoftware/smackx/pubsub/util/XmlUtils.java new file mode 100644 index 000000000..f5948718d --- /dev/null +++ b/source/org/jivesoftware/smackx/pubsub/util/XmlUtils.java @@ -0,0 +1,67 @@ +/** + * 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; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Simple utility for pretty printing xml. + * + * @author Robin Collier + */ +public class XmlUtils +{ + /** + * + * @param header Just a title for the stanza for readability. Single word no spaces since + * it is inserted as the root element in the output. + * @param xml The string to pretty print + */ + static public void prettyPrint(String header, String xml) + { + try + { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "3"); + + if (header != null) + { + xml = "\n<" + header + ">" + xml + "'; + } + transformer.transform(new StreamSource(new StringReader(xml)), new StreamResult(System.out)); + } + catch (Exception e) + { + System.out.println("Something wrong with xml in \n---------------\n" + xml + "\n---------------"); + e.printStackTrace(); + } + } + + static public void appendAttribute(StringBuilder builder, String att, String value) + { + builder.append(" "); + builder.append(att); + builder.append("='"); + builder.append(value); + builder.append("'"); + } + +} diff --git a/test/org/jivesoftware/smackx/pubsub/CarExtension.java b/test/org/jivesoftware/smackx/pubsub/CarExtension.java new file mode 100644 index 000000000..e406e1218 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/CarExtension.java @@ -0,0 +1,45 @@ +/* + * Created on 2009-05-05 + */ +package org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.PacketExtension; + +class CarExtension implements PacketExtension +{ + private String color; + private int numTires; + + public CarExtension(String col, int num) + { + color = col; + numTires = num; + } + + public String getColor() + { + return color; + } + + public int getNumTires() + { + return numTires; + } + + public String getElementName() + { + return "car"; + } + + public String getNamespace() + { + return "pubsub:test:vehicle"; + } + + public String toXML() + { + return "<" + getElementName() + " xmlns='" + getNamespace() + "'>"; + } + +} \ No newline at end of file diff --git a/test/org/jivesoftware/smackx/pubsub/CarExtensionProvider.java b/test/org/jivesoftware/smackx/pubsub/CarExtensionProvider.java new file mode 100644 index 000000000..c994b3321 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/CarExtensionProvider.java @@ -0,0 +1,35 @@ +/* + * Created on 2009-05-05 + */ +package org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +public class CarExtensionProvider implements PacketExtensionProvider +{ + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String color = null; + int numTires = 0; + + for (int i=0; i<2; i++) + { + while (parser.next() != XmlPullParser.START_TAG); + + if (parser.getName().equals("paint")) + { + color = parser.getAttributeValue(0); + } + else + { + numTires = Integer.parseInt(parser.getAttributeValue(0)); + } + } + while (parser.next() != XmlPullParser.END_TAG); + return new CarExtension(color, numTires); + } + +} \ No newline at end of file diff --git a/test/org/jivesoftware/smackx/pubsub/EntityUseCases.java b/test/org/jivesoftware/smackx/pubsub/EntityUseCases.java new file mode 100644 index 000000000..952e595ad --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/EntityUseCases.java @@ -0,0 +1,72 @@ +/* + * Created on 2009-04-09 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; +import org.jivesoftware.smackx.pubsub.test.SingleUserTestCase; +import org.jivesoftware.smackx.pubsub.util.XmlUtils; + +public class EntityUseCases extends SingleUserTestCase +{ + public void testDiscoverPubsubInfo() throws Exception + { + DiscoverInfo supportedFeatures = getManager().getSupportedFeatures(); + assertNotNull(supportedFeatures); + } + + public void testDiscoverNodeInfo() throws Exception + { + LeafNode myNode = getManager().createNode("DiscoNode" + System.currentTimeMillis()); + DiscoverInfo info = myNode.discoverInfo(); + assertTrue(info.getIdentities().hasNext()); + Identity ident = info.getIdentities().next(); + + assertEquals("leaf", ident.getType()); + } + + public void testDiscoverNodeItems() throws Exception + { + LeafNode myNode = getRandomPubnode(getManager(), true, false); + myNode.send(new Item()); + myNode.send(new Item()); + myNode.send(new Item()); + myNode.send(new Item()); + DiscoverItems items = myNode.discoverItems(); + + int count = 0; + + for(Iterator it = items.getItems(); it.hasNext(); it.next(),count++); + + assertEquals(4, count); + } + + public void testDiscoverSubscriptions() throws Exception + { + getManager().getSubscriptions(); + } + + public void testDiscoverNodeSubscriptions() throws Exception + { + LeafNode myNode = getRandomPubnode(getManager(), true, true); + myNode.subscribe(getConnection(0).getUser()); + List subscriptions = myNode.getSubscriptions(); + + assertTrue(subscriptions.size() < 3); + + for (Subscription subscription : subscriptions) + { + assertNull(subscription.getNode()); + } + } + + public void testRetrieveAffiliation() throws Exception + { + getManager().getAffiliations(); + } +} diff --git a/test/org/jivesoftware/smackx/pubsub/OwnerUseCases.java b/test/org/jivesoftware/smackx/pubsub/OwnerUseCases.java new file mode 100644 index 000000000..3841cfe65 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/OwnerUseCases.java @@ -0,0 +1,130 @@ +/* + * Created on 2009-04-09 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.Collection; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.pubsub.test.SingleUserTestCase; + +public class OwnerUseCases extends SingleUserTestCase +{ + public void testCreateInstantNode() throws Exception + { + LeafNode node = getManager().createNode(); + assertNotNull(node); + assertNotNull(node.getId()); + } + + public void testCreateNamedNode() throws Exception + { + String id = "TestNamedNode" + System.currentTimeMillis(); + LeafNode node = getManager().createNode(id); + assertEquals(id, node.getId()); + } + + public void testCreateConfiguredNode() throws Exception + { + // Generate reasonably unique for multiple tests + String id = "TestConfigNode" + System.currentTimeMillis(); + + // Create and configure a node + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setAccessModel(AccessModel.open); + form.setDeliverPayloads(false); + form.setNotifyRetract(true); + form.setPersistentItems(true); + form.setPublishModel(PublishModel.open); + + LeafNode node = (LeafNode)getManager().createNode(id, form); + + ConfigureForm currentForm = node.getNodeConfiguration(); + assertEquals(AccessModel.open, currentForm.getAccessModel()); + assertFalse(currentForm.isDeliverPayloads()); + assertTrue(currentForm.isNotifyRetract()); + assertTrue(currentForm.isPersistItems()); + assertEquals(PublishModel.open, currentForm.getPublishModel()); + } + + public void testCreateAndUpdateConfiguredNode() throws Exception + { + // Generate reasonably unique for multiple tests + String id = "TestConfigNode2" + System.currentTimeMillis(); + + // Create and configure a node + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setAccessModel(AccessModel.open); + form.setDeliverPayloads(false); + form.setNotifyRetract(true); + form.setPersistentItems(true); + form.setPublishModel(PublishModel.open); + + LeafNode myNode = (LeafNode)getManager().createNode(id, form); + ConfigureForm config = myNode.getNodeConfiguration(); + + assertEquals(AccessModel.open, config.getAccessModel()); + assertFalse(config.isDeliverPayloads()); + assertTrue(config.isNotifyRetract()); + assertTrue(config.isPersistItems()); + assertEquals(PublishModel.open, config.getPublishModel()); + + ConfigureForm submitForm = new ConfigureForm(config.createAnswerForm()); + submitForm.setAccessModel(AccessModel.whitelist); + submitForm.setDeliverPayloads(true); + submitForm.setNotifyRetract(false); + submitForm.setPersistentItems(false); + submitForm.setPublishModel(PublishModel.publishers); + myNode.sendConfigurationForm(submitForm); + + ConfigureForm newConfig = myNode.getNodeConfiguration(); + assertEquals(AccessModel.whitelist, newConfig.getAccessModel()); + assertTrue(newConfig.isDeliverPayloads()); + assertFalse(newConfig.isNotifyRetract()); + assertFalse(newConfig.isPersistItems()); + assertEquals(PublishModel.publishers, newConfig.getPublishModel()); + } + + public void testGetDefaultConfig() throws Exception + { + ConfigureForm form = getManager().getDefaultConfiguration(); + assertNotNull(form); + } + + public void testDeleteNode() throws Exception + { + LeafNode myNode = getManager().createNode(); + assertNotNull(getManager().getNode(myNode.getId())); + + getManager(0).deleteNode(myNode.getId()); + + try + { + assertNull(getManager().getNode(myNode.getId())); + fail("Node should not exist"); + } + catch (XMPPException e) + { + } + } + + public void testPurgeItems() throws XMPPException + { + LeafNode node = getRandomPubnode(getManager(), true, false); + + node.send(new Item()); + node.send(new Item()); + node.send(new Item()); + node.send(new Item()); + node.send(new Item()); + + Collection items = node.getItems(); + assertTrue(items.size() == 5); + + node.deleteAllItems(); + items = node.getItems(); + + // Pubsub service may keep the last notification (in spec), so 0 or 1 may be returned on get items. + assertTrue(items.size() < 2); + } +} diff --git a/test/org/jivesoftware/smackx/pubsub/PublisherUseCases.java b/test/org/jivesoftware/smackx/pubsub/PublisherUseCases.java new file mode 100644 index 000000000..850b63721 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/PublisherUseCases.java @@ -0,0 +1,150 @@ +/* + * Created on 2009-04-09 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.packet.XMPPError.Condition; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.jivesoftware.smackx.pubsub.test.SingleUserTestCase; + +public class PublisherUseCases extends SingleUserTestCase +{ + public void testSendNodeTrNot() throws Exception + { + getPubnode(false, false).send(); + } + + public void testSendNodeTrPay_WithOutPayload() throws XMPPException + { + LeafNode node = getPubnode(false, true); + + try + { + node.send(new Item()); + fail("Exception should be thrown when there is no payload"); + } + catch (XMPPException e) { + XMPPError err = e.getXMPPError(); + assertTrue(err.getType().equals(XMPPError.Type.MODIFY)); + assertTrue(err.getCondition().equals(Condition.bad_request.toString())); + assertNotNull(err.getExtension("payload-required", PubSubNamespace.ERROR.getXmlns())); + } + + try + { + node.send(new Item("test" + System.currentTimeMillis())); + fail("Exception should be thrown when there is no payload"); + } + catch (XMPPException e) { + XMPPError err = e.getXMPPError(); + assertTrue(err.getType().equals(XMPPError.Type.MODIFY)); + assertTrue(err.getCondition().equals(Condition.bad_request.toString())); + assertNotNull(err.getExtension("payload-required", PubSubNamespace.ERROR.getXmlns())); + } + } + + public void testSendNodeTrPay_WithPayload() throws XMPPException + { + LeafNode node = getPubnode(false, true); + node.send(new PayloadItem(null, + new SimplePayload("book", "pubsub:test:book", "Lord of the Rings"))); + node.send(new PayloadItem("test" + System.currentTimeMillis(), + new SimplePayload("book", "pubsub:test:book", "Two Towers"))); + } + + public void testSendNodePerNot() throws Exception + { + LeafNode node = getPubnode(true, false); + node.send(new Item()); + node.send(new Item("test" + System.currentTimeMillis())); + node.send(new PayloadItem(null, + new SimplePayload("book", "pubsub:test:book", "Lord of the Rings"))); + node.send(new PayloadItem("test" + System.currentTimeMillis(), + new SimplePayload("book", "pubsub:test:book", "Two Towers"))); + } + + public void testSendPerPay_WithPayload() throws Exception + { + LeafNode node = getPubnode(true, true); + node.send(new PayloadItem(null, + new SimplePayload("book", "pubsub:test:book", "Lord of the Rings"))); + node.send(new PayloadItem("test" + System.currentTimeMillis(), + new SimplePayload("book", "pubsub:test:book", "Two Towers"))); + } + + public void testSendPerPay_NoPayload() throws Exception + { + LeafNode node = getPubnode(true, true); + try + { + node.send(new Item()); + fail("Exception should be thrown when there is no payload"); + } + catch (XMPPException e) { + XMPPError err = e.getXMPPError(); + assertTrue(err.getType().equals(XMPPError.Type.MODIFY)); + assertTrue(err.getCondition().equals(Condition.bad_request.toString())); + assertNotNull(err.getExtension("payload-required", PubSubNamespace.ERROR.getXmlns())); + } + + try + { + node.send(new Item("test" + System.currentTimeMillis())); + fail("Exception should be thrown when there is no payload"); + } + catch (XMPPException e) { + XMPPError err = e.getXMPPError(); + assertTrue(err.getType().equals(XMPPError.Type.MODIFY)); + assertTrue(err.getCondition().equals(Condition.bad_request.toString())); + assertNotNull(err.getExtension("payload-required", PubSubNamespace.ERROR.getXmlns())); + } + } + + public void testDeleteItems() throws XMPPException + { + LeafNode node = getPubnode(true, false); + + node.send(new Item("1")); + node.send(new Item("2")); + node.send(new Item("3")); + node.send(new Item("4")); + + node.deleteItem("1"); + Collection items = node.getItems(); + + assertTrue(items.size() == 3); + assertEquals(items.iterator().next().getId(), "2"); + } + + public void testPersistItems() throws XMPPException + { + LeafNode node = getPubnode(true, false); + + node.send(new Item("1")); + node.send(new Item("2")); + node.send(new Item("3")); + node.send(new Item("4")); + + Collection items = node.getItems(); + + assertTrue(items.size() == 4); + } + + public void testItemOverwritten() throws XMPPException + { + LeafNode node = getPubnode(true, false); + + node.send(new PayloadItem("1", new SimplePayload("test", null, ""))); + node.send(new PayloadItem("1", new SimplePayload("test2", null, ""))); + + List items = node.getItems(); + assertEquals(1, items.size()); + assertEquals("1", items.get(0).getId()); + } +} diff --git a/test/org/jivesoftware/smackx/pubsub/SubscriberUseCases.java b/test/org/jivesoftware/smackx/pubsub/SubscriberUseCases.java new file mode 100644 index 000000000..651793e07 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/SubscriberUseCases.java @@ -0,0 +1,224 @@ +/* + * Created on 2009-04-09 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.pubsub.test.SingleUserTestCase; + +public class SubscriberUseCases extends SingleUserTestCase +{ + public void testSubscribe() throws Exception + { + LeafNode node = getPubnode(false, false); + Subscription sub = node.subscribe(getJid()); + + assertEquals(getJid(), sub.getJid()); + assertNotNull(sub.getId()); + assertEquals(node.getId(), sub.getNode()); + assertEquals(Subscription.State.subscribed, sub.getState()); + } + + public void testSubscribeBadJid() throws Exception + { + LeafNode node = getPubnode(false, false); + + try + { + node.subscribe("this@over.here"); + fail(); + } + catch (XMPPException e) + { + } + } + + public void testSubscribeWithOptions() throws Exception + { + SubscribeForm form = new SubscribeForm(FormType.submit); + form.setDeliverOn(true); + Calendar expire = Calendar.getInstance(); + expire.set(2020, 1, 1); + form.setExpiry(expire.getTime()); + LeafNode node = getPubnode(false, false); + node.subscribe(getJid(), form); + } + + public void testSubscribeConfigRequired() throws Exception + { + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setAccessModel(AccessModel.open); + + // Openfire specific field - nothing in the spec yet + FormField required = new FormField("pubsub#subscription_required"); + required.setType(FormField.TYPE_BOOLEAN); + form.addField(required); + form.setAnswer("pubsub#subscription_required", true); + LeafNode node = (LeafNode)getManager().createNode("Pubnode" + System.currentTimeMillis(), form); + + Subscription sub = node.subscribe(getJid()); + + assertEquals(getJid(), sub.getJid()); + assertNotNull(sub.getId()); + assertEquals(node.getId(), sub.getNode()); + assertEquals(true, sub.isConfigRequired()); + } + + public void testUnsubscribe() throws Exception + { + LeafNode node = getPubnode(false, false); + node.subscribe(getJid()); + Collection subs = node.getSubscriptions(); + + node.unsubscribe(getJid()); + Collection afterSubs = node.getSubscriptions(); + assertEquals(subs.size()-1, afterSubs.size()); + } + + public void testUnsubscribeWithMultipleNoSubId() throws Exception + { + LeafNode node = getPubnode(false, false); + node.subscribe(getBareJID(0)); + node.subscribe(getBareJID(0)); + node.subscribe(getBareJID(0)); + + try + { + node.unsubscribe(getBareJID(0)); + fail("Unsubscribe with no subid should fail"); + } + catch (XMPPException e) + { + } + } + + public void testUnsubscribeWithMultipleWithSubId() throws Exception + { + LeafNode node = getPubnode(false, false); + node.subscribe(getJid()); + Subscription sub = node.subscribe(getJid()); + node.subscribe(getJid()); + node.unsubscribe(getJid(), sub.getId()); + } + + public void testGetOptions() throws Exception + { + LeafNode node = getPubnode(false, false); + Subscription sub = node.subscribe(getJid()); + SubscribeForm form = node.getSubscriptionOptions(getJid(), sub.getId()); + assertNotNull(form); + } + +// public void testSubscribeWithConfig() throws Exception +// { +// LeafNode node = getPubnode(false, false); +// +// Subscription sub = node.subscribe(getBareJID(0)); +// +// assertEquals(getBareJID(0), sub.getJid()); +// assertNotNull(sub.getId()); +// assertEquals(node.getId(), sub.getNode()); +// assertEquals(true, sub.isConfigRequired()); +// } +// + public void testGetItems() throws XMPPException + { + LeafNode node = getPubnode(true, false); + + node.send((Item)null); + node.send((Item)null); + node.send((Item)null); + node.send((Item)null); + node.send((Item)null); + + Collection items = node.getItems(); + assertTrue(items.size() == 5); + + long curTime = System.currentTimeMillis(); + node.send(new Item("1-" + curTime)); + node.send(new Item("2-" + curTime)); + node.send(new Item("3-" + curTime)); + node.send(new Item("4-" + curTime)); + node.send(new Item("5-" + curTime)); + + items = node.getItems(); + assertTrue(items.size() == 10); + + LeafNode payloadNode = getPubnode(true, true); + + Map idPayload = new HashMap(); + idPayload.put("6-" + curTime, ""); + idPayload.put("7-" + curTime, ""); + idPayload.put("8-" + curTime, "text"); + idPayload.put("9-" + curTime, ""); + + for (Map.Entry payload : idPayload.entrySet()) + { + payloadNode.send(new PayloadItem(payload.getKey(), new SimplePayload("a", "pubsub:test", payload.getValue()))); + } + + payloadNode.send(new PayloadItem("6-" + curTime, new SimplePayload("a", "pubsub:test", ""))); + payloadNode.send(new PayloadItem("7-" + curTime, new SimplePayload("a", "pubsub:test", ""))); + payloadNode.send(new PayloadItem("8-" + curTime, new SimplePayload("entity", "pubsub:test", "text"))); + payloadNode.send(new PayloadItem("9-" + curTime, new SimplePayload("entity", "pubsub:test", ""))); + + Collection> payloadItems = payloadNode.getItems(); + assertTrue(payloadItems.size() == 4); + } + + public void getSpecifiedItems() throws XMPPException + { + LeafNode node = getPubnode(true, true); + + node.send(new PayloadItem("1", new SimplePayload("a", "pubsub:test", ""))); + node.send(new PayloadItem("2", new SimplePayload("a", "pubsub:test", ""))); + node.send(new PayloadItem("3", new SimplePayload("a", "pubsub:test", ""))); + node.send(new PayloadItem("4", new SimplePayload("a", "pubsub:test", ""))); + node.send(new PayloadItem("5", new SimplePayload("a", "pubsub:test", ""))); + + Collection ids = new ArrayList(3); + ids.add("1"); + ids.add("3"); + ids.add("4"); + + List> items = node.getItems(ids); + assertEquals(3, items.size()); + assertEquals(items.get(0).getId(), "1"); + assertEquals(items.get(0).getPayload().toXML(), ""); + assertEquals(items.get(1).getId(), "3"); + assertEquals(items.get(1).getPayload().toXML(), ""); + assertEquals(items.get(2).getId(), "5"); + assertEquals(items.get(2).getPayload().toXML(), ""); + } + + public void testGetLastNItems() throws XMPPException + { + LeafNode node = getPubnode(true, false); + + node.send(new Item("1")); + node.send(new Item("2")); + node.send(new Item("3")); + node.send(new Item("4")); + node.send(new Item("5")); + + List items = node.getItems(2); + assertEquals(2, items.size()); + assertEquals(items.get(0).getId(), "4"); + assertEquals(items.get(1).getId(), "5"); + } + + private String getJid() + { + return getConnection(0).getUser(); + } + +} diff --git a/test/org/jivesoftware/smackx/pubsub/TestAPI.java b/test/org/jivesoftware/smackx/pubsub/TestAPI.java new file mode 100644 index 000000000..3fe384f13 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/TestAPI.java @@ -0,0 +1,22 @@ +/* + * Created on 2009-04-09 + */ +package org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.pubsub.test.SingleUserTestCase; + +public class TestAPI extends SingleUserTestCase +{ + public void testGetNonexistentNode() + { + try + { + getManager().getNode("" + System.currentTimeMillis()); + assertTrue(false); + } + catch (XMPPException e) + { + } + } +} diff --git a/test/org/jivesoftware/smackx/pubsub/TestEvents.java b/test/org/jivesoftware/smackx/pubsub/TestEvents.java new file mode 100644 index 000000000..7ed73ab38 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/TestEvents.java @@ -0,0 +1,575 @@ +/* + * Created on 2009-04-22 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError.Type; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener; +import org.jivesoftware.smackx.pubsub.listener.ItemEventListener; +import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener; + +public class TestEvents extends SmackTestCase +{ + + public TestEvents(String str) + { + super(str); + } + + @Override + protected int getMaxConnections() + { + return 2; + } + + private String getService() + { + return "pubsub." + getServiceName(); + } + + public void testCreateAndGetNode() throws Exception + { + String nodeId = "MyTestNode"; + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = null; + try + { + creatorNode = (LeafNode)creatorMgr.getNode(nodeId); + } + catch (XMPPException e) + { + if (e.getXMPPError().getType() == Type.CANCEL && e.getXMPPError().getCondition().equals("item-not-found")) + creatorNode = creatorMgr.createNode(nodeId); + else + throw e; + } + + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + assertNotNull(subNode); + } + + public void testConfigureAndNotify() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, false, true); + + BlockingQueue queue = new ArrayBlockingQueue(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + NodeConfigListener sub1Handler = new NodeConfigCoordinator(queue, "sub1"); + subNode.subscribe(getConnection(1).getUser()); + subNode.addConfigurationListener(sub1Handler); + + ConfigureForm currentConfig = creatorNode.getNodeConfiguration(); + ConfigureForm form = new ConfigureForm(currentConfig.createAnswerForm()); + form.setPersistentItems(true); + form.setDeliverPayloads(false); + form.setNotifyConfig(true); + creatorNode.sendConfigurationForm(form); + + ConfigurationEvent event = queue.poll(5, TimeUnit.SECONDS).event; + assertEquals(nodeId, event.getNode()); + assertNull(event.getConfiguration()); + + currentConfig = creatorNode.getNodeConfiguration(); + form = new ConfigureForm(currentConfig.createAnswerForm()); + form.setDeliverPayloads(true); + creatorNode.sendConfigurationForm(form); + + event = queue.poll(5, TimeUnit.SECONDS).event; + assertEquals(nodeId, event.getNode()); + assertNotNull(event.getConfiguration()); + assertTrue(event.getConfiguration().isPersistItems()); + assertTrue(event.getConfiguration().isDeliverPayloads()); + } + + public void testSendAndReceiveNoPayload() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue> queue = new ArrayBlockingQueue>(3); + ItemEventCoordinator creatorHandler = new ItemEventCoordinator(queue, "creator"); + creatorNode.addItemEventListener(creatorHandler); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator sub1Handler = new ItemEventCoordinator(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + creatorNode.send(new Item(itemId)); + + for(int i=0; i<2; i++) + { + ItemEventCoordinator coord = queue.take(); + assertEquals(1, coord.events.getItems().size()); + assertEquals(itemId, coord.events.getItems().iterator().next().getId()); + } + } + + public void testPublishAndReceiveNoPayload() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue> queue = new ArrayBlockingQueue>(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator sub1Handler = new ItemEventCoordinator(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + creatorNode.publish(new Item(itemId)); + + ItemEventCoordinator coord = queue.take(); + assertEquals(1, coord.events.getItems().size()); + assertEquals(itemId, coord.events.getItems().get(0).getId()); + } + + public void testSendAndReceiveSimplePayload() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, true); + + BlockingQueue>> queue = new ArrayBlockingQueue>>(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator> sub1Handler = new ItemEventCoordinator>(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + String payloadString = "Sir Arthur Conan Doyle"; + creatorNode.send(new PayloadItem(itemId, new SimplePayload("book", "pubsub:test:book", payloadString))); + + ItemEventCoordinator> coord = queue.take(); + assertEquals(1, coord.events.getItems().size()); + PayloadItem item = coord.events.getItems().get(0); + assertEquals(itemId, item.getId()); + assertTrue(item.getPayload() instanceof SimplePayload); + assertEquals(payloadString, item.getPayload().toXML()); + assertEquals("book", item.getPayload().getElementName()); + } + + /* + * For this test, the following extension needs to be added to the meta-inf/smack.providers file + * + * + * car + * pubsub:test:vehicle + * org.jivesoftware.smackx.pubsub.CarExtensionProvider + * + */ + /* + public void testSendAndReceiveCarPayload() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + Node creatorNode = getPubnode(creatorMgr, nodeId, true, true); + + BlockingQueue queue = new ArrayBlockingQueue(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + Node subNode = subMgr.getNode(nodeId); + + ItemEventCoordinator sub1Handler = new ItemEventCoordinator(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + String payloadString = ""; + creatorNode.send(new Item(itemId, new SimplePayload("car", "pubsub:test:vehicle", payloadString))); + + ItemEventCoordinator coord = queue.take(); + assertEquals(1, coord.events.getItems().size()); + Item item = coord.events.getItems().get(0); + assertEquals(itemId, item.getId()); + assertTrue(item.getPayload() instanceof CarExtension); + + CarExtension car = (CarExtension)item.getPayload(); + assertEquals("green", car.getColor()); + assertEquals(4, car.getNumTires()); + } + */ + + public void testSendAndReceiveMultipleSubs() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue> queue = new ArrayBlockingQueue>(3); + ItemEventCoordinator creatorHandler = new ItemEventCoordinator(queue, "creator"); + creatorNode.addItemEventListener(creatorHandler); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator sub1Handler = new ItemEventCoordinator(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + ItemEventCoordinator sub2Handler = new ItemEventCoordinator(queue, "sub2"); + subNode.addItemEventListener(sub2Handler); + Subscription sub2 = subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + creatorNode.send(new Item(itemId)); + + for(int i=0; i<3; i++) + { + ItemEventCoordinator coord = queue.take(); + assertEquals(1, coord.events.getItems().size()); + assertEquals(itemId, coord.events.getItems().iterator().next().getId()); + + if (coord.id.equals("sub1") || coord.id.equals("sub2")) + { + assertEquals(2, coord.events.getSubscriptions().size()); + } + } + } + + public void testSendAndReceiveMultipleItems() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue> queue = new ArrayBlockingQueue>(3); + ItemEventCoordinator creatorHandler = new ItemEventCoordinator(queue, "creator"); + creatorNode.addItemEventListener(creatorHandler); + creatorNode.subscribe(getConnection(0).getUser()); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator sub1Handler = new ItemEventCoordinator(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + ItemEventCoordinator sub2Handler = new ItemEventCoordinator(queue, "sub2"); + subNode.addItemEventListener(sub2Handler); + Subscription sub2 = subNode.subscribe(getConnection(1).getUser()); + + assertEquals(Subscription.State.subscribed, sub1.getState()); + assertEquals(Subscription.State.subscribed, sub2.getState()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + + Collection items = new ArrayList(3); + items.add(new Item("First-" + itemId)); + items.add(new Item("Second-" + itemId)); + items.add(new Item("Third-" + itemId)); + creatorNode.send(items); + + for(int i=0; i<3; i++) + { + ItemEventCoordinator coord = queue.poll(5, TimeUnit.SECONDS); + if (coord == creatorHandler) + assertEquals(1, coord.events.getSubscriptions().size()); + else + assertEquals(2, coord.events.getSubscriptions().size()); + assertEquals(3, coord.events.getItems().size()); + } + } + + public void testSendAndReceiveDelayed() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + // Send event + String itemId = String.valueOf("DelayId-" + System.currentTimeMillis()); + String payloadString = "Sir Arthur Conan Doyle"; + creatorNode.send(new PayloadItem(itemId, new SimplePayload("book", "pubsub:test:book", payloadString))); + + Thread.sleep(1000); + + BlockingQueue>> queue = new ArrayBlockingQueue>>(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemEventCoordinator> sub1Handler = new ItemEventCoordinator>(queue, "sub1"); + subNode.addItemEventListener(sub1Handler); + Subscription sub1 = subNode.subscribe(getConnection(1).getUser()); + + ItemEventCoordinator> coord = queue.take(); + assertTrue(coord.events.isDelayed()); + assertNotNull(coord.events.getPublishedDate()); + } + + public void testDeleteItemAndNotify() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue queue = new ArrayBlockingQueue(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemDeleteCoordinator sub1Handler = new ItemDeleteCoordinator(queue, "sub1"); + subNode.addItemDeleteListener(sub1Handler); + subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + + Collection items = new ArrayList(3); + String id1 = "First-" + itemId; + String id2 = "Second-" + itemId; + String id3 = "Third-" + itemId; + items.add(new Item(id1)); + items.add(new Item(id2)); + items.add(new Item(id3)); + creatorNode.send(items); + + creatorNode.deleteItem(id1); + + ItemDeleteCoordinator coord = queue.poll(5, TimeUnit.SECONDS); + assertEquals(1, coord.event.getItemIds().size()); + assertEquals(id1, coord.event.getItemIds().get(0)); + + creatorNode.deleteItem(Arrays.asList(id2, id3)); + + coord = queue.poll(5, TimeUnit.SECONDS); + assertEquals(2, coord.event.getItemIds().size()); + assertTrue(coord.event.getItemIds().contains(id2)); + assertTrue(coord.event.getItemIds().contains(id3)); + } + + public void testPurgeAndNotify() throws Exception + { + // Setup event source + String nodeId = "TestNode" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + + LeafNode creatorNode = getPubnode(creatorMgr, nodeId, true, false); + + BlockingQueue queue = new ArrayBlockingQueue(3); + + // Setup event receiver + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode = (LeafNode)subMgr.getNode(nodeId); + + ItemDeleteCoordinator sub1Handler = new ItemDeleteCoordinator(queue, "sub1"); + subNode.addItemDeleteListener(sub1Handler); + subNode.subscribe(getConnection(1).getUser()); + + // Send event + String itemId = String.valueOf(System.currentTimeMillis()); + + Collection items = new ArrayList(3); + String id1 = "First-" + itemId; + String id2 = "Second-" + itemId; + String id3 = "Third-" + itemId; + items.add(new Item(id1)); + items.add(new Item(id2)); + items.add(new Item(id3)); + creatorNode.send(items); + + creatorNode.deleteAllItems(); + + ItemDeleteCoordinator coord = queue.poll(5, TimeUnit.SECONDS); + assertNull(nodeId, coord.event); + } + + public void testListenerMultipleNodes() throws Exception + { + // Setup event source + String nodeId1 = "Node-1-" + System.currentTimeMillis(); + PubSubManager creatorMgr = new PubSubManager(getConnection(0), getService()); + String nodeId2 = "Node-2-" + System.currentTimeMillis(); + + LeafNode creatorNode1 = getPubnode(creatorMgr, nodeId1, true, false); + LeafNode creatorNode2 = getPubnode(creatorMgr, nodeId2, true, false); + + BlockingQueue> queue = new ArrayBlockingQueue>(3); + + PubSubManager subMgr = new PubSubManager(getConnection(1), getService()); + LeafNode subNode1 = (LeafNode)subMgr.getNode(nodeId1); + LeafNode subNode2 = (LeafNode)subMgr.getNode(nodeId2); + + subNode1.addItemEventListener(new ItemEventCoordinator(queue, "sub1")); + subNode2.addItemEventListener(new ItemEventCoordinator(queue, "sub2")); + + subNode1.subscribe(getConnection(1).getUser()); + subNode2.subscribe(getConnection(1).getUser()); + + creatorNode1.send(new Item("item1")); + creatorNode2.send(new Item("item2")); + boolean check1 = false; + boolean check2 = false; + + for (int i=0; i<2; i++) + { + ItemEventCoordinator event = queue.take(); + + if (event.id.equals("sub1")) + { + assertEquals(event.events.getNodeId(), nodeId1); + check1 = true; + } + else + { + assertEquals(event.events.getNodeId(), nodeId2); + check2 = true; + } + } + assertTrue(check1); + assertTrue(check2); + } + + class ItemEventCoordinator implements ItemEventListener + { + private BlockingQueue> theQueue; + private ItemPublishEvent events; + private String id; + + ItemEventCoordinator(BlockingQueue> queue, String id) + { + theQueue = queue; + this.id = id; + } + + @Override + public void handlePublishedItems(ItemPublishEvent items) + { + events = items; + theQueue.add(this); + } + + @Override + public String toString() + { + return "ItemEventCoordinator: " + id; + } + + } + + class NodeConfigCoordinator implements NodeConfigListener + { + private BlockingQueue theQueue; + private String id; + private ConfigurationEvent event; + + NodeConfigCoordinator(BlockingQueue queue, String id) + { + theQueue = queue; + this.id = id; + } + + public void handleNodeConfiguration(ConfigurationEvent config) + { + event = config; + theQueue.add(this); + } + + @Override + public String toString() + { + return "NodeConfigCoordinator: " + id; + } + + } + + class ItemDeleteCoordinator implements ItemDeleteListener + { + private BlockingQueue theQueue; + private String id; + private ItemDeleteEvent event; + + ItemDeleteCoordinator(BlockingQueue queue, String id) + { + theQueue = queue; + this.id = id; + } + + public void handleDeletedItems(ItemDeleteEvent delEvent) + { + event = delEvent; + theQueue.add(this); + } + + + public void handlePurge() + { + event = null; + theQueue.add(this); + } + + @Override + public String toString() + { + return "ItemDeleteCoordinator: " + id; + } + } + + static private LeafNode getPubnode(PubSubManager manager, String id, boolean persistItems, boolean deliverPayload) + throws XMPPException + { + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setPersistentItems(persistItems); + form.setDeliverPayloads(deliverPayload); + form.setAccessModel(AccessModel.open); + return (LeafNode)manager.createNode(id, form); + } + +} diff --git a/test/org/jivesoftware/smackx/pubsub/TestMessageContent.java b/test/org/jivesoftware/smackx/pubsub/TestMessageContent.java new file mode 100644 index 000000000..3bfc353be --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/TestMessageContent.java @@ -0,0 +1,101 @@ +/* + * Created on 2009-08-05 + */ +package org.jivesoftware.smackx.pubsub; + +import junit.framework.TestCase; + +public class TestMessageContent extends TestCase +{ + String payloadXmlWithNS = ""; + + public void testItemWithId() + { + Item item = new Item("123"); + assertEquals("", item.toXML()); + assertEquals("item", item.getElementName()); + assertNull(item.getNamespace()); + } + + public void testItemWithNoId() + { + Item item = new Item(); + assertEquals("", item.toXML()); + + Item itemNull = new Item(null); + assertEquals("", itemNull.toXML()); + } + + public void testSimplePayload() + { + SimplePayload payloadNS = new SimplePayload("book", "pubsub:test:book", payloadXmlWithNS); + + assertEquals(payloadXmlWithNS, payloadNS.toXML()); + + String payloadXmlWithNoNS = ""; + SimplePayload payloadNoNS = new SimplePayload("book", null, ""); + + assertEquals(payloadXmlWithNoNS, payloadNoNS.toXML()); + + } + + public void testPayloadItemWithId() + { + SimplePayload payload = new SimplePayload("book", "pubsub:test:book", payloadXmlWithNS); + PayloadItem item = new PayloadItem("123", payload); + + String xml = "" + payloadXmlWithNS + ""; + assertEquals(xml, item.toXML()); + assertEquals("item", item.getElementName()); + } + + public void testPayloadItemWithNoId() + { + SimplePayload payload = new SimplePayload("book", "pubsub:test:book", payloadXmlWithNS); + PayloadItem item = new PayloadItem(null, payload); + + String xml = "" + payloadXmlWithNS + ""; + assertEquals(xml, item.toXML()); + } + + public void testPayloadItemWithIdNoPayload() + { + try + { + PayloadItem item = new PayloadItem("123", null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + } + } + + public void testPayloadItemWithNoIdNoPayload() + { + try + { + PayloadItem item = new PayloadItem(null, null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + } + } + + public void testRetractItem() + { + RetractItem item = new RetractItem("1234"); + + assertEquals("", item.toXML()); + assertEquals("retract", item.getElementName()); + + try + { + new RetractItem(null); + fail("Should have thrown IllegalArgumentException"); + } + catch (IllegalArgumentException e) + { + } + } +} diff --git a/test/org/jivesoftware/smackx/pubsub/test/PubSubTestCase.java b/test/org/jivesoftware/smackx/pubsub/test/PubSubTestCase.java new file mode 100644 index 000000000..5579ce8a9 --- /dev/null +++ b/test/org/jivesoftware/smackx/pubsub/test/PubSubTestCase.java @@ -0,0 +1,74 @@ +/* + * Created on 2009-05-05 + */ +package org.jivesoftware.smackx.pubsub.test; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.test.SmackTestCase; +import org.jivesoftware.smackx.pubsub.AccessModel; +import org.jivesoftware.smackx.pubsub.ConfigureForm; +import org.jivesoftware.smackx.pubsub.FormType; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PubSubManager; + +abstract public class PubSubTestCase extends SmackTestCase +{ + private PubSubManager[] manager; + + public PubSubTestCase(String arg0) + { + super(arg0); + } + + public PubSubTestCase() + { + super("PubSub Test Case"); + } + + protected LeafNode getRandomPubnode(PubSubManager pubMgr, boolean persistItems, boolean deliverPayload) throws XMPPException + { + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setPersistentItems(persistItems); + form.setDeliverPayloads(deliverPayload); + form.setAccessModel(AccessModel.open); + return (LeafNode)pubMgr.createNode("/test/Pubnode" + System.currentTimeMillis(), form); + } + + protected LeafNode getPubnode(PubSubManager pubMgr, boolean persistItems, boolean deliverPayload, String nodeId) throws XMPPException + { + LeafNode node = null; + + try + { + node = (LeafNode)pubMgr.getNode(nodeId); + } + catch (XMPPException e) + { + ConfigureForm form = new ConfigureForm(FormType.submit); + form.setPersistentItems(persistItems); + form.setDeliverPayloads(deliverPayload); + form.setAccessModel(AccessModel.open); + node = (LeafNode)pubMgr.createNode(nodeId, form); + } + return node; + } + + protected PubSubManager getManager(int idx) + { + if (manager == null) + { + manager = new PubSubManager[getMaxConnections()]; + + for(int i=0; i