001/** 002 * 003 * Copyright the original author or authors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.pubsub; 018 019import java.util.Collections; 020import java.util.List; 021import java.util.Map; 022import java.util.concurrent.ConcurrentHashMap; 023 024import org.jivesoftware.smack.SmackException.NoResponseException; 025import org.jivesoftware.smack.SmackException.NotConnectedException; 026import org.jivesoftware.smack.XMPPConnection; 027import org.jivesoftware.smack.XMPPException.XMPPErrorException; 028import org.jivesoftware.smack.packet.IQ.Type; 029import org.jivesoftware.smack.packet.Packet; 030import org.jivesoftware.smack.packet.PacketExtension; 031import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; 032import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 033import org.jivesoftware.smackx.disco.packet.DiscoverItems; 034import org.jivesoftware.smackx.pubsub.packet.PubSub; 035import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; 036import org.jivesoftware.smackx.pubsub.util.NodeUtils; 037import org.jivesoftware.smackx.xdata.Form; 038import org.jivesoftware.smackx.xdata.FormField; 039 040/** 041 * This is the starting point for access to the pubsub service. It 042 * will provide access to general information about the service, as 043 * well as create or retrieve pubsub {@link LeafNode} instances. These 044 * instances provide the bulk of the functionality as defined in the 045 * pubsub specification <a href="http://xmpp.org/extensions/xep-0060.html">XEP-0060</a>. 046 * 047 * @author Robin Collier 048 */ 049final public class PubSubManager 050{ 051 private XMPPConnection con; 052 private String to; 053 private Map<String, Node> nodeMap = new ConcurrentHashMap<String, Node>(); 054 055 /** 056 * Create a pubsub manager associated to the specified connection. Defaults the service 057 * name to <i>pubsub</i> 058 * 059 * @param connection The XMPP connection 060 */ 061 public PubSubManager(XMPPConnection connection) 062 { 063 con = connection; 064 to = "pubsub." + connection.getServiceName(); 065 } 066 067 /** 068 * Create a pubsub manager associated to the specified connection where 069 * the pubsub requests require a specific to address for packets. 070 * 071 * @param connection The XMPP connection 072 * @param toAddress The pubsub specific to address (required for some servers) 073 */ 074 public PubSubManager(XMPPConnection connection, String toAddress) 075 { 076 con = connection; 077 to = toAddress; 078 } 079 080 /** 081 * Creates an instant node, if supported. 082 * 083 * @return The node that was created 084 * @throws XMPPErrorException 085 * @throws NoResponseException 086 * @throws NotConnectedException 087 */ 088 public LeafNode createNode() throws NoResponseException, XMPPErrorException, NotConnectedException 089 { 090 PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.CREATE), null); 091 NodeExtension elem = (NodeExtension)reply.getExtension("create", PubSubNamespace.BASIC.getXmlns()); 092 093 LeafNode newNode = new LeafNode(con, elem.getNode()); 094 newNode.setTo(to); 095 nodeMap.put(newNode.getId(), newNode); 096 097 return newNode; 098 } 099 100 /** 101 * Creates a node with default configuration. 102 * 103 * @param id The id of the node, which must be unique within the 104 * pubsub service 105 * @return The node that was created 106 * @throws XMPPErrorException 107 * @throws NoResponseException 108 * @throws NotConnectedException 109 */ 110 public LeafNode createNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException 111 { 112 return (LeafNode)createNode(id, null); 113 } 114 115 /** 116 * Creates a node with specified configuration. 117 * 118 * Note: This is the only way to create a collection node. 119 * 120 * @param name The name of the node, which must be unique within the 121 * pubsub service 122 * @param config The configuration for the node 123 * @return The node that was created 124 * @throws XMPPErrorException 125 * @throws NoResponseException 126 * @throws NotConnectedException 127 */ 128 public Node createNode(String name, Form config) throws NoResponseException, XMPPErrorException, NotConnectedException 129 { 130 PubSub request = PubSub.createPubsubPacket(to, Type.SET, new NodeExtension(PubSubElementType.CREATE, name), null); 131 boolean isLeafNode = true; 132 133 if (config != null) 134 { 135 request.addExtension(new FormNode(FormNodeType.CONFIGURE, config)); 136 FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName()); 137 138 if (nodeTypeField != null) 139 isLeafNode = nodeTypeField.getValues().get(0).equals(NodeType.leaf.toString()); 140 } 141 142 // Errors will cause exceptions in getReply, so it only returns 143 // on success. 144 sendPubsubPacket(con, request); 145 Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name); 146 newNode.setTo(to); 147 nodeMap.put(newNode.getId(), newNode); 148 149 return newNode; 150 } 151 152 /** 153 * Retrieves the requested node, if it exists. It will throw an 154 * exception if it does not. 155 * 156 * @param id - The unique id of the node 157 * @return the node 158 * @throws XMPPErrorException The node does not exist 159 * @throws NoResponseException if there was no response from the server. 160 * @throws NotConnectedException 161 */ 162 @SuppressWarnings("unchecked") 163 public <T extends Node> T getNode(String id) throws NoResponseException, XMPPErrorException, NotConnectedException 164 { 165 Node node = nodeMap.get(id); 166 167 if (node == null) 168 { 169 DiscoverInfo info = new DiscoverInfo(); 170 info.setTo(to); 171 info.setNode(id); 172 173 DiscoverInfo infoReply = (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow(); 174 175 if (infoReply.getIdentities().get(0).getType().equals(NodeType.leaf.toString())) 176 node = new LeafNode(con, id); 177 else 178 node = new CollectionNode(con, id); 179 node.setTo(to); 180 nodeMap.put(id, node); 181 } 182 return (T) node; 183 } 184 185 /** 186 * Get all the nodes that currently exist as a child of the specified 187 * collection node. If the service does not support collection nodes 188 * then all nodes will be returned. 189 * 190 * To retrieve contents of the root collection node (if it exists), 191 * or there is no root collection node, pass null as the nodeId. 192 * 193 * @param nodeId - The id of the collection node for which the child 194 * nodes will be returned. 195 * @return {@link DiscoverItems} representing the existing nodes 196 * @throws XMPPErrorException 197 * @throws NoResponseException if there was no response from the server. 198 * @throws NotConnectedException 199 */ 200 public DiscoverItems discoverNodes(String nodeId) throws NoResponseException, XMPPErrorException, NotConnectedException 201 { 202 DiscoverItems items = new DiscoverItems(); 203 204 if (nodeId != null) 205 items.setNode(nodeId); 206 items.setTo(to); 207 DiscoverItems nodeItems = (DiscoverItems) con.createPacketCollectorAndSend(items).nextResultOrThrow(); 208 return nodeItems; 209 } 210 211 /** 212 * Gets the subscriptions on the root node. 213 * 214 * @return List of exceptions 215 * @throws XMPPErrorException 216 * @throws NoResponseException 217 * @throws NotConnectedException 218 */ 219 public List<Subscription> getSubscriptions() throws NoResponseException, XMPPErrorException, NotConnectedException 220 { 221 Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS), null); 222 SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns()); 223 return subElem.getSubscriptions(); 224 } 225 226 /** 227 * Gets the affiliations on the root node. 228 * 229 * @return List of affiliations 230 * @throws XMPPErrorException 231 * @throws NoResponseException 232 * @throws NotConnectedException 233 * 234 */ 235 public List<Affiliation> getAffiliations() throws NoResponseException, XMPPErrorException, NotConnectedException 236 { 237 PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.AFFILIATIONS), null); 238 AffiliationsExtension listElem = (AffiliationsExtension)reply.getExtension(PubSubElementType.AFFILIATIONS); 239 return listElem.getAffiliations(); 240 } 241 242 /** 243 * Delete the specified node 244 * 245 * @param nodeId 246 * @throws XMPPErrorException 247 * @throws NoResponseException 248 * @throws NotConnectedException 249 */ 250 public void deleteNode(String nodeId) throws NoResponseException, XMPPErrorException, NotConnectedException 251 { 252 sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace()); 253 nodeMap.remove(nodeId); 254 } 255 256 /** 257 * Returns the default settings for Node configuration. 258 * 259 * @return configuration form containing the default settings. 260 * @throws XMPPErrorException 261 * @throws NoResponseException 262 * @throws NotConnectedException 263 */ 264 public ConfigureForm getDefaultConfiguration() throws NoResponseException, XMPPErrorException, NotConnectedException 265 { 266 // Errors will cause exceptions in getReply, so it only returns 267 // on success. 268 PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace()); 269 return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT); 270 } 271 272 /** 273 * Gets the supported features of the servers pubsub implementation 274 * as a standard {@link DiscoverInfo} instance. 275 * 276 * @return The supported features 277 * @throws XMPPErrorException 278 * @throws NoResponseException 279 * @throws NotConnectedException 280 */ 281 public DiscoverInfo getSupportedFeatures() throws NoResponseException, XMPPErrorException, NotConnectedException 282 { 283 ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con); 284 return mgr.discoverInfo(to); 285 } 286 287 private Packet sendPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) 288 throws NoResponseException, XMPPErrorException, NotConnectedException { 289 return sendPubsubPacket(con, to, type, Collections.singletonList(ext), ns); 290 } 291 292 static Packet sendPubsubPacket(XMPPConnection con, String to, Type type, List<PacketExtension> extList, PubSubNamespace ns) throws NoResponseException, XMPPErrorException, NotConnectedException 293 { 294 PubSub pubSub = new PubSub(to, type, ns); 295 for (PacketExtension pe : extList) { 296 pubSub.addExtension(pe); 297 } 298 return sendPubsubPacket(con ,pubSub); 299 } 300 301 static Packet sendPubsubPacket(XMPPConnection con, PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException 302 { 303 return con.createPacketCollectorAndSend(packet).nextResultOrThrow(); 304 } 305 306}