/**
   * Gets an <tt>Endpoint</tt> participating in this <tt>Conference</tt> which has a specific
   * identifier/ID. If an <tt>Endpoint</tt> participating in this <tt>Conference</tt> with the
   * specified <tt>id</tt> does not exist at the time the method is invoked, the method optionally
   * initializes a new <tt>Endpoint</tt> instance with the specified <tt>id</tt> and adds it to the
   * list of <tt>Endpoint</tt>s participating in this <tt>Conference</tt>.
   *
   * @param id the identifier/ID of the <tt>Endpoint</tt> which is to be returned
   * @return an <tt>Endpoint</tt> participating in this <tt>Conference</tt> which has the specified
   *     <tt>id</tt> or <tt>null</tt> if there is no such <tt>Endpoint</tt> and <tt>create</tt>
   *     equals <tt>false</tt>
   */
  private Endpoint getEndpoint(String id, boolean create) {
    Endpoint endpoint = null;
    boolean changed = false;

    synchronized (endpoints) {
      for (Iterator<WeakReference<Endpoint>> i = endpoints.iterator(); i.hasNext(); ) {
        Endpoint e = i.next().get();

        if (e == null) {
          i.remove();
          changed = true;
        } else if (e.getID().equals(id)) {
          endpoint = e;
        }
      }

      if (create && endpoint == null) {
        endpoint = new Endpoint(id, this);
        // The propertyChangeListener will weakly reference this
        // Conference and will unregister itself from the endpoint
        // sooner or later.
        endpoint.addPropertyChangeListener(propertyChangeListener);
        endpoints.add(new WeakReference<>(endpoint));
        changed = true;

        EventAdmin eventAdmin = videobridge.getEventAdmin();
        if (eventAdmin != null) eventAdmin.sendEvent(EventFactory.endpointCreated(endpoint));
      }
    }

    if (changed) firePropertyChange(ENDPOINTS_PROPERTY_NAME, null, null);

    return endpoint;
  }
  /**
   * Expires this <tt>Conference</tt>, its <tt>Content</tt>s and their respective <tt>Channel</tt>s.
   * Releases the resources acquired by this instance throughout its life time and prepares it to be
   * garbage collected.
   */
  public void expire() {
    synchronized (this) {
      if (expired) return;
      else expired = true;
    }

    EventAdmin eventAdmin = videobridge.getEventAdmin();
    if (eventAdmin != null) eventAdmin.sendEvent(EventFactory.conferenceExpired(this));

    setRecording(false);
    if (recorderEventHandler != null) {
      recorderEventHandler.close();
      recorderEventHandler = null;
    }

    Videobridge videobridge = getVideobridge();

    try {
      videobridge.expireConference(this);
    } finally {
      // Expire the Contents of this Conference.
      for (Content content : getContents()) {
        try {
          content.expire();
        } catch (Throwable t) {
          logger.warn(
              "Failed to expire content " + content.getName() + " of conference " + getID() + "!",
              t);
          if (t instanceof InterruptedException) Thread.currentThread().interrupt();
          else if (t instanceof ThreadDeath) throw (ThreadDeath) t;
        }
      }

      // Close the transportManagers of this Conference. Normally, there
      // will be no TransportManager left to close at this point because
      // all Channels have expired and the last Channel to be removed from
      // a TransportManager closes the TransportManager. However, a
      // Channel may have expired before it has learned of its
      // TransportManager and then the TransportManager will not close.
      closeTransportManagers();

      if (logger.isInfoEnabled()) {
        logger.info(
            "Expired conference " + getID() + ". " + videobridge.getConferenceCountString());
      }
    }
  }
  /**
   * Gets a <tt>Content</tt> of this <tt>Conference</tt> which has a specific name. If a
   * <tt>Content</tt> of this <tt>Conference</tt> with the specified <tt>name</tt> does not exist at
   * the time the method is invoked, the method initializes a new <tt>Content</tt> instance with the
   * specified <tt>name</tt> and adds it to the list of <tt>Content</tt>s of this
   * <tt>Conference</tt>.
   *
   * @param name the name of the <tt>Content</tt> which is to be returned
   * @return a <tt>Content</tt> of this <tt>Conference</tt> which has the specified <tt>name</tt>
   */
  public Content getOrCreateContent(String name) {
    Content content;

    synchronized (contents) {
      for (Content aContent : contents) {
        if (aContent.getName().equals(name)) {
          aContent.touch(); // It seems the content is still active.
          return aContent;
        }
      }

      content = new Content(this, name);
      if (isRecording()) {
        content.setRecording(true, getRecordingPath());
      }
      contents.add(content);
    }

    if (logger.isInfoEnabled()) {
      /*
       * The method Videobridge.getChannelCount() should better be
       * executed outside synchronized blocks in order to reduce the risks
       * of causing deadlocks.
       */
      Videobridge videobridge = getVideobridge();

      logger.info(
          "Created content "
              + name
              + " of conference "
              + getID()
              + ". "
              + videobridge.getConferenceCountString());
    }

    return content;
  }
  /**
   * Initializes a new <tt>Conference</tt> instance which is to represent a conference in the terms
   * of Jitsi Videobridge which has a specific (unique) ID and is managed by a conference focus with
   * a specific JID.
   *
   * @param videobridge the <tt>Videobridge</tt> on which the new <tt>Conference</tt> instance is to
   *     be initialized
   * @param id the (unique) ID of the new instance to be initialized
   * @param focus the JID of the conference focus who has requested the initialization of the new
   *     instance and from whom further/future requests to manage the new instance must come or they
   *     will be ignored. Pass <tt>null</tt> to override this safety check.
   */
  public Conference(Videobridge videobridge, String id, String focus) {
    if (videobridge == null) throw new NullPointerException("videobridge");
    if (id == null) throw new NullPointerException("id");

    this.videobridge = videobridge;
    this.id = id;
    this.focus = focus;
    this.lastKnownFocus = focus;

    speechActivity = new ConferenceSpeechActivity(this);
    speechActivity.addPropertyChangeListener(propertyChangeListener);

    EventAdmin eventAdmin = videobridge.getEventAdmin();
    if (eventAdmin != null) eventAdmin.sendEvent(EventFactory.conferenceCreated(this));
  }