![JAR search and dependency download from the Maven repository](/logo.png)
org.jivesoftware.openfire.pep.IQPEPHandler Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.pep;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.*;
import org.jivesoftware.openfire.event.UserEventDispatcher;
import org.jivesoftware.openfire.event.UserEventListener;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.pubsub.*;
import org.jivesoftware.openfire.pubsub.models.AccessModel;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterEventDispatcher;
import org.jivesoftware.openfire.roster.RosterEventListener;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.user.PresenceEventDispatcher;
import org.jivesoftware.openfire.user.PresenceEventListener;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
/**
*
* An {@link IQHandler} used to implement XEP-0163: "Personal Eventing via Pubsub"
* Version 1.0
*
*
*
* For each user on the server there is an associated {@link PEPService} interacting
* with a single {@link PubSubEngine} for managing the user's PEP nodes.
*
*
*
* An IQHandler can only handle one namespace in its IQHandlerInfo. However, PEP
* related packets are seen having a variety of different namespaces. Thus,
* classes like {@link IQPEPOwnerHandler} are used to forward packets having these other
* namespaces to {@link IQPEPHandler#handleIQ(IQ)}.
*
*
*
* This handler is used for the following namespaces:
*
* - http://jabber.org/protocol/pubsub
* - http://jabber.org/protocol/pubsub#owner
*
*
* @author Armando Jagucki
* @author Guus der Kinderen, [email protected]
*/
public class IQPEPHandler extends IQHandler implements ServerIdentitiesProvider, ServerFeaturesProvider,
UserIdentitiesProvider, UserItemsProvider, PresenceEventListener,
RosterEventListener, UserEventListener, DiscoInfoProvider {
private static final Logger Log = LoggerFactory.getLogger(IQPEPHandler.class);
/**
* Metadata that relates to the IQ processing capabilities of this specific {@link IQHandler}.
*/
private final IQHandlerInfo info;
private PEPServiceManager pepServiceManager = null;
/**
* The managed thread pool that will do most of the processing. The amount
* of worker threads in this pool should be kept low to avoid resource
* contention.
*/
// There's room for future improvement here. If anywhere in the future,
// Openfire allows implementations to use dedicated resource pools, we can
// significantly increase the number of worker threads in this executor. The
// bottleneck for this particular executor is the database pool. During
// startup, PEP queries the database a lot, which causes all of the
// connections in the generic database pool to be used up by this PEP
// implementation. This can cause problems in other parts of Openfire that
// depend on database access (ideally, these should get dedicated resource
// pools too).
private ExecutorService executor = null;
/**
* Constructs a new {@link IQPEPHandler} instance.
*/
public IQPEPHandler() {
super("Personal Eventing Handler");
info = new IQHandlerInfo("pubsub", "http://jabber.org/protocol/pubsub");
}
/*
* (non-Javadoc)
* @see org.jivesoftware.openfire.handler.IQHandler#initialize(org.jivesoftware.openfire.XMPPServer)
*/
@Override
public void initialize(XMPPServer server) {
super.initialize(server);
pepServiceManager = new PEPServiceManager();
}
public PEPServiceManager getServiceManager()
{
return pepServiceManager;
}
/*
* (non-Javadoc)
*
* @see org.jivesoftware.openfire.container.BasicModule#destroy()
*/
@Override
public void destroy() {
super.destroy();
}
/*
* (non-Javadoc)
*
* @see org.jivesoftware.openfire.container.BasicModule#start()
*/
@Override
public void start() {
super.start();
// start the service manager
pepServiceManager.start();
// start a new executor service
startExecutor();
// Listen to presence events to manage PEP auto-subscriptions.
PresenceEventDispatcher.addListener(this);
// Listen to roster events for PEP subscription cancelling on contact deletion.
RosterEventDispatcher.addListener(this);
// Listen to user events in order to destroy a PEP service when a user is deleted.
UserEventDispatcher.addListener(this);
}
/*
* (non-Javadoc)
*
* @see org.jivesoftware.openfire.container.BasicModule#stop()
*/
@Override
public void stop() {
super.stop();
// Remove listeners
PresenceEventDispatcher.removeListener(this);
RosterEventDispatcher.removeListener(this);
UserEventDispatcher.removeListener(this);
// stop the executor service
stopExecutor();
// stop the pepservicemananger
pepServiceManager.stop();
}
/**
* Starts a new thread pool, unless an existing one is still running.
*/
private void startExecutor() {
if (executor == null || executor.isShutdown()) {
// keep the amount of workers low! See comment that goes with the
// field named 'executor'.
Log.debug("Starting executor service...");
executor = Executors.newScheduledThreadPool(2);
}
}
/**
* Shuts down the executor by dropping all tasks from the queue. This method
* will allow the executor to finish operations on running tasks for a
* period of two seconds. After that, tasks are forcefully stopped.
*
* The order in which the various shutdown routines of the executor are
* called, is:
*
* - {@link ExecutorService#shutdown()}
* - {@link ExecutorService#awaitTermination(long, TimeUnit)} (two
* seconds)
* - {@link ExecutorService#shutdownNow()}
*
*/
private void stopExecutor() {
Log.debug("Stopping executor service...");
/*
* This method gets called as part of the Component#shutdown() routine.
* If that method gets called, the component has already been removed
* from the routing tables. We don't need to worry about new packets to
* arrive - there won't be any.
*/
executor.shutdown();
try {
if (!executor.awaitTermination(2, TimeUnit.SECONDS)) {
Log.debug("Forcing a shutdown for the executor service (after a two-second timeout has elapsed...");
executor.shutdownNow();
// Note that if any IQ request stanzas had been scheduled, they
// MUST be responded to with an error here. A list of tasks that
// have never been commenced by the executor is returned by the
// #shutdownNow() method of the ExecutorService.
}
} catch (InterruptedException e) {
// ignore, as we're shutting down anyway.
}
}
/*
* (non-Javadoc)
*
* @see org.jivesoftware.openfire.handler.IQHandler#getInfo()
*/
@Override
public IQHandlerInfo getInfo() {
return info;
}
/**
* Implements ServerIdentitiesProvider and UserIdentitiesProvider, adding
* the PEP identity to the respective disco#info results.
*/
@Override
public Iterator getIdentities() {
Element identity = DocumentHelper.createElement("identity");
identity.addAttribute("category", "pubsub");
identity.addAttribute("type", "pep");
return Collections.singleton(identity).iterator();
}
/**
* Implements ServerFeaturesProvider to include all supported XEP-0060 features
* in the server's disco#info result (as per section 4 of XEP-0163).
*/
@Override
public Iterator getFeatures() {
Iterator it = XMPPServer.getInstance().getPubSubModule().getFeatures(null, null, null);
ArrayList features = new ArrayList<>();
while (it.hasNext()) {
features.add(it.next());
}
// Auto Creation of nodes is supported in PEP
features.add("http://jabber.org/protocol/pubsub#auto-create");
return features.iterator();
}
/**
* Returns true if the PEP service is enabled in the server.
*
* @return true if the PEP service is enabled in the server.
*/
// TODO: listen for property changes to stop/start this module.
public boolean isEnabled() {
return JiveGlobals.getBooleanProperty("xmpp.pep.enabled", true);
}
// *****************************************************************
// *** Generic module management ends here. From this point on ***
// *** more specific PEP related implementation after this point ***
// *****************************************************************
/*
* (non-Javadoc)
*
* @see
* org.jivesoftware.openfire.handler.IQHandler#handleIQ(org.xmpp.packet.IQ)
*/
@Override
public IQ handleIQ(IQ packet) throws UnauthorizedException {
// Do nothing if server is not enabled
if (!isEnabled()) {
IQ reply = IQ.createResultIQ(packet);
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.service_unavailable);
return reply;
}
final JID senderJID = packet.getFrom();
if (packet.getTo() == null) {
// packet addressed to service itself (not to a node/user)
final String jidFrom = senderJID.toBareJID();
packet = packet.createCopy();
packet.setTo(jidFrom);
if (packet.getType() == IQ.Type.set) {
PEPService pepService = pepServiceManager.getPEPService(jidFrom);
// If no service exists yet for jidFrom, create one.
if (pepService == null) {
try {
pepService = pepServiceManager.create(senderJID);
} catch (IllegalArgumentException ex) {
final IQ reply = IQ.createResultIQ(packet);
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.not_allowed);
return reply;
}
// Probe presences
pepServiceManager.start(pepService);
// Those who already have presence subscriptions to jidFrom
// will now automatically be subscribed to this new
// PEPService.
try {
final RosterManager rm = XMPPServer.getInstance()
.getRosterManager();
final Roster roster = rm.getRoster(senderJID.getNode());
for (final RosterItem item : roster.getRosterItems()) {
if (item.getSubStatus() == RosterItem.SUB_BOTH
|| item.getSubStatus() == RosterItem.SUB_FROM) {
createSubscriptionToPEPService(pepService, item
.getJid(), senderJID);
}
}
} catch (UserNotFoundException e) {
// Do nothing
}
}
// If publishing a node, and the node doesn't exist, create it.
final Element childElement = packet.getChildElement();
final Element publishElement = childElement.element("publish");
if (publishElement != null) {
final String nodeID = publishElement.attributeValue("node");
// Do not allow User Avatar nodes to be created.
// TODO: Implement XEP-0084
if (nodeID.startsWith("http://www.xmpp.org/extensions/xep-0084.html")) {
IQ reply = IQ.createResultIQ(packet);
reply.setChildElement(packet.getChildElement().createCopy());
reply.setError(PacketError.Condition.feature_not_implemented);
return reply;
}
if (pepService.getNode(nodeID) == null) {
// Create the node
final JID creator = new JID(jidFrom);
final LeafNode newNode = new LeafNode(pepService, pepService.getRootCollectionNode(), nodeID, creator);
newNode.addOwner(creator);
newNode.saveToDB();
}
}
// Process with PubSub as usual.
pepServiceManager.process(pepService, packet);
} else if (packet.getType() == IQ.Type.get) {
final PEPService pepService = pepServiceManager.getPEPService(jidFrom);
if (pepService != null) {
pepServiceManager.process(pepService, packet);
} else {
// Process with PubSub using a dummyService. In the case where an IQ packet is sent to
// a user who does not have a PEP service, we wish to utilize the error reporting flow
// already present in the PubSubEngine. This gives the illusion that every user has a
// PEP service, as required by the specification.
PEPService dummyService = new PEPService(XMPPServer.getInstance(), senderJID.toBareJID());
pepServiceManager.process(dummyService, packet);
}
}
}
else if (packet.getType() == IQ.Type.get || packet.getType() == IQ.Type.set) {
// packet was addressed to a node.
final String jidTo = packet.getTo().toBareJID();
final PEPService pepService = pepServiceManager.getPEPService(jidTo);
if (pepService != null) {
pepServiceManager.process(pepService, packet);
} else {
// Process with PubSub using a dummyService. In the case where an IQ packet is sent to
// a user who does not have a PEP service, we wish to utilize the error reporting flow
// already present in the PubSubEngine. This gives the illusion that every user has a
// PEP service, as required by the specification.
PEPService dummyService = new PEPService(XMPPServer.getInstance(), senderJID.toBareJID());
pepServiceManager.process(dummyService, packet);
}
} else {
// Ignore IQ packets of type 'error' or 'result'.
return null;
}
// Other error flows were handled in pubSubEngine.process(...)
return null;
}
/**
* Generates and processes an IQ stanza that subscribes to a PEP service.
*
* @param pepService the PEP service of the owner.
* @param subscriber the JID of the entity that is subscribing to the PEP service.
* @param owner the JID of the owner of the PEP service.
*/
private void createSubscriptionToPEPService(PEPService pepService, JID subscriber, JID owner) {
// If `owner` has a PEP service, generate and process a pubsub subscription packet
// that is equivalent to: (where 'from' field is JID of subscriber and 'to' field is JID of owner)
//
//
//
//
//
//
//
// http://jabber.org/protocol/pubsub#subscribe_options
//
//
// items
//
//
// all
//
//
//
//
//
IQ subscriptionPacket = new IQ(IQ.Type.set);
subscriptionPacket.setFrom(subscriber);
subscriptionPacket.setTo(owner.toBareJID());
Element pubsubElement = subscriptionPacket.setChildElement("pubsub", "http://jabber.org/protocol/pubsub");
Element subscribeElement = pubsubElement.addElement("subscribe");
subscribeElement.addAttribute("jid", subscriber.toBareJID());
Element optionsElement = pubsubElement.addElement("options");
Element xElement = optionsElement.addElement(QName.get("x", "jabber:x:data"));
DataForm dataForm = new DataForm(xElement);
FormField formField = dataForm.addField();
formField.setVariable("FORM_TYPE");
formField.setType(FormField.Type.hidden);
formField.addValue("http://jabber.org/protocol/pubsub#subscribe_options");
formField = dataForm.addField();
formField.setVariable("pubsub#subscription_type");
formField.addValue("items");
formField = dataForm.addField();
formField.setVariable("pubsub#subscription_depth");
formField.addValue("all");
pepServiceManager.process(pepService, subscriptionPacket);
}
/**
* Cancels a subscription to a PEPService's root collection node.
*
* @param unsubscriber the JID of the subscriber whose subscription is being canceled.
* @param serviceOwner the JID of the owner of the PEP service for which the subscription is being canceled.
*/
private void cancelSubscriptionToPEPService(JID unsubscriber, JID serviceOwner) {
// Retrieve recipientJID's PEP service, if it exists.
PEPService pepService = pepServiceManager.getPEPService(serviceOwner.toBareJID());
if (pepService == null) {
return;
}
// Cancel unsubscriberJID's subscription to recipientJID's PEP service, if it exists.
CollectionNode rootNode = pepService.getRootCollectionNode();
NodeSubscription nodeSubscription = rootNode.getSubscription(unsubscriber);
if (nodeSubscription != null) {
rootNode.cancelSubscription(nodeSubscription);
}
}
/**
* Implements UserItemsProvider, adding PEP related items to a disco#items
* result.
*/
@Override
public Iterator getUserItems(String name, JID senderJID) {
ArrayList items = new ArrayList<>();
String recipientJID = XMPPServer.getInstance().createJID(name, null, true).toBareJID();
PEPService pepService = pepServiceManager.getPEPService(recipientJID);
if (pepService != null) {
CollectionNode rootNode = pepService.getRootCollectionNode();
Element defaultItem = DocumentHelper.createElement("item");
defaultItem.addAttribute("jid", recipientJID);
for (Node node : pepService.getNodes()) {
// Do not include the root node as an item element.
if (node == rootNode) {
continue;
}
AccessModel accessModel = node.getAccessModel();
if (accessModel.canAccessItems(node, senderJID, new JID(recipientJID))) {
Element item = defaultItem.createCopy();
item.addAttribute("node", node.getNodeID());
items.add(item);
}
}
}
return items.iterator();
}
@Override
public void subscribedToPresence(JID subscriberJID, JID authorizerJID) {
final PEPService pepService = pepServiceManager.getPEPService(authorizerJID.toBareJID());
if (pepService != null) {
createSubscriptionToPEPService(pepService, subscriberJID, authorizerJID);
// Delete any leaf node subscriptions the subscriber may have already
// had (since a subscription to the PEP service, and thus its leaf PEP
// nodes, would be duplicating publish notifications from previous leaf
// node subscriptions).
CollectionNode rootNode = pepService.getRootCollectionNode();
for (Node node : pepService.getNodes()) {
if (rootNode.isChildNode(node)) {
for (NodeSubscription subscription : node.getSubscriptions(subscriberJID)) {
node.cancelSubscription(subscription);
}
}
}
pepService.sendLastPublishedItems(subscriberJID);
}
}
@Override
public void unsubscribedToPresence(JID unsubscriberJID, JID recipientJID) {
cancelSubscriptionToPEPService(unsubscriberJID, recipientJID);
}
@Override
public void availableSession(ClientSession session, Presence presence) {
// Do nothing if server is not enabled
if (!isEnabled()) {
return;
}
JID newlyAvailableJID = presence.getFrom();
if (newlyAvailableJID == null) {
return;
}
final GetNotificationsOnInitialPresence task = new GetNotificationsOnInitialPresence(newlyAvailableJID);
executor.submit(task);
}
@Override
public void contactDeleted(Roster roster, RosterItem item) {
JID rosterOwner = XMPPServer.getInstance().createJID(roster.getUsername(), null);
JID deletedContact = item.getJid();
cancelSubscriptionToPEPService(deletedContact, rosterOwner);
}
@Override
public void userDeleting(User user, Map params) {
final JID bareJID = XMPPServer.getInstance().createJID(user.getUsername(), null);
final PEPService pepService = pepServiceManager.getPEPService(bareJID.toString());
if (pepService == null) {
return;
}
// Remove the user's PEP service, finally.
pepServiceManager.remove(bareJID);
}
/**
* The following functions are unimplemented required interface methods.
*/
@Override
public void unavailableSession(ClientSession session, Presence presence) {
// Do nothing
}
@Override
public void presenceChanged(ClientSession session, Presence presence) {
// Do nothing
}
@Override
public boolean addingContact(Roster roster, RosterItem item, boolean persistent) {
// Do nothing
return true;
}
@Override
public void contactAdded(Roster roster, RosterItem item) {
// Do nothing
}
@Override
public void contactUpdated(Roster roster, RosterItem item) {
// Do nothing
}
@Override
public void rosterLoaded(Roster roster) {
// Do nothing
}
@Override
public void userCreated(User user, Map params) {
// Do nothing
}
@Override
public void userModified(User user, Map params) {
// Do nothing
}
// DiscoInfoProvider
/*
* With all these, there are basically two axes of binary choice.
* Either the PEPService exists already - in which case we can defer to it,
* or else it doesn't, in which case we should use the generic engine.
*
* In either case, we might be being asked for a node, or the root. If we're
* asked for a node when the PEPService doesn't exist that's an error. Otherwise
* if we're asked for the root we can safely defer to the generic engine.
*/
@Override
public Iterator getIdentities(String name, String node, JID senderJID) {
String recipientJID = XMPPServer.getInstance().createJID(name, null, true).toBareJID();
PEPService pepService = pepServiceManager.getPEPService(recipientJID);
if (node != null && pepService != null) {
Node pubNode = pepService.getNode(node);
if (pubNode == null) return null;
// Answer the identity of a given node
Element identity = DocumentHelper.createElement("identity");
identity.addAttribute("category", "pubsub");
identity.addAttribute("type", pubNode.isCollectionNode() ? "collection" : "leaf");
List identities = new LinkedList<>();
identities.add(identity);
return identities.iterator();
} else if (node != null) {
return null;
} else {
PubSubModule pubsub = XMPPServer.getInstance().getPubSubModule();
return pubsub.getIdentities(null, null, senderJID);
}
}
@Override
public Iterator getFeatures(String name, String node, JID senderJID) {
if (node == null) {
PubSubModule pubsub = XMPPServer.getInstance().getPubSubModule();
return pubsub.getFeatures(null, null, senderJID);
} else {
List features = new LinkedList<>();
features.add("http://jabber.org/protocol/pubsub");
return features.iterator();
}
}
@Override
public DataForm getExtendedInfo(String name, String node, JID senderJID) {
String recipientJID = XMPPServer.getInstance().createJID(name, null, true).toBareJID();
PEPService pepService = pepServiceManager.getPEPService(recipientJID);
if (node != null) {
// Answer the extended info of a given node
Node pubNode = pepService.getNode(node);
// Get the metadata data form
return pubNode.getMetadataForm();
}
return null;
}
@Override
public boolean hasInfo(String name, String node, JID senderJID) {
if (node == null) return true;
String recipientJID = XMPPServer.getInstance().createJID(name, null, true).toBareJID();
PEPService pepService = pepServiceManager.getPEPService(recipientJID);
return pepService.getNode(node) != null;
}
private class GetNotificationsOnInitialPresence implements Runnable {
private final JID availableSessionJID;
public GetNotificationsOnInitialPresence(final JID availableSessionJID) {
this.availableSessionJID = availableSessionJID;
}
@Override
public void run() {
// Send the last published items for the contacts on availableSessionJID's roster.
try {
final XMPPServer server = XMPPServer.getInstance();
final Roster roster = server.getRosterManager().getRoster(availableSessionJID.getNode());
for (final RosterItem item : roster.getRosterItems()) {
if (server.isLocal(item.getJid()) && (item.getSubStatus() == RosterItem.SUB_BOTH ||
item.getSubStatus() == RosterItem.SUB_TO)) {
PEPService pepService = pepServiceManager.getPEPService(item.getJid().toBareJID());
if (pepService != null) {
pepService.sendLastPublishedItems(availableSessionJID);
}
}
}
}
catch (UserNotFoundException e) {
// Do nothing
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy