/* * Jicofo, the Jitsi Conference Focus. * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jitsi.jicofo.xmpp; import net.java.sip.communicator.impl.protocol.jabber.extensions.colibri.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.Logger; import org.jitsi.jicofo.*; import org.jitsi.util.*; import org.jivesoftware.smack.packet.*; import org.xmpp.component.*; import org.xmpp.packet.IQ; import org.jitsi.videobridge.osgi.*; /** * XMPP component that listens for {@link org.jitsi.jicofo.xmpp.ConferenceIq} * and allocates {@link org.jitsi.jicofo.JitsiMeetConference}s appropriately. * * @author Pawel Domas */ public class FocusComponent extends AbstractComponent { /** * The logger. */ private final static Logger logger = Logger.getLogger(FocusComponent.class); /** * Name of system and configuration property which specifies the JID from * which shutdown requests will be accepted. */ public static final String SHUTDOWN_ALLOWED_JID_PNAME = "org.jitsi.focus.shutdown.ALLOWED_JID"; /** * The JID from which shutdown request are accepted. */ private final String shutdownAllowedJid; /** * Indicates if the focus is anonymous user or authenticated system admin. */ private final boolean isFocusAnonymous; /** * The JID of focus user that will enter the MUC room. Can be user to * recognize real focus of the conference. */ private final String focusAuthJid; /** * Optional password for focus user authentication. If authenticated login * is used we expect focus user to have admin privileges, so that it has * explicit moderator rights. Also in this case focus component will always * return 'ready=true' status, so that users don't have to wait for * the focus to create the room. If focus is authenticated and is not * an admin then will refuse to join MUC room. */ /** * The manager object that creates and expires * {@link org.jitsi.jicofo.JitsiMeetConference}s. */ private FocusManager focusManager; /** * Creates new instance of <tt>FocusComponent</tt>. * @param anonymousFocus indicates if the focus user is anonymous. * @param focusAuthJid the JID of authenticated focus user which will be * advertised to conference participants. */ public FocusComponent(boolean anonymousFocus, String focusAuthJid) { this.isFocusAnonymous = anonymousFocus; this.focusAuthJid = focusAuthJid; this.shutdownAllowedJid = FocusBundleActivator.getConfigService() .getString(SHUTDOWN_ALLOWED_JID_PNAME); new ConferenceIqProvider(); new ColibriIQProvider(); } /** * Initializes this component. */ public void init() { this.focusManager = ServiceUtils2.getService( FocusBundleActivator.bundleContext, FocusManager.class); focusManager.start(); } /** * Releases resources used by this instance. */ public void dispose() { focusManager.stop(); } @Override public String getDescription() { return "Manages Jitsi Meet conferences"; } @Override public String getName() { return "Jitsi Meet Focus"; } /** * {@inheritDoc} */ @Override protected String[] discoInfoFeatureNamespaces() { return new String[] { ConferenceIq.NAMESPACE }; } @Override protected IQ handleIQGet(IQ iq) throws Exception { try { org.jivesoftware.smack.packet.IQ smackIq = IQUtils.convert(iq); if (smackIq instanceof ColibriStatsIQ) { // Supports only empty colibri queries ColibriStatsIQ colibriStatsQuery = (ColibriStatsIQ) smackIq; // Reply with stats ColibriStatsIQ statsReply = new ColibriStatsIQ(); statsReply.setType( org.jivesoftware.smack.packet.IQ.Type.RESULT); statsReply.setPacketID(iq.getID()); statsReply.setTo(iq.getFrom().toString()); int conferenceCount = focusManager.getConferenceCount(); // Return conference count statsReply.addStat( new ColibriStatsExtension.Stat( "conferences", Integer.toString(conferenceCount))); statsReply.addStat( new ColibriStatsExtension.Stat( "graceful_shutdown", focusManager.isShutdownInProgress() ? "true" : "false")); return IQUtils.convert(statsReply); } else { return super.handleIQGet(iq); } } catch (Exception e) { logger.error(e, e); throw e; } } /** * Handles an <tt>org.xmpp.packet.IQ</tt> stanza of type <tt>set</tt> which * represents a request. * * @param iq the <tt>org.xmpp.packet.IQ</tt> stanza of type <tt>set</tt> * which represents the request to handle * @return an <tt>org.xmpp.packet.IQ</tt> stanza which represents the * response to the specified request or <tt>null</tt> to reply with * <tt>feature-not-implemented</tt> * @throws Exception to reply with <tt>internal-server-error</tt> to the * specified request * @see AbstractComponent#handleIQSet(IQ) */ @Override public IQ handleIQSet(IQ iq) throws Exception { try { org.jivesoftware.smack.packet.IQ smackIq = IQUtils.convert(iq); if (smackIq instanceof ConferenceIq) { ConferenceIq query = (ConferenceIq) smackIq; ConferenceIq response = new ConferenceIq(); String room = query.getRoom(); logger.info("Focus request for room: " + room); if (focusManager.isShutdownInProgress() && focusManager.getConference(room) == null) { // Service unavailable org.jivesoftware.smack.packet.IQ smackReply = ColibriConferenceIQ .createGracefulShutdownErrorResponse(query); // Fix error responses return IQUtils.convert(smackReply); } boolean ready = focusManager.conferenceRequest( room, query.getPropertiesMap()); if (!isFocusAnonymous) { // Focus is authenticated system admin, so we let // them in immediately. Focus will get OWNER anyway. ready = true; } response.setType(org.jivesoftware.smack.packet.IQ.Type.RESULT); response.setPacketID(query.getPacketID()); response.setFrom(query.getTo()); response.setTo(query.getFrom()); response.setRoom(query.getRoom()); response.setReady(ready); response.setFocusJid(focusAuthJid); return IQUtils.convert(response); } else if (smackIq instanceof GracefulShutdownIQ) { GracefulShutdownIQ gracefulShutdownIQ = (GracefulShutdownIQ) smackIq; String from = gracefulShutdownIQ.getFrom(); if (StringUtils.isNullOrEmpty(shutdownAllowedJid) || !shutdownAllowedJid.equals(from)) { // Forbidden XMPPError forbiddenError = new XMPPError(XMPPError.Condition.forbidden); logger.warn("Rejected shutdown request from: " + from); return IQUtils.convert( org.jivesoftware.smack.packet.IQ.createErrorResponse( smackIq, forbiddenError)); } logger.info("Accepted shutdown request from: " + from); focusManager.enableGracefulShutdownMode(); return IQUtils.convert( org.jivesoftware.smack.packet.IQ.createResultIQ(smackIq)); } else { return super.handleIQSet(iq); } } catch (Exception e) { logger.error(e, e); throw e; } } }