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