PubSubModule.java 32.3 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1 2 3 4 5
/**
 * $RCSfile: $
 * $Revision: $
 * $Date: $
 *
6
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
Matt Tucker's avatar
Matt Tucker committed
7 8
 *
 * This software is published under the terms of the GNU Public License (GPL),
9 10
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
Matt Tucker's avatar
Matt Tucker committed
11 12
 */

13
package org.jivesoftware.openfire.pubsub;
Matt Tucker's avatar
Matt Tucker committed
14 15 16

import org.dom4j.DocumentHelper;
import org.dom4j.Element;
17 18 19 20
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.RoutableChannelHandler;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.XMPPServer;
21 22
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
23
import org.jivesoftware.openfire.commands.AdHocCommandManager;
24 25
import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.container.BasicModule;
26
import org.jivesoftware.openfire.disco.*;
27 28
import org.jivesoftware.openfire.pubsub.models.AccessModel;
import org.jivesoftware.openfire.pubsub.models.PublisherModel;
29
import org.jivesoftware.util.*;
30
import org.xmpp.forms.DataForm;
Matt Tucker's avatar
Matt Tucker committed
31 32 33 34 35
import org.xmpp.packet.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
36
import java.util.concurrent.LinkedBlockingQueue;
Matt Tucker's avatar
Matt Tucker committed
37 38 39 40 41 42 43 44

/**
 * Module that implements JEP-60: Publish-Subscribe. By default node collections and
 * instant nodes are supported.
 *
 * @author Matt Tucker
 */
public class PubSubModule extends BasicModule implements ServerItemsProvider, DiscoInfoProvider,
45
        DiscoItemsProvider, RoutableChannelHandler, PubSubService, ClusterEventListener, PropertyEventListener {
Matt Tucker's avatar
Matt Tucker committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

    /**
     * the chat service's hostname
     */
    private String serviceName = null;

    /**
     * Collection node that acts as the root node of the entire node hierarchy.
     */
    private CollectionNode rootCollectionNode = null;

    /**
     * Nodes managed by this manager, table: key nodeID (String); value Node
     */
    private Map<String, Node> nodes = new ConcurrentHashMap<String, Node>();
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
    
    /**
     * Keep a registry of the presence's show value of users that subscribed to a node of
     * the pubsub service and for which the node only delivers notifications for online users
     * or node subscriptions deliver events based on the user presence show value. Offline
     * users will not have an entry in the map. Note: Key-> bare JID and Value-> Map whose key
     * is full JID of connected resource and value is show value of the last received presence.
     */
    private Map<String, Map<String, String>> barePresences =
            new ConcurrentHashMap<String, Map<String, String>>();
    
    /**
     * Queue that holds the items that need to be added to the database.
     */
    private Queue<PublishedItem> itemsToAdd = new LinkedBlockingQueue<PublishedItem>();
    /**
     * Queue that holds the items that need to be deleted from the database.
     */
    private Queue<PublishedItem> itemsToDelete = new LinkedBlockingQueue<PublishedItem>();
    
    /**
     * Manager that keeps the list of ad-hoc commands and processing command requests.
     */
    private AdHocCommandManager manager;
    
    /**
     * The time to elapse between each execution of the maintenance process. Default
     * is 2 minutes.
     */
    private int items_task_timeout = 2 * 60 * 1000;

    /**
     * Task that saves or deletes published items from the database.
     */
    private PublishedItemTask publishedItemTask;

    /**
     * Timer to save published items to the database or remove deleted or old items.
     */
    private Timer timer = new Timer("PubSub maintenance");

Matt Tucker's avatar
Matt Tucker committed
102 103 104 105 106 107 108
    /**
     * Returns the permission policy for creating nodes. A true value means that not anyone can
     * create a node, only the JIDs listed in <code>allowedToCreate</code> are allowed to create
     * nodes.
     */
    private boolean nodeCreationRestricted = false;

109 110 111 112 113 114 115
    /**
     * Flag that indicates if a user may have more than one subscription with the node. When multiple
     * subscriptions is enabled each subscription request, event notification and unsubscription request
     * should include a subid attribute.
     */
    private boolean multipleSubscriptionsEnabled = true;

Matt Tucker's avatar
Matt Tucker committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
    /**
     * Bare jids of users that are allowed to create nodes. An empty list means that anyone can
     * create nodes.
     */
    private Collection<String> allowedToCreate = new CopyOnWriteArrayList<String>();

    /**
     * Bare jids of users that are system administrators of the PubSub service. A sysadmin
     * has the same permissions as a node owner.
     */
    private Collection<String> sysadmins = new CopyOnWriteArrayList<String>();

    /**
     * The packet router for the server.
     */
    private PacketRouter router = null;

    private RoutingTable routingTable = null;

    /**
     * Default configuration to use for newly created leaf nodes.
     */
    private DefaultNodeConfiguration leafDefaultConfiguration;
    /**
     * Default configuration to use for newly created collection nodes.
     */
    private DefaultNodeConfiguration collectionDefaultConfiguration;

    /**
     * Private component that actually performs the pubsub work.
     */
    private PubSubEngine engine = null;

149 150 151 152 153
    /**
     * Flag that indicates if the service is enabled.
     */
    private boolean serviceEnabled = true;

Matt Tucker's avatar
Matt Tucker committed
154 155
    public PubSubModule() {
        super("Publish Subscribe Service");
156 157 158 159 160 161 162 163 164
        
        // Initialize the ad-hoc commands manager to use for this pubsub service
        manager = new AdHocCommandManager();
        manager.addCommand(new PendingSubscriptionsCommand(this));
        
        // Save or delete published items from the database every 2 minutes starting in
        // 2 minutes (default values)
        publishedItemTask = new PublishedItemTask(this);
        timer.schedule(publishedItemTask, items_task_timeout, items_task_timeout);
Matt Tucker's avatar
Matt Tucker committed
165 166 167 168 169 170 171 172 173 174
    }

    public void process(Packet packet) {
        // TODO Remove this method when moving PubSub as a component and removing module code
        // The MUC service will receive all the packets whose domain matches the domain of the MUC
        // service. This means that, for instance, a disco request should be responded by the
        // service itself instead of relying on the server to handle the request.
        try {
            // Check if the packet is a disco request or a packet with namespace iq:register
            if (packet instanceof IQ) {
175
                if (!engine.process(this, (IQ) packet)) {
Matt Tucker's avatar
Matt Tucker committed
176 177 178 179
                    process((IQ) packet);
                }
            }
            else if (packet instanceof Presence) {
180
                engine.process(this, (Presence) packet);
Matt Tucker's avatar
Matt Tucker committed
181 182
            }
            else {
183
                engine.process(this, (Message) packet);
Matt Tucker's avatar
Matt Tucker committed
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
            }
        }
        catch (Exception e) {
            Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
            if (packet instanceof IQ) {
                // Send internal server error
                IQ reply = IQ.createResultIQ((IQ) packet);
                reply.setError(PacketError.Condition.internal_server_error);
                send(reply);
            }
        }
    }

    private void process(IQ iq) {
        // Ignore IQs of type ERROR
        if (IQ.Type.error == iq.getType()) {
            return;
        }
        Element childElement = iq.getChildElement();
        String namespace = null;

        if (childElement != null) {
            namespace = childElement.getNamespaceURI();
        }
        if ("http://jabber.org/protocol/disco#info".equals(namespace)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
209 210 211 212
            // TODO PubSub should have an IQDiscoInfoHandler of its own when PubSub becomes
            // a component
            IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler().handleIQ(iq);
            router.route(reply);
Matt Tucker's avatar
Matt Tucker committed
213 214
        }
        else if ("http://jabber.org/protocol/disco#items".equals(namespace)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
215 216 217 218
            // TODO PubSub should have an IQDiscoItemsHandler of its own when PubSub becomes
            // a component
            IQ reply = XMPPServer.getInstance().getIQDiscoItemsHandler().handleIQ(iq);
            router.route(reply);
Matt Tucker's avatar
Matt Tucker committed
219 220
        }
        else {
Matt Tucker's avatar
Matt Tucker committed
221 222
            // Unknown namespace requested so return error to sender
            engine.sendErrorPacket(iq, PacketError.Condition.service_unavailable, null);
Matt Tucker's avatar
Matt Tucker committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
        }
    }

    public String getServiceID() {
        return "pubsub";
    }

    public boolean canCreateNode(JID creator) {
        // Node creation is always allowed for sysadmin
        if (isNodeCreationRestricted() && !isServiceAdmin(creator)) {
            // The user is not allowed to create nodes
            return false;
        }
        return true;
    }

    public boolean isServiceAdmin(JID user) {
240
        return sysadmins.contains(user.toBareJID()) || allowedToCreate.contains(user.toBareJID()) ||
241
                InternalComponentManager.getInstance().hasComponent(user);
Matt Tucker's avatar
Matt Tucker committed
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
    }

    public boolean isInstantNodeSupported() {
        return true;
    }

    public boolean isCollectionNodesSupported() {
        return true;
    }

    public CollectionNode getRootCollectionNode() {
        return rootCollectionNode;
    }

    public DefaultNodeConfiguration getDefaultNodeConfiguration(boolean leafType) {
        if (leafType) {
            return leafDefaultConfiguration;
        }
        return collectionDefaultConfiguration;
    }

263
    public Collection<String> getShowPresences(JID subscriber) {
264
        return PubSubEngine.getShowPresences(this, subscriber);
265 266 267
    }

    public void presenceSubscriptionNotRequired(Node node, JID user) {
268
        PubSubEngine.presenceSubscriptionNotRequired(this, node, user);
269 270 271
    }

    public void presenceSubscriptionRequired(Node node, JID user) {
272
        PubSubEngine.presenceSubscriptionRequired(this, node, user);
Matt Tucker's avatar
Matt Tucker committed
273 274
    }

Gaston Dombiak's avatar
Gaston Dombiak committed
275
    public void queueItemToAdd(PublishedItem newItem) {
276
        PubSubEngine.queueItemToAdd(this, newItem);
Gaston Dombiak's avatar
Gaston Dombiak committed
277 278 279
    }

    public void queueItemToRemove(PublishedItem removedItem) {
280
        PubSubEngine.queueItemToRemove(this, removedItem);
Matt Tucker's avatar
Matt Tucker committed
281 282
    }

Matt Tucker's avatar
Matt Tucker committed
283 284 285 286 287
    public String getServiceName() {
        return serviceName;
    }

    public String getServiceDomain() {
288
        return serviceName + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
Matt Tucker's avatar
Matt Tucker committed
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
    }

    public JID getAddress() {
        // TODO Cache this JID for performance?
        return new JID(null, getServiceDomain(), null);
    }

    public Collection<String> getUsersAllowedToCreate() {
        return allowedToCreate;
    }

    public Collection<String> getSysadmins() {
        return sysadmins;
    }

    public void addSysadmin(String userJID) {
        sysadmins.add(userJID.trim().toLowerCase());
        // Update the config.
        String[] jids = new String[sysadmins.size()];
308
        jids = sysadmins.toArray(jids);
Matt Tucker's avatar
Matt Tucker committed
309 310 311 312 313 314 315
        JiveGlobals.setProperty("xmpp.pubsub.sysadmin.jid", fromArray(jids));
    }

    public void removeSysadmin(String userJID) {
        sysadmins.remove(userJID.trim().toLowerCase());
        // Update the config.
        String[] jids = new String[sysadmins.size()];
316
        jids = sysadmins.toArray(jids);
Matt Tucker's avatar
Matt Tucker committed
317 318 319 320 321 322 323
        JiveGlobals.setProperty("xmpp.pubsub.sysadmin.jid", fromArray(jids));
    }

    public boolean isNodeCreationRestricted() {
        return nodeCreationRestricted;
    }

324 325 326 327
    public boolean isMultipleSubscriptionsEnabled() {
        return multipleSubscriptionsEnabled;
    }

Matt Tucker's avatar
Matt Tucker committed
328 329 330 331 332 333 334 335 336 337
    public void setNodeCreationRestricted(boolean nodeCreationRestricted) {
        this.nodeCreationRestricted = nodeCreationRestricted;
        JiveGlobals.setProperty("xmpp.pubsub.create.anyone", Boolean.toString(nodeCreationRestricted));
    }

    public void addUserAllowedToCreate(String userJID) {
        // Update the list of allowed JIDs to create nodes.
        allowedToCreate.add(userJID.trim().toLowerCase());
        // Update the config.
        String[] jids = new String[allowedToCreate.size()];
338
        jids = allowedToCreate.toArray(jids);
Matt Tucker's avatar
Matt Tucker committed
339 340 341 342 343 344 345 346
        JiveGlobals.setProperty("xmpp.pubsub.create.jid", fromArray(jids));
    }

    public void removeUserAllowedToCreate(String userJID) {
        // Update the list of allowed JIDs to create nodes.
        allowedToCreate.remove(userJID.trim().toLowerCase());
        // Update the config.
        String[] jids = new String[allowedToCreate.size()];
347
        jids = allowedToCreate.toArray(jids);
Matt Tucker's avatar
Matt Tucker committed
348 349 350 351 352 353
        JiveGlobals.setProperty("xmpp.pubsub.create.jid", fromArray(jids));
    }

    public void initialize(XMPPServer server) {
        super.initialize(server);

354 355 356 357
        // Listen to property events so that the template is always up to date
        PropertyEventDispatcher.addListener(this);

        serviceEnabled = JiveGlobals.getBooleanProperty("xmpp.pubsub.enabled", true);
Matt Tucker's avatar
Matt Tucker committed
358 359 360 361 362 363 364 365 366
        serviceName = JiveGlobals.getProperty("xmpp.pubsub.service");
        if (serviceName == null) {
            serviceName = "pubsub";
        }
        // Load the list of JIDs that are sysadmins of the PubSub service
        String property = JiveGlobals.getProperty("xmpp.pubsub.sysadmin.jid");
        String[] jids;
        if (property != null) {
            jids = property.split(",");
Gaston Dombiak's avatar
Gaston Dombiak committed
367 368
            for (String jid : jids) {
                sysadmins.add(jid.trim().toLowerCase());
Matt Tucker's avatar
Matt Tucker committed
369 370
            }
        }
371
        nodeCreationRestricted = JiveGlobals.getBooleanProperty("xmpp.pubsub.create.anyone", false);
Matt Tucker's avatar
Matt Tucker committed
372 373 374 375
        // Load the list of JIDs that are allowed to create nodes
        property = JiveGlobals.getProperty("xmpp.pubsub.create.jid");
        if (property != null) {
            jids = property.split(",");
Gaston Dombiak's avatar
Gaston Dombiak committed
376 377
            for (String jid : jids) {
                allowedToCreate.add(jid.trim().toLowerCase());
Matt Tucker's avatar
Matt Tucker committed
378 379 380
            }
        }

381 382
        multipleSubscriptionsEnabled = JiveGlobals.getBooleanProperty("xmpp.pubsub.multiple-subscriptions", true);

Matt Tucker's avatar
Matt Tucker committed
383 384 385
        routingTable = server.getRoutingTable();
        router = server.getPacketRouter();

386
        engine = new PubSubEngine(server.getPacketRouter());
Matt Tucker's avatar
Matt Tucker committed
387 388 389 390 391 392 393 394

        // Load default configuration for leaf nodes
        leafDefaultConfiguration = PubSubPersistenceManager.loadDefaultConfiguration(this, true);
        if (leafDefaultConfiguration == null) {
            // Create and save default configuration for leaf nodes;
            leafDefaultConfiguration = new DefaultNodeConfiguration(true);
            leafDefaultConfiguration.setAccessModel(AccessModel.open);
            leafDefaultConfiguration.setPublisherModel(PublisherModel.publishers);
Gaston Dombiak's avatar
Gaston Dombiak committed
395 396 397 398 399 400 401 402 403 404 405
            leafDefaultConfiguration.setDeliverPayloads(true);
            leafDefaultConfiguration.setLanguage("English");
            leafDefaultConfiguration.setMaxPayloadSize(5120);
            leafDefaultConfiguration.setNotifyConfigChanges(true);
            leafDefaultConfiguration.setNotifyDelete(true);
            leafDefaultConfiguration.setNotifyRetract(true);
            leafDefaultConfiguration.setPersistPublishedItems(false);
            leafDefaultConfiguration.setMaxPublishedItems(-1);
            leafDefaultConfiguration.setPresenceBasedDelivery(false);
            leafDefaultConfiguration.setSendItemSubscribe(true);
            leafDefaultConfiguration.setSubscriptionEnabled(true);
Matt Tucker's avatar
Matt Tucker committed
406 407 408 409 410 411 412 413 414 415 416
            leafDefaultConfiguration.setReplyPolicy(null);
            PubSubPersistenceManager.createDefaultConfiguration(this, leafDefaultConfiguration);
        }
        // Load default configuration for collection nodes
        collectionDefaultConfiguration =
                PubSubPersistenceManager.loadDefaultConfiguration(this, false);
        if (collectionDefaultConfiguration == null ) {
            // Create and save default configuration for collection nodes;
            collectionDefaultConfiguration = new DefaultNodeConfiguration(false);
            collectionDefaultConfiguration.setAccessModel(AccessModel.open);
            collectionDefaultConfiguration.setPublisherModel(PublisherModel.publishers);
Gaston Dombiak's avatar
Gaston Dombiak committed
417 418 419 420 421 422 423 424 425
            collectionDefaultConfiguration.setDeliverPayloads(false);
            collectionDefaultConfiguration.setLanguage("English");
            collectionDefaultConfiguration.setNotifyConfigChanges(true);
            collectionDefaultConfiguration.setNotifyDelete(true);
            collectionDefaultConfiguration.setNotifyRetract(true);
            collectionDefaultConfiguration.setPresenceBasedDelivery(false);
            collectionDefaultConfiguration.setSubscriptionEnabled(true);
            collectionDefaultConfiguration.setReplyPolicy(null);
            collectionDefaultConfiguration
Matt Tucker's avatar
Matt Tucker committed
426
                    .setAssociationPolicy(CollectionNode.LeafNodeAssociationPolicy.all);
Gaston Dombiak's avatar
Gaston Dombiak committed
427
            collectionDefaultConfiguration.setMaxLeafNodes(-1);
Matt Tucker's avatar
Matt Tucker committed
428 429 430 431 432 433 434 435 436 437 438
            PubSubPersistenceManager
                    .createDefaultConfiguration(this, collectionDefaultConfiguration);
        }

        // Load nodes to memory
        PubSubPersistenceManager.loadNodes(this);
        // Ensure that we have a root collection node
        String rootNodeID = JiveGlobals.getProperty("xmpp.pubsub.root.nodeID", "");
        if (nodes.isEmpty()) {
            // Create root collection node
            String creator = JiveGlobals.getProperty("xmpp.pubsub.root.creator");
439 440
//            JID creatorJID = creator != null ? new JID(creator) : server.getAdmins().iterator().next();
            JID creatorJID = creator != null ? new JID(creator) : new JID(server.getServerInfo().getXMPPDomain());
Matt Tucker's avatar
Matt Tucker committed
441 442 443 444 445 446 447 448 449
            rootCollectionNode = new CollectionNode(this, null, rootNodeID, creatorJID);
            // Add the creator as the node owner
            rootCollectionNode.addOwner(creatorJID);
            // Save new root node
            rootCollectionNode.saveToDB();
        }
        else {
            rootCollectionNode = (CollectionNode) getNode(rootNodeID);
        }
450 451
        // Listen to cluster events
        ClusterManager.addListener(this);
Matt Tucker's avatar
Matt Tucker committed
452 453 454
    }

    public void start() {
455 456 457 458
        // Check that the service is enabled
        if (!isServiceEnabled()) {
            return;
        }
Matt Tucker's avatar
Matt Tucker committed
459 460
        super.start();
        // Add the route to this service
Gaston Dombiak's avatar
Gaston Dombiak committed
461
        routingTable.addComponentRoute(getAddress(), this);
Matt Tucker's avatar
Matt Tucker committed
462
        // Start the pubsub engine
463
        engine.start(this);
Matt Tucker's avatar
Matt Tucker committed
464 465 466 467 468 469 470 471 472
        ArrayList<String> params = new ArrayList<String>();
        params.clear();
        params.add(getServiceDomain());
        Log.info(LocaleUtils.getLocalizedString("startup.starting.pubsub", params));
    }

    public void stop() {
        super.stop();
        // Remove the route to this service
Gaston Dombiak's avatar
Gaston Dombiak committed
473
        routingTable.removeComponentRoute(getAddress());
Matt Tucker's avatar
Matt Tucker committed
474 475
        // Stop the pubsub engine. This will gives us the chance to
        // save queued items to the database.
476
        engine.shutdown(this);
Matt Tucker's avatar
Matt Tucker committed
477 478
    }

479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499
    private void enableService(boolean enabled) {
        if (serviceEnabled == enabled) {
            // Do nothing if the service status has not changed
            return;
        }
        XMPPServer server = XMPPServer.getInstance();
        if (!enabled) {
            // Disable disco information
            server.getIQDiscoItemsHandler().removeServerItemsProvider(this);
            // Stop the service/module
            stop();
        }
        serviceEnabled = enabled;
        if (enabled) {
            // Start the service/module
            start();
            // Enable disco information
            server.getIQDiscoItemsHandler().addServerItemsProvider(this);
        }
    }

500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
    public void setServiceEnabled(boolean enabled) {
        // Enable/disable the service
        enableService(enabled);
        // Store the new setting
        JiveGlobals.setProperty("xmpp.pubsub.enabled", Boolean.toString(enabled));
    }

    /**
     * Returns true if the service is available. Use {@link #setServiceEnabled(boolean)} to
     * enable or disable the service.
     *
     * @return true if the MUC service is available.
     */
    public boolean isServiceEnabled() {
        return serviceEnabled;
    }

517
    public void joinedCluster() {
518 519 520 521
        // Disable the service until we know that we are the senior cluster member
        enableService(false);
    }

522
    public void joinedCluster(byte[] nodeID) {
523 524 525
        // Do nothing
    }

526 527 528 529 530
    public void leftCluster() {
        // Offer the service when not running in a cluster
        enableService(true);
    }

531 532 533 534
    public void leftCluster(byte[] nodeID) {
        // Do nothing
    }

535 536 537 538 539
    public void markedAsSeniorClusterMember() {
        // Offer the service since we are the senior cluster member
        enableService(true);
    }

Matt Tucker's avatar
Matt Tucker committed
540
    public Iterator<DiscoServerItem> getItems() {
541 542 543 544
        // Check if the service is disabled. Info is not available when disabled.
        if (!isServiceEnabled()) {
            return null;
        }
Matt Tucker's avatar
Matt Tucker committed
545
        ArrayList<DiscoServerItem> items = new ArrayList<DiscoServerItem>();
546 547 548 549 550
		final DiscoServerItem item = new DiscoServerItem(new JID(
			getServiceDomain()), "Publish-Subscribe service", null, null, this,
			this);
		items.add(item);
		return items.iterator();
Matt Tucker's avatar
Matt Tucker committed
551 552 553 554 555 556 557 558 559
    }

    public Iterator<Element> getIdentities(String name, String node, JID senderJID) {
        ArrayList<Element> identities = new ArrayList<Element>();
        if (name == null && node == null) {
            // Answer the identity of the PubSub service
            Element identity = DocumentHelper.createElement("identity");
            identity.addAttribute("category", "pubsub");
            identity.addAttribute("name", "Publish-Subscribe service");
Matt Tucker's avatar
Matt Tucker committed
560
            identity.addAttribute("type", "service");
Matt Tucker's avatar
Matt Tucker committed
561 562 563

            identities.add(identity);
        }
Matt Tucker's avatar
Matt Tucker committed
564
        else if (name == null) {
Matt Tucker's avatar
Matt Tucker committed
565 566
            // Answer the identity of a given node
            Node pubNode = getNode(node);
Matt Tucker's avatar
Matt Tucker committed
567
            if (canDiscoverNode(pubNode)) {
Matt Tucker's avatar
Matt Tucker committed
568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
                Element identity = DocumentHelper.createElement("identity");
                identity.addAttribute("category", "pubsub");
                identity.addAttribute("type", pubNode.isCollectionNode() ? "collection" : "leaf");

                identities.add(identity);
            }
        }
        return identities.iterator();
    }

    public Iterator<String> getFeatures(String name, String node, JID senderJID) {
        ArrayList<String> features = new ArrayList<String>();
        if (name == null && node == null) {
            // Answer the features of the PubSub service
            features.add("http://jabber.org/protocol/pubsub");
Matt Tucker's avatar
Matt Tucker committed
583 584 585 586
            if (isCollectionNodesSupported()) {
                // Collection nodes are supported
                features.add("http://jabber.org/protocol/pubsub#collections");
            }
Matt Tucker's avatar
Matt Tucker committed
587 588
            // Configuration of node options is supported
            features.add("http://jabber.org/protocol/pubsub#config-node");
Matt Tucker's avatar
Matt Tucker committed
589 590
            // Simultaneous creation and configuration of nodes is supported
            features.add("http://jabber.org/protocol/pubsub#create-and-configure");
Matt Tucker's avatar
Matt Tucker committed
591 592 593 594
            // Creation of nodes is supported
            features.add("http://jabber.org/protocol/pubsub#create-nodes");
            // Deletion of nodes is supported
            features.add("http://jabber.org/protocol/pubsub#delete-nodes");
Matt Tucker's avatar
Matt Tucker committed
595 596
            // Retrieval of pending subscription approvals is supported
            features.add("http://jabber.org/protocol/pubsub#get-pending");
Matt Tucker's avatar
Matt Tucker committed
597 598 599 600
            if (isInstantNodeSupported()) {
                // Creation of instant nodes is supported
                features.add("http://jabber.org/protocol/pubsub#instant-nodes");
            }
Matt Tucker's avatar
Matt Tucker committed
601 602
            // Publishers may specify item identifiers
            features.add("http://jabber.org/protocol/pubsub#item-ids");
Matt Tucker's avatar
Matt Tucker committed
603
            // TODO Time-based subscriptions are supported (clean up thread missing, rest is supported)
Matt Tucker's avatar
Matt Tucker committed
604 605 606
            //features.add("http://jabber.org/protocol/pubsub#leased-subscription");
            // Node meta-data is supported
            features.add("http://jabber.org/protocol/pubsub#meta-data");
Matt Tucker's avatar
Matt Tucker committed
607 608
            // Node owners may modify affiliations
            features.add("http://jabber.org/protocol/pubsub#modify-affiliations");
609 610
            // Node owners may manage subscriptions.
            features.add("http://jabber.org/protocol/pubsub#manage-subscriptions");
Matt Tucker's avatar
Matt Tucker committed
611 612 613 614 615 616 617 618
            // A single entity may subscribe to a node multiple times
            features.add("http://jabber.org/protocol/pubsub#multi-subscribe");
            // The outcast affiliation is supported
            features.add("http://jabber.org/protocol/pubsub#outcast-affiliation");
            // Persistent items are supported
            features.add("http://jabber.org/protocol/pubsub#persistent-items");
            // Presence-based delivery of event notifications is supported
            features.add("http://jabber.org/protocol/pubsub#presence-notifications");
Matt Tucker's avatar
Matt Tucker committed
619 620
            // Publishing items is supported (note: not valid for collection nodes)
            features.add("http://jabber.org/protocol/pubsub#publish");
Matt Tucker's avatar
Matt Tucker committed
621 622 623 624 625 626 627 628
            // The publisher affiliation is supported
            features.add("http://jabber.org/protocol/pubsub#publisher-affiliation");
            // Purging of nodes is supported
            features.add("http://jabber.org/protocol/pubsub#purge-nodes");
            // Item retraction is supported
            features.add("http://jabber.org/protocol/pubsub#retract-items");
            // Retrieval of current affiliations is supported
            features.add("http://jabber.org/protocol/pubsub#retrieve-affiliations");
629 630
            // Retrieval of default node configuration is supported.
            features.add("http://jabber.org/protocol/pubsub#retrieve-default");
Matt Tucker's avatar
Matt Tucker committed
631 632
            // Item retrieval is supported
            features.add("http://jabber.org/protocol/pubsub#retrieve-items");
633 634
            // Retrieval of current subscriptions is supported.
            features.add("http://jabber.org/protocol/pubsub#retrieve-subscriptions");
Matt Tucker's avatar
Matt Tucker committed
635 636 637 638
            // Subscribing and unsubscribing are supported
            features.add("http://jabber.org/protocol/pubsub#subscribe");
            // Configuration of subscription options is supported
            features.add("http://jabber.org/protocol/pubsub#subscription-options");
Matt Tucker's avatar
Matt Tucker committed
639 640 641 642
            // Default access model for nodes created on the service
            String modelName = getDefaultNodeConfiguration(true).getAccessModel().getName();
            features.add("http://jabber.org/protocol/pubsub#default_access_model_" + modelName);

Matt Tucker's avatar
Matt Tucker committed
643
        }
Matt Tucker's avatar
Matt Tucker committed
644
        else if (name == null) {
Matt Tucker's avatar
Matt Tucker committed
645 646
            // Answer the features of a given node
            Node pubNode = getNode(node);
Matt Tucker's avatar
Matt Tucker committed
647
            if (canDiscoverNode(pubNode)) {
Matt Tucker's avatar
Matt Tucker committed
648
                // Answer the features of the PubSub service
Matt Tucker's avatar
Matt Tucker committed
649 650 651 652 653 654
                features.add("http://jabber.org/protocol/pubsub");
            }
        }
        return features.iterator();
    }

655
    public DataForm getExtendedInfo(String name, String node, JID senderJID) {
Matt Tucker's avatar
Matt Tucker committed
656 657 658
        if (name == null && node != null) {
            // Answer the extended info of a given node
            Node pubNode = getNode(node);
Matt Tucker's avatar
Matt Tucker committed
659
            if (canDiscoverNode(pubNode)) {
Matt Tucker's avatar
Matt Tucker committed
660
                // Get the metadata data form
661
                return pubNode.getMetadataForm();
Matt Tucker's avatar
Matt Tucker committed
662 663 664 665 666 667
            }
        }
        return null;
    }

    public boolean hasInfo(String name, String node, JID senderJID) {
668 669 670 671
        // Check if the service is disabled. Info is not available when disabled.
        if (!isServiceEnabled()) {
            return false;
        }
672 673
        if (name == null && node == null) {
            // We always have info about the Pubsub service
Matt Tucker's avatar
Matt Tucker committed
674 675
            return true;
        }
Matt Tucker's avatar
Matt Tucker committed
676
        else if (name == null) {
Matt Tucker's avatar
Matt Tucker committed
677 678 679 680 681 682
            // We only have info if the node exists
            return hasNode(node);
        }
        return false;
    }

683
    public Iterator<DiscoItem> getItems(String name, String node, JID senderJID) {
684 685 686 687
        // Check if the service is disabled. Info is not available when disabled.
        if (!isServiceEnabled()) {
            return null;
        }
688
        List<DiscoItem> answer = new ArrayList<DiscoItem>();
Matt Tucker's avatar
Matt Tucker committed
689
        String serviceDomain = getServiceDomain();
Matt Tucker's avatar
Matt Tucker committed
690
        if (name == null && node == null) {
Matt Tucker's avatar
Matt Tucker committed
691 692
            // Answer all first level nodes
            for (Node pubNode : rootCollectionNode.getNodes()) {
Matt Tucker's avatar
Matt Tucker committed
693
                if (canDiscoverNode(pubNode)) {
694 695 696
                	final DiscoItem item = new DiscoItem(
						new JID(serviceDomain), pubNode.getName(),
						pubNode.getNodeID(), null);
Matt Tucker's avatar
Matt Tucker committed
697 698 699 700
                    answer.add(item);
                }
            }
        }
Matt Tucker's avatar
Matt Tucker committed
701
        else if (name == null) {
Matt Tucker's avatar
Matt Tucker committed
702 703 704 705 706 707
            Node pubNode = getNode(node);
            if (pubNode != null && canDiscoverNode(pubNode)) {
                if (pubNode.isCollectionNode()) {
                    // Answer all nested nodes as items
                    for (Node nestedNode : pubNode.getNodes()) {
                        if (canDiscoverNode(nestedNode)) {
708 709
                        	final DiscoItem item = new DiscoItem(new JID(serviceDomain), nestedNode.getName(),
								nestedNode.getNodeID(), null);
Matt Tucker's avatar
Matt Tucker committed
710 711 712 713 714 715
                            answer.add(item);
                        }
                    }
                }
                else {
                    // This is a leaf node so answer the published items which exist on the service
716
                    for (PublishedItem publishedItem : pubNode.getPublishedItems()) {
717
                        answer.add(new DiscoItem(new JID(serviceDomain), publishedItem.getID(), null, null));
Matt Tucker's avatar
Matt Tucker committed
718 719 720
                    }
                }
            }
Matt Tucker's avatar
Matt Tucker committed
721 722 723 724
            else {
                // Answer null to indicate that specified item was not found
                return null;
            }
Matt Tucker's avatar
Matt Tucker committed
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772
        }
        return answer.iterator();
    }

    public void broadcast(Node node, Message message, Collection<JID> jids) {
        // TODO Possibly use a thread pool for sending packets (based on the jids size)
        message.setFrom(getAddress());
        for (JID jid : jids) {
            message.setTo(jid);
            message.setID(
                    node.getNodeID() + "__" + jid.toBareJID() + "__" + StringUtils.randomString(5));
            router.route(message);
        }
    }

    public void send(Packet packet) {
        router.route(packet);
    }

    public void sendNotification(Node node, Message message, JID jid) {
        message.setFrom(getAddress());
        message.setTo(jid);
        message.setID(
                node.getNodeID() + "__" + jid.toBareJID() + "__" + StringUtils.randomString(5));
        router.route(message);
    }

    public Node getNode(String nodeID) {
        return nodes.get(nodeID);
    }

    public Collection<Node> getNodes() {
        return nodes.values();
    }

    private boolean hasNode(String nodeID) {
        return getNode(nodeID) != null;
    }

    public void addNode(Node node) {
        nodes.put(node.getNodeID(), node);
    }

    public void removeNode(String nodeID) {
        nodes.remove(nodeID);
    }

    private boolean canDiscoverNode(Node pubNode) {
Matt Tucker's avatar
Matt Tucker committed
773
        return true;
Matt Tucker's avatar
Matt Tucker committed
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
    }

    /**
     * Converts an array to a comma-delimitted String.
     *
     * @param array the array.
     * @return a comma delimtted String of the array values.
     */
    private static String fromArray(String [] array) {
        StringBuilder buf = new StringBuilder();
        for (int i=0; i<array.length; i++) {
            buf.append(array[i]);
            if (i != array.length-1) {
                buf.append(",");
            }
        }
        return buf.toString();
    }
792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827

    public Map<String, Map<String, String>> getBarePresences() {
        return barePresences;
    }

    public Queue<PublishedItem> getItemsToAdd() {
        return itemsToAdd;
    }

    public Queue<PublishedItem> getItemsToDelete() {
        return itemsToDelete;
    }

    public AdHocCommandManager getManager() {
        return manager;
    }

    public PublishedItemTask getPublishedItemTask() {
        return publishedItemTask;
    }

    public void setPublishedItemTask(PublishedItemTask task) {
        publishedItemTask = task;
    }

    public Timer getTimer() {
        return timer;
    }
    
    public int getItemsTaskTimeout() {
        return items_task_timeout;
    }

    public void setItemsTaskTimeout(int timeout) {
        items_task_timeout = timeout;
    }
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850

    public void propertySet(String property, Map<String, Object> params) {
        if (property.equals("xmpp.pubsub.enabled")) {
            boolean enabled = Boolean.parseBoolean((String)params.get("value"));
            // Enable/disable the service
            enableService(enabled);
        }
    }

    public void propertyDeleted(String property, Map<String, Object> params) {
        if (property.equals("xmpp.pubsub.enabled")) {
            // Enable/disable the service
            enableService(true);
        }
    }

    public void xmlPropertySet(String property, Map<String, Object> params) {
        // Do nothing
    }

    public void xmlPropertyDeleted(String property, Map<String, Object> params) {
        // Do nothing
    }
Matt Tucker's avatar
Matt Tucker committed
851
}