001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2015 Florian Schmaus
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 */
017
018package org.jivesoftware.smackx.pep;
019
020import java.util.Map;
021import java.util.Set;
022import java.util.WeakHashMap;
023import java.util.concurrent.CopyOnWriteArraySet;
024
025import org.jivesoftware.smack.Manager;
026import org.jivesoftware.smack.SmackException.NoResponseException;
027import org.jivesoftware.smack.StanzaListener;
028import org.jivesoftware.smack.SmackException.NotConnectedException;
029import org.jivesoftware.smack.XMPPConnection;
030import org.jivesoftware.smack.XMPPException.XMPPErrorException;
031import org.jivesoftware.smack.filter.AndFilter;
032import org.jivesoftware.smack.filter.StanzaFilter;
033import org.jivesoftware.smack.filter.jidtype.FromJidTypeFilter;
034import org.jivesoftware.smack.filter.jidtype.AbstractJidTypeFilter.JidType;
035import org.jivesoftware.smack.packet.Message;
036import org.jivesoftware.smack.packet.Stanza;
037import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
038import org.jivesoftware.smackx.pubsub.EventElement;
039import org.jivesoftware.smackx.pubsub.Item;
040import org.jivesoftware.smackx.pubsub.LeafNode;
041import org.jivesoftware.smackx.pubsub.PubSubFeature;
042import org.jivesoftware.smackx.pubsub.PubSubManager;
043import org.jivesoftware.smackx.pubsub.filter.EventExtensionFilter;
044import org.jxmpp.jid.BareJid;
045import org.jxmpp.jid.EntityBareJid;
046
047/**
048 *
049 * Manages Personal Event Publishing (XEP-163). A PEPManager provides a high level access to
050 * pubsub personal events. It also provides an easy way
051 * to hook up custom logic when events are received from another XMPP client through PEPListeners.
052 * 
053 * Use example:
054 * 
055 * <pre>
056 *   PEPManager pepManager = new PEPManager(smackConnection);
057 *   pepManager.addPEPListener(new PEPListener() {
058 *       public void eventReceived(EntityBareJid from, EventElement event, Message message) {
059 *           LOGGER.debug("Event received: " + event);
060 *       }
061 *   });
062 * </pre>
063 * 
064 * @author Jeff Williams
065 * @author Florian Schmaus
066 */
067public final class PEPManager extends Manager {
068
069    private static final Map<XMPPConnection, PEPManager> INSTANCES = new WeakHashMap<>();
070
071    public static synchronized PEPManager getInstanceFor(XMPPConnection connection) {
072        PEPManager pepManager = INSTANCES.get(connection);
073        if (pepManager == null) {
074            pepManager = new PEPManager(connection);
075            INSTANCES.put(connection, pepManager);
076        }
077        return pepManager;
078    }
079
080    private static final StanzaFilter FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER = new AndFilter(
081            new FromJidTypeFilter(JidType.BareJid),
082            EventExtensionFilter.INSTANCE);
083
084    private final Set<PEPListener> pepListeners = new CopyOnWriteArraySet<>();
085
086    /**
087     * Creates a new PEP exchange manager.
088     *
089     * @param connection an XMPPConnection which is used to send and receive messages.
090     */
091    private PEPManager(XMPPConnection connection) {
092        super(connection);
093        StanzaListener packetListener = new StanzaListener() {
094            @Override
095            public void processStanza(Stanza stanza) {
096                Message message = (Message) stanza;
097                EventElement event = EventElement.from(stanza);
098                assert(event != null);
099                EntityBareJid from = message.getFrom().asEntityBareJidIfPossible();
100                assert(from != null);
101                for (PEPListener listener : pepListeners) {
102                    listener.eventReceived(from, event, message);
103                }
104            }
105        };
106        // TODO Add filter to check if from supports PubSub as per xep163 2 2.4
107        connection.addSyncStanzaListener(packetListener, FROM_BARE_JID_WITH_EVENT_EXTENSION_FILTER);
108    }
109
110    /**
111     * Adds a listener to PEPs. The listener will be fired anytime PEP events
112     * are received from remote XMPP clients.
113     *
114     * @param pepListener a roster exchange listener.
115     */
116    public boolean addPEPListener(PEPListener pepListener) {
117        return pepListeners.add(pepListener);
118    }
119
120    /**
121     * Removes a listener from PEP events.
122     *
123     * @param pepListener a roster exchange listener.
124     */
125    public boolean removePEPListener(PEPListener pepListener) {
126        return pepListeners.remove(pepListener);
127    }
128
129    /**
130     * Publish an event.
131     * 
132     * @param item the item to publish.
133     * @param node the node to publish on.
134     * @throws NotConnectedException
135     * @throws InterruptedException
136     * @throws XMPPErrorException
137     * @throws NoResponseException
138     */
139    public void publish(Item item, String node) throws NotConnectedException, InterruptedException,
140                    NoResponseException, XMPPErrorException {
141        XMPPConnection connection = connection();
142        PubSubManager pubSubManager = PubSubManager.getInstance(connection, connection.getUser().asEntityBareJid());
143        LeafNode pubSubNode = pubSubManager.getNode(node);
144        pubSubNode.publish(item);
145    }
146
147    /**
148     * XEP-163 5.
149     */
150    private static final PubSubFeature[] REQUIRED_FEATURES = new PubSubFeature[] {
151        // @formatter:off
152        PubSubFeature.auto_create,
153        PubSubFeature.auto_subscribe,
154        PubSubFeature.filtered_notifications,
155        // @formatter:on
156    };
157
158    public boolean isSupported() throws NoResponseException, XMPPErrorException,
159                    NotConnectedException, InterruptedException {
160        XMPPConnection connection = connection();
161        ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
162        BareJid localBareJid = connection.getUser().asBareJid();
163        return serviceDiscoveryManager.supportsFeatures(localBareJid, REQUIRED_FEATURES);
164    }
165}