/*
* Copyright 2012 buddycloud
*
* 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 com.buddycloud.mediaserver.xmpp.pubsub;
import com.buddycloud.mediaserver.business.util.PubSubManagerFactory;
import com.buddycloud.mediaserver.commons.MediaServerConfiguration;
import com.buddycloud.mediaserver.xmpp.pubsub.capabilities.CapabilitiesDecorator;
import com.buddycloud.mediaserver.xmpp.util.AccessModel;
import com.buddycloud.mediaserver.xmpp.util.ConfigurationForm;
import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.packet.DiscoverInfo;
import org.jivesoftware.smackx.packet.DiscoverItems;
import org.jivesoftware.smackx.packet.RSMSet;
import org.jivesoftware.smackx.pubsub.Affiliation;
import org.jivesoftware.smackx.pubsub.AffiliationsExtension;
import org.jivesoftware.smackx.pubsub.ConfigureForm;
import org.jivesoftware.smackx.pubsub.Node;
import org.jivesoftware.smackx.pubsub.NodeExtension;
import org.jivesoftware.smackx.pubsub.PubSubElementType;
import org.jivesoftware.smackx.pubsub.PubSubManager;
import org.jivesoftware.smackx.pubsub.packet.PubSub;
import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* XMPP client that handles PubSub (XEP-0060) operations.
*
* @see <a href="http://xmpp.org/extensions/xep-0060.html">XEP-0060</a>
* @author Rodrigo Duarte Sousa - rodrigodsousa@gmail.com
*
*/
public class PubSubClient {
private static final String IDENTITY_CATEGORY = "pubsub";
private static final String IDENTITY_TYPE = "channels";
private static Logger LOGGER = LoggerFactory.getLogger(PubSubClient.class);
private Map<String, PubSubManager> pubSubManagersCache = new HashMap<String, PubSubManager>();
private Map<String, String> serversCache = new HashMap<String, String>();
private Connection connection;
private Properties configuration;
private PubSubManagerFactory pubsubManagerFactory;
public PubSubClient(Connection connection, Properties configuration,
PubSubManagerFactory factory) {
this.connection = connection;
this.configuration = configuration;
if (null != factory) {
setPubSubManagerFactory(factory);
}
init();
}
public PubSubClient(Connection connection, Properties configuration) {
this(connection, configuration, null);
}
private PubSubManagerFactory getPubSubManagerFactory() {
if (null == pubsubManagerFactory) {
pubsubManagerFactory = new PubSubManagerFactory(connection);
}
return pubsubManagerFactory;
}
public void setPubSubManagerFactory(PubSubManagerFactory factory) {
this.pubsubManagerFactory = factory;
}
private void init() {
Object affiliationsProvider = ProviderManager.getInstance()
.getExtensionProvider(
PubSubElementType.AFFILIATIONS.getElementName(),
PubSubNamespace.BASIC.getXmlns());
ProviderManager.getInstance().addExtensionProvider(
PubSubElementType.AFFILIATIONS.getElementName(),
PubSubNamespace.OWNER.getXmlns(), affiliationsProvider);
Object affiliationProvider = ProviderManager.getInstance()
.getExtensionProvider("affiliation",
PubSubNamespace.BASIC.getXmlns());
ProviderManager.getInstance().addExtensionProvider("affiliation",
PubSubNamespace.OWNER.getXmlns(), affiliationProvider);
getChannelServerAddress(configuration
.getProperty(MediaServerConfiguration.XMPP_CONNECTION_SERVICENAME));
}
private Node getNode(String entityId) {
JID entityJID = new JID(entityId);
String serverAddress = getChannelServerAddress(entityJID.getDomain());
Node node = null;
if (serverAddress != null) {
PubSubManager manager = getPubSubManagerFactory().create(
serverAddress);
if (manager == null) {
manager = new PubSubManager(connection, serverAddress);
pubSubManagersCache.put(serverAddress, manager);
}
try {
LOGGER.debug("Getting " + entityId
+ " node at channel server [" + serverAddress + "]");
node = manager.getNode("/user/" + entityId + "/posts");
} catch (XMPPException e) {
LOGGER.error("Error while getting " + entityId + "node", e);
}
}
return node;
}
private boolean isChannelServerIdentity(DiscoverInfo.Identity identity) {
return identity.getCategory().equals(IDENTITY_CATEGORY)
&& identity.getType().equals(IDENTITY_TYPE);
}
private String discoverDomainServer(String domain) {
ServiceDiscoveryManager discoManager = ServiceDiscoveryManager
.getInstanceFor(connection);
PubSubManager pubSubManager = getPubSubManagerFactory().create(domain);
DiscoverItems discoverItems;
try {
LOGGER.debug("Discover nodes for domain [" + domain + "]");
discoverItems = pubSubManager.discoverNodes(null);
} catch (XMPPException e) {
LOGGER.error("Error while trying to fetch domain [" + domain
+ "] node", e);
return null;
}
Iterator<DiscoverItems.Item> items = discoverItems.getItems();
while (items.hasNext()) {
String entityID = items.next().getEntityID();
DiscoverInfo discoverInfo;
try {
LOGGER.debug("Discover identities for entity [" + entityID
+ "]");
discoverInfo = discoManager.discoverInfo(entityID);
} catch (XMPPException e) {
LOGGER.error("Error while trying to fetch [" + entityID
+ "] identities");
continue;
}
Iterator<DiscoverInfo.Identity> identities = discoverInfo
.getIdentities();
while (identities.hasNext()) {
if (isChannelServerIdentity(identities.next())) {
return entityID;
}
}
}
return null;
}
private String getChannelServerAddress(String domain) {
String serverAddress = serversCache.get(domain);
if (serverAddress == null) {
LOGGER.debug("Server cache doesn't contains the channel server for domain ["
+ domain + "]");
serverAddress = discoverDomainServer(domain);
if (serverAddress != null) {
LOGGER.debug("Channel server for domain [" + domain
+ "] discovered: " + serverAddress);
serversCache.put(domain, serverAddress);
}
}
return serverAddress;
}
private Affiliation getAffiliation(Node node, String userBareJID)
throws XMPPException {
PubSub request = node.createPubsubPacket(Type.GET, new NodeExtension(
PubSubElementType.AFFILIATIONS, node.getId()),
PubSubNamespace.OWNER);
int itemCount = 0;
while (true) {
PubSub reply = (PubSub) node.sendPubsubPacket(Type.GET, request);
LOGGER.debug("Affiliations reply " + reply.toXML());
AffiliationsExtension subElem = (AffiliationsExtension) reply
.getExtension(
PubSubElementType.AFFILIATIONS.getElementName(),
PubSubNamespace.BASIC.getXmlns());
List<Affiliation> affiliations = subElem.getAffiliations();
for (Affiliation affiliation : affiliations) {
if (affiliation.getNodeId().equals(userBareJID)) {
return affiliation;
}
}
itemCount += affiliations.size();
if (reply.getRsmSet() == null
|| itemCount == reply.getRsmSet().getCount()) {
break;
}
RSMSet rsmSet = new RSMSet();
rsmSet.setAfter(reply.getRsmSet().getLast());
request.setRsmSet(rsmSet);
}
return null;
}
/**
* Verifies if an user has the desired capability (member, moderator, owner
* or publisher).
*
* @param userJID
* the user Jabber Id.
* @param entityId
* channel id.
* @param capability
* decorator that represents the desired capabilities.
* @return if the user has the desired capabilities.
*/
public boolean matchUserCapability(String userJID, String entityId,
CapabilitiesDecorator capability) {
String userBareJID = new JID(userJID).toBareJID();
// Workaround for #86 (The channel's owner is sometimes not part of
// <affiliations/>) buddycloud-server issue
if (userBareJID.equals(entityId)) {
return true;
}
Node node = getNode(entityId);
if (node == null) {
return false;
}
Affiliation affiliation;
try {
LOGGER.debug("Getting " + userBareJID
+ " affiliation for node [" + node.getId() + "]");
affiliation = getAffiliation(node, userBareJID);
} catch (XMPPException e) {
LOGGER.warn("Could not read node '" + node.getId()
+ " affiliation for '" + userBareJID + "'", e);
return false;
}
if (null == affiliation) {
LOGGER.debug("User " + userBareJID + " has no affiliation to node " + node.getId());
return false;
}
LOGGER.debug(userBareJID + " affiliation: " + affiliation.getType().toString());
return capability.isUserAllowed(affiliation.getType()
.toString());
}
/**
* Verifies if a channel is public.
*
* @param entityId
* channel to be verified.
* @return if
* @param entityId
* is public.
*/
public boolean isChannelPublic(String entityId) {
Node node = getNode(entityId);
if (node != null) {
try {
ConfigureForm config = node.getNodeConfiguration();
ConfigurationForm form = new ConfigurationForm(config);
return form.getBuddycloudAccessModel().equals(AccessModel.open);
} catch (XMPPException e) {
LOGGER.warn("Could not get node '" + node.getId() + "' "
+ "access model", e);
}
}
return false;
}
}