Commit 870f8b53 authored by Armando Jagucki's avatar Armando Jagucki Committed by ajagucki

Implementation of Entity Capabilities (XEP-0115) including server side...

Implementation of Entity Capabilities (XEP-0115) including server side cacheing and utilization by PEP. JM-1132

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@9434 b35dd754-fafc-0310-a699-88a17e54d16e
parent 1d760538
......@@ -12,6 +12,7 @@
package org.jivesoftware.openfire;
import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.entitycaps.EntityCapabilitiesManager;
import org.jivesoftware.openfire.handler.PresenceSubscribeHandler;
import org.jivesoftware.openfire.handler.PresenceUpdateHandler;
import org.jivesoftware.openfire.interceptor.InterceptorManager;
......@@ -39,6 +40,7 @@ public class PresenceRouter extends BasicModule {
private PresenceSubscribeHandler subscribeHandler;
private PresenceManager presenceManager;
private SessionManager sessionManager;
private EntityCapabilitiesManager entityCapsManager;
private MulticastRouter multicastRouter;
private String serverName;
......@@ -127,12 +129,14 @@ public class PresenceRouter extends BasicModule {
"".equals(recipientJID.getDomain()) || (recipientJID.getNode() == null &&
recipientJID.getResource() == null) &&
serverName.equals(recipientJID.getDomain())) {
entityCapsManager.process(packet);
updateHandler.process(packet);
}
else {
// Trigger events for presences of remote users
if (senderJID != null && !serverName.equals(senderJID.getDomain()) &&
!routingTable.hasComponentRoute(senderJID)) {
entityCapsManager.process(packet);
if (type == null) {
// Remote user has become available
RemotePresenceEventDispatcher.remoteUserAvailable(packet);
......@@ -203,6 +207,7 @@ public class PresenceRouter extends BasicModule {
presenceManager = server.getPresenceManager();
multicastRouter = server.getMulticastRouter();
sessionManager = server.getSessionManager();
entityCapsManager = EntityCapabilitiesManager.getInstance();
}
/**
......
/**
* $RCSfile: $
* $Revision: $
* $Date: $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.entitycaps;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.HashSet;
import java.util.Set;
import org.jivesoftware.util.cache.CacheSizes;
import org.jivesoftware.util.cache.Cacheable;
import org.jivesoftware.util.cache.ExternalizableUtil;
/**
* Contains identities and supported features describing client capabilities
* for an entity.
*
* @author Armando Jagucki
*
*/
public class EntityCapabilities implements Cacheable, Externalizable {
/**
* Identities included in these entity capabilities.
*/
private Set<String> identities = new HashSet<String>();
/**
* Features included in these entity capabilities.
*/
private Set<String> features = new HashSet<String>();
/**
* Hash string that corresponds to the entity capabilities. To be
* regenerated and used for discovering potential poisoning of entity
* capabilities information.
*/
private String verAttribute;
/**
* Adds an identity to the entity capabilities.
*
* @param identity the identity
* @return true if the entity capabilities did not already include the
* identity
*/
public boolean addIdentity(String identity) {
return identities.add(identity);
}
/**
* Adds a feature to the entity capabilities.
*
* @param feature the feature
* @return true if the entity capabilities did not already include the
* feature
*/
public boolean addFeature(String feature) {
return features.add(feature);
}
/**
* Determines whether or not a given identity is included in these entity
* capabilities.
*
* @param identity the identity
* @return true if identity is included, false if not
*/
public boolean containsIdentity(String identity) {
return identities.contains(identity);
}
/**
* Determines whether or not a given feature is included in these entity
* capabilities.
*
* @param feature the feature
* @return true if feature is included, false if not
*/
public boolean containsFeature(String feature) {
return features.contains(feature);
}
/**
* @return the verAttribute
*/
public String getVerAttribute() {
return verAttribute;
}
/**
* @param verAttribute the verAttribute to set
*/
public void setVerAttribute(String verAttribute) {
this.verAttribute = verAttribute;
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
ExternalizableUtil.getInstance().readStrings(in, identities);
ExternalizableUtil.getInstance().readStrings(in, features);
verAttribute = ExternalizableUtil.getInstance().readSafeUTF(in);
}
public void writeExternal(ObjectOutput out) throws IOException {
ExternalizableUtil.getInstance().writeStrings(out, identities);
ExternalizableUtil.getInstance().writeStrings(out, features);
ExternalizableUtil.getInstance().writeSafeUTF(out, verAttribute);
}
public int getCachedSize() {
int size = CacheSizes.sizeOfCollection(identities);
size += CacheSizes.sizeOfCollection(features);
size += CacheSizes.sizeOfString(verAttribute);
return size;
}
}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
</head>
<body>
<p>Implementation of Entity Capabilities (XEP-0115).</p>
</body>
</html>
......@@ -19,7 +19,8 @@ import org.xmpp.packet.IQ;
/**
* <p>
* An IQHandler used to implement XEP-0163: "Personal Eventing via Pubsub."
* An {@link IQHandler} used to implement XEP-0163: "Personal Eventing via Pubsub"
* Version 1.0
* </p>
*
* <p>
......
......@@ -18,6 +18,8 @@ import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.commands.AdHocCommandManager;
import org.jivesoftware.openfire.entitycaps.EntityCapabilities;
import org.jivesoftware.openfire.entitycaps.EntityCapabilitiesManager;
import org.jivesoftware.openfire.pubsub.*;
import org.jivesoftware.openfire.pubsub.models.AccessModel;
import org.jivesoftware.openfire.pubsub.models.PublisherModel;
......@@ -38,8 +40,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
/**
* A PEPService is a PubSubService for use with XEP-0163: "Personal Eventing via
* Pubsub."
* A PEPService is a {@link PubSubService} for use with XEP-0163: "Personal Eventing via
* Pubsub" Version 1.0
*
* @author Armando Jagucki
*
......@@ -105,7 +107,12 @@ public class PEPService implements PubSubService {
* Manager that keeps the list of ad-hoc commands and processing command
* requests.
*/
private AdHocCommandManager manager;
private AdHocCommandManager adHocCommandManager;
/**
* Used to handle filtered-notifications.
*/
private EntityCapabilitiesManager entityCapsManager = EntityCapabilitiesManager.getInstance();
/**
* The time to elapse between each execution of the maintenance process.
......@@ -144,8 +151,8 @@ public class PEPService implements PubSubService {
router = server.getPacketRouter();
// Initialize the ad-hoc commands manager to use for this pep service
manager = new AdHocCommandManager();
manager.addCommand(new PendingSubscriptionsCommand(this));
adHocCommandManager = new AdHocCommandManager();
adHocCommandManager.addCommand(new PendingSubscriptionsCommand(this));
// Save or delete published items from the database every 2 minutes
// starting in 2 minutes (default values)
......@@ -391,10 +398,11 @@ public class PEPService implements PubSubService {
// Check if the recipientFullJID is interested in notifications for this node.
// If the recipient has not yet requested any notification filtering, continue and send
// the notification.
Map<String, Set<String>> filteredNodesMap = XMPPServer.getInstance().getIQPEPHandler().getFilteredNodesMap();
Set<String> filteredNodesSet = filteredNodesMap.get(recipientFullJID.toString());
if (filteredNodesSet != null && !filteredNodesSet.contains(nodeID + "+notify")) {
return;
EntityCapabilities entityCaps = entityCapsManager.getEntityCapabilities(recipientFullJID);
if (entityCaps != null) {
if (!entityCaps.containsFeature(nodeID + "+notify")) {
return;
}
}
// Get the full JID of the item publisher from the node that was published to.
......@@ -534,7 +542,7 @@ public class PEPService implements PubSubService {
}
public AdHocCommandManager getManager() {
return manager;
return adHocCommandManager;
}
public PublishedItemTask getPublishedItemTask() {
......
......@@ -79,6 +79,7 @@ public class DefaultLocalCacheStrategy implements CacheFactoryStrategy {
cacheNames.put("Disco Server Features", "serverFeatures");
cacheNames.put("Disco Server Items", "serverItems");
cacheNames.put("Remote Server Configurations", "serversConfigurations");
cacheNames.put("Entity Capabilities", "entityCapabilities");
cacheProps.put("cache.fileTransfer.size", 128 * 1024l);
cacheProps.put("cache.fileTransfer.maxLifetime", 1000 * 60 * 10l);
......@@ -138,6 +139,8 @@ public class DefaultLocalCacheStrategy implements CacheFactoryStrategy {
cacheProps.put("cache.serverItems.maxLifetime", -1l);
cacheProps.put("cache.serversConfigurations.size", 128 * 1024l);
cacheProps.put("cache.serversConfigurations.maxLifetime", JiveConstants.MINUTE * 30);
cacheProps.put("cache.entityCapabilities.size", -1l);
cacheProps.put("cache.entityCapabilities.maxLifetime", JiveConstants.DAY * 2);
}
......
/**
* Copyright (C) 2004-2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.util;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Element;
import org.junit.Test;
import org.xmpp.packet.IQ;
/**
* Test cases for the EntityCapabilitiesManager class.
*
* @author Armando Jagucki
*/
public class EntityCapabilitiesManagerTest {
@Test
public void testGenerateVerHash() {
IQ iq = new IQ(IQ.Type.result);
iq.setFrom("nurse@capulet.lit/chamber");
iq.setTo("juliet@capulet.lit");
iq.setID("disco123");
Element query = iq.setChildElement("query", "http://jabber.org/protocol/disco#info");
Element identity = query.addElement("identity");
identity.addAttribute("category", "client");
identity.addAttribute("type", "pc");
Element feature = query.addElement("feature");
feature.addAttribute("var", "http://jabber.org/protocol/disco#info");
feature = query.addElement("feature");
feature.addAttribute("var", "http://jabber.org/protocol/disco#items");
feature = query.addElement("feature");
feature.addAttribute("var", "http://jabber.org/protocol/muc");
assertEquals("Generating ver Hash #1", "8RovUdtOmiAjzj+xI7SK5BCw3A8=", generateVerHash(iq));
}
@Test
public void testGenerateVerHash2() {
String S = "client/pc<http://jabber.org/protocol/disco#info<http://jabber.org/protocol/disco#items<http://jabber.org/protocol/muc<";
assertEquals("Generating ver Hash #2", "8RovUdtOmiAjzj+xI7SK5BCw3A8=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1"))));
}
@Test
public void testGenerateVerHash3() {
String S = "client/pda<http://jabber.org/protocol/geoloc<http://jabber.org/protocol/geoloc+notify<http://jabber.org/protocol/tune<http://jabber.org/protocol/tune+notify<";
assertEquals("Generating ver Hash #3", "DqGwXvV/QC6X9QrPOFAwJoDwHkk=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1"))));
}
@Test
public void testGenerateVerHash4() {
String S = "client/pc<http://jabber.org/protocol/activity<http://jabber.org/protocol/activity+notify<http://jabber.org/protocol/geoloc<http://jabber.org/protocol/geoloc+notify<http://jabber.org/protocol/muc<http://jabber.org/protocol/tune<http://jabber.org/protocol/tune+notify<";
assertEquals("Generating ver Hash #4", "Hm1UHUVZowSehEBlWo8lO8mPy/M=", StringUtils.encodeBase64(StringUtils.decodeHex(StringUtils.hash(S, "SHA-1"))));
}
/**
* Generates a 'ver' hash attribute.
*
* In order to help prevent poisoning of entity capabilities information,
* the value of the 'ver' attribute is generated according to the method
* outlined in XEP-0115.
*
* @param packet
* @return the generated 'ver' hash
*/
public String generateVerHash(IQ packet) {
// Initialize an empty string S.
String S = "";
/*
* Sort the service discovery identities by category and then by type
* (if it exists), formatted as 'category' '/' 'type'.
*/
List<String> discoIdentities = new ArrayList<String>();
Element query = packet.getChildElement();
Iterator identitiesIterator = query.elementIterator("identity");
if (identitiesIterator != null) {
while (identitiesIterator.hasNext()) {
Element identityElement = (Element) identitiesIterator.next();
String discoIdentity = identityElement.attributeValue("category");
discoIdentity += '/';
discoIdentity += identityElement.attributeValue("type");
discoIdentities.add(discoIdentity);
}
Collections.sort(discoIdentities);
}
/*
* For each identity, append the 'category/type' to S, followed by the
* '<' character.
*/
for (String discoIdentity : discoIdentities) {
S += discoIdentity;
S += '<';
}
// Sort the supported features.
List<String> discoFeatures = new ArrayList<String>();
Iterator featuresIterator = query.elementIterator("feature");
if (featuresIterator != null) {
while (featuresIterator.hasNext()) {
Element featureElement = (Element) featuresIterator.next();
String discoFeature = featureElement.attributeValue("var");
discoFeatures.add(discoFeature);
}
Collections.sort(discoFeatures);
}
/*
* For each feature, append the feature to S, followed by the '<'
* character.
*/
for (String discoFeature : discoFeatures) {
S += discoFeature;
S += '<';
}
/*
* Compute ver by hashing S using the SHA-1 algorithm as specified in
* RFC 3174 (with binary output) and encoding the hash using Base64 as
* specified in Section 4 of RFC 4648 (note: the Base64 output
* MUST NOT include whitespace and MUST set padding bits to zero).
*/
S = StringUtils.hash(S, "SHA-1");
S = StringUtils.encodeBase64(StringUtils.decodeHex(S));
return S;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment