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