public void run() {
   try {
     Log.debug("Trying to configure Clearspace.");
     doConfigClearspace();
     updateClearspaceClientSettings();
   } catch (UnauthorizedException e) {
     Log.warn(
         "Unauthorization problem trying to configure Clearspace, trying again in 1 minute", e);
     // TODO: Mark that there is an authorization problem
   } catch (Exception e) {
     Log.warn("Unknown problem trying to configure Clearspace, trying again in 1 minute", e);
   }
 }
  private synchronized void doConfigClearspace() throws UnauthorizedException {

    Log.debug("Starting Clearspace configuration.");

    List<String> bindInterfaces = getServerInterfaces();
    if (bindInterfaces.size() == 0) {
      // We aren't up and running enough to tell Clearspace what interfaces to bind to.
      Log.debug("No bind interfaces found to config Clearspace");
      throw new IllegalStateException("There are no binding interfaces.");
    }

    try {

      XMPPServerInfo serverInfo = XMPPServer.getInstance().getServerInfo();

      String path = IM_URL_PREFIX + "configureComponent/";

      // Creates the XML with the data
      Document groupDoc = DocumentHelper.createDocument();
      Element rootE = groupDoc.addElement("configureComponent");
      Element domainE = rootE.addElement("domain");
      domainE.setText(serverInfo.getXMPPDomain());
      for (String bindInterface : bindInterfaces) {
        Element hostsE = rootE.addElement("hosts");
        hostsE.setText(bindInterface);
      }
      Element portE = rootE.addElement("port");
      portE.setText(String.valueOf(ExternalComponentManager.getServicePort()));

      Log.debug(
          "Trying to configure Clearspace with: Domain: "
              + serverInfo.getXMPPDomain()
              + ", hosts: "
              + bindInterfaces.toString()
              + ", port: "
              + port);

      executeRequest(POST, path, rootE.asXML());

      // Done, Clearspace was configured correctly, clear the task
      Log.debug("Clearspace was configured, stopping the task.");
      TaskEngine.getInstance().cancelScheduledTask(configClearspaceTask);
      configClearspaceTask = null;

    } catch (UnauthorizedException ue) {
      throw ue;
    } catch (Exception e) {
      // It is not supported exception, wrap it into an UnsupportedOperationException
      throw new UnsupportedOperationException("Unexpected error", e);
    }
  }
  private synchronized void startClearspaceConfig() {
    // If the task is running, stop it
    if (configClearspaceTask != null) {
      configClearspaceTask.cancel();
      Log.debug("Stopping previous configuration Clearspace task.");
    }

    // Create and schedule a confi task every minute
    configClearspaceTask = new ConfigClearspaceTask();
    // Wait some time to start the task until Openfire has binding address
    TaskEngine.getInstance()
        .schedule(configClearspaceTask, JiveConstants.SECOND * 30, JiveConstants.MINUTE);
    Log.debug("Starting configuration Clearspace task in 10 seconds.");
  }
  private void updateClearspaceSharedSecret(String newSecret) {

    try {
      String path = IM_URL_PREFIX + "updateSharedSecret/";

      // Creates the XML with the data
      Document groupDoc = DocumentHelper.createDocument();
      Element rootE = groupDoc.addElement("updateSharedSecret");
      rootE.addElement("newSecret").setText(newSecret);

      executeRequest(POST, path, groupDoc.asXML());
    } catch (UnauthorizedException ue) {
      Log.error("Error updating the password of Clearspace", ue);
    } catch (Exception e) {
      Log.error("Error updating the password of Clearspace", e);
    }
  }
  private void updateClearspaceClientSettings() {
    String xmppBoshSslPort = "0";
    String xmppBoshPort = "0";
    String xmppPort =
        String.valueOf(XMPPServer.getInstance().getConnectionManager().getClientListenerPort());
    if (JiveGlobals.getBooleanProperty(
        HttpBindManager.HTTP_BIND_ENABLED, HttpBindManager.HTTP_BIND_ENABLED_DEFAULT)) {
      int boshSslPort = HttpBindManager.getInstance().getHttpBindSecurePort();
      int boshPort = HttpBindManager.getInstance().getHttpBindUnsecurePort();
      try {
        if (HttpBindManager.getInstance().isHttpsBindActive()
            && LocalClientSession.getTLSPolicy()
                != org.jivesoftware.openfire.Connection.TLSPolicy.disabled) {
          xmppBoshSslPort = String.valueOf(boshSslPort);
        }
      } catch (Exception e) {
        // Exception while working with certificate
        Log.debug(
            "Error while checking SSL certificate.  Instructing Clearspace not to use SSL port.");
      }
      if (HttpBindManager.getInstance().isHttpBindActive() && boshPort > 0) {
        xmppBoshPort = String.valueOf(boshPort);
      }
    }

    try {
      String path = CHAT_URL_PREFIX + "updateClientSettings/";

      // Creates the XML with the data
      Document groupDoc = DocumentHelper.createDocument();
      Element rootE = groupDoc.addElement("updateClientSettings");
      rootE.addElement("boshSslPort").setText(xmppBoshSslPort);
      rootE.addElement("boshPort").setText(xmppBoshPort);
      rootE.addElement("tcpPort").setText(xmppPort);

      executeRequest(POST, path, groupDoc.asXML());
    } catch (UnauthorizedException ue) {
      Log.error("Error updating the client settings of Clearspace", ue);
    } catch (Exception e) {
      Log.error("Error updating the client settings of Clearspace", e);
    }
  }
  /**
   * Returns a nonce generated by Clearspace to be used in a SSO login.
   *
   * @return a unique nonce.
   */
  public String getNonce() {
    try {
      String path = IM_URL_PREFIX + "generateNonce";
      Element element = executeRequest(GET, path);

      return WSUtils.getReturn(element);
    } catch (Exception e) {
      Log.error("Failed executing #generateNonce with Clearspace", e);
    }

    return null;
  }
  public synchronized boolean configClearspace() {
    // If the task is running, stop it
    if (configClearspaceTask != null) {
      configClearspaceTask.cancel();
      Log.debug("Stopping previous configuration Clearspace task.");
    }

    boolean configured = false;
    try {
      doConfigClearspace();
      updateClearspaceClientSettings();
      configured = true;
    } catch (UnauthorizedException e) {
      Log.info("Unauthorized to configure Clearspace.", e);
    } catch (UnsupportedOperationException e) {
      Log.info("Error configuring Clearspace.", e);
    }

    if (!configured) {
      startClearspaceConfig();
    }
    return configured;
  }
  /**
   * Tests the web services connection with Clearspace given the manager's current configuration.
   *
   * @return The exception or null if connection test was successful.
   */
  public Throwable testConnection() {
    // Test invoking a simple method
    try {
      // If there is a problem with the URL or the user/password this service throws an exception
      String path = IM_URL_PREFIX + "testCredentials";
      executeRequest(GET, path);

      return null;
    } catch (Exception e) {
      // It is not ok, return false.
      Log.warn("Failed testing communicating with Clearspace", e);
      return e;
    }
  }
  /**
   * Check a username/password pair for valid authentication.
   *
   * @param username Username to authenticate against.
   * @param password Password to use for authentication.
   * @return True or false of the authentication succeeded.
   */
  public Boolean checkAuthentication(String username, String password) {
    try {
      // Un-escape username.
      username = JID.unescapeNode(username);
      // Encode potentially non-ASCII characters
      username = URLUTF8Encoder.encode(username);
      String path = ClearspaceAuthProvider.URL_PREFIX + "authenticate/" + username + "/" + password;
      executeRequest(GET, path);
      return true;
    } catch (Exception e) {
      // Nothing to do.
      Log.warn("Failed authenticating user with Clearspace. User = " + username, e);
    }

    return false;
  }
  /**
   * Sets the shared secret for the Clearspace service we're connecting to.
   *
   * @param sharedSecret the password configured in Clearspace to authenticate Openfire.
   */
  public void setSharedSecret(String sharedSecret) {
    // Set new password for external component
    ExternalComponentConfiguration configuration =
        new ExternalComponentConfiguration(
            "clearspace", true, ExternalComponentConfiguration.Permission.allowed, sharedSecret);
    try {
      ExternalComponentManager.allowAccess(configuration);
    } catch (ModificationNotAllowedException e) {
      Log.warn("Failed to configure password for Clearspace", e);
    }

    // After updating the component information we can update the field, but not before.
    // If it is done before, OF won't be able to execute the updateSharedsecret webservice
    // since it would try with the new password.
    this.sharedSecret = sharedSecret;
    properties.put("clearspace.sharedSecret", sharedSecret);
  }
  static {
    try {
      factory = XmlPullParserFactory.newInstance(MXParser.class.getName(), null);
      factory.setNamespaceAware(true);
    } catch (XmlPullParserException e) {
      Log.error("Error creating a parser factory", e);
    }
    // Create xmpp parser to keep in each thread
    localParser =
        new ThreadLocal<XMPPPacketReader>() {
          protected XMPPPacketReader initialValue() {
            XMPPPacketReader parser = new XMPPPacketReader();
            factory.setNamespaceAware(true);
            parser.setXPPFactory(factory);
            return parser;
          }
        };

    // Add a new exception map from CS to OF and it will be automatically translated.
    exceptionMap = new HashMap<String, String>();
    exceptionMap.put(
        "com.jivesoftware.base.UserNotFoundException",
        "org.jivesoftware.openfire.user.UserNotFoundException");
    exceptionMap.put(
        "com.jivesoftware.base.UserAlreadyExistsException",
        "org.jivesoftware.openfire.user.UserAlreadyExistsException");
    exceptionMap.put(
        "com.jivesoftware.base.GroupNotFoundException",
        "org.jivesoftware.openfire.group.GroupNotFoundException");
    exceptionMap.put(
        "com.jivesoftware.base.GroupAlreadyExistsException",
        "org.jivesoftware.openfire.group.GroupAlreadyExistsException");
    exceptionMap.put(
        "org.acegisecurity.BadCredentialsException",
        "org.jivesoftware.openfire.auth.UnauthorizedException");
    exceptionMap.put(
        "com.jivesoftware.base.UnauthorizedException",
        "org.jivesoftware.openfire.auth.UnauthorizedException");
    exceptionMap.put(
        "com.jivesoftware.community.NotFoundException", "org.jivesoftware.util.NotFoundException");
  }
  /**
   * Makes a rest request of any type at the specified urlSuffix. The urlSuffix should be of the
   * form /userService/users. If CS throws an exception it handled and transalated to a Openfire
   * exception if possible. This is done using the check fault method that tries to throw the best
   * maching exception.
   *
   * @param type Must be GET or DELETE
   * @param urlSuffix The url suffix of the rest request
   * @param xmlParams The xml with the request params, must be null if type is GET or DELETE only
   * @return The response as a xml doc.
   * @throws ConnectionException Thrown if there are issues perfoming the request.
   * @throws Exception Thrown if the response from Clearspace contains an exception.
   */
  public Element executeRequest(HttpType type, String urlSuffix, String xmlParams)
      throws ConnectionException, Exception {
    if (Log.isDebugEnabled()) {
      Log.debug("Outgoing REST call [" + type + "] to " + urlSuffix + ": " + xmlParams);
    }

    String wsUrl = getConnectionURI() + WEBSERVICES_PATH + urlSuffix;

    String secret = getSharedSecret();

    HttpClient client = new HttpClient();
    HttpMethod method;

    // Configures the authentication
    client.getParams().setAuthenticationPreemptive(true);
    Credentials credentials = new UsernamePasswordCredentials(OPENFIRE_USERNAME, secret);
    AuthScope scope = new AuthScope(host, port, AuthScope.ANY_REALM);
    client.getState().setCredentials(scope, credentials);

    // Creates the method
    switch (type) {
      case GET:
        method = new GetMethod(wsUrl);
        break;
      case POST:
        PostMethod pm = new PostMethod(wsUrl);
        StringRequestEntity requestEntity = new StringRequestEntity(xmlParams);
        pm.setRequestEntity(requestEntity);
        method = pm;
        break;
      case PUT:
        PutMethod pm1 = new PutMethod(wsUrl);
        StringRequestEntity requestEntity1 = new StringRequestEntity(xmlParams);
        pm1.setRequestEntity(requestEntity1);
        method = pm1;
        break;
      case DELETE:
        method = new DeleteMethod(wsUrl);
        break;
      default:
        throw new IllegalArgumentException();
    }

    method.setRequestHeader("Accept", "text/xml");
    method.setDoAuthentication(true);

    try {
      // Executes the request
      client.executeMethod(method);

      // Parses the result
      String body = method.getResponseBodyAsString();
      if (Log.isDebugEnabled()) {
        Log.debug("Outgoing REST call results: " + body);
      }

      // Checks the http status
      if (method.getStatusCode() != 200) {
        if (method.getStatusCode() == 401) {
          throw new ConnectionException(
              "Invalid password to connect to Clearspace.",
              ConnectionException.ErrorType.AUTHENTICATION);
        } else if (method.getStatusCode() == 404) {
          throw new ConnectionException(
              "Web service not found in Clearspace.", ConnectionException.ErrorType.PAGE_NOT_FOUND);
        } else if (method.getStatusCode() == 503) {
          throw new ConnectionException(
              "Web service not avaible in Clearspace.",
              ConnectionException.ErrorType.SERVICE_NOT_AVAIBLE);
        } else {
          throw new ConnectionException(
              "Error connecting to Clearspace, http status code: " + method.getStatusCode(),
              new HTTPConnectionException(method.getStatusCode()),
              ConnectionException.ErrorType.OTHER);
        }
      } else if (body.contains("Clearspace Upgrade Console")) {
        // TODO Change CS to send a more standard error message
        throw new ConnectionException(
            "Clearspace is in an update state.", ConnectionException.ErrorType.UPDATE_STATE);
      }

      Element response = localParser.get().parseDocument(body).getRootElement();

      // Check for exceptions
      checkFault(response);

      // Since there is no exception, returns the response
      return response;
    } catch (DocumentException e) {
      throw new ConnectionException(
          "Error parsing the response of Clearspace.", e, ConnectionException.ErrorType.OTHER);
    } catch (HttpException e) {
      throw new ConnectionException(
          "Error performing http request to Clearspace", e, ConnectionException.ErrorType.OTHER);
    } catch (UnknownHostException e) {
      throw new ConnectionException(
          "Unknown Host " + getConnectionURI() + " trying to connect to Clearspace",
          e,
          ConnectionException.ErrorType.UNKNOWN_HOST);
    } catch (IOException e) {
      throw new ConnectionException(
          "Error peforming http request to Clearspace.", e, ConnectionException.ErrorType.OTHER);
    } finally {
      method.releaseConnection();
    }
  }
  public void start() throws IllegalStateException {
    super.start();
    if (isEnabled()) {
      // Before starting up service make sure there is a default secret
      if (ExternalComponentManager.getDefaultSecret() == null
          || "".equals(ExternalComponentManager.getDefaultSecret())) {
        try {
          ExternalComponentManager.setDefaultSecret(StringUtils.randomString(10));
        } catch (ModificationNotAllowedException e) {
          Log.warn("Failed to set a default secret to external component service", e);
        }
      }
      // Make sure that external component service is enabled
      if (!ExternalComponentManager.isServiceEnabled()) {
        try {
          ExternalComponentManager.setServiceEnabled(true);
        } catch (ModificationNotAllowedException e) {
          Log.warn("Failed to start external component service", e);
        }
      }
      // Listen for changes to external component settings
      ExternalComponentManager.addListener(this);
      // Listen for registration of new components
      InternalComponentManager.getInstance().addListener(this);
      // Listen for changes in certificates
      CertificateManager.addListener(this);
      // Listen for property changes
      PropertyEventDispatcher.addListener(this);
      // Set up custom clearspace MUC service
      // Create service if it doesn't exist, load if it does.
      MultiUserChatServiceImpl muc =
          (MultiUserChatServiceImpl)
              XMPPServer.getInstance()
                  .getMultiUserChatManager()
                  .getMultiUserChatService(MUC_SUBDOMAIN);
      if (muc == null) {
        try {
          muc =
              XMPPServer.getInstance()
                  .getMultiUserChatManager()
                  .createMultiUserChatService(MUC_SUBDOMAIN, MUC_DESCRIPTION, true);
        } catch (AlreadyExistsException e) {
          Log.error(
              "ClearspaceManager: Found no "
                  + MUC_SUBDOMAIN
                  + " service, but got already exists when creation attempted?  Service probably not started!");
        }
      }
      if (muc != null) {
        // Set up special delegate for Clearspace MUC service
        muc.setMUCDelegate(new ClearspaceMUCEventDelegate());
        // Set up additional features for Clearspace MUC service
        muc.addExtraFeature("clearspace:service");
        // Set up additional identity of conference service to Clearspace MUC service
        muc.addExtraIdentity("conference", "Clearspace Chat Service", "text");
      }

      // Starts the clearspace configuration task
      startClearspaceConfig();

      // Starts the Clearspace MUC transcript manager
      mucTranscriptManager.start();
    }
  }
  private void init() {
    // Register the trust manager to use when using HTTPS
    Protocol easyhttps =
        new Protocol("https", (ProtocolSocketFactory) new SSLProtocolSocketFactory(this), 443);
    Protocol.registerProtocol("https", easyhttps);

    // Convert XML based provider setup to Database based
    JiveGlobals.migrateProperty("clearspace.uri");
    JiveGlobals.migrateProperty("clearspace.sharedSecret");

    // Make sure that all Clearspace components are set up, unless they were overridden
    // Note that the auth provider is our way of knowing that we are set up with Clearspace,
    // so don't bother checking to set it.
    if (isEnabled()) {
      if (JiveGlobals.getProperty("provider.user.className") == null) {
        JiveGlobals.setProperty(
            "provider.user.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceUserProvider");
      }
      if (JiveGlobals.getProperty("provider.group.className") == null) {
        JiveGlobals.setProperty(
            "provider.group.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceGroupProvider");
      }
      if (JiveGlobals.getProperty("provider.vcard.className") == null) {
        JiveGlobals.setProperty(
            "provider.vcard.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceVCardProvider");
      }
      if (JiveGlobals.getProperty("provider.lockout.className") == null) {
        JiveGlobals.setProperty(
            "provider.lockout.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceLockOutProvider");
      }
      if (JiveGlobals.getProperty("provider.securityAudit.className") == null) {
        JiveGlobals.setProperty(
            "provider.securityAudit.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceSecurityAuditProvider");
      }
      if (JiveGlobals.getProperty("provider.admin.className") == null) {
        JiveGlobals.setProperty(
            "provider.admin.className",
            "org.jivesoftware.openfire.clearspace.ClearspaceAdminProvider");
      }
    }

    this.uri = properties.get("clearspace.uri");
    if (uri != null) {
      if (!this.uri.endsWith("/")) {
        this.uri = this.uri + "/";
      }
      // Updates the host/port attributes based on the uri
      updateHostPort();
    }
    sharedSecret = properties.get("clearspace.sharedSecret");

    // Creates the cache maps
    userIDCache = new DefaultCache<String, Long>("clearspace.userid", 1000, JiveConstants.DAY);
    groupIDCache = new DefaultCache<String, Long>("clearspace.groupid", 1000, JiveConstants.DAY);
    usernameCache = new DefaultCache<Long, String>("clearspace.username", 1000, JiveConstants.DAY);

    if (Log.isDebugEnabled()) {
      StringBuilder buf = new StringBuilder();
      buf.append("Created new ClearspaceManager() instance, fields:\n");
      buf.append("\t URI: ").append(uri).append("\n");
      buf.append("\t sharedSecret: ").append(sharedSecret).append("\n");

      Log.debug("ClearspaceManager: " + buf.toString());
    }

    // Init nonce cache
    nonceCache = CacheFactory.createCache("Clearspace SSO Nonce");
    // Init nonce generator
    nonceGenerator = new Random();
  }