/*
* Jicofo, the Jitsi Conference Focus.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.impl.protocol.xmpp;
import net.java.sip.communicator.impl.protocol.jabber.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabber.*;
import org.jitsi.protocol.xmpp.*;
import org.jitsi.util.*;
import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smackx.packet.*;
import java.util.*;
/**
* XMPP protocol provider service used by Jitsi Meet focus to create anonymous
* accounts. Implemented with Smack.
*
* @author Pawel Domas
*/
public class XmppProtocolProvider
extends AbstractProtocolProviderService
{
/**
* The logger used by this class.
*/
private final static Logger logger
= Logger.getLogger(XmppProtocolProvider.class);
/**
* Active account.
*/
private final JabberAccountID jabberAccountID;
/**
* Jingle operation set.
*/
private final OperationSetJingleImpl jingleOpSet;
/**
* Current registration state.
*/
private RegistrationState registrationState
= RegistrationState.UNREGISTERED;
/**
* The XMPP connection used by this instance.
*/
private XMPPConnection connection;
/**
* Colibri operation set.
*/
private OperationSetColibriConferenceImpl colibriTools
= new OperationSetColibriConferenceImpl();
/**
* Smack connection adapter to {@link XmppConnection} used by this instance.
*/
private XmppConnectionAdapter connectionAdapter;
/**
* Jitsi service discovery manager.
*/
private ScServiceDiscoveryManager discoInfoManager;
/**
* Creates new instance of {@link XmppProtocolProvider} for given AccountID.
*
* @param accountID the <tt>JabberAccountID</tt> that will be used by new
* instance.
*/
public XmppProtocolProvider(AccountID accountID)
{
this.jabberAccountID = (JabberAccountID) accountID;
addSupportedOperationSet(
OperationSetColibriConference.class, colibriTools);
this.jingleOpSet = new OperationSetJingleImpl(this);
addSupportedOperationSet(
OperationSetJingle.class, jingleOpSet);
addSupportedOperationSet(
OperationSetMultiUserChat.class,
new OperationSetMultiUserChatImpl(this));
addSupportedOperationSet(
OperationSetJitsiMeetTools.class,
new OperationSetMeetToolsImpl());
addSupportedOperationSet(
OperationSetSimpleCaps.class,
new OpSetSimpleCapsImpl(this));
addSupportedOperationSet(
OperationSetDirectSmackXmpp.class,
new OpSetDirectSmackXmppImpl(this));
addSupportedOperationSet(
OperationSetSubscription.class,
new OpSetSubscriptionImpl(this));
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void register(SecurityAuthority securityAuthority)
throws OperationFailedException
{
String serviceName
= org.jivesoftware.smack.util.StringUtils.parseServer(
getAccountID().getUserID());
String serverAddressUserSetting
= jabberAccountID.getServerAddress();
int serverPort
= getAccountID().getAccountPropertyInt(
ProtocolProviderFactory.SERVER_PORT, 5222);
ConnectionConfiguration connConfig
= new ConnectionConfiguration(
serverAddressUserSetting, serverPort, serviceName);
connection = new XMPPConnection(connConfig);
try
{
connection.connect();
if (jabberAccountID.isAnonymousAuthUsed())
{
connection.loginAnonymously();
}
else
{
String login = jabberAccountID.getAuthorizationName();
String pass = jabberAccountID.getPassword();
String resource = jabberAccountID.getResource();
connection.login(login, pass, resource);
}
}
catch (XMPPException e)
{
throw new OperationFailedException(
"Failed to connect",
OperationFailedException.GENERAL_ERROR, e);
}
colibriTools.initialize(getConnectionAdapter());
jingleOpSet.initialize();
discoInfoManager = new ScServiceDiscoveryManager(
this, connection,
new String[]{}, new String[]{}, false);
registrationState = RegistrationState.REGISTERED;
fireRegistrationStateChanged(
RegistrationState.UNREGISTERED,
RegistrationState.REGISTERED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
null);
logger.info("XMPP provider " + jabberAccountID + " connected (JID: "
+ connection.getUser() + ")");
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void unregister()
throws OperationFailedException
{
if (connection != null)
{
connection.disconnect();
logger.info(
"XMPP provider "
+ jabberAccountID + " disconnected");
RegistrationState prevState = registrationState;
registrationState = RegistrationState.UNREGISTERED;
fireRegistrationStateChanged(
prevState,
RegistrationState.UNREGISTERED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
null);
connection = null;
}
}
/**
* {@inheritDoc}
*/
@Override
public RegistrationState getRegistrationState()
{
return registrationState;
}
/**
* {@inheritDoc}
*/
@Override
public String getProtocolName()
{
return ProtocolNames.JABBER;
}
/**
* {@inheritDoc}
*/
@Override
public ProtocolIcon getProtocolIcon()
{
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void shutdown()
{
if (connection != null)
{
try
{
unregister();
}
catch (OperationFailedException e)
{
logger.error(e.getMessage(), e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public AccountID getAccountID()
{
return jabberAccountID;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSignalingTransportSecure()
{
return false;
}
/**
* {@inheritDoc}
*/
@Override
public TransportProtocol getTransportProtocol()
{
return TransportProtocol.UNKNOWN;
}
/**
* Returns implementation of {@link org.jitsi.protocol.xmpp.XmppConnection}.
*/
public XMPPConnection getConnection()
{
return connection;
}
/**
* Returns our JID if we're connected or <tt>null</tt> otherwise.
*/
public String getOurJid()
{
return connection != null ? connection.getUser() : null;
}
/**
* Lazy initializer for {@link #connectionAdapter}.
*
* @return {@link XmppConnection} provided by this instance.
*/
XmppConnection getConnectionAdapter()
{
if (connectionAdapter == null)
{
connectionAdapter
= new XmppConnectionAdapter(connection);
}
return connectionAdapter;
}
/**
* FIXME: move to operation set together with ScServiceDiscoveryManager
*/
public boolean checkFeatureSupport(String contactAddress, String[] features)
{
try
{
//FIXME: fix logging levels
logger.debug("Discovering info for: " + contactAddress);
DiscoverInfo info = discoInfoManager.discoverInfo(contactAddress);
logger.debug("HAVE Discovering info for: " + contactAddress);
logger.debug("Features");
Iterator<DiscoverInfo.Feature> featuresList = info.getFeatures();
while (featuresList.hasNext())
{
DiscoverInfo.Feature f = featuresList.next();
logger.debug(f.toXML());
}
logger.debug("Identities");
Iterator<DiscoverInfo.Identity> identities = info.getIdentities();
while (identities.hasNext())
{
DiscoverInfo.Identity identity = identities.next();
logger.debug(identity.toXML());
}
}
catch (XMPPException e)
{
logger.error("Error discovering features", e);
}
for (String feature : features)
{
if (!discoInfoManager.supportsFeature(contactAddress, feature))
{
return false;
}
}
return true;
}
public boolean checkFeatureSupport(String node, String subnode,
String[] features)
{
try
{
//FIXME: fix logging levels
logger.info("Discovering info for: " + node + " subnode: " + subnode);
DiscoverInfo info = discoInfoManager.discoverInfo(node, subnode);
logger.info("Features");
Iterator<DiscoverInfo.Feature> featuresList = info.getFeatures();
while (featuresList.hasNext())
{
DiscoverInfo.Feature f = featuresList.next();
logger.info(f.toXML());
}
logger.info("Identities");
Iterator<DiscoverInfo.Identity> identities = info.getIdentities();
while (identities.hasNext())
{
DiscoverInfo.Identity identity = identities.next();
logger.info(identity.toXML());
}
}
catch (XMPPException e)
{
logger.error(e, e);
}
for (String feature : features)
{
if (!discoInfoManager.supportsFeature(node, feature))
{
return false;
}
}
return true;
}
/**
* FIXME: move to operation set together with ScServiceDiscoveryManager
*/
public List<String> discoverItems(String node)
throws XMPPException
{
DiscoverItems itemsDisco = discoInfoManager.discoverItems(node);
//FIXME: fix logging levels
logger.info("HAVE Discovered items for: " + node);
ArrayList<String> result = new ArrayList<String>();
Iterator<DiscoverItems.Item> items = itemsDisco.getItems();
while (items.hasNext())
{
DiscoverItems.Item item = items.next();
logger.info(item.toXML());
if (item.getNode() != null && item.getEntityID().equals(node))
{
// Subnode
result.add(item.getNode());
}
else
{
result.add(item.getEntityID());
}
}
return result;
}
/**
* Implements {@link XmppConnection}.
*/
class XmppConnectionAdapter
implements XmppConnection
{
private final XMPPConnection connection;
XmppConnectionAdapter(XMPPConnection connection)
{
this.connection = connection;
}
@Override
public void sendPacket(Packet packet)
{
connection.sendPacket(packet);
}
@Override
public Packet sendPacketAndGetReply(Packet packet)
{
PacketCollector packetCollector
= connection.createPacketCollector(
new PacketIDFilter(packet.getPacketID()));
connection.sendPacket(packet);
//FIXME: retry allocation on timeout
Packet response = packetCollector.nextResult(60000); // BAO
packetCollector.cancel();
return response;
}
}
}