1/** 2 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 3 * you may not use this file except in compliance with the License. 4 * You may obtain a copy of the License at 5 * 6 * http://www.apache.org/licenses/LICENSE-2.0 7 * 8 * Unless required by applicable law or agreed to in writing, software 9 * distributed under the License is distributed on an "AS IS" BASIS, 10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 * See the License for the specific language governing permissions and 12 * limitations under the License. 13 */ 14package org.jivesoftware.smackx.pubsub; 15 16import java.util.List; 17import java.util.Map; 18import java.util.concurrent.ConcurrentHashMap; 19 20import org.jivesoftware.smack.Connection; 21import org.jivesoftware.smack.XMPPException; 22import org.jivesoftware.smack.packet.IQ.Type; 23import org.jivesoftware.smack.packet.Packet; 24import org.jivesoftware.smack.packet.PacketExtension; 25import org.jivesoftware.smackx.Form; 26import org.jivesoftware.smackx.FormField; 27import org.jivesoftware.smackx.ServiceDiscoveryManager; 28import org.jivesoftware.smackx.packet.DiscoverInfo; 29import org.jivesoftware.smackx.packet.DiscoverItems; 30import org.jivesoftware.smackx.pubsub.packet.PubSub; 31import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; 32import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; 33import org.jivesoftware.smackx.pubsub.util.NodeUtils; 34 35/** 36 * This is the starting point for access to the pubsub service. It 37 * will provide access to general information about the service, as 38 * well as create or retrieve pubsub {@link LeafNode} instances. These 39 * instances provide the bulk of the functionality as defined in the 40 * pubsub specification <a href="http://xmpp.org/extensions/xep-0060.html">XEP-0060</a>. 41 * 42 * @author Robin Collier 43 */ 44final public class PubSubManager 45{ 46 private Connection con; 47 private String to; 48 private Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>(); 49 50 /** 51 * Create a pubsub manager associated to the specified connection. Defaults the service 52 * name to <i>pubsub</i> 53 * 54 * @param connection The XMPP connection 55 */ 56 public PubSubManager(Connection connection) 57 { 58 con = connection; 59 to = "pubsub." + connection.getServiceName(); 60 } 61 62 /** 63 * Create a pubsub manager associated to the specified connection where 64 * the pubsub requests require a specific to address for packets. 65 * 66 * @param connection The XMPP connection 67 * @param toAddress The pubsub specific to address (required for some servers) 68 */ 69 public PubSubManager(Connection connection, String toAddress) 70 { 71 con = connection; 72 to = toAddress; 73 } 74 75 /** 76 * Creates an instant node, if supported. 77 * 78 * @return The node that was created 79 * @exception XMPPException 80 */ 81 public LeafNode createNode() 82 throws XMPPException 83 { 84 PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.CREATE)); 85 NodeExtension elem = (NodeExtension)reply.getExtension("create", PubSubNamespace.BASIC.getXmlns()); 86 87 LeafNode newNode = new LeafNode(con, elem.getNode()); 88 newNode.setTo(to); 89 nodeMap.put(newNode.getId(), newNode); 90 91 return newNode; 92 } 93 94 /** 95 * Creates a node with default configuration. 96 * 97 * @param id The id of the node, which must be unique within the 98 * pubsub service 99 * @return The node that was created 100 * @exception XMPPException 101 */ 102 public LeafNode createNode(String id) 103 throws XMPPException 104 { 105 return (LeafNode)createNode(id, null); 106 } 107 108 /** 109 * Creates a node with specified configuration. 110 * 111 * Note: This is the only way to create a collection node. 112 * 113 * @param name The name of the node, which must be unique within the 114 * pubsub service 115 * @param config The configuration for the node 116 * @return The node that was created 117 * @exception XMPPException 118 */ 119 public Node createNode(String name, Form config) 120 throws XMPPException 121 { 122 PubSub request = createPubsubPacket(to, Type.SET, new NodeExtension(PubSubElementType.CREATE, name)); 123 boolean isLeafNode = true; 124 125 if (config != null) 126 { 127 request.addExtension(new FormNode(FormNodeType.CONFIGURE, config)); 128 FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName()); 129 130 if (nodeTypeField != null) 131 isLeafNode = nodeTypeField.getValues().next().equals(NodeType.leaf.toString()); 132 } 133 134 // Errors will cause exceptions in getReply, so it only returns 135 // on success. 136 sendPubsubPacket(con, to, Type.SET, request); 137 Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name); 138 newNode.setTo(to); 139 nodeMap.put(newNode.getId(), newNode); 140 141 return newNode; 142 } 143 144 /** 145 * Retrieves the requested node, if it exists. It will throw an 146 * exception if it does not. 147 * 148 * @param id - The unique id of the node 149 * @return the node 150 * @throws XMPPException The node does not exist 151 */ 152 public <T extends Node> T getNode(String id) 153 throws XMPPException 154 { 155 Node node = nodeMap.get(id); 156 157 if (node == null) 158 { 159 DiscoverInfo info = new DiscoverInfo(); 160 info.setTo(to); 161 info.setNode(id); 162 163 DiscoverInfo infoReply = (DiscoverInfo)SyncPacketSend.getReply(con, info); 164 165 if (infoReply.getIdentities().next().getType().equals(NodeType.leaf.toString())) 166 node = new LeafNode(con, id); 167 else 168 node = new CollectionNode(con, id); 169 node.setTo(to); 170 nodeMap.put(id, node); 171 } 172 return (T) node; 173 } 174 175 /** 176 * Get all the nodes that currently exist as a child of the specified 177 * collection node. If the service does not support collection nodes 178 * then all nodes will be returned. 179 * 180 * To retrieve contents of the root collection node (if it exists), 181 * or there is no root collection node, pass null as the nodeId. 182 * 183 * @param nodeId - The id of the collection node for which the child 184 * nodes will be returned. 185 * @return {@link DiscoverItems} representing the existing nodes 186 * 187 * @throws XMPPException 188 */ 189 public DiscoverItems discoverNodes(String nodeId) 190 throws XMPPException 191 { 192 DiscoverItems items = new DiscoverItems(); 193 194 if (nodeId != null) 195 items.setNode(nodeId); 196 items.setTo(to); 197 DiscoverItems nodeItems = (DiscoverItems)SyncPacketSend.getReply(con, items); 198 return nodeItems; 199 } 200 201 /** 202 * Gets the subscriptions on the root node. 203 * 204 * @return List of exceptions 205 * 206 * @throws XMPPException 207 */ 208 public List<Subscription> getSubscriptions() 209 throws XMPPException 210 { 211 Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS)); 212 SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns()); 213 return subElem.getSubscriptions(); 214 } 215 216 /** 217 * Gets the affiliations on the root node. 218 * 219 * @return List of affiliations 220 * 221 * @throws XMPPException 222 */ 223 public List<Affiliation> getAffiliations() 224 throws XMPPException 225 { 226 PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.AFFILIATIONS)); 227 AffiliationsExtension listElem = (AffiliationsExtension)reply.getExtension(PubSubElementType.AFFILIATIONS); 228 return listElem.getAffiliations(); 229 } 230 231 /** 232 * Delete the specified node 233 * 234 * @param nodeId 235 * @throws XMPPException 236 */ 237 public void deleteNode(String nodeId) 238 throws XMPPException 239 { 240 sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace()); 241 nodeMap.remove(nodeId); 242 } 243 244 /** 245 * Returns the default settings for Node configuration. 246 * 247 * @return configuration form containing the default settings. 248 */ 249 public ConfigureForm getDefaultConfiguration() 250 throws XMPPException 251 { 252 // Errors will cause exceptions in getReply, so it only returns 253 // on success. 254 PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace()); 255 return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT); 256 } 257 258 /** 259 * Gets the supported features of the servers pubsub implementation 260 * as a standard {@link DiscoverInfo} instance. 261 * 262 * @return The supported features 263 * 264 * @throws XMPPException 265 */ 266 public DiscoverInfo getSupportedFeatures() 267 throws XMPPException 268 { 269 ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con); 270 return mgr.discoverInfo(to); 271 } 272 273 private Packet sendPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) 274 throws XMPPException 275 { 276 return sendPubsubPacket(con, to, type, ext, ns); 277 } 278 279 private Packet sendPubsubPacket(Type type, PacketExtension ext) 280 throws XMPPException 281 { 282 return sendPubsubPacket(type, ext, null); 283 } 284 285 static PubSub createPubsubPacket(String to, Type type, PacketExtension ext) 286 { 287 return createPubsubPacket(to, type, ext, null); 288 } 289 290 static PubSub createPubsubPacket(String to, Type type, PacketExtension ext, PubSubNamespace ns) 291 { 292 PubSub request = new PubSub(); 293 request.setTo(to); 294 request.setType(type); 295 296 if (ns != null) 297 { 298 request.setPubSubNamespace(ns); 299 } 300 request.addExtension(ext); 301 302 return request; 303 } 304 305 static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext) 306 throws XMPPException 307 { 308 return sendPubsubPacket(con, to, type, ext, null); 309 } 310 311 static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext, PubSubNamespace ns) 312 throws XMPPException 313 { 314 return SyncPacketSend.getReply(con, createPubsubPacket(to, type, ext, ns)); 315 } 316 317 static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet) 318 throws XMPPException 319 { 320 return sendPubsubPacket(con, to, type, packet, null); 321 } 322 323 static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet, PubSubNamespace ns) 324 throws XMPPException 325 { 326 return SyncPacketSend.getReply(con, packet); 327 } 328 329} 330