DbHostAuth.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.openr66.database.data;

import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.waarp.common.database.DbConstant;
import org.waarp.common.database.DbPreparedStatement;
import org.waarp.common.database.DbSession;
import org.waarp.common.database.exception.WaarpDatabaseException;
import org.waarp.common.database.exception.WaarpDatabaseNoConnectionException;
import org.waarp.common.database.exception.WaarpDatabaseNoDataException;
import org.waarp.common.database.exception.WaarpDatabaseSqlException;
import org.waarp.common.digest.FilesystemBasedDigest;
import org.waarp.common.json.JsonHandler;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;
import org.waarp.common.role.RoleDefault;
import org.waarp.common.utility.ParametersChecker;
import org.waarp.common.utility.WaarpStringUtils;
import org.waarp.openr66.context.R66Session;
import org.waarp.openr66.dao.AbstractDAO;
import org.waarp.openr66.dao.DAOFactory;
import org.waarp.openr66.dao.Filter;
import org.waarp.openr66.dao.HostDAO;
import org.waarp.openr66.dao.database.DBHostDAO;
import org.waarp.openr66.dao.database.StatementExecutor;
import org.waarp.openr66.dao.exception.DAOConnectionException;
import org.waarp.openr66.dao.exception.DAONoDataException;
import org.waarp.openr66.pojo.Host;
import org.waarp.openr66.protocol.configuration.Configuration;
import org.waarp.openr66.protocol.networkhandler.NetworkTransaction;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Host Authentication Table object
 */
public class DbHostAuth extends AbstractDbDataDao<Host> {
  private static final String CHECKED = "checked";

  private static final String CANNOT_FIND_HOST = "Cannot find host";

  public static final String DEFAULT_CLIENT_ADDRESS = "0.0.0.0";

  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(DbHostAuth.class);
  private static final byte[] VALUE_0_BYTE = {};
  private static final DbHostAuth[] DBHOSTAUTH_0_SIZE = new DbHostAuth[0];
  private static final Pattern BACKSLASH =
      Pattern.compile("\"", Pattern.LITERAL);
  private static final Pattern COMMA = Pattern.compile(",", Pattern.LITERAL);

  public enum Columns {
    ADDRESS, PORT, ISSSL, HOSTKEY, ADMINROLE, ISCLIENT, ISACTIVE, ISPROXIFIED,
    UPDATEDINFO, HOSTID
  }

  public static final int[] dbTypes = {
      Types.NVARCHAR, Types.INTEGER, Types.BIT, Types.VARBINARY, Types.BIT,
      Types.BIT, Types.BIT, Types.BIT, Types.INTEGER, Types.NVARCHAR
  };

  public static final String table = " HOSTS ";

  protected static final String selectAllFields =
      Columns.ADDRESS.name() + ',' + Columns.PORT.name() + ',' +
      Columns.ISSSL.name() + ',' + Columns.HOSTKEY.name() + ',' +
      Columns.ADMINROLE.name() + ',' + Columns.ISCLIENT.name() + ',' +
      Columns.ISACTIVE.name() + ',' + Columns.ISPROXIFIED.name() + ',' +
      Columns.UPDATEDINFO.name() + ',' + Columns.HOSTID.name();

  public static final Columns[] indexes = {
      Columns.UPDATEDINFO
  };

  @Override
  protected final void initObject() {
    //nothing
  }

  @Override
  protected final String getTable() {
    return table;
  }

  @Override
  protected final AbstractDAO<Host> getDao(final boolean isCacheable)
      throws DAOConnectionException {
    return DAOFactory.getInstance().getHostDAO(isCacheable);
  }

  @Override
  protected final String getPrimaryKey() {
    if (pojo != null) {
      return pojo.getHostid();
    }
    throw new IllegalArgumentException("pojo is null");
  }

  @Override
  protected final String getPrimaryField() {
    return Columns.HOSTID.name();
  }

  /**
   * @param hostid
   * @param address
   * @param port
   * @param isSSL
   * @param hostkey
   * @param adminrole
   * @param isClient
   */
  public DbHostAuth(final String hostid, final String address, final int port,
                    final boolean isSSL, final byte[] hostkey,
                    final boolean adminrole, final boolean isClient)
      throws WaarpDatabaseSqlException {
    pojo = new Host(hostid, address, port, hostkey, isSSL, isClient, false,
                    adminrole);
    if (hostkey != null) {
      try {
        // Save as crypted with the local Key and HEX
        pojo.setHostkey(
            Configuration.configuration.getCryptoKey().cryptToHex(hostkey)
                                       .getBytes(WaarpStringUtils.UTF8));
      } catch (final Exception e) {
        logger.warn("Error while cyphering hostkey" + " : {}", e.getMessage());
        pojo.setHostkey(VALUE_0_BYTE);
      }
    }
    if (port < 0) {
      pojo.setClient(true);
      pojo.setAddress(DEFAULT_CLIENT_ADDRESS);
    }
    checkValues();
    isSaved = false;
  }

  private DbHostAuth(final Host host) {
    if (host == null) {
      throw new IllegalArgumentException(
          "Argument in constructor cannot be null");
    }
    this.pojo = host;
  }

  public DbHostAuth(final ObjectNode source) throws WaarpDatabaseSqlException {
    pojo = new Host();
    setFromJson(source, false);
  }

  @Override
  protected final void checkValues() throws WaarpDatabaseSqlException {
    pojo.checkValues();
  }

  @Override
  public final void setFromJson(final ObjectNode node,
                                final boolean ignorePrimaryKey)
      throws WaarpDatabaseSqlException {
    super.setFromJson(node, ignorePrimaryKey);
    if (pojo.getHostkey() == null || pojo.getHostkey().length == 0 ||
        ParametersChecker.isEmpty(pojo.getAddress()) ||
        ParametersChecker.isEmpty(pojo.getHostid())) {
      throw new WaarpDatabaseSqlException(
          "Not enough argument to create the object");
    }
    if (pojo.getHostkey() != null) {
      try {
        // Save as crypted with the local Key and Base64
        pojo.setHostkey(Configuration.configuration.getCryptoKey().cryptToHex(
            pojo.getHostkey()).getBytes(WaarpStringUtils.UTF8));
      } catch (final Exception e) {
        pojo.setHostkey(VALUE_0_BYTE);
      }
      isSaved = false;
    }
    if (pojo.getPort() < 0) {
      pojo.setClient(true);
      pojo.setAddress(DEFAULT_CLIENT_ADDRESS);
      isSaved = false;
    }
    if (!ignorePrimaryKey) {
      try {
        insert();
      } catch (final WaarpDatabaseException e) {
        try {
          update();
        } catch (final WaarpDatabaseException ex) {
          logger.error("Cannot save item: {}", ex.getMessage());
        }
      }
    }
  }

  @Override
  protected final void setFromJson(final String field, final JsonNode value) {
    if (value == null) {
      return;
    }
    for (final Columns column : Columns.values()) {
      if (column.name().equalsIgnoreCase(field)) {
        switch (column) {
          case ADDRESS:
            pojo.setAddress(value.asText());
            break;
          case ADMINROLE:
            pojo.setAdmin(value.asBoolean());
            break;
          case HOSTKEY:
            // Change from Base64 to Byte for HostKey
            try {
              final byte[] key =
                  Base64Variants.getDefaultVariant().decode(value.asText());
              pojo.setHostkey(key);
            } catch (final IllegalArgumentException e) {
              logger.warn("HostKey not in Base64 from Jackson: {}",
                          e.getMessage());
              pojo.setHostkey(value.asText().getBytes(WaarpStringUtils.UTF8));
            }
            break;
          case ISACTIVE:
            pojo.setActive(value.asBoolean());
            break;
          case ISCLIENT:
            pojo.setClient(value.asBoolean());
            break;
          case ISPROXIFIED:
            pojo.setProxified(value.asBoolean());
            break;
          case ISSSL:
            pojo.setSSL(value.asBoolean());
            break;
          case PORT:
            pojo.setPort(value.asInt());
            break;
          case UPDATEDINFO:
            pojo.setUpdatedInfo(
                org.waarp.openr66.pojo.UpdatedInfo.valueOf(value.asInt()));
            break;
          case HOSTID:
            pojo.setHostid(value.asText());
            break;
        }
      }
    }
  }

  /**
   * @param hostid
   *
   * @throws WaarpDatabaseException
   */
  public DbHostAuth(final String hostid) throws WaarpDatabaseException {
    if (hostid == null) {
      throw new WaarpDatabaseException("No host id passed");
    }
    HostDAO hostAccess = null;
    try {
      hostAccess = DAOFactory.getInstance().getHostDAO(true);
      pojo = hostAccess.select(hostid);
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseException(e);
    } catch (final DAONoDataException e) {
      throw new WaarpDatabaseNoDataException(CANNOT_FIND_HOST, e);
    } finally {
      DAOFactory.closeDAO(hostAccess);
    }
  }

  /**
   * Delete all entries (used when purge and reload)
   *
   * @return the previous existing array of DbRule
   *
   * @throws WaarpDatabaseException
   */
  public static DbHostAuth[] deleteAll() throws WaarpDatabaseException {
    HostDAO hostAccess = null;
    final List<DbHostAuth> res = new ArrayList<DbHostAuth>();
    List<Host> hosts;
    try {
      hostAccess = DAOFactory.getInstance().getHostDAO(false);
      hosts = hostAccess.getAll();
      hostAccess.deleteAll();
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseException(e);
    } finally {
      DAOFactory.closeDAO(hostAccess);
    }
    for (final Host host : hosts) {
      final DbHostAuth hostAuth = new DbHostAuth(host);
      hostAuth.isSaved = false;
      res.add(hostAuth);
    }
    return res.toArray(new DbHostAuth[0]);
  }

  /**
   * Private constructor for Commander only
   */
  private DbHostAuth() {
    pojo = new Host();
  }

  /**
   * Get All DbHostAuth from database or from internal hashMap in case of no
   * database support
   *
   * @return the array of DbHostAuth
   *
   * @throws WaarpDatabaseNoConnectionException
   * @throws WaarpDatabaseSqlException
   */
  public static DbHostAuth[] getAllHosts()
      throws WaarpDatabaseNoConnectionException {
    HostDAO hostAccess = null;
    final List<DbHostAuth> res = new ArrayList<DbHostAuth>();
    List<Host> hosts;
    try {
      hostAccess = DAOFactory.getInstance().getHostDAO(false);
      hosts = hostAccess.getAll();
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } finally {
      DAOFactory.closeDAO(hostAccess);
    }
    for (final Host host : hosts) {
      final DbHostAuth dbHostAuth = new DbHostAuth(host);
      dbHostAuth.isSaved = true;
      res.add(dbHostAuth);
    }
    return res.toArray(DBHOSTAUTH_0_SIZE);
  }

  /**
   * For instance from Commander when getting updated information
   *
   * @param preparedStatement
   *
   * @return the next updated DbHostAuth
   *
   * @throws WaarpDatabaseNoConnectionException
   * @throws WaarpDatabaseSqlException
   */
  public static DbHostAuth getFromStatement(
      final DbPreparedStatement preparedStatement)
      throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
    final DbHostAuth dbHostAuth = new DbHostAuth();
    AbstractDAO<Host> hostDAO = null;
    try {
      hostDAO = dbHostAuth.getDao(false);
      dbHostAuth.pojo = ((StatementExecutor<Host>) hostDAO).getFromResultSet(
          preparedStatement.getResultSet());
      return dbHostAuth;
    } catch (final SQLException e) {
      DbConstant.error(e);
      throw new WaarpDatabaseSqlException("Getting values in error", e);
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseSqlException("Getting values in error", e);
    } finally {
      DAOFactory.closeDAO(hostDAO);
    }
  }

  public static DbHostAuth[] getUpdatedPreparedStatement()
      throws WaarpDatabaseNoConnectionException {
    final List<Filter> filters = new ArrayList<Filter>(1);
    filters.add(new Filter(DBHostDAO.UPDATED_INFO_FIELD, "=",
                           org.waarp.openr66.pojo.UpdatedInfo.fromLegacy(
                               UpdatedInfo.TOSUBMIT).ordinal()));
    HostDAO hostAccess = null;
    List<Host> hosts;
    try {
      hostAccess = DAOFactory.getInstance().getHostDAO(false);
      hosts = hostAccess.find(filters);
    } catch (final DAOConnectionException e) {
      throw new WaarpDatabaseNoConnectionException(e);
    } finally {
      DAOFactory.closeDAO(hostAccess);
    }
    final DbHostAuth[] res = new DbHostAuth[hosts.size()];
    int i = 0;
    for (final Host host : hosts) {
      res[i] = new DbHostAuth(host);
      i++;
    }
    return res;
  }

  /**
   * @param session
   * @param host
   * @param addr
   * @param ssl
   * @param active
   *
   * @return the DbPreparedStatement according to the filter
   *
   * @throws WaarpDatabaseNoConnectionException
   * @throws WaarpDatabaseSqlException
   */
  public static DbPreparedStatement getFilterPrepareStament(
      final DbSession session, final String host, final String addr,
      final boolean ssl, final boolean active)
      throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
    final DbPreparedStatement preparedStatement =
        new DbPreparedStatement(session);
    final String request =
        "SELECT " + selectAllFields + " FROM " + table + " WHERE ";
    String condition = null;
    if (ParametersChecker.isNotEmpty(host)) {
      condition = Columns.HOSTID.name() + " = '" + host + "' ";
    }
    if (ParametersChecker.isNotEmpty(addr)) {
      if (condition != null) {
        condition += " AND ";
      } else {
        condition = "";
      }
      condition += Columns.ADDRESS.name() + " = '" + addr + "' ";
    }
    if (condition != null) {
      condition += " AND ";
    } else {
      condition = "";
    }
    condition += Columns.ISSSL.name() + " = ? AND ";
    condition += Columns.ISACTIVE.name() + " = ? ";
    preparedStatement.createPrepareStatement(
        request + condition + " ORDER BY " + Columns.HOSTID.name());
    try {
      preparedStatement.getPreparedStatement().setBoolean(1, ssl);
      preparedStatement.getPreparedStatement().setBoolean(2, active);
    } catch (final SQLException e) {
      preparedStatement.realClose();
      throw new WaarpDatabaseSqlException(e);
    }
    return preparedStatement;
  }

  /**
   * @param session
   * @param host
   * @param addr
   *
   * @return the DbPreparedStatement according to the filter
   *
   * @throws WaarpDatabaseNoConnectionException
   * @throws WaarpDatabaseSqlException
   */
  public static DbPreparedStatement getFilterPrepareStament(
      final DbSession session, final String host, final String addr)
      throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
    final DbPreparedStatement preparedStatement =
        new DbPreparedStatement(session);
    final String request = "SELECT " + selectAllFields + " FROM " + table;
    String condition = null;
    if (ParametersChecker.isNotEmpty(host)) {
      condition = Columns.HOSTID.name() + " = '" + host + "' ";
    }
    if (ParametersChecker.isNotEmpty(addr)) {
      if (condition != null) {
        condition += " AND ";
      } else {
        condition = "";
      }
      condition += Columns.ADDRESS.name() + " = '" + addr + "' ";
    }
    if (condition != null) {
      condition = " WHERE " + condition;
    } else {
      condition = "";
    }
    preparedStatement.createPrepareStatement(
        request + condition + " ORDER BY " + Columns.HOSTID.name());
    return preparedStatement;
  }

  @Override
  public final void changeUpdatedInfo(final UpdatedInfo info) {
    isSaved = false;
    pojo.setUpdatedInfo(org.waarp.openr66.pojo.UpdatedInfo.fromLegacy(info));
  }

  /**
   * @return the isActive
   */
  public final boolean isActive() {
    return pojo.isActive();
  }

  /**
   * @param isActive the isActive to set
   */
  public final void setActive(final boolean isActive) {
    isSaved = false;
    pojo.setActive(isActive);
  }

  /**
   * @return the isProxified
   */
  public final boolean isProxified() {
    return pojo.isProxified();
  }

  /**
   * @param isProxified the isProxified to set
   */
  public final void setProxified(final boolean isProxified) {
    isSaved = false;
    pojo.setProxified(isProxified);
  }

  /**
   * Is the given key a valid one
   *
   * @param newkey
   *
   * @return True if the key is valid (or any key is valid)
   */
  public final boolean isKeyValid(final byte[] newkey) {
    // It is valid to not have a key
    // Check before if any key is passed or if account is active
    if (pojo.getHostkey() == null) {
      return true;
    }
    // Check before if any key is passed or if account is active
    if (newkey == null || !isActive()) {
      return false;
    }
    try {
      return FilesystemBasedDigest.equalPasswd(
          Configuration.configuration.getCryptoKey()
                                     .decryptHexInBytes(pojo.getHostkey()),
          newkey);
    } catch (final Exception e) {
      logger.info("Error while checking key", e);
      return false;
    }
  }

  /**
   * @return the hostkey
   */
  public final byte[] getHostkey() {
    if (pojo.getHostkey() == null) {
      return null;
    }
    try {
      return Configuration.configuration.getCryptoKey()
                                        .decryptHexInBytes(pojo.getHostkey());
    } catch (final Exception e) {
      logger.info("Error while checking key", e);
      return VALUE_0_BYTE;
    }
  }

  /**
   * @return the adminrole
   */
  public final boolean isAdminrole() {
    return pojo.isAdmin();
  }

  /**
   * Test if the address is 0.0.0.0 for a client or isClient
   *
   * @return True if the address is a client address (0.0.0.0) or isClient
   */
  public final boolean isClient() {
    return pojo.isClient() || isNoAddress();
  }

  /**
   * True if the address is a client address (0.0.0.0) or if the port is < 0
   *
   * @return True if the address is a client address (0.0.0.0) or if the port
   *     is < 0
   */
  public final boolean isNoAddress() {
    return pojo.getAddress().equals(DEFAULT_CLIENT_ADDRESS) ||
           pojo.getPort() < 0;
  }

  /**
   * @return the SocketAddress from the address and port
   *
   * @throws IllegalArgumentException when the address is for a Client
   *     and
   *     therefore cannot be checked
   */
  public final SocketAddress getSocketAddress()
      throws IllegalArgumentException {
    if (isNoAddress()) {
      throw new IllegalArgumentException("Not a server");
    }
    return new InetSocketAddress(pojo.getAddress(), pojo.getPort());
  }

  /**
   * @return True if this Host ref is with SSL support
   */
  public final boolean isSsl() {
    return pojo.isSSL();
  }

  /**
   * @return the hostid
   */
  public final String getHostid() {
    return pojo.getHostid();
  }

  /**
   * @return the address
   */
  public final String getAddress() {
    return pojo.getAddress();
  }

  /**
   * @return the port
   */
  public final int getPort() {
    return pojo.getPort();
  }

  private static String getVersion(final String host) {
    String remoteHost = host;
    String alias = "";
    if (Configuration.configuration.getAliases().containsKey(remoteHost)) {
      remoteHost = Configuration.configuration.getAliases().get(remoteHost);
      alias += "(Alias: " + remoteHost + ") ";
    }
    if (Configuration.configuration.getReverseAliases()
                                   .containsKey(remoteHost)) {
      final StringBuilder alias2 = new StringBuilder("(ReverseAlias: ");
      final String[] list =
          Configuration.configuration.getReverseAliases().get(remoteHost);
      boolean found = false;
      for (final String string : list) {
        if (string.equals(host)) {
          continue;
        }
        found = true;
        alias2.append(string).append(' ');
      }
      if (found) {
        alias += alias2 + ") ";
      }
    }
    if (Configuration.configuration.getBusinessWhiteSet()
                                   .contains(remoteHost)) {
      alias += "(Business: Allowed) ";
    }
    if (Configuration.configuration.getRoles().containsKey(remoteHost)) {
      final RoleDefault item =
          Configuration.configuration.getRoles().get(remoteHost);
      alias += "(Role: " + item + ") ";
    }
    return alias +
           (Configuration.configuration.getVersions().containsKey(remoteHost)?
               Configuration.configuration.getVersions().get(remoteHost)
                                          .toString() : "Version Unknown");
  }

  @Override
  public final String toString() {
    return "HostAuth: " + getHostid() + " address: " + getAddress() + ':' +
           getPort() + " isSSL: " + isSsl() + " admin: " + isAdminrole() +
           " isClient: " + isClient() + " isActive: " + isActive() +
           " isProxified: " + isProxified() + " (" +
           (pojo.getHostkey() != null? pojo.getHostkey().length : 0) +
           ") Version: " + getVersion(getHostid());
  }

  /**
   * Write selected DbHostAuth to a Json String
   *
   * @param preparedStatement
   *
   * @return the associated Json String
   *
   * @throws WaarpDatabaseNoConnectionException
   * @throws WaarpDatabaseSqlException
   */
  public static String getJson(final DbPreparedStatement preparedStatement,
                               final int limit)
      throws WaarpDatabaseNoConnectionException, WaarpDatabaseSqlException {
    final ArrayNode arrayNode = JsonHandler.createArrayNode();
    try {
      preparedStatement.executeQuery();
      int nb = 0;
      while (preparedStatement.getNext()) {
        final DbHostAuth host = getFromStatement(preparedStatement);
        final ObjectNode node = host.getInternalJson();
        arrayNode.add(node);
        nb++;
        if (nb >= limit) {
          break;
        }
      }
    } finally {
      preparedStatement.realClose();
    }
    return JsonHandler.writeAsString(arrayNode);
  }

  private ObjectNode getInternalJson() {
    final ObjectNode node = getJson();
    try {
      node.put(Columns.HOSTKEY.name(),
               new String(getHostkey(), WaarpStringUtils.UTF8));
    } catch (final Exception e1) {
      node.put(Columns.HOSTKEY.name(), "");
    }
    int nb;
    try {
      nb = NetworkTransaction.nbAttachedConnection(getSocketAddress(),
                                                   getHostid());
    } catch (final Exception e) {
      nb = -1;
    }
    node.put("Connection", nb);
    node.put("Version", COMMA.matcher(BACKSLASH.matcher(getVersion(getHostid()))
                                               .replaceAll(
                                                   Matcher.quoteReplacement(
                                                       "")))
                             .replaceAll(Matcher.quoteReplacement(", ")));
    return node;
  }

  /**
   * @return the Json string for this
   */
  public final String getJsonAsString() {
    final ObjectNode node = getInternalJson();
    return JsonHandler.writeAsString(node);
  }

  /**
   * @param session
   * @param body
   * @param crypted True if the Key is kept crypted, False it will be
   *     in
   *     clear form
   *
   * @return the runner in Html format specified by body by replacing all
   *     instance of fields
   */
  public final String toSpecializedHtml(final R66Session session,
                                        final String body,
                                        final boolean crypted) {
    final StringBuilder builder = new StringBuilder(body);
    WaarpStringUtils.replace(builder, "XXXHOSTXXX", getHostid());
    WaarpStringUtils.replace(builder, "XXXADDRXXX", getAddress());
    WaarpStringUtils.replace(builder, "XXXPORTXXX",
                             Integer.toString(getPort()));
    if (crypted) {
      WaarpStringUtils.replace(builder, "XXXKEYXXX",
                               new String(getHostkey(), WaarpStringUtils.UTF8));
    } else {
      try {
        WaarpStringUtils.replace(builder, "XXXKEYXXX", new String(getHostkey(),
                                                                  WaarpStringUtils.UTF8));
      } catch (final Exception e) {
        WaarpStringUtils.replace(builder, "XXXKEYXXX", "BAD DECRYPT");
      }
    }
    WaarpStringUtils.replace(builder, "XXXSSLXXX", isSsl()? CHECKED : "");
    WaarpStringUtils.replace(builder, "XXXADMXXX", isAdminrole()? CHECKED : "");
    WaarpStringUtils.replace(builder, "XXXISCXXX", isClient()? CHECKED : "");
    WaarpStringUtils.replace(builder, "XXXISAXXX", isActive()? CHECKED : "");
    WaarpStringUtils.replace(builder, "XXXISPXXX", isProxified()? CHECKED : "");
    WaarpStringUtils.replace(builder, "XXXVERSIONXXX",
                             COMMA.matcher(getVersion(getHostid()))
                                  .replaceAll(Matcher.quoteReplacement(", ")));
    int nb;
    try {
      nb = NetworkTransaction.nbAttachedConnection(getSocketAddress(),
                                                   getHostid());
    } catch (final Exception e) {
      nb = -1;
    }
    WaarpStringUtils.replace(builder, "XXXCONNXXX",
                             nb > 0? "(" + nb + " Connected) " : "");
    return builder.toString();
  }

  /**
   * @return True if any of the server has the isProxified property
   */
  public static boolean hasProxifiedHosts() {
    final List<Filter> filters = new ArrayList<Filter>();
    filters.add(new Filter(DBHostDAO.IS_PROXIFIED_FIELD, "=", true));

    HostDAO hostAccess = null;
    try {
      hostAccess = DAOFactory.getInstance().getHostDAO(false);
      return hostAccess.count(filters) > 0;
    } catch (final DAOConnectionException e) {
      logger.error("DAO Access error", e);
      return false;
    } finally {
      DAOFactory.closeDAO(hostAccess);
    }
  }
}