/**
     * Function called when an audio device is plugged or unplugged.
     *
     * @param event The property change event which may concern the audio device.
     */
    public void propertyChange(PropertyChangeEvent event) {
      if (DeviceConfiguration.PROP_AUDIO_SYSTEM_DEVICES.equals(event.getPropertyName())) {
        NotificationService notificationService = getNotificationService();

        if (notificationService != null) {
          // Registers only once to the  popup message notification
          // handler.
          if (!isRegisteredToPopupMessageListener) {
            isRegisteredToPopupMessageListener = true;
            managePopupMessageListenerRegistration(true);
          }

          // Fires the popup notification.
          ResourceManagementService resources = NeomediaActivator.getResources();
          Map<String, Object> extras = new HashMap<String, Object>();

          extras.put(NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, this);
          notificationService.fireNotification(
              DEVICE_CONFIGURATION_HAS_CHANGED,
              resources.getI18NString("impl.media.configform" + ".AUDIO_DEVICE_CONFIG_CHANGED"),
              resources.getI18NString(
                  "impl.media.configform" + ".AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"),
              null,
              extras);
        }
      }
    }
  /**
   * Add a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   * @param node the node (of the caps packet extension)
   * @param hash the hashing algorithm used to calculate <tt>ver</tt>
   * @param ver the version (of the caps packet extension)
   * @param ext the ext (of the caps packet extension)
   * @param online indicates if the user is online
   */
  private void addUserCapsNode(
      String user, String node, String hash, String ver, String ext, boolean online) {
    if ((user != null) && (node != null) && (hash != null) && (ver != null)) {
      Caps caps = userCaps.get(user);

      if ((caps == null)
          || !caps.node.equals(node)
          || !caps.hash.equals(hash)
          || !caps.ver.equals(ver)) {
        caps = new Caps(node, hash, ver, ext);

        userCaps.put(user, caps);
      } else return;

      // Fire userCapsNodeAdded.
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeAdded(user, nodeVer, online);
      }
    }
  }
  /**
   * Remove records telling what entity caps node a contact has.
   *
   * @param contact the contact
   */
  public void removeContactCapsNode(Contact contact) {
    Caps caps = null;
    String lastRemovedJid = null;

    Iterator<String> iter = userCaps.keySet().iterator();
    while (iter.hasNext()) {
      String jid = iter.next();

      if (StringUtils.parseBareAddress(jid).equals(contact.getAddress())) {
        caps = userCaps.get(jid);
        lastRemovedJid = jid;
        iter.remove();
      }
    }

    // fire only for the last one, at the end the event out
    // of the protocol will be one and for the contact
    if (caps != null) {
      UserCapsNodeListener[] listeners;
      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(lastRemovedJid, nodeVer, false);
      }
    }
  }
Exemple #4
0
  /**
   * Allocates new focus for given MUC room.
   *
   * @param room the name of MUC room for which new conference has to be allocated.
   * @param properties configuration properties map included in the request.
   * @return <tt>true</tt> if conference focus is in the room and ready to handle session
   *     participants.
   * @throws Exception if for any reason we have failed to create the conference
   */
  public synchronized boolean conferenceRequest(String room, Map<String, String> properties)
      throws Exception {
    if (StringUtils.isNullOrEmpty(room)) return false;

    if (shutdownInProgress && !conferences.containsKey(room)) return false;

    if (!conferences.containsKey(room)) {
      createConference(room, properties);
    }

    JitsiMeetConference conference = conferences.get(room);

    return conference.isInTheRoom();
  }
  /**
   * Closes given {@link #transportManagers} of this <tt>Conference</tt> and removes corresponding
   * channel bundle.
   */
  void closeTransportManager(TransportManager transportManager) {
    synchronized (transportManagers) {
      for (Iterator<IceUdpTransportManager> i = transportManagers.values().iterator();
          i.hasNext(); ) {
        if (i.next() == transportManager) {
          i.remove();
          // Presumably, we have a single association for
          // transportManager.
          break;
        }
      }

      // Close manager
      try {
        transportManager.close();
      } catch (Throwable t) {
        logger.warn(
            "Failed to close an IceUdpTransportManager of" + " conference " + getID() + "!", t);
        // The whole point of explicitly closing the
        // transportManagers of this Conference is to prevent memory
        // leaks. Hence, it does not make sense to possibly leave
        // TransportManagers open because a TransportManager has
        // failed to close.
        if (t instanceof InterruptedException) Thread.currentThread().interrupt();
        else if (t instanceof ThreadDeath) throw (ThreadDeath) t;
      }
    }
  }
  /**
   * Returns default <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   *
   * @return <tt>WebRtcDataStream</tt> if it's ready or <tt>null</tt> otherwise.
   * @throws IOException
   */
  public WebRtcDataStream getDefaultDataStream() throws IOException {
    WebRtcDataStream def;

    synchronized (this) {
      if (sctpSocket == null) {
        def = null;
      } else {
        // Channel that runs on sid 0
        def = channels.get(0);
        if (def == null) {
          def = openChannel(0, 0, 0, 0, "default");
        }
        // Pawel Domas: Must be acknowledged before use
        /*
         * XXX Lyubomir Marinov: We're always sending ordered. According
         * to "WebRTC Data Channel Establishment Protocol", we can start
         * sending messages containing user data after the
         * DATA_CHANNEL_OPEN message has been sent without waiting for
         * the reception of the corresponding DATA_CHANNEL_ACK message.
         */
        //                if (!def.isAcknowledged())
        //                    def = null;
      }
    }
    return def;
  }
  /**
   * Add {@link DiscoverInfo} to our caps database.
   *
   * <p><b>Warning</b>: The specified <tt>DiscoverInfo</tt> is trusted to be valid with respect to
   * the specified <tt>Caps</tt> for performance reasons because the <tt>DiscoverInfo</tt> should
   * have already been validated in order to be used elsewhere anyway.
   *
   * @param caps the <tt>Caps<tt/> i.e. the node, the hash and the ver for which a
   *     <tt>DiscoverInfo</tt> is to be added to our caps database.
   * @param info {@link DiscoverInfo} for the specified <tt>Caps</tt>.
   */
  public static void addDiscoverInfoByCaps(Caps caps, DiscoverInfo info) {
    cleanupDiscoverInfo(info);
    /*
     * DiscoverInfo carries the node we're now associating it with a
     * specific node so we'd better keep them in sync.
     */
    info.setNode(caps.getNodeVer());

    synchronized (caps2discoverInfo) {
      DiscoverInfo oldInfo = caps2discoverInfo.put(caps, info);

      /*
       * If the specified info is a new association for the specified
       * node, remember it across application instances in order to not
       * query for it over the network.
       */
      if ((oldInfo == null) || !oldInfo.equals(info)) {
        String xml = info.getChildElementXML();

        if ((xml != null) && (xml.length() != 0)) {
          getConfigService().setProperty(getCapsPropertyName(caps), xml);
        }
      }
    }
  }
  /**
   * Makes sure that conference is allocated for given <tt>room</tt>.
   *
   * @param room name of the MUC room of Jitsi Meet conference.
   * @param properties configuration properties, see {@link JitsiMeetConfig} for the list of valid
   *     properties.
   * @throws Exception if any error occurs.
   */
  private void createConference(String room, Map<String, String> properties) throws Exception {
    JitsiMeetConfig config = new JitsiMeetConfig(properties);

    JitsiMeetConference conference =
        new JitsiMeetConference(room, focusUserName, protocolProviderHandler, this, config);

    conferences.put(room, conference);

    StringBuilder options = new StringBuilder();
    for (Map.Entry<String, String> option : properties.entrySet()) {
      options.append("\n    ").append(option.getKey()).append(": ").append(option.getValue());
    }

    logger.info(
        "Created new focus for "
            + room
            + "@"
            + focusUserDomain
            + " conferences count: "
            + conferences.size()
            + " options:"
            + options.toString());

    // Send focus created event
    EventAdmin eventAdmin = FocusBundleActivator.getEventAdmin();
    if (eventAdmin != null) {
      eventAdmin.sendEvent(EventFactory.focusCreated(conference.getId(), conference.getRoomName()));
    }

    try {
      conference.start();
    } catch (Exception e) {
      logger.info("Exception while trying to start the conference", e);

      // stop() method is called by the conference automatically in order
      // to not release the lock on JitsiMeetConference instance and avoid
      // a deadlock. It may happen when this thread is about to call
      // conference.stop() and another thread has entered the method
      // before us. That other thread will try to call
      // FocusManager.conferenceEnded, but we're still holding the lock
      // on FocusManager instance.

      // conference.stop();

      throw e;
    }
  }
  /**
   * Initializes and creates an account corresponding to the specified accountProperties.
   *
   * @param accountProperties a set of protocol (or implementation) specific properties defining the
   *     new account.
   * @return the AccountID of the newly created account
   */
  public AccountID createAccount(Map<String, String> accountProperties) {
    BundleContext bundleContext = getBundleContext();
    if (bundleContext == null)
      throw new NullPointerException("The specified BundleContext was null");

    if (accountProperties == null)
      throw new NullPointerException("The specified property map was null");

    String userID = accountProperties.get(USER_ID);
    if (userID == null)
      throw new NullPointerException("The account properties contained no user id.");

    String protocolName = getProtocolName();
    if (!accountProperties.containsKey(PROTOCOL)) accountProperties.put(PROTOCOL, protocolName);

    return createAccountID(userID, accountProperties);
  }
Exemple #10
0
  /** {@inheritDoc} */
  @Override
  public synchronized void conferenceEnded(JitsiMeetConference conference) {
    String roomName = conference.getRoomName();

    conferences.remove(roomName);

    logger.info(
        "Disposed conference for room: " + roomName + " conference count: " + conferences.size());

    if (focusAllocListener != null) {
      focusAllocListener.onFocusDestroyed(roomName);
    }

    // Send focus destroyed event
    FocusBundleActivator.getEventAdmin()
        .sendEvent(EventFactory.focusDestroyed(conference.getId(), conference.getRoomName()));

    maybeDoShutdown();
  }
Exemple #11
0
  private void maybeDoShutdown() {
    if (shutdownInProgress && conferences.size() == 0) {
      logger.info("Focus is shutting down NOW");

      ShutdownService shutdownService =
          ServiceUtils.getService(FocusBundleActivator.bundleContext, ShutdownService.class);

      shutdownService.beginShutdown();
    }
  }
  /**
   * Returns {@link JitsiMeetConference} for given MUC {@code roomName} or {@code null} if no
   * conference has been allocated yet.
   *
   * @param roomName the name of MUC room for which we want get the {@code JitsiMeetConference}
   *     instance.
   * @return the {@code JitsiMeetConference} for the specified {@code roomName} or {@code null} if
   *     no conference has been allocated yet
   */
  public JitsiMeetConference getConference(String roomName) {
    roomName = roomName.toLowerCase();

    // Other public methods which read from and/or write to the field
    // conferences are sychronized (e.g. conferenceEnded, conferenceRequest)
    // so synchronization is necessary here as well.
    synchronized (this) {
      return conferences.get(roomName);
    }
  }
  /**
   * Returns, the <tt>TransportManager</tt> instance for the channel-bundle with ID
   * <tt>channelBundleId</tt>. If no instance exists and <tt>create</tt> is <tt>true</tt>, one will
   * be created.
   *
   * @param channelBundleId the ID of the channel-bundle for which to return the
   *     <tt>TransportManager</tt>.
   * @param create whether to create a new instance, if one doesn't exist.
   * @return the <tt>TransportManager</tt> instance for the channel-bundle with ID
   *     <tt>channelBundleId</tt>.
   */
  IceUdpTransportManager getTransportManager(String channelBundleId, boolean create) {
    IceUdpTransportManager transportManager;

    synchronized (transportManagers) {
      transportManager = transportManagers.get(channelBundleId);
      if (transportManager == null && create && !isExpired()) {
        try {
          // FIXME: the initiator is hard-coded
          // We assume rtcp-mux when bundle is used, so we make only
          // one component.
          transportManager = new IceUdpTransportManager(this, true, 1);
        } catch (IOException ioe) {
          throw new UndeclaredThrowableException(ioe);
        }
        transportManagers.put(channelBundleId, transportManager);
      }
    }

    return transportManager;
  }
  /**
   * Adds the channel-bundles of this <tt>Conference</tt> as
   * <tt>ColibriConferenceIQ.ChannelBundle</tt> instances in <tt>iq</tt>.
   *
   * @param iq the <tt>ColibriConferenceIQ</tt> in which to describe.
   */
  void describeChannelBundles(ColibriConferenceIQ iq) {
    synchronized (transportManagers) {
      for (Map.Entry<String, IceUdpTransportManager> entry : transportManagers.entrySet()) {
        ColibriConferenceIQ.ChannelBundle responseBundleIQ =
            new ColibriConferenceIQ.ChannelBundle(entry.getKey());

        entry.getValue().describe(responseBundleIQ);
        iq.addChannelBundle(responseBundleIQ);
      }
    }
  }
  /** Closes the {@link #transportManagers} of this <tt>Conference</tt>. */
  private void closeTransportManagers() {
    synchronized (transportManagers) {
      for (Iterator<IceUdpTransportManager> i = transportManagers.values().iterator();
          i.hasNext(); ) {
        IceUdpTransportManager transportManager = i.next();

        i.remove();
        closeTransportManager(transportManager);
      }
    }
  }
Exemple #16
0
  /**
   * Makes sure that conference is allocated for given <tt>room</tt>.
   *
   * @param room name of the MUC room of Jitsi Meet conference.
   * @param properties configuration properties, see {@link JitsiMeetConfig} for the list of valid
   *     properties.
   * @throws Exception if any error occurs.
   */
  private void createConference(String room, Map<String, String> properties) throws Exception {
    JitsiMeetConfig config = new JitsiMeetConfig(properties);

    JitsiMeetConference conference =
        new JitsiMeetConference(room, focusUserName, protocolProviderHandler, this, config);

    conferences.put(room, conference);

    StringBuilder options = new StringBuilder();
    for (Map.Entry<String, String> option : properties.entrySet()) {
      options.append("\n    ").append(option.getKey()).append(": ").append(option.getValue());
    }

    logger.info(
        "Created new focus for "
            + room
            + "@"
            + focusUserDomain
            + " conferences count: "
            + conferences.size()
            + " options:"
            + options.toString());

    // Send focus created event
    EventAdmin eventAdmin = FocusBundleActivator.getEventAdmin();
    if (eventAdmin != null) {
      eventAdmin.sendEvent(EventFactory.focusCreated(conference.getId(), conference.getRoomName()));
    }

    try {
      conference.start();
    } catch (Exception e) {
      logger.info("Exception while trying to start the conference", e);

      conference.stop();

      throw e;
    }
  }
  /**
   * Remove a record telling what entity caps node a user has.
   *
   * @param user the user (Full JID)
   */
  public void removeUserCapsNode(String user) {
    Caps caps = userCaps.remove(user);

    // Fire userCapsNodeRemoved.
    if (caps != null) {
      UserCapsNodeListener[] listeners;

      synchronized (userCapsNodeListeners) {
        listeners = userCapsNodeListeners.toArray(NO_USER_CAPS_NODE_LISTENERS);
      }
      if (listeners.length != 0) {
        String nodeVer = caps.getNodeVer();

        for (UserCapsNodeListener listener : listeners)
          listener.userCapsNodeRemoved(user, nodeVer, false);
      }
    }
  }
  /**
   * {@inheritDoc}
   *
   * <p>SCTP input data callback.
   */
  @Override
  public void onSctpPacket(
      byte[] data, int sid, int ssn, int tsn, long ppid, int context, int flags) {
    if (ppid == WEB_RTC_PPID_CTRL) {
      // Channel control PPID
      try {
        onCtrlPacket(data, sid);
      } catch (IOException e) {
        logger.error("IOException when processing ctrl packet", e);
      }
    } else if (ppid == WEB_RTC_PPID_STRING || ppid == WEB_RTC_PPID_BIN) {
      WebRtcDataStream channel;

      synchronized (this) {
        channel = channels.get(sid);
      }

      if (channel == null) {
        logger.error("No channel found for sid: " + sid);
        return;
      }
      if (ppid == WEB_RTC_PPID_STRING) {
        // WebRTC String
        String str;
        String charsetName = "UTF-8";

        try {
          str = new String(data, charsetName);
        } catch (UnsupportedEncodingException uee) {
          logger.error("Unsupported charset encoding/name " + charsetName, uee);
          str = null;
        }
        channel.onStringMsg(str);
      } else {
        // WebRTC Binary
        channel.onBinaryMsg(data);
      }
    } else {
      logger.warn("Got message on unsupported PPID: " + ppid);
    }
  }
  /**
   * Opens new WebRTC data channel using specified parameters.
   *
   * @param type channel type as defined in control protocol description. Use 0 for "reliable".
   * @param prio channel priority. The higher the number, the lower the priority.
   * @param reliab Reliability Parameter<br>
   *     This field is ignored if a reliable channel is used. If a partial reliable channel with
   *     limited number of retransmissions is used, this field specifies the number of
   *     retransmissions. If a partial reliable channel with limited lifetime is used, this field
   *     specifies the maximum lifetime in milliseconds. The following table summarizes this:<br>
   *     </br>
   *     <p>+------------------------------------------------+------------------+ | Channel Type |
   *     Reliability | | | Parameter |
   *     +------------------------------------------------+------------------+ |
   *     DATA_CHANNEL_RELIABLE | Ignored | | DATA_CHANNEL_RELIABLE_UNORDERED | Ignored | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT | Number of RTX | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED | Number of RTX | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_TIMED | Lifetime in ms | |
   *     DATA_CHANNEL_PARTIAL_RELIABLE_TIMED_UNORDERED | Lifetime in ms |
   *     +------------------------------------------------+------------------+
   * @param sid SCTP stream id that will be used by new channel (it must not be already used).
   * @param label text label for the channel.
   * @return new instance of <tt>WebRtcDataStream</tt> that represents opened WebRTC data channel.
   * @throws IOException if IO error occurs.
   */
  public synchronized WebRtcDataStream openChannel(
      int type, int prio, long reliab, int sid, String label) throws IOException {
    if (channels.containsKey(sid)) {
      throw new IOException("Channel on sid: " + sid + " already exists");
    }

    // Label Length & Label
    byte[] labelBytes;
    int labelByteLength;

    if (label == null) {
      labelBytes = null;
      labelByteLength = 0;
    } else {
      labelBytes = label.getBytes("UTF-8");
      labelByteLength = labelBytes.length;
      if (labelByteLength > 0xFFFF) labelByteLength = 0xFFFF;
    }

    // Protocol Length & Protocol
    String protocol = WEBRTC_DATA_CHANNEL_PROTOCOL;
    byte[] protocolBytes;
    int protocolByteLength;

    if (protocol == null) {
      protocolBytes = null;
      protocolByteLength = 0;
    } else {
      protocolBytes = protocol.getBytes("UTF-8");
      protocolByteLength = protocolBytes.length;
      if (protocolByteLength > 0xFFFF) protocolByteLength = 0xFFFF;
    }

    ByteBuffer packet = ByteBuffer.allocate(12 + labelByteLength + protocolByteLength);

    // Message open new channel on current sid
    // Message Type
    packet.put((byte) MSG_OPEN_CHANNEL);
    // Channel Type
    packet.put((byte) type);
    // Priority
    packet.putShort((short) prio);
    // Reliability Parameter
    packet.putInt((int) reliab);
    // Label Length
    packet.putShort((short) labelByteLength);
    // Protocol Length
    packet.putShort((short) protocolByteLength);
    // Label
    if (labelByteLength != 0) {
      packet.put(labelBytes, 0, labelByteLength);
    }
    // Protocol
    if (protocolByteLength != 0) {
      packet.put(protocolBytes, 0, protocolByteLength);
    }

    int sentCount = sctpSocket.send(packet.array(), true, sid, WEB_RTC_PPID_CTRL);

    if (sentCount != packet.capacity()) {
      throw new IOException("Failed to open new chanel on sid: " + sid);
    }

    WebRtcDataStream channel = new WebRtcDataStream(sctpSocket, sid, label, false);

    channels.put(sid, channel);

    return channel;
  }
  /**
   * Handles control packet.
   *
   * @param data raw packet data that arrived on control PPID.
   * @param sid SCTP stream id on which the data has arrived.
   */
  private synchronized void onCtrlPacket(byte[] data, int sid) throws IOException {
    ByteBuffer buffer = ByteBuffer.wrap(data);
    int messageType = /* 1 byte unsigned integer */ 0xFF & buffer.get();

    if (messageType == MSG_CHANNEL_ACK) {
      if (logger.isDebugEnabled()) {
        logger.debug(getEndpoint().getID() + " ACK received SID: " + sid);
      }
      // Open channel ACK
      WebRtcDataStream channel = channels.get(sid);
      if (channel != null) {
        // Ack check prevents from firing multiple notifications
        // if we get more than one ACKs (by mistake/bug).
        if (!channel.isAcknowledged()) {
          channel.ackReceived();
          notifyChannelOpened(channel);
        } else {
          logger.warn("Redundant ACK received for SID: " + sid);
        }
      } else {
        logger.error("No channel exists on sid: " + sid);
      }
    } else if (messageType == MSG_OPEN_CHANNEL) {
      int channelType = /* 1 byte unsigned integer */ 0xFF & buffer.get();
      int priority = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      long reliability = /* 4 bytes unsigned integer */ 0xFFFFFFFFL & buffer.getInt();
      int labelLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      int protocolLength = /* 2 bytes unsigned integer */ 0xFFFF & buffer.getShort();
      String label;
      String protocol;

      if (labelLength == 0) {
        label = "";
      } else {
        byte[] labelBytes = new byte[labelLength];

        buffer.get(labelBytes);
        label = new String(labelBytes, "UTF-8");
      }
      if (protocolLength == 0) {
        protocol = "";
      } else {
        byte[] protocolBytes = new byte[protocolLength];

        buffer.get(protocolBytes);
        protocol = new String(protocolBytes, "UTF-8");
      }

      if (logger.isDebugEnabled()) {
        logger.debug(
            "!!! "
                + getEndpoint().getID()
                + " data channel open request on SID: "
                + sid
                + " type: "
                + channelType
                + " prio: "
                + priority
                + " reliab: "
                + reliability
                + " label: "
                + label
                + " proto: "
                + protocol);
      }

      if (channels.containsKey(sid)) {
        logger.error("Channel on sid: " + sid + " already exists");
      }

      WebRtcDataStream newChannel = new WebRtcDataStream(sctpSocket, sid, label, true);
      channels.put(sid, newChannel);

      sendOpenChannelAck(sid);

      notifyChannelOpened(newChannel);
    } else {
      logger.error("Unexpected ctrl msg type: " + messageType);
    }
  }
 /**
  * Gets the <tt>Caps</tt> i.e. the node, the hash and the ver of a user.
  *
  * @param user the user (Full JID)
  * @return the <tt>Caps</tt> i.e. the node, the hash and the ver of <tt>user</tt>
  */
 public Caps getCapsByUser(String user) {
   return userCaps.get(user);
 }
Exemple #22
0
 /** Returns the number of currently allocated focus instances. */
 public int getConferenceCount() {
   return conferences.size();
 }
Exemple #23
0
 /**
  * Returns {@link JitsiMeetConference} for given MUC <tt>roomName</tt> or <tt>null</tt> if no
  * conference has been allocated yet.
  *
  * @param roomName the name of MUC room for which we want get the {@link JitsiMeetConference}
  *     instance.
  */
 public JitsiMeetConference getConference(String roomName) {
   return conferences.get(roomName);
 }
  /**
   * Get the discover info given a user name. The discover info is returned if the user has a
   * node#ver associated with it and the node#ver has a discover info associated with it.
   *
   * @param user user name (Full JID)
   * @return the discovered info
   */
  public DiscoverInfo getDiscoverInfoByUser(String user) {
    Caps caps = userCaps.get(user);

    return (caps == null) ? null : getDiscoverInfoByCaps(caps);
  }
  /**
   * Retrieve DiscoverInfo for a specific node.
   *
   * @param caps the <tt>Caps</tt> i.e. the node, the hash and the ver
   * @return The corresponding DiscoverInfo or null if none is known.
   */
  public static DiscoverInfo getDiscoverInfoByCaps(Caps caps) {
    synchronized (caps2discoverInfo) {
      DiscoverInfo discoverInfo = caps2discoverInfo.get(caps);

      /*
       * If we don't have the discoverInfo in the runtime cache yet, we
       * may have it remembered in a previous application instance.
       */
      if (discoverInfo == null) {
        ConfigurationService configurationService = getConfigService();
        String capsPropertyName = getCapsPropertyName(caps);
        String xml = configurationService.getString(capsPropertyName);

        if ((xml != null) && (xml.length() != 0)) {
          IQProvider discoverInfoProvider =
              (IQProvider)
                  ProviderManager.getInstance()
                      .getIQProvider("query", "http://jabber.org/protocol/disco#info");

          if (discoverInfoProvider != null) {
            XmlPullParser parser = new MXParser();

            try {
              parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
              parser.setInput(new StringReader(xml));
              // Start the parser.
              parser.next();
            } catch (XmlPullParserException xppex) {
              parser = null;
            } catch (IOException ioex) {
              parser = null;
            }

            if (parser != null) {
              try {
                discoverInfo = (DiscoverInfo) discoverInfoProvider.parseIQ(parser);
              } catch (Exception ex) {
              }

              if (discoverInfo != null) {
                if (caps.isValid(discoverInfo)) caps2discoverInfo.put(caps, discoverInfo);
                else {
                  logger.error(
                      "Invalid DiscoverInfo for " + caps.getNodeVer() + ": " + discoverInfo);
                  /*
                   * The discoverInfo doesn't seem valid
                   * according to the caps which means that we
                   * must have stored invalid information.
                   * Delete the invalid information in order
                   * to not try to validate it again.
                   */
                  configurationService.removeProperty(capsPropertyName);
                }
              }
            }
          }
        }
      }
      return discoverInfo;
    }
  }