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 */ 119 public boolean addPEPListener(PEPListener pepListener) { 120 return pepListeners.add(pepListener); 121 } 122 123 /** 124 * Removes a listener from PEP events. 125 * 126 * @param pepListener a roster exchange listener. 127 */ 128 public boolean removePEPListener(PEPListener pepListener) { 129 return pepListeners.remove(pepListener); 130 } 131 132 /** 133 * Publish an event. 134 * 135 * @param item the item to publish. 136 * @param node the node to publish on. 137 * @throws NotConnectedException 138 * @throws InterruptedException 139 * @throws XMPPErrorException 140 * @throws NoResponseException 141 * @throws NotAPubSubNodeException 142 */ 143 public void publish(Item item, String node) throws NotConnectedException, InterruptedException, 144 NoResponseException, XMPPErrorException, NotAPubSubNodeException { 145 XMPPConnection connection = connection(); 146 PubSubManager pubSubManager = PubSubManager.getInstance(connection, connection.getUser().asEntityBareJid()); 147 LeafNode pubSubNode = pubSubManager.getNode(node); 148 pubSubNode.publish(item); 149 } 150 151 /** 152 * XEP-163 5. 153 */ 154 private static final PubSubFeature[] REQUIRED_FEATURES = new PubSubFeature[] { 155 // @formatter:off 156 PubSubFeature.auto_create, 157 PubSubFeature.auto_subscribe, 158 PubSubFeature.filtered_notifications, 159 // @formatter:on 160 }; 161 162 public boolean isSupported() throws NoResponseException, XMPPErrorException, 163 NotConnectedException, InterruptedException { 164 XMPPConnection connection = connection(); 165 ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); 166 BareJid localBareJid = connection.getUser().asBareJid(); 167 return serviceDiscoveryManager.supportsFeatures(localBareJid, REQUIRED_FEATURES); 168 } 169}