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